diff --git a/L2J_Mobius_1.0_Ertheia/dist/db_installer/sql/game/mdt_bets.sql b/L2J_Mobius_1.0_Ertheia/dist/db_installer/sql/game/mdt_bets.sql new file mode 100644 index 0000000000..8d7d4cb738 --- /dev/null +++ b/L2J_Mobius_1.0_Ertheia/dist/db_installer/sql/game/mdt_bets.sql @@ -0,0 +1,15 @@ +CREATE TABLE IF NOT EXISTS `mdt_bets` ( + `lane_id` INT(1) DEFAULT 0, + `bet` INT DEFAULT 0, + PRIMARY KEY (`lane_id`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci; + +INSERT INTO `mdt_bets` VALUES +('1','0'), +('2','0'), +('3','0'), +('4','0'), +('5','0'), +('6','0'), +('7','0'), +('8','0'); \ No newline at end of file diff --git a/L2J_Mobius_1.0_Ertheia/dist/db_installer/sql/game/mdt_history.sql b/L2J_Mobius_1.0_Ertheia/dist/db_installer/sql/game/mdt_history.sql new file mode 100644 index 0000000000..06978f3377 --- /dev/null +++ b/L2J_Mobius_1.0_Ertheia/dist/db_installer/sql/game/mdt_history.sql @@ -0,0 +1,7 @@ +CREATE TABLE IF NOT EXISTS `mdt_history` ( + `race_id` MEDIUMINT DEFAULT 0, + `first` INT(1) DEFAULT 0, + `second` INT(1) DEFAULT 0, + `odd_rate` DOUBLE(10,2) DEFAULT 0, + PRIMARY KEY (`race_id`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci; \ No newline at end of file diff --git a/L2J_Mobius_1.0_Ertheia/dist/game/config/AdminCommands.xml b/L2J_Mobius_1.0_Ertheia/dist/game/config/AdminCommands.xml index 36ab1240b5..215b7c679a 100644 --- a/L2J_Mobius_1.0_Ertheia/dist/game/config/AdminCommands.xml +++ b/L2J_Mobius_1.0_Ertheia/dist/game/config/AdminCommands.xml @@ -416,9 +416,6 @@ - - - diff --git a/L2J_Mobius_1.0_Ertheia/dist/game/data/html/default/30995-1.htm b/L2J_Mobius_1.0_Ertheia/dist/game/data/html/default/30995-1.htm index 4962bdf3a2..d38d518149 100644 --- a/L2J_Mobius_1.0_Ertheia/dist/game/data/html/default/30995-1.htm +++ b/L2J_Mobius_1.0_Ertheia/dist/game/data/html/default/30995-1.htm @@ -1,19 +1,22 @@ -

-
-In a monster race eight monsters race at the same time. If you guess which monster is expected to finish first and buy that monster's race ticket, based upon the odds to win you will receive an appropriate amount of Adena.
-How the Race WorksMonster races run every 20 minutes in real time.
-How to Participate
-You can purchase a monster race ticket through a race manager NPC. Only between 18 minutes before the start of a race until 3 minutes before the race begins can you buy a ticket for the applicable race. When you purchase a ticket through a race manager, you can find out the names of the participating monsters and their current statistics. Three minutes before a race starts, the race manager will stop ticket sales and announce the monsters' individual odds for the current race.
-Betting
-There are two types of race betting. The Win bet means that you back a particular monster to win 1st place. The Place bet means that you will have to guess the 1st and 2nd place monsters, regardless of who finishes first or second.
+ +
+ +
+In a monster race eight monsters race at the same time. If you guess which monster is expected to finish first and buy that monster's race ticket, based upon the odds to win you will receive an appropriate amount of adena.
+Racing Schedule
+Monster races every 20 minutes in real time.
+Method of Race Participation
+You can purchase a monster race ticket through a race manager NPC. Only between 18 minutes before the start of a race until 3 minutes before the race begins you can buy a ticket for the applicable race. When you purchase a ticket through a conversation with a race manager, you can find out the names of participating monsters in the race and their current statistics. Five minutes before a race starts, the race manager will stop ticket sales and announce monsters' individual odds rate for the current race.
+Types of Race
+There are two types of race betting. The Win type betting means that you back a monster that will win the first prize. Place type of betting means that you will have to guess the 1st and 2nd winning monsters, regardless of who finishes first or second.
Win - * Among the entire betting amount for Win betting, after subtracting the race track's own share of profit, the amount left will be your total winnings. Three minutes before the race, each monster's expected odds are announced. Each Win ticket's dividend amount is calculated by multiplying the odds by the price of a ticket. The odds are never less than 1. - * The prize money does not get transferred to the next race.
+* Among the entire betting amount for Win betting, after subtracting the race track's own share of profit, the amount left will be your total winnings. Three minutes before the race each monster's expected odds are announced. Each Win ticket's dividend amount is calculated by multiplying the odds by the price of a ticket. The odds rate is never less than 1. +* The prize money does not get transferred to the next race.
Place - * Place betting is a way of betting when you correctly guess the two monsters who will finish first and second regardless of their order. The method of calculating the dividend amount is the same as Win betting. - * Currently place betting is not available.
+* Place betting is a way of betting when you correctly guess the two monsters who will finish first and second regardless of their order. The method of calculating dividend amount is the same as Win betting.
+* Currently place betting is not offered.
End of Race
-When a race is over, the race manager will shout the results of the race. You can trade your ticket for Adena by speaking to a race manager NPC. Check the Winning Information Data to find out the types of bets and total winnings for the race you bet on, as well as your win status and prize amount. If you won, you can receive your prize money thorough a race manager NPC. If you did not win, a race manager will buy back your ticket for a small amount of money.
+When a race is over, the race manager will shout out the results of the race. Through a conversation with a race manager NPC, you can trade your ticket for adena. By looking up on the Winning Information Data, you can find out the types of tickets and quantity of the winnings in the race you betted on and your win status and prize amount you won. If you won, you can receive your prize money thorough a race manager NPC. If you did not win, a race manager will buy back your ticket by paying you a very small amount of money.
If a server goes down while tickets are being sold, or while a race is in progress, the race will be cancelled. You can receive a full refund for your ticket for a cancelled race through a race manager.
- + \ No newline at end of file diff --git a/L2J_Mobius_1.0_Ertheia/dist/game/data/html/default/30995-3.htm b/L2J_Mobius_1.0_Ertheia/dist/game/data/html/default/30995-3.htm index 5577c579a4..7ae65a37a4 100644 --- a/L2J_Mobius_1.0_Ertheia/dist/game/data/html/default/30995-3.htm +++ b/L2J_Mobius_1.0_Ertheia/dist/game/data/html/default/30995-3.htm @@ -3,6 +3,7 @@ + @@ -11,6 +12,7 @@
Lane SelectionOdds
+ @@ -20,18 +22,21 @@
13%
+
Select Bet Amount -100 Adena , 500 Adena -1,000 Adena , 5,000 Adena -10,000 Adena , 20,000 Adena -50,000 Adena , 100,000 Adena +100 Adena , 500 Adena +1,000 Adena , 5,000 Adena +10,000 Adena , 20,000 Adena +50,000 Adena , 100,000 Adena
+ + @@ -39,4 +44,5 @@
+ \ No newline at end of file diff --git a/L2J_Mobius_1.0_Ertheia/dist/game/data/html/default/30995-4.htm b/L2J_Mobius_1.0_Ertheia/dist/game/data/html/default/30995-4.htm index bcd1f0d520..30c508b9cb 100644 --- a/L2J_Mobius_1.0_Ertheia/dist/game/data/html/default/30995-4.htm +++ b/L2J_Mobius_1.0_Ertheia/dist/game/data/html/default/30995-4.htm @@ -11,6 +11,7 @@ Odds + @@ -20,6 +21,7 @@
13%
+ @@ -37,11 +39,13 @@ -
Bet Amount Total 0total Adena

+ +
If the information is correct, please click Confirm.

+ diff --git a/L2J_Mobius_1.0_Ertheia/dist/game/data/html/default/30995-5.htm b/L2J_Mobius_1.0_Ertheia/dist/game/data/html/default/30995-5.htm index b9729e01b0..59a05e14a0 100644 --- a/L2J_Mobius_1.0_Ertheia/dist/game/data/html/default/30995-5.htm +++ b/L2J_Mobius_1.0_Ertheia/dist/game/data/html/default/30995-5.htm @@ -15,49 +15,49 @@ - + - + - + - + - + - + - + - +
No.1 Mob1&$804; Odd1
No.2 Mob2&$804; Odd2
No.3 Mob3&$804; Odd3
No.4 Mob4&$804; Odd4
No.5 Mob5&$804; Odd5
No.6 Mob6&$804; Odd6
No.7 Mob7&$804; Odd7
No.8 Mob8&$804; Odd8
diff --git a/L2J_Mobius_1.0_Ertheia/dist/game/data/html/default/30995-7.htm b/L2J_Mobius_1.0_Ertheia/dist/game/data/html/default/30995-7.htm index a058e0e0b3..53eb213a52 100644 --- a/L2J_Mobius_1.0_Ertheia/dist/game/data/html/default/30995-7.htm +++ b/L2J_Mobius_1.0_Ertheia/dist/game/data/html/default/30995-7.htm @@ -3,22 +3,12 @@ - - + +
Winnings for a Race.LaneBet Amount
Winnings for a Race.LaneBet Amount
- - - - - +%tickets%
1 Number100 Adena
- - - - -
1 Page
- diff --git a/L2J_Mobius_1.0_Ertheia/dist/game/data/html/default/30995-8.htm b/L2J_Mobius_1.0_Ertheia/dist/game/data/html/default/30995-8.htm index ebbcf542b2..5f856a222f 100644 --- a/L2J_Mobius_1.0_Ertheia/dist/game/data/html/default/30995-8.htm +++ b/L2J_Mobius_1.0_Ertheia/dist/game/data/html/default/30995-8.htm @@ -4,11 +4,11 @@
-
Race #19090
-Lane 1
-Bet: 100 adena
-1st place: Lane [4]
-Odds:[0.01] +
Race #%raceId%
+Lane: %lane%
+Bet: %bet% Adena
+1st place: Lane [%firstLane%]
+Odds: [%odd%]
@@ -18,8 +18,8 @@ Odds:[0.01] - + - +
\ No newline at end of file diff --git a/L2J_Mobius_1.0_Ertheia/dist/game/data/html/default/30995-9.htm b/L2J_Mobius_1.0_Ertheia/dist/game/data/html/default/30995-9.htm index ac1201d6f2..c17ab4849a 100644 --- a/L2J_Mobius_1.0_Ertheia/dist/game/data/html/default/30995-9.htm +++ b/L2J_Mobius_1.0_Ertheia/dist/game/data/html/default/30995-9.htm @@ -7,41 +7,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +%infos%
7 th8 Lane 1 Lane0.00 Times
6 th4 Lane 8 Lane0.00 Times
5 th7 Lane 2 Lane0.00 Times
4 th6 Lane 8 Lane0.00 Times
3 th4 Lane 1 Lane0.00 Times
2 th1 Lane 8 Lane0.00 Times
1 th7 Lane 6 Lane0.00 Times
diff --git a/L2J_Mobius_1.0_Ertheia/dist/game/data/html/default/30995.htm b/L2J_Mobius_1.0_Ertheia/dist/game/data/html/default/30995.htm index 89f40742ae..271c4ee284 100644 --- a/L2J_Mobius_1.0_Ertheia/dist/game/data/html/default/30995.htm +++ b/L2J_Mobius_1.0_Ertheia/dist/game/data/html/default/30995.htm @@ -1,15 +1,15 @@ - -
Monster Race Manager
- - - - - - - - - -
- + + +
+ + + + + + + + + + \ No newline at end of file diff --git a/L2J_Mobius_1.0_Ertheia/dist/game/data/scripts/handlers/MasterHandler.java b/L2J_Mobius_1.0_Ertheia/dist/game/data/scripts/handlers/MasterHandler.java index 2591f58ad9..d367f94c55 100644 --- a/L2J_Mobius_1.0_Ertheia/dist/game/data/scripts/handlers/MasterHandler.java +++ b/L2J_Mobius_1.0_Ertheia/dist/game/data/scripts/handlers/MasterHandler.java @@ -105,7 +105,6 @@ import handlers.admincommandhandlers.AdminMenu; import handlers.admincommandhandlers.AdminMessages; import handlers.admincommandhandlers.AdminMissingHtmls; import handlers.admincommandhandlers.AdminMobGroup; -import handlers.admincommandhandlers.AdminMonsterRace; import handlers.admincommandhandlers.AdminOlympiad; import handlers.admincommandhandlers.AdminOnline; import handlers.admincommandhandlers.AdminPForge; @@ -440,7 +439,6 @@ public class MasterHandler AdminMessages.class, AdminMissingHtmls.class, AdminMobGroup.class, - AdminMonsterRace.class, AdminOlympiad.class, AdminOnline.class, AdminPathNode.class, diff --git a/L2J_Mobius_1.0_Ertheia/dist/game/data/scripts/handlers/admincommandhandlers/AdminMonsterRace.java b/L2J_Mobius_1.0_Ertheia/dist/game/data/scripts/handlers/admincommandhandlers/AdminMonsterRace.java deleted file mode 100644 index e465186490..0000000000 --- a/L2J_Mobius_1.0_Ertheia/dist/game/data/scripts/handlers/admincommandhandlers/AdminMonsterRace.java +++ /dev/null @@ -1,166 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * This program 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. - * - * This program 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 . - */ -package handlers.admincommandhandlers; - -import org.l2jmobius.commons.concurrent.ThreadPool; -import org.l2jmobius.gameserver.MonsterRace; -import org.l2jmobius.gameserver.handler.IAdminCommandHandler; -import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance; -import org.l2jmobius.gameserver.network.SystemMessageId; -import org.l2jmobius.gameserver.network.serverpackets.DeleteObject; -import org.l2jmobius.gameserver.network.serverpackets.MonRaceInfo; -import org.l2jmobius.gameserver.network.serverpackets.PlaySound; -import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; - -/** - * This class handles following admin commands: - invul = turns invulnerability on/off - * @version $Revision: 1.1.6.4 $ $Date: 2007/07/31 10:06:00 $ - */ -public class AdminMonsterRace implements IAdminCommandHandler -{ - private static final String[] ADMIN_COMMANDS = - { - "admin_mons" - }; - - static int state = -1; - - @Override - public boolean useAdminCommand(String command, PlayerInstance activeChar) - { - if (command.equalsIgnoreCase("admin_mons")) - { - handleSendPacket(activeChar); - } - return true; - } - - @Override - public String[] getAdminCommandList() - { - return ADMIN_COMMANDS; - } - - private void handleSendPacket(PlayerInstance activeChar) - { - /* - * -1 0 to initialize the race 0 15322 to start race 13765 -1 in middle of race -1 0 to end the race 8003 to 8027 - */ - - final int[][] codes = - { - { - -1, - 0 - }, - { - 0, - 15322 - }, - { - 13765, - -1 - }, - { - -1, - 0 - } - }; - final MonsterRace race = MonsterRace.getInstance(); - - if (state == -1) - { - state++; - race.newRace(); - race.newSpeeds(); - final MonRaceInfo spk = new MonRaceInfo(codes[state][0], codes[state][1], race.getMonsters(), race.getSpeeds()); - activeChar.sendPacket(spk); - activeChar.broadcastPacket(spk); - } - else if (state == 0) - { - state++; - final SystemMessage sm = new SystemMessage(SystemMessageId.THEY_RE_OFF); - sm.addInt(0); - activeChar.sendPacket(sm); - final PlaySound sRace = new PlaySound(1, "S_Race", 0, 0, 0, 0, 0); - activeChar.sendPacket(sRace); - activeChar.broadcastPacket(sRace); - final PlaySound sRace2 = new PlaySound(0, "ItemSound2.race_start", 1, 121209259, 12125, 182487, -3559); - activeChar.sendPacket(sRace2); - activeChar.broadcastPacket(sRace2); - final MonRaceInfo spk = new MonRaceInfo(codes[state][0], codes[state][1], race.getMonsters(), race.getSpeeds()); - activeChar.sendPacket(spk); - activeChar.broadcastPacket(spk); - - ThreadPool.schedule(new RunRace(codes, activeChar), 5000); - } - - } - - private class RunRace implements Runnable - { - - private final int[][] codes; - private final PlayerInstance activeChar; - - public RunRace(int[][] pCodes, PlayerInstance pActiveChar) - { - codes = pCodes; - activeChar = pActiveChar; - } - - @Override - public void run() - { - // int[][] speeds1 = MonsterRace.getInstance().getSpeeds(); - // MonsterRace.getInstance().newSpeeds(); - // int[][] speeds2 = MonsterRace.getInstance().getSpeeds(); - /* - * int[] speed = new int[8]; for (int i=0; i<8; i++) { for (int j=0; j<20; j++) { //LOGGER.info("Adding "+speeds1[i][j] +" and "+ speeds2[i][j]); speed[i] += (speeds1[i][j]*1); // + (speeds2[i][j]*1); } LOGGER.info("Total speed for "+(i+1)+" = "+speed[i]); } - */ - - final MonRaceInfo spk = new MonRaceInfo(codes[2][0], codes[2][1], MonsterRace.getInstance().getMonsters(), MonsterRace.getInstance().getSpeeds()); - activeChar.sendPacket(spk); - activeChar.broadcastPacket(spk); - ThreadPool.schedule(new RunEnd(activeChar), 30000); - } - } - - private static class RunEnd implements Runnable - { - private final PlayerInstance activeChar; - - public RunEnd(PlayerInstance pActiveChar) - { - activeChar = pActiveChar; - } - - @Override - public void run() - { - DeleteObject obj = null; - for (int i = 0; i < 8; i++) - { - obj = new DeleteObject(MonsterRace.getInstance().getMonsters()[i]); - activeChar.sendPacket(obj); - activeChar.broadcastPacket(obj); - } - state = -1; - } - } -} diff --git a/L2J_Mobius_1.0_Ertheia/java/org/l2jmobius/gameserver/GameServer.java b/L2J_Mobius_1.0_Ertheia/java/org/l2jmobius/gameserver/GameServer.java index 3745068ff7..4846f1d22f 100644 --- a/L2J_Mobius_1.0_Ertheia/java/org/l2jmobius/gameserver/GameServer.java +++ b/L2J_Mobius_1.0_Ertheia/java/org/l2jmobius/gameserver/GameServer.java @@ -140,6 +140,7 @@ import org.l2jmobius.gameserver.instancemanager.SiegeGuardManager; import org.l2jmobius.gameserver.instancemanager.SiegeManager; import org.l2jmobius.gameserver.instancemanager.WalkingManager; import org.l2jmobius.gameserver.instancemanager.ZoneManager; +import org.l2jmobius.gameserver.instancemanager.games.MonsterRace; import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.entity.Hero; import org.l2jmobius.gameserver.model.events.EventDispatcher; @@ -396,10 +397,8 @@ public class GameServer ItemsAutoDestroy.getInstance(); } - if (Config.ALLOW_RACE) - { - MonsterRace.getInstance(); - } + MonsterRace.getInstance(); + TaskManager.getInstance(); AntiFeedManager.getInstance().registerEvent(AntiFeedManager.GAME_ID); diff --git a/L2J_Mobius_1.0_Ertheia/java/org/l2jmobius/gameserver/MonsterRace.java b/L2J_Mobius_1.0_Ertheia/java/org/l2jmobius/gameserver/MonsterRace.java deleted file mode 100644 index cdd92a9a07..0000000000 --- a/L2J_Mobius_1.0_Ertheia/java/org/l2jmobius/gameserver/MonsterRace.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * This program 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. - * - * This program 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 . - */ -package org.l2jmobius.gameserver; - -import java.util.logging.Level; -import java.util.logging.Logger; - -import org.l2jmobius.commons.util.Rnd; -import org.l2jmobius.gameserver.data.xml.impl.NpcData; -import org.l2jmobius.gameserver.model.actor.Npc; -import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate; - -public class MonsterRace -{ - protected static final Logger LOGGER = Logger.getLogger(MonsterRace.class.getName()); - - private final Npc[] _monsters; - private int[][] _speeds; - private final int[] _first; - private final int[] _second; - - protected MonsterRace() - { - _monsters = new Npc[8]; - _speeds = new int[8][20]; - _first = new int[2]; - _second = new int[2]; - } - - public void newRace() - { - int random = 0; - - for (int i = 0; i < 8; i++) - { - final int id = 31003; - random = Rnd.get(24); - while (true) - { - for (int j = i - 1; j >= 0; j--) - { - if (_monsters[j].getTemplate().getId() == (id + random)) - { - random = Rnd.get(24); - } - } - break; - } - try - { - final NpcTemplate template = NpcData.getInstance().getTemplate(id + random); - _monsters[i] = (Npc) Class.forName("org.l2jmobius.gameserver.model.actor.instance." + template.getType() + "Instance").getConstructors()[0].newInstance(template); - } - catch (Exception e) - { - LOGGER.log(Level.WARNING, "Unable to create monster!", e); - } - } - newSpeeds(); - } - - public void newSpeeds() - { - _speeds = new int[8][20]; - int total = 0; - _first[1] = 0; - _second[1] = 0; - for (int i = 0; i < 8; i++) - { - total = 0; - for (int j = 0; j < 20; j++) - { - _speeds[i][j] = j == 19 ? 100 : Rnd.get(60) + 65; - total += _speeds[i][j]; - } - if (total >= _first[1]) - { - _second[0] = _first[0]; - _second[1] = _first[1]; - _first[0] = 8 - i; - _first[1] = total; - } - else if (total >= _second[1]) - { - _second[0] = 8 - i; - _second[1] = total; - } - } - } - - /** - * @return Returns the monsters. - */ - public Npc[] getMonsters() - { - return _monsters; - } - - /** - * @return Returns the speeds. - */ - public int[][] getSpeeds() - { - return _speeds; - } - - public int getFirstPlace() - { - return _first[0]; - } - - public int getSecondPlace() - { - return _second[0]; - } - - public static MonsterRace getInstance() - { - return SingletonHolder.INSTANCE; - } - - private static class SingletonHolder - { - protected static final MonsterRace INSTANCE = new MonsterRace(); - } -} diff --git a/L2J_Mobius_1.0_Ertheia/java/org/l2jmobius/gameserver/instancemanager/games/MonsterRace.java b/L2J_Mobius_1.0_Ertheia/java/org/l2jmobius/gameserver/instancemanager/games/MonsterRace.java new file mode 100644 index 0000000000..2fa4c519f6 --- /dev/null +++ b/L2J_Mobius_1.0_Ertheia/java/org/l2jmobius/gameserver/instancemanager/games/MonsterRace.java @@ -0,0 +1,623 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program 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. + * + * This program 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 . + */ +package org.l2jmobius.gameserver.instancemanager.games; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.Config; +import org.l2jmobius.commons.concurrent.ThreadPool; +import org.l2jmobius.commons.database.DatabaseFactory; +import org.l2jmobius.commons.util.Rnd; +import org.l2jmobius.gameserver.data.xml.impl.NpcData; +import org.l2jmobius.gameserver.model.actor.Npc; +import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.model.zone.type.DerbyTrackZone; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.serverpackets.DeleteObject; +import org.l2jmobius.gameserver.network.serverpackets.MonRaceInfo; +import org.l2jmobius.gameserver.network.serverpackets.PlaySound; +import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; +import org.l2jmobius.gameserver.util.Broadcast; + +public class MonsterRace +{ + protected static final Logger LOGGER = Logger.getLogger(MonsterRace.class.getName()); + + public static enum RaceState + { + ACCEPTING_BETS, + WAITING, + STARTING_RACE, + RACE_END + } + + protected static final PlaySound SOUND_1 = new PlaySound(1, "S_Race", 0, 0, 0, 0, 0); + protected static final PlaySound SOUND_2 = new PlaySound("ItemSound2.race_start"); + + protected static final int[][] CODES = + { + { + -1, + 0 + }, + { + 0, + 15322 + }, + { + 13765, + -1 + } + }; + + protected final List _npcTemplates = new ArrayList<>(); // List holding npc templates, shuffled on a new race. + protected final List _history = new ArrayList<>(); // List holding old race records. + protected final Map _betsPerLane = new ConcurrentHashMap<>(); // Map holding all bets for each lane ; values setted to 0 after every race. + protected final List _odds = new ArrayList<>(); // List holding sorted odds per lane ; cleared at new odds calculation. + + protected int _raceNumber = 1; + protected int _finalCountdown = 0; + protected RaceState _state = RaceState.RACE_END; + + protected MonRaceInfo _packet; + + private final Npc[] _monsters = new Npc[8]; + private int[][] _speeds = new int[8][20]; + private final int[] _first = new int[2]; + private final int[] _second = new int[2]; + + protected MonsterRace() + { + if (!Config.ALLOW_RACE) + { + return; + } + + // Feed _history with previous race results. + loadHistory(); + + // Feed _betsPerLane with stored informations on bets. + loadBets(); + + // Feed _npcTemplates, we will only have to shuffle it when needed. + for (int i = 31003; i < 31027; i++) + { + _npcTemplates.add(i); + } + + ThreadPool.scheduleAtFixedRate(new Announcement(), 0, 1000); + } + + public static class HistoryInfo + { + private final int _raceId; + private int _first; + private int _second; + private double _oddRate; + + public HistoryInfo(int raceId, int first, int second, double oddRate) + { + _raceId = raceId; + _first = first; + _second = second; + _oddRate = oddRate; + } + + public int getRaceId() + { + return _raceId; + } + + public int getFirst() + { + return _first; + } + + public int getSecond() + { + return _second; + } + + public double getOddRate() + { + return _oddRate; + } + + public void setFirst(int first) + { + _first = first; + } + + public void setSecond(int second) + { + _second = second; + } + + public void setOddRate(double oddRate) + { + _oddRate = oddRate; + } + } + + private class Announcement implements Runnable + { + public Announcement() + { + } + + @Override + public void run() + { + if (_finalCountdown > 1200) + { + _finalCountdown = 0; + } + + switch (_finalCountdown) + { + case 0: + { + newRace(); + newSpeeds(); + + _state = RaceState.ACCEPTING_BETS; + _packet = new MonRaceInfo(CODES[0][0], CODES[0][1], getMonsters(), getSpeeds()); + + final SystemMessage msg = new SystemMessage(SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, _packet, msg); + 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 + { + final SystemMessage msg = new SystemMessage(SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + break; + } + case 300: // 5 min + { + final SystemMessage msg = new SystemMessage(SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S); + msg2.addInt(10); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + break; + } + case 600: // 10 min + { + final SystemMessage msg = new SystemMessage(SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S); + msg2.addInt(5); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + break; + } + case 840: // 14 min + { + final SystemMessage msg = new SystemMessage(SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S); + msg2.addInt(1); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + break; + } + case 900: // 15 min + { + _state = RaceState.WAITING; + + calculateOdds(); + + final SystemMessage msg = new SystemMessage(SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.TICKETS_SALES_ARE_CLOSED_FOR_MONSTER_RACE_S1_ODDS_ARE_POSTED); + msg2.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + break; + } + case 960: // 16 min + case 1020: // 17 min + { + final int minutes = (_finalCountdown == 960) ? 2 : 1; + final SystemMessage msg = new SystemMessage(SystemMessageId.MONSTER_RACE_S2_WILL_BEGIN_IN_S1_MINUTE_S); + msg.addInt(minutes); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + break; + } + case 1050: // 17 min 30 sec + { + final SystemMessage msg = new SystemMessage(SystemMessageId.MONSTER_RACE_S1_WILL_BEGIN_IN_30_SECONDS); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + break; + } + case 1070: // 17 min 50 sec + { + final SystemMessage msg = new SystemMessage(SystemMessageId.MONSTER_RACE_S1_IS_ABOUT_TO_BEGIN_COUNTDOWN_IN_FIVE_SECONDS); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + 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; + final SystemMessage msg = new SystemMessage(SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SECOND_S); + msg.addInt(seconds); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + break; + } + case 1080: // 18 min + { + _state = RaceState.STARTING_RACE; + _packet = new MonRaceInfo(CODES[1][0], CODES[1][1], getMonsters(), getSpeeds()); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, new SystemMessage(SystemMessageId.THEY_RE_OFF), SOUND_1, SOUND_2, _packet); + break; + } + case 1085: // 18 min 5 sec + { + _packet = new MonRaceInfo(CODES[2][0], CODES[2][1], getMonsters(), getSpeeds()); + + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, _packet); + break; + } + case 1115: // 18 min 35 sec + { + _state = RaceState.RACE_END; + + // Populate history info with data, stores it in database. + final HistoryInfo info = _history.get(_history.size() - 1); + info.setFirst(getFirstPlace()); + info.setSecond(getSecondPlace()); + info.setOddRate(_odds.get(getFirstPlace() - 1)); + + saveHistory(info); + clearBets(); + + final SystemMessage msg = new SystemMessage(SystemMessageId.FIRST_PRIZE_GOES_TO_THE_PLAYER_IN_LANE_S1_SECOND_PRIZE_GOES_TO_THE_PLAYER_IN_LANE_S2); + msg.addInt(getFirstPlace()); + msg.addInt(getSecondPlace()); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.MONSTER_RACE_S1_IS_FINISHED); + msg2.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + _raceNumber++; + break; + } + case 1140: // 19 min + { + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, new DeleteObject(getMonsters()[0]), new DeleteObject(getMonsters()[1]), new DeleteObject(getMonsters()[2]), new DeleteObject(getMonsters()[3]), new DeleteObject(getMonsters()[4]), new DeleteObject(getMonsters()[5]), new DeleteObject(getMonsters()[6]), new DeleteObject(getMonsters()[7])); + break; + } + } + _finalCountdown += 1; + } + } + + public void newRace() + { + // Edit _history. + _history.add(new HistoryInfo(_raceNumber, 0, 0, 0)); + + // Randomize _npcTemplates. + Collections.shuffle(_npcTemplates); + + // Setup 8 new creatures ; pickup the first 8 from _npcTemplates. + for (int i = 0; i < 8; i++) + { + try + { + final NpcTemplate template = NpcData.getInstance().getTemplate(_npcTemplates.get(i)); + _monsters[i] = (Npc) Class.forName("org.l2jmobius.gameserver.model.actor.instance." + template.getType() + "Instance").getConstructors()[0].newInstance(template); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "", e); + } + } + } + + public void newSpeeds() + { + _speeds = new int[8][20]; + int total = 0; + _first[1] = 0; + _second[1] = 0; + + for (int i = 0; i < 8; i++) + { + total = 0; + for (int j = 0; j < 20; j++) + { + if (j == 19) + { + _speeds[i][j] = 100; + } + else + { + _speeds[i][j] = Rnd.get(60) + 65; + } + total += _speeds[i][j]; + } + + if (total >= _first[1]) + { + _second[0] = _first[0]; + _second[1] = _first[1]; + _first[0] = 8 - i; + _first[1] = total; + } + else if (total >= _second[1]) + { + _second[0] = 8 - i; + _second[1] = total; + } + } + } + + /** + * Load past races informations, feeding _history arrayList.
+ * Also sets _raceNumber, based on latest HistoryInfo loaded. + */ + protected void loadHistory() + { + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("SELECT * FROM mdt_history"); + ResultSet rset = statement.executeQuery(); + + while (rset.next()) + { + _history.add(new HistoryInfo(rset.getInt("race_id"), rset.getInt("first"), rset.getInt("second"), rset.getDouble("odd_rate"))); + _raceNumber++; + } + rset.close(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't load history: " + e.getMessage(), e); + } + LOGGER.info("MonsterRace: loaded " + _history.size() + " records, currently on race #" + _raceNumber); + } + + /** + * Save an history record into database. + * @param history The infos to store. + */ + protected void saveHistory(HistoryInfo history) + { + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("INSERT INTO mdt_history (race_id, first, second, odd_rate) VALUES (?,?,?,?)"); + statement.setInt(1, history.getRaceId()); + statement.setInt(2, history.getFirst()); + statement.setInt(3, history.getSecond()); + statement.setDouble(4, history.getOddRate()); + statement.execute(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't save history: " + e.getMessage(), e); + } + } + + /** + * Load current bets per lane ; initialize the map keys. + */ + protected void loadBets() + { + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("SELECT * FROM mdt_bets"); + ResultSet rset = statement.executeQuery(); + + while (rset.next()) + { + setBetOnLane(rset.getInt("lane_id"), rset.getLong("bet"), false); + } + + rset.close(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't load bets: " + e.getMessage(), e); + } + } + + /** + * Save the current lane bet into database. + * @param lane : The lane to affect. + * @param sum : The sum to set. + */ + protected void saveBet(int lane, long sum) + { + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("REPLACE INTO mdt_bets (lane_id, bet) VALUES (?,?)"); + statement.setInt(1, lane); + statement.setLong(2, sum); + statement.execute(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't save bet: " + e.getMessage(), e); + } + } + + /** + * Clear all lanes bets, either on database or Map. + */ + protected void clearBets() + { + for (int key : _betsPerLane.keySet()) + { + _betsPerLane.put(key, 0L); + } + + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("UPDATE mdt_bets SET bet = 0"); + statement.execute(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't clear bets: " + e.getMessage(), e); + } + } + + /** + * Setup lane bet, based on previous value (if any). + * @param lane : The lane to edit. + * @param amount : The amount to add. + * @param saveOnDb : Should it be saved on db or not. + */ + public void setBetOnLane(int lane, long amount, boolean saveOnDb) + { + final long sum = (_betsPerLane.containsKey(lane)) ? _betsPerLane.get(lane) + amount : amount; + + _betsPerLane.put(lane, sum); + + if (saveOnDb) + { + saveBet(lane, sum); + } + } + + /** + * Calculate odds for every lane, based on others lanes. + */ + protected void calculateOdds() + { + // Clear previous List holding old odds. + _odds.clear(); + + // Sort bets lanes per lane. + final Map 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; + } + + // 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)); + } + } + + public Npc[] getMonsters() + { + return _monsters; + } + + public int[][] getSpeeds() + { + return _speeds; + } + + public int getFirstPlace() + { + return _first[0]; + } + + public int getSecondPlace() + { + return _second[0]; + } + + public MonRaceInfo getRacePacket() + { + return _packet; + } + + public RaceState getCurrentRaceState() + { + return _state; + } + + public int getRaceNumber() + { + return _raceNumber; + } + + public List getHistory() + { + return _history; + } + + public List getOdds() + { + return _odds; + } + + public static MonsterRace getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final MonsterRace INSTANCE = new MonsterRace(); + } +} \ No newline at end of file diff --git a/L2J_Mobius_1.0_Ertheia/java/org/l2jmobius/gameserver/model/actor/instance/RaceManagerInstance.java b/L2J_Mobius_1.0_Ertheia/java/org/l2jmobius/gameserver/model/actor/instance/RaceManagerInstance.java index f893d4f5f2..2375543774 100644 --- a/L2J_Mobius_1.0_Ertheia/java/org/l2jmobius/gameserver/model/actor/instance/RaceManagerInstance.java +++ b/L2J_Mobius_1.0_Ertheia/java/org/l2jmobius/gameserver/model/actor/instance/RaceManagerInstance.java @@ -16,69 +16,26 @@ */ package org.l2jmobius.gameserver.model.actor.instance; -import java.util.Collection; -import java.util.concurrent.ConcurrentHashMap; +import java.util.List; +import java.util.Locale; import org.l2jmobius.Config; -import org.l2jmobius.commons.concurrent.ThreadPool; -import org.l2jmobius.gameserver.MonsterRace; -import org.l2jmobius.gameserver.enums.InstanceType; +import org.l2jmobius.commons.util.StringUtil; import org.l2jmobius.gameserver.idfactory.IdFactory; -import org.l2jmobius.gameserver.model.actor.Creature; +import org.l2jmobius.gameserver.instancemanager.games.MonsterRace; +import org.l2jmobius.gameserver.instancemanager.games.MonsterRace.HistoryInfo; +import org.l2jmobius.gameserver.instancemanager.games.MonsterRace.RaceState; import org.l2jmobius.gameserver.model.actor.Npc; import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate; -import org.l2jmobius.gameserver.model.itemcontainer.Inventory; import org.l2jmobius.gameserver.model.items.instance.ItemInstance; import org.l2jmobius.gameserver.network.SystemMessageId; import org.l2jmobius.gameserver.network.serverpackets.ActionFailed; -import org.l2jmobius.gameserver.network.serverpackets.DeleteObject; -import org.l2jmobius.gameserver.network.serverpackets.IClientOutgoingPacket; -import org.l2jmobius.gameserver.network.serverpackets.InventoryUpdate; -import org.l2jmobius.gameserver.network.serverpackets.MonRaceInfo; import org.l2jmobius.gameserver.network.serverpackets.NpcHtmlMessage; -import org.l2jmobius.gameserver.network.serverpackets.PlaySound; import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; -import org.l2jmobius.gameserver.util.Broadcast; public class RaceManagerInstance extends Npc { - public static final int LANES = 8; - public static final int WINDOW_START = 0; - - private static Collection _managers; - protected static int _raceNumber = 4; - - // Time Constants - private static final long SECOND = 1000; - private static final long MINUTE = 60 * SECOND; - - private static int _minutes = 5; - - // States - private static final int ACCEPTING_BETS = 0; - private static final int WAITING = 1; - private static final int STARTING_RACE = 2; - private static final int RACE_END = 3; - private static int _state = RACE_END; - - protected static final int[][] _codes = - { - { - -1, - 0 - }, - { - 0, - 15322 - }, - { - 13765, - -1 - } - }; - private static boolean _notInitialized = true; - protected static MonRaceInfo _packet; - protected static final int[] _cost = + protected static final int TICKET_PRICES[] = { 100, 500, @@ -93,223 +50,167 @@ public class RaceManagerInstance extends Npc public RaceManagerInstance(NpcTemplate template) { super(template); - setInstanceType(InstanceType.RaceManagerInstance); - - if (!Config.ALLOW_RACE) - { - return; - } - - if (_notInitialized) - { - _notInitialized = false; - - _managers = ConcurrentHashMap.newKeySet(); - - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1), 0, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1), 30 * SECOND, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1), MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1), MINUTE + (30 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S), 2 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S), 3 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S), 4 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S), 5 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S), 6 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKETS_SALES_ARE_CLOSED_FOR_MONSTER_RACE_S1_ODDS_ARE_POSTED), 7 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.MONSTER_RACE_S2_WILL_BEGIN_IN_S1_MINUTE_S), 7 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.MONSTER_RACE_S2_WILL_BEGIN_IN_S1_MINUTE_S), 8 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.MONSTER_RACE_S1_WILL_BEGIN_IN_30_SECONDS), (8 * MINUTE) + (30 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.MONSTER_RACE_S1_IS_ABOUT_TO_BEGIN_COUNTDOWN_IN_FIVE_SECONDS), (8 * MINUTE) + (50 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SECOND_S), (8 * MINUTE) + (55 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SECOND_S), (8 * MINUTE) + (56 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SECOND_S), (8 * MINUTE) + (57 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SECOND_S), (8 * MINUTE) + (58 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SECOND_S), (8 * MINUTE) + (59 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.THEY_RE_OFF), 9 * MINUTE, 10 * MINUTE); - } - _managers.add(this); - } - - @Override - public boolean isAutoAttackable(Creature attacker) - { - if (attacker.isMonster()) - { - return true; - } - - return super.isAutoAttackable(attacker); - } - - class Announcement implements Runnable - { - private final SystemMessageId _type; - - public Announcement(SystemMessageId pType) - { - _type = pType; - } - - @Override - public void run() - { - makeAnnouncement(_type); - } - } - - public void makeAnnouncement(SystemMessageId type) - { - final SystemMessage sm = new SystemMessage(type); - switch (type.getId()) - { - case 816: // SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1 - case 817: // SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1 - { - if (_state != ACCEPTING_BETS) - {// LOGGER.info("Race Initializing"); - _state = ACCEPTING_BETS; - startRace(); - } // else{LOGGER.info("Race open");} - sm.addInt(_raceNumber); - break; - } - case 818: // SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S - case 820: // SystemMessageId.MONSTER_RACE_S2_WILL_BEGIN_IN_S1_MINUTE_S - case 823: // SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SECOND_S - { - sm.addInt(_minutes); - if (type.getId() == 820) - { - sm.addInt(_raceNumber); - } - _minutes--; - break; - } - case 819: // SystemMessageId.TICKETS_SALES_ARE_CLOSED_FOR_MONSTER_RACE_S1_ODDS_ARE_POSTED - { - // LOGGER.info("Sales closed"); - sm.addInt(_raceNumber); - _state = WAITING; - _minutes = 2; - break; - } - case 821: // SystemMessageId.MONSTER_RACE_S1_WILL_BEGIN_IN_30_SECONDS - case 822: // SystemMessageId.MONSTER_RACE_S1_IS_ABOUT_TO_BEGIN_COUNTDOWN_IN_FIVE_SECONDS - case 825: // SystemMessageId.MONSTER_RACE_S1_IS_FINISHED - { - sm.addInt(_raceNumber); - _minutes = 5; - break; - } - case 826: // SystemMessageId.FIRST_PRIZE_GOES_TO_THE_PLAYER_IN_LANE_S1_SECOND_PRIZE_GOES_TO_THE_PLAYER_IN_LANE_S2 - { - // LOGGER.info("Placing"); - _state = RACE_END; - sm.addInt(MonsterRace.getInstance().getFirstPlace()); - sm.addInt(MonsterRace.getInstance().getSecondPlace()); - break; - } - } - // _logn.info("Counter: "+minutes); - // LOGGER.info("State: "+state); - broadcast(sm); - // LOGGER.info("Player's known: "+getKnownPlayers().size()); - - if (type == SystemMessageId.THEY_RE_OFF) - { - // LOGGER.info("Starting race"); - _state = STARTING_RACE; - startRace(); - _minutes = 5; - } - } - - protected void broadcast(IClientOutgoingPacket pkt) - { - for (RaceManagerInstance manager : _managers) - { - if (!manager.isDead()) - { - Broadcast.toKnownPlayers(manager, pkt); - } - } - } - - public void sendMonsterInfo() - { - broadcast(_packet); - } - - private void startRace() - { - final MonsterRace race = MonsterRace.getInstance(); - if (_state == STARTING_RACE) - { - // state++; - broadcast(new PlaySound(1, "S_Race", 0, 0, 0, 0, 0)); - broadcast(new PlaySound(0, "ItemSound2.race_start", 1, 121209259, 12125, 182487, -3559)); - _packet = new MonRaceInfo(_codes[1][0], _codes[1][1], race.getMonsters(), race.getSpeeds()); - sendMonsterInfo(); - - ThreadPool.schedule(new RunRace(), 5000); - } - else - { - // state++; - race.newRace(); - race.newSpeeds(); - _packet = new MonRaceInfo(_codes[0][0], _codes[0][1], race.getMonsters(), race.getSpeeds()); - sendMonsterInfo(); - } } @Override public void onBypassFeedback(PlayerInstance player, String command) { - if (command.startsWith("BuyTicket") && (_state != ACCEPTING_BETS)) - { - if (!Config.ALLOW_RACE) - { - return; - } - player.sendPacket(SystemMessageId.MONSTER_RACE_TICKETS_ARE_NO_LONGER_AVAILABLE); - command = "Chat 0"; - } - if (command.startsWith("ShowOdds") && (_state == ACCEPTING_BETS)) - { - if (!Config.ALLOW_RACE) - { - return; - } - player.sendPacket(SystemMessageId.MONSTER_RACE_PAYOUT_INFORMATION_IS_NOT_AVAILABLE_WHILE_TICKETS_ARE_BEING_SOLD); - command = "Chat 0"; - } - if (command.startsWith("BuyTicket")) { - if (!Config.ALLOW_RACE) + if (!Config.ALLOW_RACE || (MonsterRace.getInstance().getCurrentRaceState() != RaceState.ACCEPTING_BETS)) { + player.sendPacket(SystemMessageId.MONSTER_RACE_TICKETS_ARE_NO_LONGER_AVAILABLE); + super.onBypassFeedback(player, "Chat 0"); return; } + int 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))) { val = 0; } - showBuyTicket(player, val); + + String search, replace; + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + + if (val < 10) + { + html.setFile(player, getHtmlPath(getId(), 2, player)); + for (int i = 0; i < 8; i++) + { + int n = i + 1; + search = "Mob" + n; + html.replace(search, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); + } + search = "No1"; + if (val == 0) + { + html.replace(search, ""); + } + else + { + html.replace(search, val); + player.setRace(0, val); + } + } + else if (val < 20) + { + if (player.getRace(0) == 0) + { + return; + } + + html.setFile(player, getHtmlPath(getId(), 3, player)); + html.replace("0place", player.getRace(0)); + search = "Mob1"; + replace = MonsterRace.getInstance().getMonsters()[player.getRace(0) - 1].getTemplate().getName(); + html.replace(search, replace); + search = "0adena"; + + if (val == 10) + { + html.replace(search, ""); + } + else + { + html.replace(search, TICKET_PRICES[val - 11]); + player.setRace(1, val - 10); + } + } + else if (val == 20) + { + if ((player.getRace(0) == 0) || (player.getRace(1) == 0)) + { + return; + } + + html.setFile(player, getHtmlPath(getId(), 4, player)); + html.replace("0place", player.getRace(0)); + search = "Mob1"; + replace = MonsterRace.getInstance().getMonsters()[player.getRace(0) - 1].getTemplate().getName(); + 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); + } + else + { + if ((player.getRace(0) == 0) || (player.getRace(1) == 0)) + { + return; + } + + int ticket = player.getRace(0); + int priceId = player.getRace(1); + + if (!player.reduceAdena("Race", TICKET_PRICES[priceId - 1], this, true)) + { + return; + } + + player.setRace(0, 0); + player.setRace(1, 0); + + ItemInstance item = new ItemInstance(IdFactory.getInstance().getNextId(), 4443); + item.setCount(1); + item.setEnchantLevel(MonsterRace.getInstance().getRaceNumber()); + item.setCustomType1(ticket); + item.setCustomType2(TICKET_PRICES[priceId - 1] / 100); + + player.addItem("Race", item, player, false); + final SystemMessage msg = new SystemMessage(SystemMessageId.ACQUIRED_S1_S2); + msg.addInt(MonsterRace.getInstance().getRaceNumber()); + msg.addItemName(4443); + player.sendPacket(msg); + + // Refresh lane bet. + MonsterRace.getInstance().setBetOnLane(ticket, TICKET_PRICES[priceId - 1], true); + super.onBypassFeedback(player, "Chat 0"); + return; + } + html.replace("1race", MonsterRace.getInstance().getRaceNumber()); + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); } else if (command.equals("ShowOdds")) { - if (!Config.ALLOW_RACE) + if (!Config.ALLOW_RACE || (MonsterRace.getInstance().getCurrentRaceState() == RaceState.ACCEPTING_BETS)) { + player.sendPacket(SystemMessageId.MONSTER_RACE_PAYOUT_INFORMATION_IS_NOT_AVAILABLE_WHILE_TICKETS_ARE_BEING_SOLD); + super.onBypassFeedback(player, "Chat 0"); return; } - showOdds(player); + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 5, player)); + for (int i = 0; i < 8; i++) + { + final int n = i + 1; + + html.replace("Mob" + n, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); + + // Odd + final double odd = MonsterRace.getInstance().getOdds().get(i); + html.replace("Odd" + n, (odd > 0D) ? String.format(Locale.ENGLISH, "%.1f", odd) : "&$804;"); + } + html.replace("1race", MonsterRace.getInstance().getRaceNumber()); + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); } else if (command.equals("ShowInfo")) { @@ -317,269 +218,160 @@ public class RaceManagerInstance extends Npc { return; } - showMonsterInfo(player); + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 6, player)); + + for (int i = 0; i < 8; i++) + { + int n = i + 1; + String search = "Mob" + n; + html.replace(search, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); + } + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); } - else if (command.equals("calculateWin")) + else if (command.equals("ShowTickets")) { if (!Config.ALLOW_RACE) { + super.onBypassFeedback(player, "Chat 0"); return; } - // displayCalculateWinnings(player); + + // Generate data. + final StringBuilder sb = new StringBuilder(); + + // Retrieve player's tickets. + for (ItemInstance ticket : player.getInventory().getAllItemsByItemId(4443)) + { + // Don't list current race tickets. + if (ticket.getEnchantLevel() == MonsterRace.getInstance().getRaceNumber()) + { + continue; + } + + StringUtil.append(sb, "", "" + ticket.getEnchantLevel(), " Race Number", "" + ticket.getCustomType1(), " Number", "" + (ticket.getCustomType2() * 100), " Adena"); + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 7, player)); + html.replace("%tickets%", sb.toString()); + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); } - else if (command.equals("viewHistory")) + else if (command.startsWith("ShowTicket")) + { + // Retrieve ticket objectId. + final int val = Integer.parseInt(command.substring(11)); + if (!Config.ALLOW_RACE || (val == 0)) + { + super.onBypassFeedback(player, "Chat 0"); + return; + } + + // Retrieve ticket on player's inventory. + final ItemInstance 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; + + // Retrieve HistoryInfo for that race. + final HistoryInfo info = MonsterRace.getInstance().getHistory().get(raceId - 1); + if (info == null) + { + super.onBypassFeedback(player, "Chat 0"); + return; + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 8, player)); + html.replace("%raceId%", raceId); + html.replace("%lane%", lane); + html.replace("%bet%", bet); + html.replace("%firstLane%", info.getFirst()); + html.replace("%odd%", (lane == info.getFirst()) ? String.format(Locale.ENGLISH, "%.2f", info.getOddRate()) : "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 (!Config.ALLOW_RACE || (val == 0)) + { + super.onBypassFeedback(player, "Chat 0"); + return; + } + + // Delete ticket on player's inventory. + final ItemInstance 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; + + // Retrieve HistoryInfo for that race. + final HistoryInfo info = MonsterRace.getInstance().getHistory().get(raceId - 1); + if (info == 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()) ? info.getOddRate() : 0.01)), this, true); + } + + super.onBypassFeedback(player, "Chat 0"); + return; + } + else if (command.equals("ViewHistory")) { if (!Config.ALLOW_RACE) { + super.onBypassFeedback(player, "Chat 0"); return; } - // displayHistory(player); + + // Generate data. + final StringBuilder sb = new StringBuilder(); + + // Use whole history, pickup from 'last element' and stop at 'latest element - 7'. + final List history = MonsterRace.getInstance().getHistory(); + for (int i = history.size() - 1; i >= Math.max(0, history.size() - 7); i--) + { + final HistoryInfo info = history.get(i); + StringUtil.append(sb, "", "" + info.getRaceId(), " th", "" + info.getFirst(), " Lane ", "" + info.getSecond(), " Lane", String.format(Locale.ENGLISH, "%.2f", info.getOddRate()), " Times"); + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 9, player)); + html.replace("%infos%", sb.toString()); + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); } else { - // getKnownList().removeKnownObject(player); super.onBypassFeedback(player, command); } } - - public void showOdds(PlayerInstance player) - { - if (_state == ACCEPTING_BETS) - { - return; - } - final int npcId = getTemplate().getId(); - String filename; - String search; - final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); - filename = getHtmlPath(npcId, 5, player); - html.setFile(player, filename); - for (int i = 0; i < 8; i++) - { - final int n = i + 1; - search = "Mob" + n; - html.replace(search, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); - } - html.replace("1race", String.valueOf(_raceNumber)); - html.replace("%objectId%", String.valueOf(getObjectId())); - player.sendPacket(html); - player.sendPacket(ActionFailed.STATIC_PACKET); - } - - public void showMonsterInfo(PlayerInstance player) - { - final int npcId = getTemplate().getId(); - String filename; - String search; - final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); - filename = getHtmlPath(npcId, 6, player); - html.setFile(player, filename); - for (int i = 0; i < 8; i++) - { - final int n = i + 1; - search = "Mob" + n; - html.replace(search, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); - } - html.replace("%objectId%", String.valueOf(getObjectId())); - player.sendPacket(html); - player.sendPacket(ActionFailed.STATIC_PACKET); - } - - public void showBuyTicket(PlayerInstance player, int value) - { - if (_state != ACCEPTING_BETS) - { - return; - } - final int npcId = getTemplate().getId(); - SystemMessage sm; - String filename; - String search; - String replace; - final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); - if (value < 10) - { - filename = getHtmlPath(npcId, 2, player); - html.setFile(player, filename); - for (int i = 0; i < 8; i++) - { - final int n = i + 1; - search = "Mob" + n; - html.replace(search, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); - } - search = "No1"; - if (value == 0) - { - html.replace(search, ""); - } - else - { - html.replace(search, Integer.toString(value)); - player.setRace(0, value); - } - } - else if (value < 20) - { - if (player.getRace(0) == 0) - { - return; - } - filename = getHtmlPath(npcId, 3, player); - html.setFile(player, filename); - html.replace("0place", Integer.toString(player.getRace(0))); - search = "Mob1"; - replace = MonsterRace.getInstance().getMonsters()[player.getRace(0) - 1].getTemplate().getName(); - html.replace(search, replace); - search = "0adena"; - if (value == 10) - { - html.replace(search, ""); - } - else - { - html.replace(search, Integer.toString(_cost[value - 11])); - player.setRace(1, value - 10); - } - } - else if (value == 20) - { - if ((player.getRace(0) == 0) || (player.getRace(1) == 0)) - { - return; - } - filename = getHtmlPath(npcId, 4, player); - html.setFile(player, filename); - html.replace("0place", Integer.toString(player.getRace(0))); - search = "Mob1"; - replace = MonsterRace.getInstance().getMonsters()[player.getRace(0) - 1].getTemplate().getName(); - html.replace(search, replace); - search = "0adena"; - final int price = _cost[player.getRace(1) - 1]; - html.replace(search, Integer.toString(price)); - search = "0tax"; - final int tax = 0; - html.replace(search, Integer.toString(tax)); - search = "0total"; - final int total = price + tax; - html.replace(search, Integer.toString(total)); - } - else - { - if ((player.getRace(0) == 0) || (player.getRace(1) == 0)) - { - return; - } - final int ticket = player.getRace(0); - final int priceId = player.getRace(1); - if (!player.reduceAdena("Race", _cost[priceId - 1], this, true)) - { - return; - } - player.setRace(0, 0); - player.setRace(1, 0); - sm = new SystemMessage(SystemMessageId.ACQUIRED_S1_S2); - sm.addInt(_raceNumber); - sm.addItemName(4443); - player.sendPacket(sm); - final ItemInstance item = new ItemInstance(IdFactory.getInstance().getNextId(), 4443); - item.setCount(1); - item.setEnchantLevel(_raceNumber); - item.setCustomType1(ticket); - item.setCustomType2(_cost[priceId - 1] / 100); - player.getInventory().addItem("Race", item, player, this); - final InventoryUpdate iu = new InventoryUpdate(); - iu.addItem(item); - final ItemInstance adenaupdate = player.getInventory().getItemByItemId(Inventory.ADENA_ID); - iu.addModifiedItem(adenaupdate); - player.sendInventoryUpdate(iu); - return; - } - html.replace("1race", String.valueOf(_raceNumber)); - html.replace("%objectId%", String.valueOf(getObjectId())); - player.sendPacket(html); - player.sendPacket(ActionFailed.STATIC_PACKET); - } - - public static class Race - { - private final Info[] _info; - - public Race(Info[] pInfo) - { - _info = pInfo; - } - - public Info getLaneInfo(int lane) - { - return _info[lane]; - } - - public class Info - { - private final int _id; - private final int _place; - private final int _odds; - private final int _payout; - - public Info(int pId, int pPlace, int pOdds, int pPayout) - { - _id = pId; - _place = pPlace; - _odds = pOdds; - _payout = pPayout; - } - - public int getId() - { - return _id; - } - - public int getOdds() - { - return _odds; - } - - public int getPayout() - { - return _payout; - } - - public int getPlace() - { - return _place; - } - } - - } - - class RunRace implements Runnable - { - @Override - public void run() - { - _packet = new MonRaceInfo(_codes[2][0], _codes[2][1], MonsterRace.getInstance().getMonsters(), MonsterRace.getInstance().getSpeeds()); - sendMonsterInfo(); - ThreadPool.schedule(new RunEnd(), 30000); - } - } - - class RunEnd implements Runnable - { - @Override - public void run() - { - makeAnnouncement(SystemMessageId.FIRST_PRIZE_GOES_TO_THE_PLAYER_IN_LANE_S1_SECOND_PRIZE_GOES_TO_THE_PLAYER_IN_LANE_S2); - makeAnnouncement(SystemMessageId.MONSTER_RACE_S1_IS_FINISHED); - _raceNumber++; - - DeleteObject obj = null; - for (int i = 0; i < 8; i++) - { - obj = new DeleteObject(MonsterRace.getInstance().getMonsters()[i]); - broadcast(obj); - MonsterRace.getInstance().getMonsters()[i].deleteMe(); - } - } - } - } diff --git a/L2J_Mobius_1.0_Ertheia/java/org/l2jmobius/gameserver/util/Broadcast.java b/L2J_Mobius_1.0_Ertheia/java/org/l2jmobius/gameserver/util/Broadcast.java index 60eb0ff361..8d751afc53 100644 --- a/L2J_Mobius_1.0_Ertheia/java/org/l2jmobius/gameserver/util/Broadcast.java +++ b/L2J_Mobius_1.0_Ertheia/java/org/l2jmobius/gameserver/util/Broadcast.java @@ -20,10 +20,12 @@ import java.util.logging.Level; import java.util.logging.Logger; import org.l2jmobius.gameserver.enums.ChatType; +import org.l2jmobius.gameserver.instancemanager.ZoneManager; import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.actor.Creature; import org.l2jmobius.gameserver.model.actor.Summon; import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance; +import org.l2jmobius.gameserver.model.zone.ZoneType; import org.l2jmobius.gameserver.network.serverpackets.CharInfo; import org.l2jmobius.gameserver.network.serverpackets.CreatureSay; import org.l2jmobius.gameserver.network.serverpackets.ExShowScreenMessage; @@ -191,4 +193,29 @@ public class Broadcast { toAllOnlinePlayers(new ExShowScreenMessage(text, 10000)); } + + /** + * Send a packet to all players in a specific zone type. + * @param ZoneType. + * @param zoneType : The zone type to send packets. + * @param packets : The packets to send. + */ + public static void toAllPlayersInZoneType(Class zoneType, IClientOutgoingPacket... packets) + { + for (ZoneType zone : ZoneManager.getInstance().getAllZones(zoneType)) + { + for (Creature creature : zone.getCharactersInside()) + { + if (creature == null) + { + continue; + } + + for (IClientOutgoingPacket packet : packets) + { + creature.sendPacket(packet); + } + } + } + } } diff --git a/L2J_Mobius_2.5_Underground/dist/db_installer/sql/game/mdt_bets.sql b/L2J_Mobius_2.5_Underground/dist/db_installer/sql/game/mdt_bets.sql new file mode 100644 index 0000000000..8d7d4cb738 --- /dev/null +++ b/L2J_Mobius_2.5_Underground/dist/db_installer/sql/game/mdt_bets.sql @@ -0,0 +1,15 @@ +CREATE TABLE IF NOT EXISTS `mdt_bets` ( + `lane_id` INT(1) DEFAULT 0, + `bet` INT DEFAULT 0, + PRIMARY KEY (`lane_id`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci; + +INSERT INTO `mdt_bets` VALUES +('1','0'), +('2','0'), +('3','0'), +('4','0'), +('5','0'), +('6','0'), +('7','0'), +('8','0'); \ No newline at end of file diff --git a/L2J_Mobius_2.5_Underground/dist/db_installer/sql/game/mdt_history.sql b/L2J_Mobius_2.5_Underground/dist/db_installer/sql/game/mdt_history.sql new file mode 100644 index 0000000000..06978f3377 --- /dev/null +++ b/L2J_Mobius_2.5_Underground/dist/db_installer/sql/game/mdt_history.sql @@ -0,0 +1,7 @@ +CREATE TABLE IF NOT EXISTS `mdt_history` ( + `race_id` MEDIUMINT DEFAULT 0, + `first` INT(1) DEFAULT 0, + `second` INT(1) DEFAULT 0, + `odd_rate` DOUBLE(10,2) DEFAULT 0, + PRIMARY KEY (`race_id`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci; \ No newline at end of file diff --git a/L2J_Mobius_2.5_Underground/dist/game/config/AdminCommands.xml b/L2J_Mobius_2.5_Underground/dist/game/config/AdminCommands.xml index 36ab1240b5..215b7c679a 100644 --- a/L2J_Mobius_2.5_Underground/dist/game/config/AdminCommands.xml +++ b/L2J_Mobius_2.5_Underground/dist/game/config/AdminCommands.xml @@ -416,9 +416,6 @@ - - - diff --git a/L2J_Mobius_2.5_Underground/dist/game/data/html/default/30995-1.htm b/L2J_Mobius_2.5_Underground/dist/game/data/html/default/30995-1.htm index 4962bdf3a2..d38d518149 100644 --- a/L2J_Mobius_2.5_Underground/dist/game/data/html/default/30995-1.htm +++ b/L2J_Mobius_2.5_Underground/dist/game/data/html/default/30995-1.htm @@ -1,19 +1,22 @@ -

-
-In a monster race eight monsters race at the same time. If you guess which monster is expected to finish first and buy that monster's race ticket, based upon the odds to win you will receive an appropriate amount of Adena.
-How the Race WorksMonster races run every 20 minutes in real time.
-How to Participate
-You can purchase a monster race ticket through a race manager NPC. Only between 18 minutes before the start of a race until 3 minutes before the race begins can you buy a ticket for the applicable race. When you purchase a ticket through a race manager, you can find out the names of the participating monsters and their current statistics. Three minutes before a race starts, the race manager will stop ticket sales and announce the monsters' individual odds for the current race.
-Betting
-There are two types of race betting. The Win bet means that you back a particular monster to win 1st place. The Place bet means that you will have to guess the 1st and 2nd place monsters, regardless of who finishes first or second.
+ +
+ +
+In a monster race eight monsters race at the same time. If you guess which monster is expected to finish first and buy that monster's race ticket, based upon the odds to win you will receive an appropriate amount of adena.
+Racing Schedule
+Monster races every 20 minutes in real time.
+Method of Race Participation
+You can purchase a monster race ticket through a race manager NPC. Only between 18 minutes before the start of a race until 3 minutes before the race begins you can buy a ticket for the applicable race. When you purchase a ticket through a conversation with a race manager, you can find out the names of participating monsters in the race and their current statistics. Five minutes before a race starts, the race manager will stop ticket sales and announce monsters' individual odds rate for the current race.
+Types of Race
+There are two types of race betting. The Win type betting means that you back a monster that will win the first prize. Place type of betting means that you will have to guess the 1st and 2nd winning monsters, regardless of who finishes first or second.
Win - * Among the entire betting amount for Win betting, after subtracting the race track's own share of profit, the amount left will be your total winnings. Three minutes before the race, each monster's expected odds are announced. Each Win ticket's dividend amount is calculated by multiplying the odds by the price of a ticket. The odds are never less than 1. - * The prize money does not get transferred to the next race.
+* Among the entire betting amount for Win betting, after subtracting the race track's own share of profit, the amount left will be your total winnings. Three minutes before the race each monster's expected odds are announced. Each Win ticket's dividend amount is calculated by multiplying the odds by the price of a ticket. The odds rate is never less than 1. +* The prize money does not get transferred to the next race.
Place - * Place betting is a way of betting when you correctly guess the two monsters who will finish first and second regardless of their order. The method of calculating the dividend amount is the same as Win betting. - * Currently place betting is not available.
+* Place betting is a way of betting when you correctly guess the two monsters who will finish first and second regardless of their order. The method of calculating dividend amount is the same as Win betting.
+* Currently place betting is not offered.
End of Race
-When a race is over, the race manager will shout the results of the race. You can trade your ticket for Adena by speaking to a race manager NPC. Check the Winning Information Data to find out the types of bets and total winnings for the race you bet on, as well as your win status and prize amount. If you won, you can receive your prize money thorough a race manager NPC. If you did not win, a race manager will buy back your ticket for a small amount of money.
+When a race is over, the race manager will shout out the results of the race. Through a conversation with a race manager NPC, you can trade your ticket for adena. By looking up on the Winning Information Data, you can find out the types of tickets and quantity of the winnings in the race you betted on and your win status and prize amount you won. If you won, you can receive your prize money thorough a race manager NPC. If you did not win, a race manager will buy back your ticket by paying you a very small amount of money.
If a server goes down while tickets are being sold, or while a race is in progress, the race will be cancelled. You can receive a full refund for your ticket for a cancelled race through a race manager.
- + \ No newline at end of file diff --git a/L2J_Mobius_2.5_Underground/dist/game/data/html/default/30995-3.htm b/L2J_Mobius_2.5_Underground/dist/game/data/html/default/30995-3.htm index 5577c579a4..7ae65a37a4 100644 --- a/L2J_Mobius_2.5_Underground/dist/game/data/html/default/30995-3.htm +++ b/L2J_Mobius_2.5_Underground/dist/game/data/html/default/30995-3.htm @@ -3,6 +3,7 @@ + @@ -11,6 +12,7 @@
Lane SelectionOdds
+ @@ -20,18 +22,21 @@
13%
+
Select Bet Amount -100 Adena , 500 Adena -1,000 Adena , 5,000 Adena -10,000 Adena , 20,000 Adena -50,000 Adena , 100,000 Adena +100 Adena , 500 Adena +1,000 Adena , 5,000 Adena +10,000 Adena , 20,000 Adena +50,000 Adena , 100,000 Adena
+ + @@ -39,4 +44,5 @@
+ \ No newline at end of file diff --git a/L2J_Mobius_2.5_Underground/dist/game/data/html/default/30995-4.htm b/L2J_Mobius_2.5_Underground/dist/game/data/html/default/30995-4.htm index bcd1f0d520..30c508b9cb 100644 --- a/L2J_Mobius_2.5_Underground/dist/game/data/html/default/30995-4.htm +++ b/L2J_Mobius_2.5_Underground/dist/game/data/html/default/30995-4.htm @@ -11,6 +11,7 @@ Odds + @@ -20,6 +21,7 @@
13%
+ @@ -37,11 +39,13 @@ -
Bet Amount Total 0total Adena

+ +
If the information is correct, please click Confirm.

+ diff --git a/L2J_Mobius_2.5_Underground/dist/game/data/html/default/30995-5.htm b/L2J_Mobius_2.5_Underground/dist/game/data/html/default/30995-5.htm index b9729e01b0..59a05e14a0 100644 --- a/L2J_Mobius_2.5_Underground/dist/game/data/html/default/30995-5.htm +++ b/L2J_Mobius_2.5_Underground/dist/game/data/html/default/30995-5.htm @@ -15,49 +15,49 @@ - + - + - + - + - + - + - + - +
No.1 Mob1&$804; Odd1
No.2 Mob2&$804; Odd2
No.3 Mob3&$804; Odd3
No.4 Mob4&$804; Odd4
No.5 Mob5&$804; Odd5
No.6 Mob6&$804; Odd6
No.7 Mob7&$804; Odd7
No.8 Mob8&$804; Odd8
diff --git a/L2J_Mobius_2.5_Underground/dist/game/data/html/default/30995-7.htm b/L2J_Mobius_2.5_Underground/dist/game/data/html/default/30995-7.htm index a058e0e0b3..53eb213a52 100644 --- a/L2J_Mobius_2.5_Underground/dist/game/data/html/default/30995-7.htm +++ b/L2J_Mobius_2.5_Underground/dist/game/data/html/default/30995-7.htm @@ -3,22 +3,12 @@ - - + +
Winnings for a Race.LaneBet Amount
Winnings for a Race.LaneBet Amount
- - - - - +%tickets%
1 Number100 Adena
- - - - -
1 Page
- diff --git a/L2J_Mobius_2.5_Underground/dist/game/data/html/default/30995-8.htm b/L2J_Mobius_2.5_Underground/dist/game/data/html/default/30995-8.htm index ebbcf542b2..5f856a222f 100644 --- a/L2J_Mobius_2.5_Underground/dist/game/data/html/default/30995-8.htm +++ b/L2J_Mobius_2.5_Underground/dist/game/data/html/default/30995-8.htm @@ -4,11 +4,11 @@
-
Race #19090
-Lane 1
-Bet: 100 adena
-1st place: Lane [4]
-Odds:[0.01] +
Race #%raceId%
+Lane: %lane%
+Bet: %bet% Adena
+1st place: Lane [%firstLane%]
+Odds: [%odd%]
@@ -18,8 +18,8 @@ Odds:[0.01] - + - +
\ No newline at end of file diff --git a/L2J_Mobius_2.5_Underground/dist/game/data/html/default/30995-9.htm b/L2J_Mobius_2.5_Underground/dist/game/data/html/default/30995-9.htm index ac1201d6f2..c17ab4849a 100644 --- a/L2J_Mobius_2.5_Underground/dist/game/data/html/default/30995-9.htm +++ b/L2J_Mobius_2.5_Underground/dist/game/data/html/default/30995-9.htm @@ -7,41 +7,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +%infos%
7 th8 Lane 1 Lane0.00 Times
6 th4 Lane 8 Lane0.00 Times
5 th7 Lane 2 Lane0.00 Times
4 th6 Lane 8 Lane0.00 Times
3 th4 Lane 1 Lane0.00 Times
2 th1 Lane 8 Lane0.00 Times
1 th7 Lane 6 Lane0.00 Times
diff --git a/L2J_Mobius_2.5_Underground/dist/game/data/html/default/30995.htm b/L2J_Mobius_2.5_Underground/dist/game/data/html/default/30995.htm index 89f40742ae..271c4ee284 100644 --- a/L2J_Mobius_2.5_Underground/dist/game/data/html/default/30995.htm +++ b/L2J_Mobius_2.5_Underground/dist/game/data/html/default/30995.htm @@ -1,15 +1,15 @@ - -
Monster Race Manager
- - - - - - - - - -
- + + +
+ + + + + + + + + + \ No newline at end of file diff --git a/L2J_Mobius_2.5_Underground/dist/game/data/scripts/handlers/MasterHandler.java b/L2J_Mobius_2.5_Underground/dist/game/data/scripts/handlers/MasterHandler.java index 1fee85e747..a260b24863 100644 --- a/L2J_Mobius_2.5_Underground/dist/game/data/scripts/handlers/MasterHandler.java +++ b/L2J_Mobius_2.5_Underground/dist/game/data/scripts/handlers/MasterHandler.java @@ -105,7 +105,6 @@ import handlers.admincommandhandlers.AdminMenu; import handlers.admincommandhandlers.AdminMessages; import handlers.admincommandhandlers.AdminMissingHtmls; import handlers.admincommandhandlers.AdminMobGroup; -import handlers.admincommandhandlers.AdminMonsterRace; import handlers.admincommandhandlers.AdminOlympiad; import handlers.admincommandhandlers.AdminOnline; import handlers.admincommandhandlers.AdminPForge; @@ -441,7 +440,6 @@ public class MasterHandler AdminMessages.class, AdminMissingHtmls.class, AdminMobGroup.class, - AdminMonsterRace.class, AdminOlympiad.class, AdminOnline.class, AdminPathNode.class, diff --git a/L2J_Mobius_2.5_Underground/dist/game/data/scripts/handlers/admincommandhandlers/AdminMonsterRace.java b/L2J_Mobius_2.5_Underground/dist/game/data/scripts/handlers/admincommandhandlers/AdminMonsterRace.java deleted file mode 100644 index e465186490..0000000000 --- a/L2J_Mobius_2.5_Underground/dist/game/data/scripts/handlers/admincommandhandlers/AdminMonsterRace.java +++ /dev/null @@ -1,166 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * This program 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. - * - * This program 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 . - */ -package handlers.admincommandhandlers; - -import org.l2jmobius.commons.concurrent.ThreadPool; -import org.l2jmobius.gameserver.MonsterRace; -import org.l2jmobius.gameserver.handler.IAdminCommandHandler; -import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance; -import org.l2jmobius.gameserver.network.SystemMessageId; -import org.l2jmobius.gameserver.network.serverpackets.DeleteObject; -import org.l2jmobius.gameserver.network.serverpackets.MonRaceInfo; -import org.l2jmobius.gameserver.network.serverpackets.PlaySound; -import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; - -/** - * This class handles following admin commands: - invul = turns invulnerability on/off - * @version $Revision: 1.1.6.4 $ $Date: 2007/07/31 10:06:00 $ - */ -public class AdminMonsterRace implements IAdminCommandHandler -{ - private static final String[] ADMIN_COMMANDS = - { - "admin_mons" - }; - - static int state = -1; - - @Override - public boolean useAdminCommand(String command, PlayerInstance activeChar) - { - if (command.equalsIgnoreCase("admin_mons")) - { - handleSendPacket(activeChar); - } - return true; - } - - @Override - public String[] getAdminCommandList() - { - return ADMIN_COMMANDS; - } - - private void handleSendPacket(PlayerInstance activeChar) - { - /* - * -1 0 to initialize the race 0 15322 to start race 13765 -1 in middle of race -1 0 to end the race 8003 to 8027 - */ - - final int[][] codes = - { - { - -1, - 0 - }, - { - 0, - 15322 - }, - { - 13765, - -1 - }, - { - -1, - 0 - } - }; - final MonsterRace race = MonsterRace.getInstance(); - - if (state == -1) - { - state++; - race.newRace(); - race.newSpeeds(); - final MonRaceInfo spk = new MonRaceInfo(codes[state][0], codes[state][1], race.getMonsters(), race.getSpeeds()); - activeChar.sendPacket(spk); - activeChar.broadcastPacket(spk); - } - else if (state == 0) - { - state++; - final SystemMessage sm = new SystemMessage(SystemMessageId.THEY_RE_OFF); - sm.addInt(0); - activeChar.sendPacket(sm); - final PlaySound sRace = new PlaySound(1, "S_Race", 0, 0, 0, 0, 0); - activeChar.sendPacket(sRace); - activeChar.broadcastPacket(sRace); - final PlaySound sRace2 = new PlaySound(0, "ItemSound2.race_start", 1, 121209259, 12125, 182487, -3559); - activeChar.sendPacket(sRace2); - activeChar.broadcastPacket(sRace2); - final MonRaceInfo spk = new MonRaceInfo(codes[state][0], codes[state][1], race.getMonsters(), race.getSpeeds()); - activeChar.sendPacket(spk); - activeChar.broadcastPacket(spk); - - ThreadPool.schedule(new RunRace(codes, activeChar), 5000); - } - - } - - private class RunRace implements Runnable - { - - private final int[][] codes; - private final PlayerInstance activeChar; - - public RunRace(int[][] pCodes, PlayerInstance pActiveChar) - { - codes = pCodes; - activeChar = pActiveChar; - } - - @Override - public void run() - { - // int[][] speeds1 = MonsterRace.getInstance().getSpeeds(); - // MonsterRace.getInstance().newSpeeds(); - // int[][] speeds2 = MonsterRace.getInstance().getSpeeds(); - /* - * int[] speed = new int[8]; for (int i=0; i<8; i++) { for (int j=0; j<20; j++) { //LOGGER.info("Adding "+speeds1[i][j] +" and "+ speeds2[i][j]); speed[i] += (speeds1[i][j]*1); // + (speeds2[i][j]*1); } LOGGER.info("Total speed for "+(i+1)+" = "+speed[i]); } - */ - - final MonRaceInfo spk = new MonRaceInfo(codes[2][0], codes[2][1], MonsterRace.getInstance().getMonsters(), MonsterRace.getInstance().getSpeeds()); - activeChar.sendPacket(spk); - activeChar.broadcastPacket(spk); - ThreadPool.schedule(new RunEnd(activeChar), 30000); - } - } - - private static class RunEnd implements Runnable - { - private final PlayerInstance activeChar; - - public RunEnd(PlayerInstance pActiveChar) - { - activeChar = pActiveChar; - } - - @Override - public void run() - { - DeleteObject obj = null; - for (int i = 0; i < 8; i++) - { - obj = new DeleteObject(MonsterRace.getInstance().getMonsters()[i]); - activeChar.sendPacket(obj); - activeChar.broadcastPacket(obj); - } - state = -1; - } - } -} diff --git a/L2J_Mobius_2.5_Underground/java/org/l2jmobius/gameserver/GameServer.java b/L2J_Mobius_2.5_Underground/java/org/l2jmobius/gameserver/GameServer.java index 307e9126e7..43f89ae991 100644 --- a/L2J_Mobius_2.5_Underground/java/org/l2jmobius/gameserver/GameServer.java +++ b/L2J_Mobius_2.5_Underground/java/org/l2jmobius/gameserver/GameServer.java @@ -144,6 +144,7 @@ import org.l2jmobius.gameserver.instancemanager.SiegeGuardManager; import org.l2jmobius.gameserver.instancemanager.SiegeManager; import org.l2jmobius.gameserver.instancemanager.WalkingManager; import org.l2jmobius.gameserver.instancemanager.ZoneManager; +import org.l2jmobius.gameserver.instancemanager.games.MonsterRace; import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.entity.Hero; import org.l2jmobius.gameserver.model.events.EventDispatcher; @@ -404,10 +405,8 @@ public class GameServer ItemsAutoDestroy.getInstance(); } - if (Config.ALLOW_RACE) - { - MonsterRace.getInstance(); - } + MonsterRace.getInstance(); + TaskManager.getInstance(); AntiFeedManager.getInstance().registerEvent(AntiFeedManager.GAME_ID); diff --git a/L2J_Mobius_2.5_Underground/java/org/l2jmobius/gameserver/MonsterRace.java b/L2J_Mobius_2.5_Underground/java/org/l2jmobius/gameserver/MonsterRace.java deleted file mode 100644 index cdd92a9a07..0000000000 --- a/L2J_Mobius_2.5_Underground/java/org/l2jmobius/gameserver/MonsterRace.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * This program 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. - * - * This program 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 . - */ -package org.l2jmobius.gameserver; - -import java.util.logging.Level; -import java.util.logging.Logger; - -import org.l2jmobius.commons.util.Rnd; -import org.l2jmobius.gameserver.data.xml.impl.NpcData; -import org.l2jmobius.gameserver.model.actor.Npc; -import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate; - -public class MonsterRace -{ - protected static final Logger LOGGER = Logger.getLogger(MonsterRace.class.getName()); - - private final Npc[] _monsters; - private int[][] _speeds; - private final int[] _first; - private final int[] _second; - - protected MonsterRace() - { - _monsters = new Npc[8]; - _speeds = new int[8][20]; - _first = new int[2]; - _second = new int[2]; - } - - public void newRace() - { - int random = 0; - - for (int i = 0; i < 8; i++) - { - final int id = 31003; - random = Rnd.get(24); - while (true) - { - for (int j = i - 1; j >= 0; j--) - { - if (_monsters[j].getTemplate().getId() == (id + random)) - { - random = Rnd.get(24); - } - } - break; - } - try - { - final NpcTemplate template = NpcData.getInstance().getTemplate(id + random); - _monsters[i] = (Npc) Class.forName("org.l2jmobius.gameserver.model.actor.instance." + template.getType() + "Instance").getConstructors()[0].newInstance(template); - } - catch (Exception e) - { - LOGGER.log(Level.WARNING, "Unable to create monster!", e); - } - } - newSpeeds(); - } - - public void newSpeeds() - { - _speeds = new int[8][20]; - int total = 0; - _first[1] = 0; - _second[1] = 0; - for (int i = 0; i < 8; i++) - { - total = 0; - for (int j = 0; j < 20; j++) - { - _speeds[i][j] = j == 19 ? 100 : Rnd.get(60) + 65; - total += _speeds[i][j]; - } - if (total >= _first[1]) - { - _second[0] = _first[0]; - _second[1] = _first[1]; - _first[0] = 8 - i; - _first[1] = total; - } - else if (total >= _second[1]) - { - _second[0] = 8 - i; - _second[1] = total; - } - } - } - - /** - * @return Returns the monsters. - */ - public Npc[] getMonsters() - { - return _monsters; - } - - /** - * @return Returns the speeds. - */ - public int[][] getSpeeds() - { - return _speeds; - } - - public int getFirstPlace() - { - return _first[0]; - } - - public int getSecondPlace() - { - return _second[0]; - } - - public static MonsterRace getInstance() - { - return SingletonHolder.INSTANCE; - } - - private static class SingletonHolder - { - protected static final MonsterRace INSTANCE = new MonsterRace(); - } -} diff --git a/L2J_Mobius_2.5_Underground/java/org/l2jmobius/gameserver/instancemanager/games/MonsterRace.java b/L2J_Mobius_2.5_Underground/java/org/l2jmobius/gameserver/instancemanager/games/MonsterRace.java new file mode 100644 index 0000000000..2fa4c519f6 --- /dev/null +++ b/L2J_Mobius_2.5_Underground/java/org/l2jmobius/gameserver/instancemanager/games/MonsterRace.java @@ -0,0 +1,623 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program 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. + * + * This program 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 . + */ +package org.l2jmobius.gameserver.instancemanager.games; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.Config; +import org.l2jmobius.commons.concurrent.ThreadPool; +import org.l2jmobius.commons.database.DatabaseFactory; +import org.l2jmobius.commons.util.Rnd; +import org.l2jmobius.gameserver.data.xml.impl.NpcData; +import org.l2jmobius.gameserver.model.actor.Npc; +import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.model.zone.type.DerbyTrackZone; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.serverpackets.DeleteObject; +import org.l2jmobius.gameserver.network.serverpackets.MonRaceInfo; +import org.l2jmobius.gameserver.network.serverpackets.PlaySound; +import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; +import org.l2jmobius.gameserver.util.Broadcast; + +public class MonsterRace +{ + protected static final Logger LOGGER = Logger.getLogger(MonsterRace.class.getName()); + + public static enum RaceState + { + ACCEPTING_BETS, + WAITING, + STARTING_RACE, + RACE_END + } + + protected static final PlaySound SOUND_1 = new PlaySound(1, "S_Race", 0, 0, 0, 0, 0); + protected static final PlaySound SOUND_2 = new PlaySound("ItemSound2.race_start"); + + protected static final int[][] CODES = + { + { + -1, + 0 + }, + { + 0, + 15322 + }, + { + 13765, + -1 + } + }; + + protected final List _npcTemplates = new ArrayList<>(); // List holding npc templates, shuffled on a new race. + protected final List _history = new ArrayList<>(); // List holding old race records. + protected final Map _betsPerLane = new ConcurrentHashMap<>(); // Map holding all bets for each lane ; values setted to 0 after every race. + protected final List _odds = new ArrayList<>(); // List holding sorted odds per lane ; cleared at new odds calculation. + + protected int _raceNumber = 1; + protected int _finalCountdown = 0; + protected RaceState _state = RaceState.RACE_END; + + protected MonRaceInfo _packet; + + private final Npc[] _monsters = new Npc[8]; + private int[][] _speeds = new int[8][20]; + private final int[] _first = new int[2]; + private final int[] _second = new int[2]; + + protected MonsterRace() + { + if (!Config.ALLOW_RACE) + { + return; + } + + // Feed _history with previous race results. + loadHistory(); + + // Feed _betsPerLane with stored informations on bets. + loadBets(); + + // Feed _npcTemplates, we will only have to shuffle it when needed. + for (int i = 31003; i < 31027; i++) + { + _npcTemplates.add(i); + } + + ThreadPool.scheduleAtFixedRate(new Announcement(), 0, 1000); + } + + public static class HistoryInfo + { + private final int _raceId; + private int _first; + private int _second; + private double _oddRate; + + public HistoryInfo(int raceId, int first, int second, double oddRate) + { + _raceId = raceId; + _first = first; + _second = second; + _oddRate = oddRate; + } + + public int getRaceId() + { + return _raceId; + } + + public int getFirst() + { + return _first; + } + + public int getSecond() + { + return _second; + } + + public double getOddRate() + { + return _oddRate; + } + + public void setFirst(int first) + { + _first = first; + } + + public void setSecond(int second) + { + _second = second; + } + + public void setOddRate(double oddRate) + { + _oddRate = oddRate; + } + } + + private class Announcement implements Runnable + { + public Announcement() + { + } + + @Override + public void run() + { + if (_finalCountdown > 1200) + { + _finalCountdown = 0; + } + + switch (_finalCountdown) + { + case 0: + { + newRace(); + newSpeeds(); + + _state = RaceState.ACCEPTING_BETS; + _packet = new MonRaceInfo(CODES[0][0], CODES[0][1], getMonsters(), getSpeeds()); + + final SystemMessage msg = new SystemMessage(SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, _packet, msg); + 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 + { + final SystemMessage msg = new SystemMessage(SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + break; + } + case 300: // 5 min + { + final SystemMessage msg = new SystemMessage(SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S); + msg2.addInt(10); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + break; + } + case 600: // 10 min + { + final SystemMessage msg = new SystemMessage(SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S); + msg2.addInt(5); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + break; + } + case 840: // 14 min + { + final SystemMessage msg = new SystemMessage(SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S); + msg2.addInt(1); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + break; + } + case 900: // 15 min + { + _state = RaceState.WAITING; + + calculateOdds(); + + final SystemMessage msg = new SystemMessage(SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.TICKETS_SALES_ARE_CLOSED_FOR_MONSTER_RACE_S1_ODDS_ARE_POSTED); + msg2.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + break; + } + case 960: // 16 min + case 1020: // 17 min + { + final int minutes = (_finalCountdown == 960) ? 2 : 1; + final SystemMessage msg = new SystemMessage(SystemMessageId.MONSTER_RACE_S2_WILL_BEGIN_IN_S1_MINUTE_S); + msg.addInt(minutes); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + break; + } + case 1050: // 17 min 30 sec + { + final SystemMessage msg = new SystemMessage(SystemMessageId.MONSTER_RACE_S1_WILL_BEGIN_IN_30_SECONDS); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + break; + } + case 1070: // 17 min 50 sec + { + final SystemMessage msg = new SystemMessage(SystemMessageId.MONSTER_RACE_S1_IS_ABOUT_TO_BEGIN_COUNTDOWN_IN_FIVE_SECONDS); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + 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; + final SystemMessage msg = new SystemMessage(SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SECOND_S); + msg.addInt(seconds); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + break; + } + case 1080: // 18 min + { + _state = RaceState.STARTING_RACE; + _packet = new MonRaceInfo(CODES[1][0], CODES[1][1], getMonsters(), getSpeeds()); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, new SystemMessage(SystemMessageId.THEY_RE_OFF), SOUND_1, SOUND_2, _packet); + break; + } + case 1085: // 18 min 5 sec + { + _packet = new MonRaceInfo(CODES[2][0], CODES[2][1], getMonsters(), getSpeeds()); + + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, _packet); + break; + } + case 1115: // 18 min 35 sec + { + _state = RaceState.RACE_END; + + // Populate history info with data, stores it in database. + final HistoryInfo info = _history.get(_history.size() - 1); + info.setFirst(getFirstPlace()); + info.setSecond(getSecondPlace()); + info.setOddRate(_odds.get(getFirstPlace() - 1)); + + saveHistory(info); + clearBets(); + + final SystemMessage msg = new SystemMessage(SystemMessageId.FIRST_PRIZE_GOES_TO_THE_PLAYER_IN_LANE_S1_SECOND_PRIZE_GOES_TO_THE_PLAYER_IN_LANE_S2); + msg.addInt(getFirstPlace()); + msg.addInt(getSecondPlace()); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.MONSTER_RACE_S1_IS_FINISHED); + msg2.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + _raceNumber++; + break; + } + case 1140: // 19 min + { + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, new DeleteObject(getMonsters()[0]), new DeleteObject(getMonsters()[1]), new DeleteObject(getMonsters()[2]), new DeleteObject(getMonsters()[3]), new DeleteObject(getMonsters()[4]), new DeleteObject(getMonsters()[5]), new DeleteObject(getMonsters()[6]), new DeleteObject(getMonsters()[7])); + break; + } + } + _finalCountdown += 1; + } + } + + public void newRace() + { + // Edit _history. + _history.add(new HistoryInfo(_raceNumber, 0, 0, 0)); + + // Randomize _npcTemplates. + Collections.shuffle(_npcTemplates); + + // Setup 8 new creatures ; pickup the first 8 from _npcTemplates. + for (int i = 0; i < 8; i++) + { + try + { + final NpcTemplate template = NpcData.getInstance().getTemplate(_npcTemplates.get(i)); + _monsters[i] = (Npc) Class.forName("org.l2jmobius.gameserver.model.actor.instance." + template.getType() + "Instance").getConstructors()[0].newInstance(template); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "", e); + } + } + } + + public void newSpeeds() + { + _speeds = new int[8][20]; + int total = 0; + _first[1] = 0; + _second[1] = 0; + + for (int i = 0; i < 8; i++) + { + total = 0; + for (int j = 0; j < 20; j++) + { + if (j == 19) + { + _speeds[i][j] = 100; + } + else + { + _speeds[i][j] = Rnd.get(60) + 65; + } + total += _speeds[i][j]; + } + + if (total >= _first[1]) + { + _second[0] = _first[0]; + _second[1] = _first[1]; + _first[0] = 8 - i; + _first[1] = total; + } + else if (total >= _second[1]) + { + _second[0] = 8 - i; + _second[1] = total; + } + } + } + + /** + * Load past races informations, feeding _history arrayList.
+ * Also sets _raceNumber, based on latest HistoryInfo loaded. + */ + protected void loadHistory() + { + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("SELECT * FROM mdt_history"); + ResultSet rset = statement.executeQuery(); + + while (rset.next()) + { + _history.add(new HistoryInfo(rset.getInt("race_id"), rset.getInt("first"), rset.getInt("second"), rset.getDouble("odd_rate"))); + _raceNumber++; + } + rset.close(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't load history: " + e.getMessage(), e); + } + LOGGER.info("MonsterRace: loaded " + _history.size() + " records, currently on race #" + _raceNumber); + } + + /** + * Save an history record into database. + * @param history The infos to store. + */ + protected void saveHistory(HistoryInfo history) + { + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("INSERT INTO mdt_history (race_id, first, second, odd_rate) VALUES (?,?,?,?)"); + statement.setInt(1, history.getRaceId()); + statement.setInt(2, history.getFirst()); + statement.setInt(3, history.getSecond()); + statement.setDouble(4, history.getOddRate()); + statement.execute(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't save history: " + e.getMessage(), e); + } + } + + /** + * Load current bets per lane ; initialize the map keys. + */ + protected void loadBets() + { + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("SELECT * FROM mdt_bets"); + ResultSet rset = statement.executeQuery(); + + while (rset.next()) + { + setBetOnLane(rset.getInt("lane_id"), rset.getLong("bet"), false); + } + + rset.close(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't load bets: " + e.getMessage(), e); + } + } + + /** + * Save the current lane bet into database. + * @param lane : The lane to affect. + * @param sum : The sum to set. + */ + protected void saveBet(int lane, long sum) + { + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("REPLACE INTO mdt_bets (lane_id, bet) VALUES (?,?)"); + statement.setInt(1, lane); + statement.setLong(2, sum); + statement.execute(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't save bet: " + e.getMessage(), e); + } + } + + /** + * Clear all lanes bets, either on database or Map. + */ + protected void clearBets() + { + for (int key : _betsPerLane.keySet()) + { + _betsPerLane.put(key, 0L); + } + + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("UPDATE mdt_bets SET bet = 0"); + statement.execute(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't clear bets: " + e.getMessage(), e); + } + } + + /** + * Setup lane bet, based on previous value (if any). + * @param lane : The lane to edit. + * @param amount : The amount to add. + * @param saveOnDb : Should it be saved on db or not. + */ + public void setBetOnLane(int lane, long amount, boolean saveOnDb) + { + final long sum = (_betsPerLane.containsKey(lane)) ? _betsPerLane.get(lane) + amount : amount; + + _betsPerLane.put(lane, sum); + + if (saveOnDb) + { + saveBet(lane, sum); + } + } + + /** + * Calculate odds for every lane, based on others lanes. + */ + protected void calculateOdds() + { + // Clear previous List holding old odds. + _odds.clear(); + + // Sort bets lanes per lane. + final Map 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; + } + + // 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)); + } + } + + public Npc[] getMonsters() + { + return _monsters; + } + + public int[][] getSpeeds() + { + return _speeds; + } + + public int getFirstPlace() + { + return _first[0]; + } + + public int getSecondPlace() + { + return _second[0]; + } + + public MonRaceInfo getRacePacket() + { + return _packet; + } + + public RaceState getCurrentRaceState() + { + return _state; + } + + public int getRaceNumber() + { + return _raceNumber; + } + + public List getHistory() + { + return _history; + } + + public List getOdds() + { + return _odds; + } + + public static MonsterRace getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final MonsterRace INSTANCE = new MonsterRace(); + } +} \ No newline at end of file diff --git a/L2J_Mobius_2.5_Underground/java/org/l2jmobius/gameserver/model/actor/instance/RaceManagerInstance.java b/L2J_Mobius_2.5_Underground/java/org/l2jmobius/gameserver/model/actor/instance/RaceManagerInstance.java index f893d4f5f2..2375543774 100644 --- a/L2J_Mobius_2.5_Underground/java/org/l2jmobius/gameserver/model/actor/instance/RaceManagerInstance.java +++ b/L2J_Mobius_2.5_Underground/java/org/l2jmobius/gameserver/model/actor/instance/RaceManagerInstance.java @@ -16,69 +16,26 @@ */ package org.l2jmobius.gameserver.model.actor.instance; -import java.util.Collection; -import java.util.concurrent.ConcurrentHashMap; +import java.util.List; +import java.util.Locale; import org.l2jmobius.Config; -import org.l2jmobius.commons.concurrent.ThreadPool; -import org.l2jmobius.gameserver.MonsterRace; -import org.l2jmobius.gameserver.enums.InstanceType; +import org.l2jmobius.commons.util.StringUtil; import org.l2jmobius.gameserver.idfactory.IdFactory; -import org.l2jmobius.gameserver.model.actor.Creature; +import org.l2jmobius.gameserver.instancemanager.games.MonsterRace; +import org.l2jmobius.gameserver.instancemanager.games.MonsterRace.HistoryInfo; +import org.l2jmobius.gameserver.instancemanager.games.MonsterRace.RaceState; import org.l2jmobius.gameserver.model.actor.Npc; import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate; -import org.l2jmobius.gameserver.model.itemcontainer.Inventory; import org.l2jmobius.gameserver.model.items.instance.ItemInstance; import org.l2jmobius.gameserver.network.SystemMessageId; import org.l2jmobius.gameserver.network.serverpackets.ActionFailed; -import org.l2jmobius.gameserver.network.serverpackets.DeleteObject; -import org.l2jmobius.gameserver.network.serverpackets.IClientOutgoingPacket; -import org.l2jmobius.gameserver.network.serverpackets.InventoryUpdate; -import org.l2jmobius.gameserver.network.serverpackets.MonRaceInfo; import org.l2jmobius.gameserver.network.serverpackets.NpcHtmlMessage; -import org.l2jmobius.gameserver.network.serverpackets.PlaySound; import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; -import org.l2jmobius.gameserver.util.Broadcast; public class RaceManagerInstance extends Npc { - public static final int LANES = 8; - public static final int WINDOW_START = 0; - - private static Collection _managers; - protected static int _raceNumber = 4; - - // Time Constants - private static final long SECOND = 1000; - private static final long MINUTE = 60 * SECOND; - - private static int _minutes = 5; - - // States - private static final int ACCEPTING_BETS = 0; - private static final int WAITING = 1; - private static final int STARTING_RACE = 2; - private static final int RACE_END = 3; - private static int _state = RACE_END; - - protected static final int[][] _codes = - { - { - -1, - 0 - }, - { - 0, - 15322 - }, - { - 13765, - -1 - } - }; - private static boolean _notInitialized = true; - protected static MonRaceInfo _packet; - protected static final int[] _cost = + protected static final int TICKET_PRICES[] = { 100, 500, @@ -93,223 +50,167 @@ public class RaceManagerInstance extends Npc public RaceManagerInstance(NpcTemplate template) { super(template); - setInstanceType(InstanceType.RaceManagerInstance); - - if (!Config.ALLOW_RACE) - { - return; - } - - if (_notInitialized) - { - _notInitialized = false; - - _managers = ConcurrentHashMap.newKeySet(); - - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1), 0, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1), 30 * SECOND, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1), MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1), MINUTE + (30 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S), 2 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S), 3 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S), 4 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S), 5 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S), 6 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKETS_SALES_ARE_CLOSED_FOR_MONSTER_RACE_S1_ODDS_ARE_POSTED), 7 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.MONSTER_RACE_S2_WILL_BEGIN_IN_S1_MINUTE_S), 7 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.MONSTER_RACE_S2_WILL_BEGIN_IN_S1_MINUTE_S), 8 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.MONSTER_RACE_S1_WILL_BEGIN_IN_30_SECONDS), (8 * MINUTE) + (30 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.MONSTER_RACE_S1_IS_ABOUT_TO_BEGIN_COUNTDOWN_IN_FIVE_SECONDS), (8 * MINUTE) + (50 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SECOND_S), (8 * MINUTE) + (55 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SECOND_S), (8 * MINUTE) + (56 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SECOND_S), (8 * MINUTE) + (57 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SECOND_S), (8 * MINUTE) + (58 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SECOND_S), (8 * MINUTE) + (59 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.THEY_RE_OFF), 9 * MINUTE, 10 * MINUTE); - } - _managers.add(this); - } - - @Override - public boolean isAutoAttackable(Creature attacker) - { - if (attacker.isMonster()) - { - return true; - } - - return super.isAutoAttackable(attacker); - } - - class Announcement implements Runnable - { - private final SystemMessageId _type; - - public Announcement(SystemMessageId pType) - { - _type = pType; - } - - @Override - public void run() - { - makeAnnouncement(_type); - } - } - - public void makeAnnouncement(SystemMessageId type) - { - final SystemMessage sm = new SystemMessage(type); - switch (type.getId()) - { - case 816: // SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1 - case 817: // SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1 - { - if (_state != ACCEPTING_BETS) - {// LOGGER.info("Race Initializing"); - _state = ACCEPTING_BETS; - startRace(); - } // else{LOGGER.info("Race open");} - sm.addInt(_raceNumber); - break; - } - case 818: // SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S - case 820: // SystemMessageId.MONSTER_RACE_S2_WILL_BEGIN_IN_S1_MINUTE_S - case 823: // SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SECOND_S - { - sm.addInt(_minutes); - if (type.getId() == 820) - { - sm.addInt(_raceNumber); - } - _minutes--; - break; - } - case 819: // SystemMessageId.TICKETS_SALES_ARE_CLOSED_FOR_MONSTER_RACE_S1_ODDS_ARE_POSTED - { - // LOGGER.info("Sales closed"); - sm.addInt(_raceNumber); - _state = WAITING; - _minutes = 2; - break; - } - case 821: // SystemMessageId.MONSTER_RACE_S1_WILL_BEGIN_IN_30_SECONDS - case 822: // SystemMessageId.MONSTER_RACE_S1_IS_ABOUT_TO_BEGIN_COUNTDOWN_IN_FIVE_SECONDS - case 825: // SystemMessageId.MONSTER_RACE_S1_IS_FINISHED - { - sm.addInt(_raceNumber); - _minutes = 5; - break; - } - case 826: // SystemMessageId.FIRST_PRIZE_GOES_TO_THE_PLAYER_IN_LANE_S1_SECOND_PRIZE_GOES_TO_THE_PLAYER_IN_LANE_S2 - { - // LOGGER.info("Placing"); - _state = RACE_END; - sm.addInt(MonsterRace.getInstance().getFirstPlace()); - sm.addInt(MonsterRace.getInstance().getSecondPlace()); - break; - } - } - // _logn.info("Counter: "+minutes); - // LOGGER.info("State: "+state); - broadcast(sm); - // LOGGER.info("Player's known: "+getKnownPlayers().size()); - - if (type == SystemMessageId.THEY_RE_OFF) - { - // LOGGER.info("Starting race"); - _state = STARTING_RACE; - startRace(); - _minutes = 5; - } - } - - protected void broadcast(IClientOutgoingPacket pkt) - { - for (RaceManagerInstance manager : _managers) - { - if (!manager.isDead()) - { - Broadcast.toKnownPlayers(manager, pkt); - } - } - } - - public void sendMonsterInfo() - { - broadcast(_packet); - } - - private void startRace() - { - final MonsterRace race = MonsterRace.getInstance(); - if (_state == STARTING_RACE) - { - // state++; - broadcast(new PlaySound(1, "S_Race", 0, 0, 0, 0, 0)); - broadcast(new PlaySound(0, "ItemSound2.race_start", 1, 121209259, 12125, 182487, -3559)); - _packet = new MonRaceInfo(_codes[1][0], _codes[1][1], race.getMonsters(), race.getSpeeds()); - sendMonsterInfo(); - - ThreadPool.schedule(new RunRace(), 5000); - } - else - { - // state++; - race.newRace(); - race.newSpeeds(); - _packet = new MonRaceInfo(_codes[0][0], _codes[0][1], race.getMonsters(), race.getSpeeds()); - sendMonsterInfo(); - } } @Override public void onBypassFeedback(PlayerInstance player, String command) { - if (command.startsWith("BuyTicket") && (_state != ACCEPTING_BETS)) - { - if (!Config.ALLOW_RACE) - { - return; - } - player.sendPacket(SystemMessageId.MONSTER_RACE_TICKETS_ARE_NO_LONGER_AVAILABLE); - command = "Chat 0"; - } - if (command.startsWith("ShowOdds") && (_state == ACCEPTING_BETS)) - { - if (!Config.ALLOW_RACE) - { - return; - } - player.sendPacket(SystemMessageId.MONSTER_RACE_PAYOUT_INFORMATION_IS_NOT_AVAILABLE_WHILE_TICKETS_ARE_BEING_SOLD); - command = "Chat 0"; - } - if (command.startsWith("BuyTicket")) { - if (!Config.ALLOW_RACE) + if (!Config.ALLOW_RACE || (MonsterRace.getInstance().getCurrentRaceState() != RaceState.ACCEPTING_BETS)) { + player.sendPacket(SystemMessageId.MONSTER_RACE_TICKETS_ARE_NO_LONGER_AVAILABLE); + super.onBypassFeedback(player, "Chat 0"); return; } + int 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))) { val = 0; } - showBuyTicket(player, val); + + String search, replace; + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + + if (val < 10) + { + html.setFile(player, getHtmlPath(getId(), 2, player)); + for (int i = 0; i < 8; i++) + { + int n = i + 1; + search = "Mob" + n; + html.replace(search, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); + } + search = "No1"; + if (val == 0) + { + html.replace(search, ""); + } + else + { + html.replace(search, val); + player.setRace(0, val); + } + } + else if (val < 20) + { + if (player.getRace(0) == 0) + { + return; + } + + html.setFile(player, getHtmlPath(getId(), 3, player)); + html.replace("0place", player.getRace(0)); + search = "Mob1"; + replace = MonsterRace.getInstance().getMonsters()[player.getRace(0) - 1].getTemplate().getName(); + html.replace(search, replace); + search = "0adena"; + + if (val == 10) + { + html.replace(search, ""); + } + else + { + html.replace(search, TICKET_PRICES[val - 11]); + player.setRace(1, val - 10); + } + } + else if (val == 20) + { + if ((player.getRace(0) == 0) || (player.getRace(1) == 0)) + { + return; + } + + html.setFile(player, getHtmlPath(getId(), 4, player)); + html.replace("0place", player.getRace(0)); + search = "Mob1"; + replace = MonsterRace.getInstance().getMonsters()[player.getRace(0) - 1].getTemplate().getName(); + 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); + } + else + { + if ((player.getRace(0) == 0) || (player.getRace(1) == 0)) + { + return; + } + + int ticket = player.getRace(0); + int priceId = player.getRace(1); + + if (!player.reduceAdena("Race", TICKET_PRICES[priceId - 1], this, true)) + { + return; + } + + player.setRace(0, 0); + player.setRace(1, 0); + + ItemInstance item = new ItemInstance(IdFactory.getInstance().getNextId(), 4443); + item.setCount(1); + item.setEnchantLevel(MonsterRace.getInstance().getRaceNumber()); + item.setCustomType1(ticket); + item.setCustomType2(TICKET_PRICES[priceId - 1] / 100); + + player.addItem("Race", item, player, false); + final SystemMessage msg = new SystemMessage(SystemMessageId.ACQUIRED_S1_S2); + msg.addInt(MonsterRace.getInstance().getRaceNumber()); + msg.addItemName(4443); + player.sendPacket(msg); + + // Refresh lane bet. + MonsterRace.getInstance().setBetOnLane(ticket, TICKET_PRICES[priceId - 1], true); + super.onBypassFeedback(player, "Chat 0"); + return; + } + html.replace("1race", MonsterRace.getInstance().getRaceNumber()); + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); } else if (command.equals("ShowOdds")) { - if (!Config.ALLOW_RACE) + if (!Config.ALLOW_RACE || (MonsterRace.getInstance().getCurrentRaceState() == RaceState.ACCEPTING_BETS)) { + player.sendPacket(SystemMessageId.MONSTER_RACE_PAYOUT_INFORMATION_IS_NOT_AVAILABLE_WHILE_TICKETS_ARE_BEING_SOLD); + super.onBypassFeedback(player, "Chat 0"); return; } - showOdds(player); + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 5, player)); + for (int i = 0; i < 8; i++) + { + final int n = i + 1; + + html.replace("Mob" + n, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); + + // Odd + final double odd = MonsterRace.getInstance().getOdds().get(i); + html.replace("Odd" + n, (odd > 0D) ? String.format(Locale.ENGLISH, "%.1f", odd) : "&$804;"); + } + html.replace("1race", MonsterRace.getInstance().getRaceNumber()); + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); } else if (command.equals("ShowInfo")) { @@ -317,269 +218,160 @@ public class RaceManagerInstance extends Npc { return; } - showMonsterInfo(player); + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 6, player)); + + for (int i = 0; i < 8; i++) + { + int n = i + 1; + String search = "Mob" + n; + html.replace(search, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); + } + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); } - else if (command.equals("calculateWin")) + else if (command.equals("ShowTickets")) { if (!Config.ALLOW_RACE) { + super.onBypassFeedback(player, "Chat 0"); return; } - // displayCalculateWinnings(player); + + // Generate data. + final StringBuilder sb = new StringBuilder(); + + // Retrieve player's tickets. + for (ItemInstance ticket : player.getInventory().getAllItemsByItemId(4443)) + { + // Don't list current race tickets. + if (ticket.getEnchantLevel() == MonsterRace.getInstance().getRaceNumber()) + { + continue; + } + + StringUtil.append(sb, "", "" + ticket.getEnchantLevel(), " Race Number", "" + ticket.getCustomType1(), " Number", "" + (ticket.getCustomType2() * 100), " Adena"); + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 7, player)); + html.replace("%tickets%", sb.toString()); + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); } - else if (command.equals("viewHistory")) + else if (command.startsWith("ShowTicket")) + { + // Retrieve ticket objectId. + final int val = Integer.parseInt(command.substring(11)); + if (!Config.ALLOW_RACE || (val == 0)) + { + super.onBypassFeedback(player, "Chat 0"); + return; + } + + // Retrieve ticket on player's inventory. + final ItemInstance 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; + + // Retrieve HistoryInfo for that race. + final HistoryInfo info = MonsterRace.getInstance().getHistory().get(raceId - 1); + if (info == null) + { + super.onBypassFeedback(player, "Chat 0"); + return; + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 8, player)); + html.replace("%raceId%", raceId); + html.replace("%lane%", lane); + html.replace("%bet%", bet); + html.replace("%firstLane%", info.getFirst()); + html.replace("%odd%", (lane == info.getFirst()) ? String.format(Locale.ENGLISH, "%.2f", info.getOddRate()) : "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 (!Config.ALLOW_RACE || (val == 0)) + { + super.onBypassFeedback(player, "Chat 0"); + return; + } + + // Delete ticket on player's inventory. + final ItemInstance 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; + + // Retrieve HistoryInfo for that race. + final HistoryInfo info = MonsterRace.getInstance().getHistory().get(raceId - 1); + if (info == 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()) ? info.getOddRate() : 0.01)), this, true); + } + + super.onBypassFeedback(player, "Chat 0"); + return; + } + else if (command.equals("ViewHistory")) { if (!Config.ALLOW_RACE) { + super.onBypassFeedback(player, "Chat 0"); return; } - // displayHistory(player); + + // Generate data. + final StringBuilder sb = new StringBuilder(); + + // Use whole history, pickup from 'last element' and stop at 'latest element - 7'. + final List history = MonsterRace.getInstance().getHistory(); + for (int i = history.size() - 1; i >= Math.max(0, history.size() - 7); i--) + { + final HistoryInfo info = history.get(i); + StringUtil.append(sb, "", "" + info.getRaceId(), " th", "" + info.getFirst(), " Lane ", "" + info.getSecond(), " Lane", String.format(Locale.ENGLISH, "%.2f", info.getOddRate()), " Times"); + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 9, player)); + html.replace("%infos%", sb.toString()); + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); } else { - // getKnownList().removeKnownObject(player); super.onBypassFeedback(player, command); } } - - public void showOdds(PlayerInstance player) - { - if (_state == ACCEPTING_BETS) - { - return; - } - final int npcId = getTemplate().getId(); - String filename; - String search; - final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); - filename = getHtmlPath(npcId, 5, player); - html.setFile(player, filename); - for (int i = 0; i < 8; i++) - { - final int n = i + 1; - search = "Mob" + n; - html.replace(search, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); - } - html.replace("1race", String.valueOf(_raceNumber)); - html.replace("%objectId%", String.valueOf(getObjectId())); - player.sendPacket(html); - player.sendPacket(ActionFailed.STATIC_PACKET); - } - - public void showMonsterInfo(PlayerInstance player) - { - final int npcId = getTemplate().getId(); - String filename; - String search; - final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); - filename = getHtmlPath(npcId, 6, player); - html.setFile(player, filename); - for (int i = 0; i < 8; i++) - { - final int n = i + 1; - search = "Mob" + n; - html.replace(search, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); - } - html.replace("%objectId%", String.valueOf(getObjectId())); - player.sendPacket(html); - player.sendPacket(ActionFailed.STATIC_PACKET); - } - - public void showBuyTicket(PlayerInstance player, int value) - { - if (_state != ACCEPTING_BETS) - { - return; - } - final int npcId = getTemplate().getId(); - SystemMessage sm; - String filename; - String search; - String replace; - final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); - if (value < 10) - { - filename = getHtmlPath(npcId, 2, player); - html.setFile(player, filename); - for (int i = 0; i < 8; i++) - { - final int n = i + 1; - search = "Mob" + n; - html.replace(search, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); - } - search = "No1"; - if (value == 0) - { - html.replace(search, ""); - } - else - { - html.replace(search, Integer.toString(value)); - player.setRace(0, value); - } - } - else if (value < 20) - { - if (player.getRace(0) == 0) - { - return; - } - filename = getHtmlPath(npcId, 3, player); - html.setFile(player, filename); - html.replace("0place", Integer.toString(player.getRace(0))); - search = "Mob1"; - replace = MonsterRace.getInstance().getMonsters()[player.getRace(0) - 1].getTemplate().getName(); - html.replace(search, replace); - search = "0adena"; - if (value == 10) - { - html.replace(search, ""); - } - else - { - html.replace(search, Integer.toString(_cost[value - 11])); - player.setRace(1, value - 10); - } - } - else if (value == 20) - { - if ((player.getRace(0) == 0) || (player.getRace(1) == 0)) - { - return; - } - filename = getHtmlPath(npcId, 4, player); - html.setFile(player, filename); - html.replace("0place", Integer.toString(player.getRace(0))); - search = "Mob1"; - replace = MonsterRace.getInstance().getMonsters()[player.getRace(0) - 1].getTemplate().getName(); - html.replace(search, replace); - search = "0adena"; - final int price = _cost[player.getRace(1) - 1]; - html.replace(search, Integer.toString(price)); - search = "0tax"; - final int tax = 0; - html.replace(search, Integer.toString(tax)); - search = "0total"; - final int total = price + tax; - html.replace(search, Integer.toString(total)); - } - else - { - if ((player.getRace(0) == 0) || (player.getRace(1) == 0)) - { - return; - } - final int ticket = player.getRace(0); - final int priceId = player.getRace(1); - if (!player.reduceAdena("Race", _cost[priceId - 1], this, true)) - { - return; - } - player.setRace(0, 0); - player.setRace(1, 0); - sm = new SystemMessage(SystemMessageId.ACQUIRED_S1_S2); - sm.addInt(_raceNumber); - sm.addItemName(4443); - player.sendPacket(sm); - final ItemInstance item = new ItemInstance(IdFactory.getInstance().getNextId(), 4443); - item.setCount(1); - item.setEnchantLevel(_raceNumber); - item.setCustomType1(ticket); - item.setCustomType2(_cost[priceId - 1] / 100); - player.getInventory().addItem("Race", item, player, this); - final InventoryUpdate iu = new InventoryUpdate(); - iu.addItem(item); - final ItemInstance adenaupdate = player.getInventory().getItemByItemId(Inventory.ADENA_ID); - iu.addModifiedItem(adenaupdate); - player.sendInventoryUpdate(iu); - return; - } - html.replace("1race", String.valueOf(_raceNumber)); - html.replace("%objectId%", String.valueOf(getObjectId())); - player.sendPacket(html); - player.sendPacket(ActionFailed.STATIC_PACKET); - } - - public static class Race - { - private final Info[] _info; - - public Race(Info[] pInfo) - { - _info = pInfo; - } - - public Info getLaneInfo(int lane) - { - return _info[lane]; - } - - public class Info - { - private final int _id; - private final int _place; - private final int _odds; - private final int _payout; - - public Info(int pId, int pPlace, int pOdds, int pPayout) - { - _id = pId; - _place = pPlace; - _odds = pOdds; - _payout = pPayout; - } - - public int getId() - { - return _id; - } - - public int getOdds() - { - return _odds; - } - - public int getPayout() - { - return _payout; - } - - public int getPlace() - { - return _place; - } - } - - } - - class RunRace implements Runnable - { - @Override - public void run() - { - _packet = new MonRaceInfo(_codes[2][0], _codes[2][1], MonsterRace.getInstance().getMonsters(), MonsterRace.getInstance().getSpeeds()); - sendMonsterInfo(); - ThreadPool.schedule(new RunEnd(), 30000); - } - } - - class RunEnd implements Runnable - { - @Override - public void run() - { - makeAnnouncement(SystemMessageId.FIRST_PRIZE_GOES_TO_THE_PLAYER_IN_LANE_S1_SECOND_PRIZE_GOES_TO_THE_PLAYER_IN_LANE_S2); - makeAnnouncement(SystemMessageId.MONSTER_RACE_S1_IS_FINISHED); - _raceNumber++; - - DeleteObject obj = null; - for (int i = 0; i < 8; i++) - { - obj = new DeleteObject(MonsterRace.getInstance().getMonsters()[i]); - broadcast(obj); - MonsterRace.getInstance().getMonsters()[i].deleteMe(); - } - } - } - } diff --git a/L2J_Mobius_2.5_Underground/java/org/l2jmobius/gameserver/util/Broadcast.java b/L2J_Mobius_2.5_Underground/java/org/l2jmobius/gameserver/util/Broadcast.java index 60eb0ff361..8d751afc53 100644 --- a/L2J_Mobius_2.5_Underground/java/org/l2jmobius/gameserver/util/Broadcast.java +++ b/L2J_Mobius_2.5_Underground/java/org/l2jmobius/gameserver/util/Broadcast.java @@ -20,10 +20,12 @@ import java.util.logging.Level; import java.util.logging.Logger; import org.l2jmobius.gameserver.enums.ChatType; +import org.l2jmobius.gameserver.instancemanager.ZoneManager; import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.actor.Creature; import org.l2jmobius.gameserver.model.actor.Summon; import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance; +import org.l2jmobius.gameserver.model.zone.ZoneType; import org.l2jmobius.gameserver.network.serverpackets.CharInfo; import org.l2jmobius.gameserver.network.serverpackets.CreatureSay; import org.l2jmobius.gameserver.network.serverpackets.ExShowScreenMessage; @@ -191,4 +193,29 @@ public class Broadcast { toAllOnlinePlayers(new ExShowScreenMessage(text, 10000)); } + + /** + * Send a packet to all players in a specific zone type. + * @param ZoneType. + * @param zoneType : The zone type to send packets. + * @param packets : The packets to send. + */ + public static void toAllPlayersInZoneType(Class zoneType, IClientOutgoingPacket... packets) + { + for (ZoneType zone : ZoneManager.getInstance().getAllZones(zoneType)) + { + for (Creature creature : zone.getCharactersInside()) + { + if (creature == null) + { + continue; + } + + for (IClientOutgoingPacket packet : packets) + { + creature.sendPacket(packet); + } + } + } + } } diff --git a/L2J_Mobius_3.0_Helios/dist/db_installer/sql/game/mdt_bets.sql b/L2J_Mobius_3.0_Helios/dist/db_installer/sql/game/mdt_bets.sql new file mode 100644 index 0000000000..8d7d4cb738 --- /dev/null +++ b/L2J_Mobius_3.0_Helios/dist/db_installer/sql/game/mdt_bets.sql @@ -0,0 +1,15 @@ +CREATE TABLE IF NOT EXISTS `mdt_bets` ( + `lane_id` INT(1) DEFAULT 0, + `bet` INT DEFAULT 0, + PRIMARY KEY (`lane_id`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci; + +INSERT INTO `mdt_bets` VALUES +('1','0'), +('2','0'), +('3','0'), +('4','0'), +('5','0'), +('6','0'), +('7','0'), +('8','0'); \ No newline at end of file diff --git a/L2J_Mobius_3.0_Helios/dist/db_installer/sql/game/mdt_history.sql b/L2J_Mobius_3.0_Helios/dist/db_installer/sql/game/mdt_history.sql new file mode 100644 index 0000000000..06978f3377 --- /dev/null +++ b/L2J_Mobius_3.0_Helios/dist/db_installer/sql/game/mdt_history.sql @@ -0,0 +1,7 @@ +CREATE TABLE IF NOT EXISTS `mdt_history` ( + `race_id` MEDIUMINT DEFAULT 0, + `first` INT(1) DEFAULT 0, + `second` INT(1) DEFAULT 0, + `odd_rate` DOUBLE(10,2) DEFAULT 0, + PRIMARY KEY (`race_id`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci; \ No newline at end of file diff --git a/L2J_Mobius_3.0_Helios/dist/game/config/AdminCommands.xml b/L2J_Mobius_3.0_Helios/dist/game/config/AdminCommands.xml index 36ab1240b5..215b7c679a 100644 --- a/L2J_Mobius_3.0_Helios/dist/game/config/AdminCommands.xml +++ b/L2J_Mobius_3.0_Helios/dist/game/config/AdminCommands.xml @@ -416,9 +416,6 @@ - - - diff --git a/L2J_Mobius_3.0_Helios/dist/game/data/html/default/30995-1.htm b/L2J_Mobius_3.0_Helios/dist/game/data/html/default/30995-1.htm index 4962bdf3a2..d38d518149 100644 --- a/L2J_Mobius_3.0_Helios/dist/game/data/html/default/30995-1.htm +++ b/L2J_Mobius_3.0_Helios/dist/game/data/html/default/30995-1.htm @@ -1,19 +1,22 @@ -

-
-In a monster race eight monsters race at the same time. If you guess which monster is expected to finish first and buy that monster's race ticket, based upon the odds to win you will receive an appropriate amount of Adena.
-How the Race WorksMonster races run every 20 minutes in real time.
-How to Participate
-You can purchase a monster race ticket through a race manager NPC. Only between 18 minutes before the start of a race until 3 minutes before the race begins can you buy a ticket for the applicable race. When you purchase a ticket through a race manager, you can find out the names of the participating monsters and their current statistics. Three minutes before a race starts, the race manager will stop ticket sales and announce the monsters' individual odds for the current race.
-Betting
-There are two types of race betting. The Win bet means that you back a particular monster to win 1st place. The Place bet means that you will have to guess the 1st and 2nd place monsters, regardless of who finishes first or second.
+ +
+ +
+In a monster race eight monsters race at the same time. If you guess which monster is expected to finish first and buy that monster's race ticket, based upon the odds to win you will receive an appropriate amount of adena.
+Racing Schedule
+Monster races every 20 minutes in real time.
+Method of Race Participation
+You can purchase a monster race ticket through a race manager NPC. Only between 18 minutes before the start of a race until 3 minutes before the race begins you can buy a ticket for the applicable race. When you purchase a ticket through a conversation with a race manager, you can find out the names of participating monsters in the race and their current statistics. Five minutes before a race starts, the race manager will stop ticket sales and announce monsters' individual odds rate for the current race.
+Types of Race
+There are two types of race betting. The Win type betting means that you back a monster that will win the first prize. Place type of betting means that you will have to guess the 1st and 2nd winning monsters, regardless of who finishes first or second.
Win - * Among the entire betting amount for Win betting, after subtracting the race track's own share of profit, the amount left will be your total winnings. Three minutes before the race, each monster's expected odds are announced. Each Win ticket's dividend amount is calculated by multiplying the odds by the price of a ticket. The odds are never less than 1. - * The prize money does not get transferred to the next race.
+* Among the entire betting amount for Win betting, after subtracting the race track's own share of profit, the amount left will be your total winnings. Three minutes before the race each monster's expected odds are announced. Each Win ticket's dividend amount is calculated by multiplying the odds by the price of a ticket. The odds rate is never less than 1. +* The prize money does not get transferred to the next race.
Place - * Place betting is a way of betting when you correctly guess the two monsters who will finish first and second regardless of their order. The method of calculating the dividend amount is the same as Win betting. - * Currently place betting is not available.
+* Place betting is a way of betting when you correctly guess the two monsters who will finish first and second regardless of their order. The method of calculating dividend amount is the same as Win betting.
+* Currently place betting is not offered.
End of Race
-When a race is over, the race manager will shout the results of the race. You can trade your ticket for Adena by speaking to a race manager NPC. Check the Winning Information Data to find out the types of bets and total winnings for the race you bet on, as well as your win status and prize amount. If you won, you can receive your prize money thorough a race manager NPC. If you did not win, a race manager will buy back your ticket for a small amount of money.
+When a race is over, the race manager will shout out the results of the race. Through a conversation with a race manager NPC, you can trade your ticket for adena. By looking up on the Winning Information Data, you can find out the types of tickets and quantity of the winnings in the race you betted on and your win status and prize amount you won. If you won, you can receive your prize money thorough a race manager NPC. If you did not win, a race manager will buy back your ticket by paying you a very small amount of money.
If a server goes down while tickets are being sold, or while a race is in progress, the race will be cancelled. You can receive a full refund for your ticket for a cancelled race through a race manager.
- + \ No newline at end of file diff --git a/L2J_Mobius_3.0_Helios/dist/game/data/html/default/30995-3.htm b/L2J_Mobius_3.0_Helios/dist/game/data/html/default/30995-3.htm index 5577c579a4..7ae65a37a4 100644 --- a/L2J_Mobius_3.0_Helios/dist/game/data/html/default/30995-3.htm +++ b/L2J_Mobius_3.0_Helios/dist/game/data/html/default/30995-3.htm @@ -3,6 +3,7 @@ + @@ -11,6 +12,7 @@
Lane SelectionOdds
+ @@ -20,18 +22,21 @@
13%
+
Select Bet Amount -100 Adena , 500 Adena -1,000 Adena , 5,000 Adena -10,000 Adena , 20,000 Adena -50,000 Adena , 100,000 Adena +100 Adena , 500 Adena +1,000 Adena , 5,000 Adena +10,000 Adena , 20,000 Adena +50,000 Adena , 100,000 Adena
+ + @@ -39,4 +44,5 @@
+ \ No newline at end of file diff --git a/L2J_Mobius_3.0_Helios/dist/game/data/html/default/30995-4.htm b/L2J_Mobius_3.0_Helios/dist/game/data/html/default/30995-4.htm index bcd1f0d520..30c508b9cb 100644 --- a/L2J_Mobius_3.0_Helios/dist/game/data/html/default/30995-4.htm +++ b/L2J_Mobius_3.0_Helios/dist/game/data/html/default/30995-4.htm @@ -11,6 +11,7 @@ Odds + @@ -20,6 +21,7 @@
13%
+ @@ -37,11 +39,13 @@ -
Bet Amount Total 0total Adena

+ +
If the information is correct, please click Confirm.

+ diff --git a/L2J_Mobius_3.0_Helios/dist/game/data/html/default/30995-5.htm b/L2J_Mobius_3.0_Helios/dist/game/data/html/default/30995-5.htm index b9729e01b0..59a05e14a0 100644 --- a/L2J_Mobius_3.0_Helios/dist/game/data/html/default/30995-5.htm +++ b/L2J_Mobius_3.0_Helios/dist/game/data/html/default/30995-5.htm @@ -15,49 +15,49 @@ - + - + - + - + - + - + - + - +
No.1 Mob1&$804; Odd1
No.2 Mob2&$804; Odd2
No.3 Mob3&$804; Odd3
No.4 Mob4&$804; Odd4
No.5 Mob5&$804; Odd5
No.6 Mob6&$804; Odd6
No.7 Mob7&$804; Odd7
No.8 Mob8&$804; Odd8
diff --git a/L2J_Mobius_3.0_Helios/dist/game/data/html/default/30995-7.htm b/L2J_Mobius_3.0_Helios/dist/game/data/html/default/30995-7.htm index a058e0e0b3..53eb213a52 100644 --- a/L2J_Mobius_3.0_Helios/dist/game/data/html/default/30995-7.htm +++ b/L2J_Mobius_3.0_Helios/dist/game/data/html/default/30995-7.htm @@ -3,22 +3,12 @@ - - + +
Winnings for a Race.LaneBet Amount
Winnings for a Race.LaneBet Amount
- - - - - +%tickets%
1 Number100 Adena
- - - - -
1 Page
- diff --git a/L2J_Mobius_3.0_Helios/dist/game/data/html/default/30995-8.htm b/L2J_Mobius_3.0_Helios/dist/game/data/html/default/30995-8.htm index ebbcf542b2..5f856a222f 100644 --- a/L2J_Mobius_3.0_Helios/dist/game/data/html/default/30995-8.htm +++ b/L2J_Mobius_3.0_Helios/dist/game/data/html/default/30995-8.htm @@ -4,11 +4,11 @@
-
Race #19090
-Lane 1
-Bet: 100 adena
-1st place: Lane [4]
-Odds:[0.01] +
Race #%raceId%
+Lane: %lane%
+Bet: %bet% Adena
+1st place: Lane [%firstLane%]
+Odds: [%odd%]
@@ -18,8 +18,8 @@ Odds:[0.01] - + - +
\ No newline at end of file diff --git a/L2J_Mobius_3.0_Helios/dist/game/data/html/default/30995-9.htm b/L2J_Mobius_3.0_Helios/dist/game/data/html/default/30995-9.htm index ac1201d6f2..c17ab4849a 100644 --- a/L2J_Mobius_3.0_Helios/dist/game/data/html/default/30995-9.htm +++ b/L2J_Mobius_3.0_Helios/dist/game/data/html/default/30995-9.htm @@ -7,41 +7,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +%infos%
7 th8 Lane 1 Lane0.00 Times
6 th4 Lane 8 Lane0.00 Times
5 th7 Lane 2 Lane0.00 Times
4 th6 Lane 8 Lane0.00 Times
3 th4 Lane 1 Lane0.00 Times
2 th1 Lane 8 Lane0.00 Times
1 th7 Lane 6 Lane0.00 Times
diff --git a/L2J_Mobius_3.0_Helios/dist/game/data/html/default/30995.htm b/L2J_Mobius_3.0_Helios/dist/game/data/html/default/30995.htm index 89f40742ae..271c4ee284 100644 --- a/L2J_Mobius_3.0_Helios/dist/game/data/html/default/30995.htm +++ b/L2J_Mobius_3.0_Helios/dist/game/data/html/default/30995.htm @@ -1,15 +1,15 @@ - -
Monster Race Manager
- - - - - - - - - -
- + + +
+ + + + + + + + + + \ No newline at end of file diff --git a/L2J_Mobius_3.0_Helios/dist/game/data/scripts/handlers/MasterHandler.java b/L2J_Mobius_3.0_Helios/dist/game/data/scripts/handlers/MasterHandler.java index 9ff84f140c..04132b481d 100644 --- a/L2J_Mobius_3.0_Helios/dist/game/data/scripts/handlers/MasterHandler.java +++ b/L2J_Mobius_3.0_Helios/dist/game/data/scripts/handlers/MasterHandler.java @@ -105,7 +105,6 @@ import handlers.admincommandhandlers.AdminMenu; import handlers.admincommandhandlers.AdminMessages; import handlers.admincommandhandlers.AdminMissingHtmls; import handlers.admincommandhandlers.AdminMobGroup; -import handlers.admincommandhandlers.AdminMonsterRace; import handlers.admincommandhandlers.AdminOlympiad; import handlers.admincommandhandlers.AdminOnline; import handlers.admincommandhandlers.AdminPForge; @@ -442,7 +441,6 @@ public class MasterHandler AdminMessages.class, AdminMissingHtmls.class, AdminMobGroup.class, - AdminMonsterRace.class, AdminOlympiad.class, AdminOnline.class, AdminPathNode.class, diff --git a/L2J_Mobius_3.0_Helios/dist/game/data/scripts/handlers/admincommandhandlers/AdminMonsterRace.java b/L2J_Mobius_3.0_Helios/dist/game/data/scripts/handlers/admincommandhandlers/AdminMonsterRace.java deleted file mode 100644 index e465186490..0000000000 --- a/L2J_Mobius_3.0_Helios/dist/game/data/scripts/handlers/admincommandhandlers/AdminMonsterRace.java +++ /dev/null @@ -1,166 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * This program 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. - * - * This program 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 . - */ -package handlers.admincommandhandlers; - -import org.l2jmobius.commons.concurrent.ThreadPool; -import org.l2jmobius.gameserver.MonsterRace; -import org.l2jmobius.gameserver.handler.IAdminCommandHandler; -import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance; -import org.l2jmobius.gameserver.network.SystemMessageId; -import org.l2jmobius.gameserver.network.serverpackets.DeleteObject; -import org.l2jmobius.gameserver.network.serverpackets.MonRaceInfo; -import org.l2jmobius.gameserver.network.serverpackets.PlaySound; -import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; - -/** - * This class handles following admin commands: - invul = turns invulnerability on/off - * @version $Revision: 1.1.6.4 $ $Date: 2007/07/31 10:06:00 $ - */ -public class AdminMonsterRace implements IAdminCommandHandler -{ - private static final String[] ADMIN_COMMANDS = - { - "admin_mons" - }; - - static int state = -1; - - @Override - public boolean useAdminCommand(String command, PlayerInstance activeChar) - { - if (command.equalsIgnoreCase("admin_mons")) - { - handleSendPacket(activeChar); - } - return true; - } - - @Override - public String[] getAdminCommandList() - { - return ADMIN_COMMANDS; - } - - private void handleSendPacket(PlayerInstance activeChar) - { - /* - * -1 0 to initialize the race 0 15322 to start race 13765 -1 in middle of race -1 0 to end the race 8003 to 8027 - */ - - final int[][] codes = - { - { - -1, - 0 - }, - { - 0, - 15322 - }, - { - 13765, - -1 - }, - { - -1, - 0 - } - }; - final MonsterRace race = MonsterRace.getInstance(); - - if (state == -1) - { - state++; - race.newRace(); - race.newSpeeds(); - final MonRaceInfo spk = new MonRaceInfo(codes[state][0], codes[state][1], race.getMonsters(), race.getSpeeds()); - activeChar.sendPacket(spk); - activeChar.broadcastPacket(spk); - } - else if (state == 0) - { - state++; - final SystemMessage sm = new SystemMessage(SystemMessageId.THEY_RE_OFF); - sm.addInt(0); - activeChar.sendPacket(sm); - final PlaySound sRace = new PlaySound(1, "S_Race", 0, 0, 0, 0, 0); - activeChar.sendPacket(sRace); - activeChar.broadcastPacket(sRace); - final PlaySound sRace2 = new PlaySound(0, "ItemSound2.race_start", 1, 121209259, 12125, 182487, -3559); - activeChar.sendPacket(sRace2); - activeChar.broadcastPacket(sRace2); - final MonRaceInfo spk = new MonRaceInfo(codes[state][0], codes[state][1], race.getMonsters(), race.getSpeeds()); - activeChar.sendPacket(spk); - activeChar.broadcastPacket(spk); - - ThreadPool.schedule(new RunRace(codes, activeChar), 5000); - } - - } - - private class RunRace implements Runnable - { - - private final int[][] codes; - private final PlayerInstance activeChar; - - public RunRace(int[][] pCodes, PlayerInstance pActiveChar) - { - codes = pCodes; - activeChar = pActiveChar; - } - - @Override - public void run() - { - // int[][] speeds1 = MonsterRace.getInstance().getSpeeds(); - // MonsterRace.getInstance().newSpeeds(); - // int[][] speeds2 = MonsterRace.getInstance().getSpeeds(); - /* - * int[] speed = new int[8]; for (int i=0; i<8; i++) { for (int j=0; j<20; j++) { //LOGGER.info("Adding "+speeds1[i][j] +" and "+ speeds2[i][j]); speed[i] += (speeds1[i][j]*1); // + (speeds2[i][j]*1); } LOGGER.info("Total speed for "+(i+1)+" = "+speed[i]); } - */ - - final MonRaceInfo spk = new MonRaceInfo(codes[2][0], codes[2][1], MonsterRace.getInstance().getMonsters(), MonsterRace.getInstance().getSpeeds()); - activeChar.sendPacket(spk); - activeChar.broadcastPacket(spk); - ThreadPool.schedule(new RunEnd(activeChar), 30000); - } - } - - private static class RunEnd implements Runnable - { - private final PlayerInstance activeChar; - - public RunEnd(PlayerInstance pActiveChar) - { - activeChar = pActiveChar; - } - - @Override - public void run() - { - DeleteObject obj = null; - for (int i = 0; i < 8; i++) - { - obj = new DeleteObject(MonsterRace.getInstance().getMonsters()[i]); - activeChar.sendPacket(obj); - activeChar.broadcastPacket(obj); - } - state = -1; - } - } -} diff --git a/L2J_Mobius_3.0_Helios/java/org/l2jmobius/gameserver/GameServer.java b/L2J_Mobius_3.0_Helios/java/org/l2jmobius/gameserver/GameServer.java index 307e9126e7..43f89ae991 100644 --- a/L2J_Mobius_3.0_Helios/java/org/l2jmobius/gameserver/GameServer.java +++ b/L2J_Mobius_3.0_Helios/java/org/l2jmobius/gameserver/GameServer.java @@ -144,6 +144,7 @@ import org.l2jmobius.gameserver.instancemanager.SiegeGuardManager; import org.l2jmobius.gameserver.instancemanager.SiegeManager; import org.l2jmobius.gameserver.instancemanager.WalkingManager; import org.l2jmobius.gameserver.instancemanager.ZoneManager; +import org.l2jmobius.gameserver.instancemanager.games.MonsterRace; import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.entity.Hero; import org.l2jmobius.gameserver.model.events.EventDispatcher; @@ -404,10 +405,8 @@ public class GameServer ItemsAutoDestroy.getInstance(); } - if (Config.ALLOW_RACE) - { - MonsterRace.getInstance(); - } + MonsterRace.getInstance(); + TaskManager.getInstance(); AntiFeedManager.getInstance().registerEvent(AntiFeedManager.GAME_ID); diff --git a/L2J_Mobius_3.0_Helios/java/org/l2jmobius/gameserver/MonsterRace.java b/L2J_Mobius_3.0_Helios/java/org/l2jmobius/gameserver/MonsterRace.java deleted file mode 100644 index cdd92a9a07..0000000000 --- a/L2J_Mobius_3.0_Helios/java/org/l2jmobius/gameserver/MonsterRace.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * This program 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. - * - * This program 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 . - */ -package org.l2jmobius.gameserver; - -import java.util.logging.Level; -import java.util.logging.Logger; - -import org.l2jmobius.commons.util.Rnd; -import org.l2jmobius.gameserver.data.xml.impl.NpcData; -import org.l2jmobius.gameserver.model.actor.Npc; -import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate; - -public class MonsterRace -{ - protected static final Logger LOGGER = Logger.getLogger(MonsterRace.class.getName()); - - private final Npc[] _monsters; - private int[][] _speeds; - private final int[] _first; - private final int[] _second; - - protected MonsterRace() - { - _monsters = new Npc[8]; - _speeds = new int[8][20]; - _first = new int[2]; - _second = new int[2]; - } - - public void newRace() - { - int random = 0; - - for (int i = 0; i < 8; i++) - { - final int id = 31003; - random = Rnd.get(24); - while (true) - { - for (int j = i - 1; j >= 0; j--) - { - if (_monsters[j].getTemplate().getId() == (id + random)) - { - random = Rnd.get(24); - } - } - break; - } - try - { - final NpcTemplate template = NpcData.getInstance().getTemplate(id + random); - _monsters[i] = (Npc) Class.forName("org.l2jmobius.gameserver.model.actor.instance." + template.getType() + "Instance").getConstructors()[0].newInstance(template); - } - catch (Exception e) - { - LOGGER.log(Level.WARNING, "Unable to create monster!", e); - } - } - newSpeeds(); - } - - public void newSpeeds() - { - _speeds = new int[8][20]; - int total = 0; - _first[1] = 0; - _second[1] = 0; - for (int i = 0; i < 8; i++) - { - total = 0; - for (int j = 0; j < 20; j++) - { - _speeds[i][j] = j == 19 ? 100 : Rnd.get(60) + 65; - total += _speeds[i][j]; - } - if (total >= _first[1]) - { - _second[0] = _first[0]; - _second[1] = _first[1]; - _first[0] = 8 - i; - _first[1] = total; - } - else if (total >= _second[1]) - { - _second[0] = 8 - i; - _second[1] = total; - } - } - } - - /** - * @return Returns the monsters. - */ - public Npc[] getMonsters() - { - return _monsters; - } - - /** - * @return Returns the speeds. - */ - public int[][] getSpeeds() - { - return _speeds; - } - - public int getFirstPlace() - { - return _first[0]; - } - - public int getSecondPlace() - { - return _second[0]; - } - - public static MonsterRace getInstance() - { - return SingletonHolder.INSTANCE; - } - - private static class SingletonHolder - { - protected static final MonsterRace INSTANCE = new MonsterRace(); - } -} diff --git a/L2J_Mobius_3.0_Helios/java/org/l2jmobius/gameserver/instancemanager/games/MonsterRace.java b/L2J_Mobius_3.0_Helios/java/org/l2jmobius/gameserver/instancemanager/games/MonsterRace.java new file mode 100644 index 0000000000..2fa4c519f6 --- /dev/null +++ b/L2J_Mobius_3.0_Helios/java/org/l2jmobius/gameserver/instancemanager/games/MonsterRace.java @@ -0,0 +1,623 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program 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. + * + * This program 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 . + */ +package org.l2jmobius.gameserver.instancemanager.games; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.Config; +import org.l2jmobius.commons.concurrent.ThreadPool; +import org.l2jmobius.commons.database.DatabaseFactory; +import org.l2jmobius.commons.util.Rnd; +import org.l2jmobius.gameserver.data.xml.impl.NpcData; +import org.l2jmobius.gameserver.model.actor.Npc; +import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.model.zone.type.DerbyTrackZone; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.serverpackets.DeleteObject; +import org.l2jmobius.gameserver.network.serverpackets.MonRaceInfo; +import org.l2jmobius.gameserver.network.serverpackets.PlaySound; +import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; +import org.l2jmobius.gameserver.util.Broadcast; + +public class MonsterRace +{ + protected static final Logger LOGGER = Logger.getLogger(MonsterRace.class.getName()); + + public static enum RaceState + { + ACCEPTING_BETS, + WAITING, + STARTING_RACE, + RACE_END + } + + protected static final PlaySound SOUND_1 = new PlaySound(1, "S_Race", 0, 0, 0, 0, 0); + protected static final PlaySound SOUND_2 = new PlaySound("ItemSound2.race_start"); + + protected static final int[][] CODES = + { + { + -1, + 0 + }, + { + 0, + 15322 + }, + { + 13765, + -1 + } + }; + + protected final List _npcTemplates = new ArrayList<>(); // List holding npc templates, shuffled on a new race. + protected final List _history = new ArrayList<>(); // List holding old race records. + protected final Map _betsPerLane = new ConcurrentHashMap<>(); // Map holding all bets for each lane ; values setted to 0 after every race. + protected final List _odds = new ArrayList<>(); // List holding sorted odds per lane ; cleared at new odds calculation. + + protected int _raceNumber = 1; + protected int _finalCountdown = 0; + protected RaceState _state = RaceState.RACE_END; + + protected MonRaceInfo _packet; + + private final Npc[] _monsters = new Npc[8]; + private int[][] _speeds = new int[8][20]; + private final int[] _first = new int[2]; + private final int[] _second = new int[2]; + + protected MonsterRace() + { + if (!Config.ALLOW_RACE) + { + return; + } + + // Feed _history with previous race results. + loadHistory(); + + // Feed _betsPerLane with stored informations on bets. + loadBets(); + + // Feed _npcTemplates, we will only have to shuffle it when needed. + for (int i = 31003; i < 31027; i++) + { + _npcTemplates.add(i); + } + + ThreadPool.scheduleAtFixedRate(new Announcement(), 0, 1000); + } + + public static class HistoryInfo + { + private final int _raceId; + private int _first; + private int _second; + private double _oddRate; + + public HistoryInfo(int raceId, int first, int second, double oddRate) + { + _raceId = raceId; + _first = first; + _second = second; + _oddRate = oddRate; + } + + public int getRaceId() + { + return _raceId; + } + + public int getFirst() + { + return _first; + } + + public int getSecond() + { + return _second; + } + + public double getOddRate() + { + return _oddRate; + } + + public void setFirst(int first) + { + _first = first; + } + + public void setSecond(int second) + { + _second = second; + } + + public void setOddRate(double oddRate) + { + _oddRate = oddRate; + } + } + + private class Announcement implements Runnable + { + public Announcement() + { + } + + @Override + public void run() + { + if (_finalCountdown > 1200) + { + _finalCountdown = 0; + } + + switch (_finalCountdown) + { + case 0: + { + newRace(); + newSpeeds(); + + _state = RaceState.ACCEPTING_BETS; + _packet = new MonRaceInfo(CODES[0][0], CODES[0][1], getMonsters(), getSpeeds()); + + final SystemMessage msg = new SystemMessage(SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, _packet, msg); + 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 + { + final SystemMessage msg = new SystemMessage(SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + break; + } + case 300: // 5 min + { + final SystemMessage msg = new SystemMessage(SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S); + msg2.addInt(10); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + break; + } + case 600: // 10 min + { + final SystemMessage msg = new SystemMessage(SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S); + msg2.addInt(5); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + break; + } + case 840: // 14 min + { + final SystemMessage msg = new SystemMessage(SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S); + msg2.addInt(1); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + break; + } + case 900: // 15 min + { + _state = RaceState.WAITING; + + calculateOdds(); + + final SystemMessage msg = new SystemMessage(SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.TICKETS_SALES_ARE_CLOSED_FOR_MONSTER_RACE_S1_ODDS_ARE_POSTED); + msg2.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + break; + } + case 960: // 16 min + case 1020: // 17 min + { + final int minutes = (_finalCountdown == 960) ? 2 : 1; + final SystemMessage msg = new SystemMessage(SystemMessageId.MONSTER_RACE_S2_WILL_BEGIN_IN_S1_MINUTE_S); + msg.addInt(minutes); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + break; + } + case 1050: // 17 min 30 sec + { + final SystemMessage msg = new SystemMessage(SystemMessageId.MONSTER_RACE_S1_WILL_BEGIN_IN_30_SECONDS); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + break; + } + case 1070: // 17 min 50 sec + { + final SystemMessage msg = new SystemMessage(SystemMessageId.MONSTER_RACE_S1_IS_ABOUT_TO_BEGIN_COUNTDOWN_IN_FIVE_SECONDS); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + 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; + final SystemMessage msg = new SystemMessage(SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SECOND_S); + msg.addInt(seconds); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + break; + } + case 1080: // 18 min + { + _state = RaceState.STARTING_RACE; + _packet = new MonRaceInfo(CODES[1][0], CODES[1][1], getMonsters(), getSpeeds()); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, new SystemMessage(SystemMessageId.THEY_RE_OFF), SOUND_1, SOUND_2, _packet); + break; + } + case 1085: // 18 min 5 sec + { + _packet = new MonRaceInfo(CODES[2][0], CODES[2][1], getMonsters(), getSpeeds()); + + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, _packet); + break; + } + case 1115: // 18 min 35 sec + { + _state = RaceState.RACE_END; + + // Populate history info with data, stores it in database. + final HistoryInfo info = _history.get(_history.size() - 1); + info.setFirst(getFirstPlace()); + info.setSecond(getSecondPlace()); + info.setOddRate(_odds.get(getFirstPlace() - 1)); + + saveHistory(info); + clearBets(); + + final SystemMessage msg = new SystemMessage(SystemMessageId.FIRST_PRIZE_GOES_TO_THE_PLAYER_IN_LANE_S1_SECOND_PRIZE_GOES_TO_THE_PLAYER_IN_LANE_S2); + msg.addInt(getFirstPlace()); + msg.addInt(getSecondPlace()); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.MONSTER_RACE_S1_IS_FINISHED); + msg2.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + _raceNumber++; + break; + } + case 1140: // 19 min + { + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, new DeleteObject(getMonsters()[0]), new DeleteObject(getMonsters()[1]), new DeleteObject(getMonsters()[2]), new DeleteObject(getMonsters()[3]), new DeleteObject(getMonsters()[4]), new DeleteObject(getMonsters()[5]), new DeleteObject(getMonsters()[6]), new DeleteObject(getMonsters()[7])); + break; + } + } + _finalCountdown += 1; + } + } + + public void newRace() + { + // Edit _history. + _history.add(new HistoryInfo(_raceNumber, 0, 0, 0)); + + // Randomize _npcTemplates. + Collections.shuffle(_npcTemplates); + + // Setup 8 new creatures ; pickup the first 8 from _npcTemplates. + for (int i = 0; i < 8; i++) + { + try + { + final NpcTemplate template = NpcData.getInstance().getTemplate(_npcTemplates.get(i)); + _monsters[i] = (Npc) Class.forName("org.l2jmobius.gameserver.model.actor.instance." + template.getType() + "Instance").getConstructors()[0].newInstance(template); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "", e); + } + } + } + + public void newSpeeds() + { + _speeds = new int[8][20]; + int total = 0; + _first[1] = 0; + _second[1] = 0; + + for (int i = 0; i < 8; i++) + { + total = 0; + for (int j = 0; j < 20; j++) + { + if (j == 19) + { + _speeds[i][j] = 100; + } + else + { + _speeds[i][j] = Rnd.get(60) + 65; + } + total += _speeds[i][j]; + } + + if (total >= _first[1]) + { + _second[0] = _first[0]; + _second[1] = _first[1]; + _first[0] = 8 - i; + _first[1] = total; + } + else if (total >= _second[1]) + { + _second[0] = 8 - i; + _second[1] = total; + } + } + } + + /** + * Load past races informations, feeding _history arrayList.
+ * Also sets _raceNumber, based on latest HistoryInfo loaded. + */ + protected void loadHistory() + { + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("SELECT * FROM mdt_history"); + ResultSet rset = statement.executeQuery(); + + while (rset.next()) + { + _history.add(new HistoryInfo(rset.getInt("race_id"), rset.getInt("first"), rset.getInt("second"), rset.getDouble("odd_rate"))); + _raceNumber++; + } + rset.close(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't load history: " + e.getMessage(), e); + } + LOGGER.info("MonsterRace: loaded " + _history.size() + " records, currently on race #" + _raceNumber); + } + + /** + * Save an history record into database. + * @param history The infos to store. + */ + protected void saveHistory(HistoryInfo history) + { + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("INSERT INTO mdt_history (race_id, first, second, odd_rate) VALUES (?,?,?,?)"); + statement.setInt(1, history.getRaceId()); + statement.setInt(2, history.getFirst()); + statement.setInt(3, history.getSecond()); + statement.setDouble(4, history.getOddRate()); + statement.execute(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't save history: " + e.getMessage(), e); + } + } + + /** + * Load current bets per lane ; initialize the map keys. + */ + protected void loadBets() + { + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("SELECT * FROM mdt_bets"); + ResultSet rset = statement.executeQuery(); + + while (rset.next()) + { + setBetOnLane(rset.getInt("lane_id"), rset.getLong("bet"), false); + } + + rset.close(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't load bets: " + e.getMessage(), e); + } + } + + /** + * Save the current lane bet into database. + * @param lane : The lane to affect. + * @param sum : The sum to set. + */ + protected void saveBet(int lane, long sum) + { + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("REPLACE INTO mdt_bets (lane_id, bet) VALUES (?,?)"); + statement.setInt(1, lane); + statement.setLong(2, sum); + statement.execute(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't save bet: " + e.getMessage(), e); + } + } + + /** + * Clear all lanes bets, either on database or Map. + */ + protected void clearBets() + { + for (int key : _betsPerLane.keySet()) + { + _betsPerLane.put(key, 0L); + } + + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("UPDATE mdt_bets SET bet = 0"); + statement.execute(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't clear bets: " + e.getMessage(), e); + } + } + + /** + * Setup lane bet, based on previous value (if any). + * @param lane : The lane to edit. + * @param amount : The amount to add. + * @param saveOnDb : Should it be saved on db or not. + */ + public void setBetOnLane(int lane, long amount, boolean saveOnDb) + { + final long sum = (_betsPerLane.containsKey(lane)) ? _betsPerLane.get(lane) + amount : amount; + + _betsPerLane.put(lane, sum); + + if (saveOnDb) + { + saveBet(lane, sum); + } + } + + /** + * Calculate odds for every lane, based on others lanes. + */ + protected void calculateOdds() + { + // Clear previous List holding old odds. + _odds.clear(); + + // Sort bets lanes per lane. + final Map 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; + } + + // 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)); + } + } + + public Npc[] getMonsters() + { + return _monsters; + } + + public int[][] getSpeeds() + { + return _speeds; + } + + public int getFirstPlace() + { + return _first[0]; + } + + public int getSecondPlace() + { + return _second[0]; + } + + public MonRaceInfo getRacePacket() + { + return _packet; + } + + public RaceState getCurrentRaceState() + { + return _state; + } + + public int getRaceNumber() + { + return _raceNumber; + } + + public List getHistory() + { + return _history; + } + + public List getOdds() + { + return _odds; + } + + public static MonsterRace getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final MonsterRace INSTANCE = new MonsterRace(); + } +} \ No newline at end of file diff --git a/L2J_Mobius_3.0_Helios/java/org/l2jmobius/gameserver/model/actor/instance/RaceManagerInstance.java b/L2J_Mobius_3.0_Helios/java/org/l2jmobius/gameserver/model/actor/instance/RaceManagerInstance.java index f893d4f5f2..2375543774 100644 --- a/L2J_Mobius_3.0_Helios/java/org/l2jmobius/gameserver/model/actor/instance/RaceManagerInstance.java +++ b/L2J_Mobius_3.0_Helios/java/org/l2jmobius/gameserver/model/actor/instance/RaceManagerInstance.java @@ -16,69 +16,26 @@ */ package org.l2jmobius.gameserver.model.actor.instance; -import java.util.Collection; -import java.util.concurrent.ConcurrentHashMap; +import java.util.List; +import java.util.Locale; import org.l2jmobius.Config; -import org.l2jmobius.commons.concurrent.ThreadPool; -import org.l2jmobius.gameserver.MonsterRace; -import org.l2jmobius.gameserver.enums.InstanceType; +import org.l2jmobius.commons.util.StringUtil; import org.l2jmobius.gameserver.idfactory.IdFactory; -import org.l2jmobius.gameserver.model.actor.Creature; +import org.l2jmobius.gameserver.instancemanager.games.MonsterRace; +import org.l2jmobius.gameserver.instancemanager.games.MonsterRace.HistoryInfo; +import org.l2jmobius.gameserver.instancemanager.games.MonsterRace.RaceState; import org.l2jmobius.gameserver.model.actor.Npc; import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate; -import org.l2jmobius.gameserver.model.itemcontainer.Inventory; import org.l2jmobius.gameserver.model.items.instance.ItemInstance; import org.l2jmobius.gameserver.network.SystemMessageId; import org.l2jmobius.gameserver.network.serverpackets.ActionFailed; -import org.l2jmobius.gameserver.network.serverpackets.DeleteObject; -import org.l2jmobius.gameserver.network.serverpackets.IClientOutgoingPacket; -import org.l2jmobius.gameserver.network.serverpackets.InventoryUpdate; -import org.l2jmobius.gameserver.network.serverpackets.MonRaceInfo; import org.l2jmobius.gameserver.network.serverpackets.NpcHtmlMessage; -import org.l2jmobius.gameserver.network.serverpackets.PlaySound; import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; -import org.l2jmobius.gameserver.util.Broadcast; public class RaceManagerInstance extends Npc { - public static final int LANES = 8; - public static final int WINDOW_START = 0; - - private static Collection _managers; - protected static int _raceNumber = 4; - - // Time Constants - private static final long SECOND = 1000; - private static final long MINUTE = 60 * SECOND; - - private static int _minutes = 5; - - // States - private static final int ACCEPTING_BETS = 0; - private static final int WAITING = 1; - private static final int STARTING_RACE = 2; - private static final int RACE_END = 3; - private static int _state = RACE_END; - - protected static final int[][] _codes = - { - { - -1, - 0 - }, - { - 0, - 15322 - }, - { - 13765, - -1 - } - }; - private static boolean _notInitialized = true; - protected static MonRaceInfo _packet; - protected static final int[] _cost = + protected static final int TICKET_PRICES[] = { 100, 500, @@ -93,223 +50,167 @@ public class RaceManagerInstance extends Npc public RaceManagerInstance(NpcTemplate template) { super(template); - setInstanceType(InstanceType.RaceManagerInstance); - - if (!Config.ALLOW_RACE) - { - return; - } - - if (_notInitialized) - { - _notInitialized = false; - - _managers = ConcurrentHashMap.newKeySet(); - - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1), 0, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1), 30 * SECOND, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1), MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1), MINUTE + (30 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S), 2 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S), 3 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S), 4 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S), 5 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S), 6 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKETS_SALES_ARE_CLOSED_FOR_MONSTER_RACE_S1_ODDS_ARE_POSTED), 7 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.MONSTER_RACE_S2_WILL_BEGIN_IN_S1_MINUTE_S), 7 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.MONSTER_RACE_S2_WILL_BEGIN_IN_S1_MINUTE_S), 8 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.MONSTER_RACE_S1_WILL_BEGIN_IN_30_SECONDS), (8 * MINUTE) + (30 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.MONSTER_RACE_S1_IS_ABOUT_TO_BEGIN_COUNTDOWN_IN_FIVE_SECONDS), (8 * MINUTE) + (50 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SECOND_S), (8 * MINUTE) + (55 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SECOND_S), (8 * MINUTE) + (56 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SECOND_S), (8 * MINUTE) + (57 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SECOND_S), (8 * MINUTE) + (58 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SECOND_S), (8 * MINUTE) + (59 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.THEY_RE_OFF), 9 * MINUTE, 10 * MINUTE); - } - _managers.add(this); - } - - @Override - public boolean isAutoAttackable(Creature attacker) - { - if (attacker.isMonster()) - { - return true; - } - - return super.isAutoAttackable(attacker); - } - - class Announcement implements Runnable - { - private final SystemMessageId _type; - - public Announcement(SystemMessageId pType) - { - _type = pType; - } - - @Override - public void run() - { - makeAnnouncement(_type); - } - } - - public void makeAnnouncement(SystemMessageId type) - { - final SystemMessage sm = new SystemMessage(type); - switch (type.getId()) - { - case 816: // SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1 - case 817: // SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1 - { - if (_state != ACCEPTING_BETS) - {// LOGGER.info("Race Initializing"); - _state = ACCEPTING_BETS; - startRace(); - } // else{LOGGER.info("Race open");} - sm.addInt(_raceNumber); - break; - } - case 818: // SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S - case 820: // SystemMessageId.MONSTER_RACE_S2_WILL_BEGIN_IN_S1_MINUTE_S - case 823: // SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SECOND_S - { - sm.addInt(_minutes); - if (type.getId() == 820) - { - sm.addInt(_raceNumber); - } - _minutes--; - break; - } - case 819: // SystemMessageId.TICKETS_SALES_ARE_CLOSED_FOR_MONSTER_RACE_S1_ODDS_ARE_POSTED - { - // LOGGER.info("Sales closed"); - sm.addInt(_raceNumber); - _state = WAITING; - _minutes = 2; - break; - } - case 821: // SystemMessageId.MONSTER_RACE_S1_WILL_BEGIN_IN_30_SECONDS - case 822: // SystemMessageId.MONSTER_RACE_S1_IS_ABOUT_TO_BEGIN_COUNTDOWN_IN_FIVE_SECONDS - case 825: // SystemMessageId.MONSTER_RACE_S1_IS_FINISHED - { - sm.addInt(_raceNumber); - _minutes = 5; - break; - } - case 826: // SystemMessageId.FIRST_PRIZE_GOES_TO_THE_PLAYER_IN_LANE_S1_SECOND_PRIZE_GOES_TO_THE_PLAYER_IN_LANE_S2 - { - // LOGGER.info("Placing"); - _state = RACE_END; - sm.addInt(MonsterRace.getInstance().getFirstPlace()); - sm.addInt(MonsterRace.getInstance().getSecondPlace()); - break; - } - } - // _logn.info("Counter: "+minutes); - // LOGGER.info("State: "+state); - broadcast(sm); - // LOGGER.info("Player's known: "+getKnownPlayers().size()); - - if (type == SystemMessageId.THEY_RE_OFF) - { - // LOGGER.info("Starting race"); - _state = STARTING_RACE; - startRace(); - _minutes = 5; - } - } - - protected void broadcast(IClientOutgoingPacket pkt) - { - for (RaceManagerInstance manager : _managers) - { - if (!manager.isDead()) - { - Broadcast.toKnownPlayers(manager, pkt); - } - } - } - - public void sendMonsterInfo() - { - broadcast(_packet); - } - - private void startRace() - { - final MonsterRace race = MonsterRace.getInstance(); - if (_state == STARTING_RACE) - { - // state++; - broadcast(new PlaySound(1, "S_Race", 0, 0, 0, 0, 0)); - broadcast(new PlaySound(0, "ItemSound2.race_start", 1, 121209259, 12125, 182487, -3559)); - _packet = new MonRaceInfo(_codes[1][0], _codes[1][1], race.getMonsters(), race.getSpeeds()); - sendMonsterInfo(); - - ThreadPool.schedule(new RunRace(), 5000); - } - else - { - // state++; - race.newRace(); - race.newSpeeds(); - _packet = new MonRaceInfo(_codes[0][0], _codes[0][1], race.getMonsters(), race.getSpeeds()); - sendMonsterInfo(); - } } @Override public void onBypassFeedback(PlayerInstance player, String command) { - if (command.startsWith("BuyTicket") && (_state != ACCEPTING_BETS)) - { - if (!Config.ALLOW_RACE) - { - return; - } - player.sendPacket(SystemMessageId.MONSTER_RACE_TICKETS_ARE_NO_LONGER_AVAILABLE); - command = "Chat 0"; - } - if (command.startsWith("ShowOdds") && (_state == ACCEPTING_BETS)) - { - if (!Config.ALLOW_RACE) - { - return; - } - player.sendPacket(SystemMessageId.MONSTER_RACE_PAYOUT_INFORMATION_IS_NOT_AVAILABLE_WHILE_TICKETS_ARE_BEING_SOLD); - command = "Chat 0"; - } - if (command.startsWith("BuyTicket")) { - if (!Config.ALLOW_RACE) + if (!Config.ALLOW_RACE || (MonsterRace.getInstance().getCurrentRaceState() != RaceState.ACCEPTING_BETS)) { + player.sendPacket(SystemMessageId.MONSTER_RACE_TICKETS_ARE_NO_LONGER_AVAILABLE); + super.onBypassFeedback(player, "Chat 0"); return; } + int 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))) { val = 0; } - showBuyTicket(player, val); + + String search, replace; + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + + if (val < 10) + { + html.setFile(player, getHtmlPath(getId(), 2, player)); + for (int i = 0; i < 8; i++) + { + int n = i + 1; + search = "Mob" + n; + html.replace(search, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); + } + search = "No1"; + if (val == 0) + { + html.replace(search, ""); + } + else + { + html.replace(search, val); + player.setRace(0, val); + } + } + else if (val < 20) + { + if (player.getRace(0) == 0) + { + return; + } + + html.setFile(player, getHtmlPath(getId(), 3, player)); + html.replace("0place", player.getRace(0)); + search = "Mob1"; + replace = MonsterRace.getInstance().getMonsters()[player.getRace(0) - 1].getTemplate().getName(); + html.replace(search, replace); + search = "0adena"; + + if (val == 10) + { + html.replace(search, ""); + } + else + { + html.replace(search, TICKET_PRICES[val - 11]); + player.setRace(1, val - 10); + } + } + else if (val == 20) + { + if ((player.getRace(0) == 0) || (player.getRace(1) == 0)) + { + return; + } + + html.setFile(player, getHtmlPath(getId(), 4, player)); + html.replace("0place", player.getRace(0)); + search = "Mob1"; + replace = MonsterRace.getInstance().getMonsters()[player.getRace(0) - 1].getTemplate().getName(); + 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); + } + else + { + if ((player.getRace(0) == 0) || (player.getRace(1) == 0)) + { + return; + } + + int ticket = player.getRace(0); + int priceId = player.getRace(1); + + if (!player.reduceAdena("Race", TICKET_PRICES[priceId - 1], this, true)) + { + return; + } + + player.setRace(0, 0); + player.setRace(1, 0); + + ItemInstance item = new ItemInstance(IdFactory.getInstance().getNextId(), 4443); + item.setCount(1); + item.setEnchantLevel(MonsterRace.getInstance().getRaceNumber()); + item.setCustomType1(ticket); + item.setCustomType2(TICKET_PRICES[priceId - 1] / 100); + + player.addItem("Race", item, player, false); + final SystemMessage msg = new SystemMessage(SystemMessageId.ACQUIRED_S1_S2); + msg.addInt(MonsterRace.getInstance().getRaceNumber()); + msg.addItemName(4443); + player.sendPacket(msg); + + // Refresh lane bet. + MonsterRace.getInstance().setBetOnLane(ticket, TICKET_PRICES[priceId - 1], true); + super.onBypassFeedback(player, "Chat 0"); + return; + } + html.replace("1race", MonsterRace.getInstance().getRaceNumber()); + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); } else if (command.equals("ShowOdds")) { - if (!Config.ALLOW_RACE) + if (!Config.ALLOW_RACE || (MonsterRace.getInstance().getCurrentRaceState() == RaceState.ACCEPTING_BETS)) { + player.sendPacket(SystemMessageId.MONSTER_RACE_PAYOUT_INFORMATION_IS_NOT_AVAILABLE_WHILE_TICKETS_ARE_BEING_SOLD); + super.onBypassFeedback(player, "Chat 0"); return; } - showOdds(player); + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 5, player)); + for (int i = 0; i < 8; i++) + { + final int n = i + 1; + + html.replace("Mob" + n, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); + + // Odd + final double odd = MonsterRace.getInstance().getOdds().get(i); + html.replace("Odd" + n, (odd > 0D) ? String.format(Locale.ENGLISH, "%.1f", odd) : "&$804;"); + } + html.replace("1race", MonsterRace.getInstance().getRaceNumber()); + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); } else if (command.equals("ShowInfo")) { @@ -317,269 +218,160 @@ public class RaceManagerInstance extends Npc { return; } - showMonsterInfo(player); + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 6, player)); + + for (int i = 0; i < 8; i++) + { + int n = i + 1; + String search = "Mob" + n; + html.replace(search, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); + } + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); } - else if (command.equals("calculateWin")) + else if (command.equals("ShowTickets")) { if (!Config.ALLOW_RACE) { + super.onBypassFeedback(player, "Chat 0"); return; } - // displayCalculateWinnings(player); + + // Generate data. + final StringBuilder sb = new StringBuilder(); + + // Retrieve player's tickets. + for (ItemInstance ticket : player.getInventory().getAllItemsByItemId(4443)) + { + // Don't list current race tickets. + if (ticket.getEnchantLevel() == MonsterRace.getInstance().getRaceNumber()) + { + continue; + } + + StringUtil.append(sb, "", "" + ticket.getEnchantLevel(), " Race Number", "" + ticket.getCustomType1(), " Number", "" + (ticket.getCustomType2() * 100), " Adena"); + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 7, player)); + html.replace("%tickets%", sb.toString()); + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); } - else if (command.equals("viewHistory")) + else if (command.startsWith("ShowTicket")) + { + // Retrieve ticket objectId. + final int val = Integer.parseInt(command.substring(11)); + if (!Config.ALLOW_RACE || (val == 0)) + { + super.onBypassFeedback(player, "Chat 0"); + return; + } + + // Retrieve ticket on player's inventory. + final ItemInstance 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; + + // Retrieve HistoryInfo for that race. + final HistoryInfo info = MonsterRace.getInstance().getHistory().get(raceId - 1); + if (info == null) + { + super.onBypassFeedback(player, "Chat 0"); + return; + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 8, player)); + html.replace("%raceId%", raceId); + html.replace("%lane%", lane); + html.replace("%bet%", bet); + html.replace("%firstLane%", info.getFirst()); + html.replace("%odd%", (lane == info.getFirst()) ? String.format(Locale.ENGLISH, "%.2f", info.getOddRate()) : "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 (!Config.ALLOW_RACE || (val == 0)) + { + super.onBypassFeedback(player, "Chat 0"); + return; + } + + // Delete ticket on player's inventory. + final ItemInstance 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; + + // Retrieve HistoryInfo for that race. + final HistoryInfo info = MonsterRace.getInstance().getHistory().get(raceId - 1); + if (info == 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()) ? info.getOddRate() : 0.01)), this, true); + } + + super.onBypassFeedback(player, "Chat 0"); + return; + } + else if (command.equals("ViewHistory")) { if (!Config.ALLOW_RACE) { + super.onBypassFeedback(player, "Chat 0"); return; } - // displayHistory(player); + + // Generate data. + final StringBuilder sb = new StringBuilder(); + + // Use whole history, pickup from 'last element' and stop at 'latest element - 7'. + final List history = MonsterRace.getInstance().getHistory(); + for (int i = history.size() - 1; i >= Math.max(0, history.size() - 7); i--) + { + final HistoryInfo info = history.get(i); + StringUtil.append(sb, "", "" + info.getRaceId(), " th", "" + info.getFirst(), " Lane ", "" + info.getSecond(), " Lane", String.format(Locale.ENGLISH, "%.2f", info.getOddRate()), " Times"); + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 9, player)); + html.replace("%infos%", sb.toString()); + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); } else { - // getKnownList().removeKnownObject(player); super.onBypassFeedback(player, command); } } - - public void showOdds(PlayerInstance player) - { - if (_state == ACCEPTING_BETS) - { - return; - } - final int npcId = getTemplate().getId(); - String filename; - String search; - final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); - filename = getHtmlPath(npcId, 5, player); - html.setFile(player, filename); - for (int i = 0; i < 8; i++) - { - final int n = i + 1; - search = "Mob" + n; - html.replace(search, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); - } - html.replace("1race", String.valueOf(_raceNumber)); - html.replace("%objectId%", String.valueOf(getObjectId())); - player.sendPacket(html); - player.sendPacket(ActionFailed.STATIC_PACKET); - } - - public void showMonsterInfo(PlayerInstance player) - { - final int npcId = getTemplate().getId(); - String filename; - String search; - final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); - filename = getHtmlPath(npcId, 6, player); - html.setFile(player, filename); - for (int i = 0; i < 8; i++) - { - final int n = i + 1; - search = "Mob" + n; - html.replace(search, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); - } - html.replace("%objectId%", String.valueOf(getObjectId())); - player.sendPacket(html); - player.sendPacket(ActionFailed.STATIC_PACKET); - } - - public void showBuyTicket(PlayerInstance player, int value) - { - if (_state != ACCEPTING_BETS) - { - return; - } - final int npcId = getTemplate().getId(); - SystemMessage sm; - String filename; - String search; - String replace; - final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); - if (value < 10) - { - filename = getHtmlPath(npcId, 2, player); - html.setFile(player, filename); - for (int i = 0; i < 8; i++) - { - final int n = i + 1; - search = "Mob" + n; - html.replace(search, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); - } - search = "No1"; - if (value == 0) - { - html.replace(search, ""); - } - else - { - html.replace(search, Integer.toString(value)); - player.setRace(0, value); - } - } - else if (value < 20) - { - if (player.getRace(0) == 0) - { - return; - } - filename = getHtmlPath(npcId, 3, player); - html.setFile(player, filename); - html.replace("0place", Integer.toString(player.getRace(0))); - search = "Mob1"; - replace = MonsterRace.getInstance().getMonsters()[player.getRace(0) - 1].getTemplate().getName(); - html.replace(search, replace); - search = "0adena"; - if (value == 10) - { - html.replace(search, ""); - } - else - { - html.replace(search, Integer.toString(_cost[value - 11])); - player.setRace(1, value - 10); - } - } - else if (value == 20) - { - if ((player.getRace(0) == 0) || (player.getRace(1) == 0)) - { - return; - } - filename = getHtmlPath(npcId, 4, player); - html.setFile(player, filename); - html.replace("0place", Integer.toString(player.getRace(0))); - search = "Mob1"; - replace = MonsterRace.getInstance().getMonsters()[player.getRace(0) - 1].getTemplate().getName(); - html.replace(search, replace); - search = "0adena"; - final int price = _cost[player.getRace(1) - 1]; - html.replace(search, Integer.toString(price)); - search = "0tax"; - final int tax = 0; - html.replace(search, Integer.toString(tax)); - search = "0total"; - final int total = price + tax; - html.replace(search, Integer.toString(total)); - } - else - { - if ((player.getRace(0) == 0) || (player.getRace(1) == 0)) - { - return; - } - final int ticket = player.getRace(0); - final int priceId = player.getRace(1); - if (!player.reduceAdena("Race", _cost[priceId - 1], this, true)) - { - return; - } - player.setRace(0, 0); - player.setRace(1, 0); - sm = new SystemMessage(SystemMessageId.ACQUIRED_S1_S2); - sm.addInt(_raceNumber); - sm.addItemName(4443); - player.sendPacket(sm); - final ItemInstance item = new ItemInstance(IdFactory.getInstance().getNextId(), 4443); - item.setCount(1); - item.setEnchantLevel(_raceNumber); - item.setCustomType1(ticket); - item.setCustomType2(_cost[priceId - 1] / 100); - player.getInventory().addItem("Race", item, player, this); - final InventoryUpdate iu = new InventoryUpdate(); - iu.addItem(item); - final ItemInstance adenaupdate = player.getInventory().getItemByItemId(Inventory.ADENA_ID); - iu.addModifiedItem(adenaupdate); - player.sendInventoryUpdate(iu); - return; - } - html.replace("1race", String.valueOf(_raceNumber)); - html.replace("%objectId%", String.valueOf(getObjectId())); - player.sendPacket(html); - player.sendPacket(ActionFailed.STATIC_PACKET); - } - - public static class Race - { - private final Info[] _info; - - public Race(Info[] pInfo) - { - _info = pInfo; - } - - public Info getLaneInfo(int lane) - { - return _info[lane]; - } - - public class Info - { - private final int _id; - private final int _place; - private final int _odds; - private final int _payout; - - public Info(int pId, int pPlace, int pOdds, int pPayout) - { - _id = pId; - _place = pPlace; - _odds = pOdds; - _payout = pPayout; - } - - public int getId() - { - return _id; - } - - public int getOdds() - { - return _odds; - } - - public int getPayout() - { - return _payout; - } - - public int getPlace() - { - return _place; - } - } - - } - - class RunRace implements Runnable - { - @Override - public void run() - { - _packet = new MonRaceInfo(_codes[2][0], _codes[2][1], MonsterRace.getInstance().getMonsters(), MonsterRace.getInstance().getSpeeds()); - sendMonsterInfo(); - ThreadPool.schedule(new RunEnd(), 30000); - } - } - - class RunEnd implements Runnable - { - @Override - public void run() - { - makeAnnouncement(SystemMessageId.FIRST_PRIZE_GOES_TO_THE_PLAYER_IN_LANE_S1_SECOND_PRIZE_GOES_TO_THE_PLAYER_IN_LANE_S2); - makeAnnouncement(SystemMessageId.MONSTER_RACE_S1_IS_FINISHED); - _raceNumber++; - - DeleteObject obj = null; - for (int i = 0; i < 8; i++) - { - obj = new DeleteObject(MonsterRace.getInstance().getMonsters()[i]); - broadcast(obj); - MonsterRace.getInstance().getMonsters()[i].deleteMe(); - } - } - } - } diff --git a/L2J_Mobius_3.0_Helios/java/org/l2jmobius/gameserver/util/Broadcast.java b/L2J_Mobius_3.0_Helios/java/org/l2jmobius/gameserver/util/Broadcast.java index 60eb0ff361..8d751afc53 100644 --- a/L2J_Mobius_3.0_Helios/java/org/l2jmobius/gameserver/util/Broadcast.java +++ b/L2J_Mobius_3.0_Helios/java/org/l2jmobius/gameserver/util/Broadcast.java @@ -20,10 +20,12 @@ import java.util.logging.Level; import java.util.logging.Logger; import org.l2jmobius.gameserver.enums.ChatType; +import org.l2jmobius.gameserver.instancemanager.ZoneManager; import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.actor.Creature; import org.l2jmobius.gameserver.model.actor.Summon; import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance; +import org.l2jmobius.gameserver.model.zone.ZoneType; import org.l2jmobius.gameserver.network.serverpackets.CharInfo; import org.l2jmobius.gameserver.network.serverpackets.CreatureSay; import org.l2jmobius.gameserver.network.serverpackets.ExShowScreenMessage; @@ -191,4 +193,29 @@ public class Broadcast { toAllOnlinePlayers(new ExShowScreenMessage(text, 10000)); } + + /** + * Send a packet to all players in a specific zone type. + * @param ZoneType. + * @param zoneType : The zone type to send packets. + * @param packets : The packets to send. + */ + public static void toAllPlayersInZoneType(Class zoneType, IClientOutgoingPacket... packets) + { + for (ZoneType zone : ZoneManager.getInstance().getAllZones(zoneType)) + { + for (Creature creature : zone.getCharactersInside()) + { + if (creature == null) + { + continue; + } + + for (IClientOutgoingPacket packet : packets) + { + creature.sendPacket(packet); + } + } + } + } } diff --git a/L2J_Mobius_4.0_GrandCrusade/dist/db_installer/sql/game/mdt_bets.sql b/L2J_Mobius_4.0_GrandCrusade/dist/db_installer/sql/game/mdt_bets.sql new file mode 100644 index 0000000000..8d7d4cb738 --- /dev/null +++ b/L2J_Mobius_4.0_GrandCrusade/dist/db_installer/sql/game/mdt_bets.sql @@ -0,0 +1,15 @@ +CREATE TABLE IF NOT EXISTS `mdt_bets` ( + `lane_id` INT(1) DEFAULT 0, + `bet` INT DEFAULT 0, + PRIMARY KEY (`lane_id`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci; + +INSERT INTO `mdt_bets` VALUES +('1','0'), +('2','0'), +('3','0'), +('4','0'), +('5','0'), +('6','0'), +('7','0'), +('8','0'); \ No newline at end of file diff --git a/L2J_Mobius_4.0_GrandCrusade/dist/db_installer/sql/game/mdt_history.sql b/L2J_Mobius_4.0_GrandCrusade/dist/db_installer/sql/game/mdt_history.sql new file mode 100644 index 0000000000..06978f3377 --- /dev/null +++ b/L2J_Mobius_4.0_GrandCrusade/dist/db_installer/sql/game/mdt_history.sql @@ -0,0 +1,7 @@ +CREATE TABLE IF NOT EXISTS `mdt_history` ( + `race_id` MEDIUMINT DEFAULT 0, + `first` INT(1) DEFAULT 0, + `second` INT(1) DEFAULT 0, + `odd_rate` DOUBLE(10,2) DEFAULT 0, + PRIMARY KEY (`race_id`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci; \ No newline at end of file diff --git a/L2J_Mobius_4.0_GrandCrusade/dist/game/config/AdminCommands.xml b/L2J_Mobius_4.0_GrandCrusade/dist/game/config/AdminCommands.xml index 36ab1240b5..215b7c679a 100644 --- a/L2J_Mobius_4.0_GrandCrusade/dist/game/config/AdminCommands.xml +++ b/L2J_Mobius_4.0_GrandCrusade/dist/game/config/AdminCommands.xml @@ -416,9 +416,6 @@ - - - diff --git a/L2J_Mobius_4.0_GrandCrusade/dist/game/data/html/default/30995-1.htm b/L2J_Mobius_4.0_GrandCrusade/dist/game/data/html/default/30995-1.htm index 4962bdf3a2..d38d518149 100644 --- a/L2J_Mobius_4.0_GrandCrusade/dist/game/data/html/default/30995-1.htm +++ b/L2J_Mobius_4.0_GrandCrusade/dist/game/data/html/default/30995-1.htm @@ -1,19 +1,22 @@ -

-
-In a monster race eight monsters race at the same time. If you guess which monster is expected to finish first and buy that monster's race ticket, based upon the odds to win you will receive an appropriate amount of Adena.
-How the Race WorksMonster races run every 20 minutes in real time.
-How to Participate
-You can purchase a monster race ticket through a race manager NPC. Only between 18 minutes before the start of a race until 3 minutes before the race begins can you buy a ticket for the applicable race. When you purchase a ticket through a race manager, you can find out the names of the participating monsters and their current statistics. Three minutes before a race starts, the race manager will stop ticket sales and announce the monsters' individual odds for the current race.
-Betting
-There are two types of race betting. The Win bet means that you back a particular monster to win 1st place. The Place bet means that you will have to guess the 1st and 2nd place monsters, regardless of who finishes first or second.
+ +
+ +
+In a monster race eight monsters race at the same time. If you guess which monster is expected to finish first and buy that monster's race ticket, based upon the odds to win you will receive an appropriate amount of adena.
+Racing Schedule
+Monster races every 20 minutes in real time.
+Method of Race Participation
+You can purchase a monster race ticket through a race manager NPC. Only between 18 minutes before the start of a race until 3 minutes before the race begins you can buy a ticket for the applicable race. When you purchase a ticket through a conversation with a race manager, you can find out the names of participating monsters in the race and their current statistics. Five minutes before a race starts, the race manager will stop ticket sales and announce monsters' individual odds rate for the current race.
+Types of Race
+There are two types of race betting. The Win type betting means that you back a monster that will win the first prize. Place type of betting means that you will have to guess the 1st and 2nd winning monsters, regardless of who finishes first or second.
Win - * Among the entire betting amount for Win betting, after subtracting the race track's own share of profit, the amount left will be your total winnings. Three minutes before the race, each monster's expected odds are announced. Each Win ticket's dividend amount is calculated by multiplying the odds by the price of a ticket. The odds are never less than 1. - * The prize money does not get transferred to the next race.
+* Among the entire betting amount for Win betting, after subtracting the race track's own share of profit, the amount left will be your total winnings. Three minutes before the race each monster's expected odds are announced. Each Win ticket's dividend amount is calculated by multiplying the odds by the price of a ticket. The odds rate is never less than 1. +* The prize money does not get transferred to the next race.
Place - * Place betting is a way of betting when you correctly guess the two monsters who will finish first and second regardless of their order. The method of calculating the dividend amount is the same as Win betting. - * Currently place betting is not available.
+* Place betting is a way of betting when you correctly guess the two monsters who will finish first and second regardless of their order. The method of calculating dividend amount is the same as Win betting.
+* Currently place betting is not offered.
End of Race
-When a race is over, the race manager will shout the results of the race. You can trade your ticket for Adena by speaking to a race manager NPC. Check the Winning Information Data to find out the types of bets and total winnings for the race you bet on, as well as your win status and prize amount. If you won, you can receive your prize money thorough a race manager NPC. If you did not win, a race manager will buy back your ticket for a small amount of money.
+When a race is over, the race manager will shout out the results of the race. Through a conversation with a race manager NPC, you can trade your ticket for adena. By looking up on the Winning Information Data, you can find out the types of tickets and quantity of the winnings in the race you betted on and your win status and prize amount you won. If you won, you can receive your prize money thorough a race manager NPC. If you did not win, a race manager will buy back your ticket by paying you a very small amount of money.
If a server goes down while tickets are being sold, or while a race is in progress, the race will be cancelled. You can receive a full refund for your ticket for a cancelled race through a race manager.
- + \ No newline at end of file diff --git a/L2J_Mobius_4.0_GrandCrusade/dist/game/data/html/default/30995-3.htm b/L2J_Mobius_4.0_GrandCrusade/dist/game/data/html/default/30995-3.htm index 5577c579a4..7ae65a37a4 100644 --- a/L2J_Mobius_4.0_GrandCrusade/dist/game/data/html/default/30995-3.htm +++ b/L2J_Mobius_4.0_GrandCrusade/dist/game/data/html/default/30995-3.htm @@ -3,6 +3,7 @@ + @@ -11,6 +12,7 @@
Lane SelectionOdds
+ @@ -20,18 +22,21 @@
13%
+
Select Bet Amount -100 Adena , 500 Adena -1,000 Adena , 5,000 Adena -10,000 Adena , 20,000 Adena -50,000 Adena , 100,000 Adena +100 Adena , 500 Adena +1,000 Adena , 5,000 Adena +10,000 Adena , 20,000 Adena +50,000 Adena , 100,000 Adena
+ + @@ -39,4 +44,5 @@
+ \ No newline at end of file diff --git a/L2J_Mobius_4.0_GrandCrusade/dist/game/data/html/default/30995-4.htm b/L2J_Mobius_4.0_GrandCrusade/dist/game/data/html/default/30995-4.htm index bcd1f0d520..30c508b9cb 100644 --- a/L2J_Mobius_4.0_GrandCrusade/dist/game/data/html/default/30995-4.htm +++ b/L2J_Mobius_4.0_GrandCrusade/dist/game/data/html/default/30995-4.htm @@ -11,6 +11,7 @@ Odds + @@ -20,6 +21,7 @@
13%
+ @@ -37,11 +39,13 @@ -
Bet Amount Total 0total Adena

+ +
If the information is correct, please click Confirm.

+ diff --git a/L2J_Mobius_4.0_GrandCrusade/dist/game/data/html/default/30995-5.htm b/L2J_Mobius_4.0_GrandCrusade/dist/game/data/html/default/30995-5.htm index b9729e01b0..59a05e14a0 100644 --- a/L2J_Mobius_4.0_GrandCrusade/dist/game/data/html/default/30995-5.htm +++ b/L2J_Mobius_4.0_GrandCrusade/dist/game/data/html/default/30995-5.htm @@ -15,49 +15,49 @@ - + - + - + - + - + - + - + - +
No.1 Mob1&$804; Odd1
No.2 Mob2&$804; Odd2
No.3 Mob3&$804; Odd3
No.4 Mob4&$804; Odd4
No.5 Mob5&$804; Odd5
No.6 Mob6&$804; Odd6
No.7 Mob7&$804; Odd7
No.8 Mob8&$804; Odd8
diff --git a/L2J_Mobius_4.0_GrandCrusade/dist/game/data/html/default/30995-7.htm b/L2J_Mobius_4.0_GrandCrusade/dist/game/data/html/default/30995-7.htm index a058e0e0b3..53eb213a52 100644 --- a/L2J_Mobius_4.0_GrandCrusade/dist/game/data/html/default/30995-7.htm +++ b/L2J_Mobius_4.0_GrandCrusade/dist/game/data/html/default/30995-7.htm @@ -3,22 +3,12 @@ - - + +
Winnings for a Race.LaneBet Amount
Winnings for a Race.LaneBet Amount
- - - - - +%tickets%
1 Number100 Adena
- - - - -
1 Page
- diff --git a/L2J_Mobius_4.0_GrandCrusade/dist/game/data/html/default/30995-8.htm b/L2J_Mobius_4.0_GrandCrusade/dist/game/data/html/default/30995-8.htm index ebbcf542b2..5f856a222f 100644 --- a/L2J_Mobius_4.0_GrandCrusade/dist/game/data/html/default/30995-8.htm +++ b/L2J_Mobius_4.0_GrandCrusade/dist/game/data/html/default/30995-8.htm @@ -4,11 +4,11 @@
-
Race #19090
-Lane 1
-Bet: 100 adena
-1st place: Lane [4]
-Odds:[0.01] +
Race #%raceId%
+Lane: %lane%
+Bet: %bet% Adena
+1st place: Lane [%firstLane%]
+Odds: [%odd%]
@@ -18,8 +18,8 @@ Odds:[0.01] - + - +
\ No newline at end of file diff --git a/L2J_Mobius_4.0_GrandCrusade/dist/game/data/html/default/30995-9.htm b/L2J_Mobius_4.0_GrandCrusade/dist/game/data/html/default/30995-9.htm index ac1201d6f2..c17ab4849a 100644 --- a/L2J_Mobius_4.0_GrandCrusade/dist/game/data/html/default/30995-9.htm +++ b/L2J_Mobius_4.0_GrandCrusade/dist/game/data/html/default/30995-9.htm @@ -7,41 +7,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +%infos%
7 th8 Lane 1 Lane0.00 Times
6 th4 Lane 8 Lane0.00 Times
5 th7 Lane 2 Lane0.00 Times
4 th6 Lane 8 Lane0.00 Times
3 th4 Lane 1 Lane0.00 Times
2 th1 Lane 8 Lane0.00 Times
1 th7 Lane 6 Lane0.00 Times
diff --git a/L2J_Mobius_4.0_GrandCrusade/dist/game/data/html/default/30995.htm b/L2J_Mobius_4.0_GrandCrusade/dist/game/data/html/default/30995.htm index 89f40742ae..271c4ee284 100644 --- a/L2J_Mobius_4.0_GrandCrusade/dist/game/data/html/default/30995.htm +++ b/L2J_Mobius_4.0_GrandCrusade/dist/game/data/html/default/30995.htm @@ -1,15 +1,15 @@ - -
Monster Race Manager
- - - - - - - - - -
- + + +
+ + + + + + + + + + \ No newline at end of file diff --git a/L2J_Mobius_4.0_GrandCrusade/dist/game/data/scripts/handlers/MasterHandler.java b/L2J_Mobius_4.0_GrandCrusade/dist/game/data/scripts/handlers/MasterHandler.java index 9ff84f140c..04132b481d 100644 --- a/L2J_Mobius_4.0_GrandCrusade/dist/game/data/scripts/handlers/MasterHandler.java +++ b/L2J_Mobius_4.0_GrandCrusade/dist/game/data/scripts/handlers/MasterHandler.java @@ -105,7 +105,6 @@ import handlers.admincommandhandlers.AdminMenu; import handlers.admincommandhandlers.AdminMessages; import handlers.admincommandhandlers.AdminMissingHtmls; import handlers.admincommandhandlers.AdminMobGroup; -import handlers.admincommandhandlers.AdminMonsterRace; import handlers.admincommandhandlers.AdminOlympiad; import handlers.admincommandhandlers.AdminOnline; import handlers.admincommandhandlers.AdminPForge; @@ -442,7 +441,6 @@ public class MasterHandler AdminMessages.class, AdminMissingHtmls.class, AdminMobGroup.class, - AdminMonsterRace.class, AdminOlympiad.class, AdminOnline.class, AdminPathNode.class, diff --git a/L2J_Mobius_4.0_GrandCrusade/dist/game/data/scripts/handlers/admincommandhandlers/AdminMonsterRace.java b/L2J_Mobius_4.0_GrandCrusade/dist/game/data/scripts/handlers/admincommandhandlers/AdminMonsterRace.java deleted file mode 100644 index e465186490..0000000000 --- a/L2J_Mobius_4.0_GrandCrusade/dist/game/data/scripts/handlers/admincommandhandlers/AdminMonsterRace.java +++ /dev/null @@ -1,166 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * This program 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. - * - * This program 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 . - */ -package handlers.admincommandhandlers; - -import org.l2jmobius.commons.concurrent.ThreadPool; -import org.l2jmobius.gameserver.MonsterRace; -import org.l2jmobius.gameserver.handler.IAdminCommandHandler; -import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance; -import org.l2jmobius.gameserver.network.SystemMessageId; -import org.l2jmobius.gameserver.network.serverpackets.DeleteObject; -import org.l2jmobius.gameserver.network.serverpackets.MonRaceInfo; -import org.l2jmobius.gameserver.network.serverpackets.PlaySound; -import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; - -/** - * This class handles following admin commands: - invul = turns invulnerability on/off - * @version $Revision: 1.1.6.4 $ $Date: 2007/07/31 10:06:00 $ - */ -public class AdminMonsterRace implements IAdminCommandHandler -{ - private static final String[] ADMIN_COMMANDS = - { - "admin_mons" - }; - - static int state = -1; - - @Override - public boolean useAdminCommand(String command, PlayerInstance activeChar) - { - if (command.equalsIgnoreCase("admin_mons")) - { - handleSendPacket(activeChar); - } - return true; - } - - @Override - public String[] getAdminCommandList() - { - return ADMIN_COMMANDS; - } - - private void handleSendPacket(PlayerInstance activeChar) - { - /* - * -1 0 to initialize the race 0 15322 to start race 13765 -1 in middle of race -1 0 to end the race 8003 to 8027 - */ - - final int[][] codes = - { - { - -1, - 0 - }, - { - 0, - 15322 - }, - { - 13765, - -1 - }, - { - -1, - 0 - } - }; - final MonsterRace race = MonsterRace.getInstance(); - - if (state == -1) - { - state++; - race.newRace(); - race.newSpeeds(); - final MonRaceInfo spk = new MonRaceInfo(codes[state][0], codes[state][1], race.getMonsters(), race.getSpeeds()); - activeChar.sendPacket(spk); - activeChar.broadcastPacket(spk); - } - else if (state == 0) - { - state++; - final SystemMessage sm = new SystemMessage(SystemMessageId.THEY_RE_OFF); - sm.addInt(0); - activeChar.sendPacket(sm); - final PlaySound sRace = new PlaySound(1, "S_Race", 0, 0, 0, 0, 0); - activeChar.sendPacket(sRace); - activeChar.broadcastPacket(sRace); - final PlaySound sRace2 = new PlaySound(0, "ItemSound2.race_start", 1, 121209259, 12125, 182487, -3559); - activeChar.sendPacket(sRace2); - activeChar.broadcastPacket(sRace2); - final MonRaceInfo spk = new MonRaceInfo(codes[state][0], codes[state][1], race.getMonsters(), race.getSpeeds()); - activeChar.sendPacket(spk); - activeChar.broadcastPacket(spk); - - ThreadPool.schedule(new RunRace(codes, activeChar), 5000); - } - - } - - private class RunRace implements Runnable - { - - private final int[][] codes; - private final PlayerInstance activeChar; - - public RunRace(int[][] pCodes, PlayerInstance pActiveChar) - { - codes = pCodes; - activeChar = pActiveChar; - } - - @Override - public void run() - { - // int[][] speeds1 = MonsterRace.getInstance().getSpeeds(); - // MonsterRace.getInstance().newSpeeds(); - // int[][] speeds2 = MonsterRace.getInstance().getSpeeds(); - /* - * int[] speed = new int[8]; for (int i=0; i<8; i++) { for (int j=0; j<20; j++) { //LOGGER.info("Adding "+speeds1[i][j] +" and "+ speeds2[i][j]); speed[i] += (speeds1[i][j]*1); // + (speeds2[i][j]*1); } LOGGER.info("Total speed for "+(i+1)+" = "+speed[i]); } - */ - - final MonRaceInfo spk = new MonRaceInfo(codes[2][0], codes[2][1], MonsterRace.getInstance().getMonsters(), MonsterRace.getInstance().getSpeeds()); - activeChar.sendPacket(spk); - activeChar.broadcastPacket(spk); - ThreadPool.schedule(new RunEnd(activeChar), 30000); - } - } - - private static class RunEnd implements Runnable - { - private final PlayerInstance activeChar; - - public RunEnd(PlayerInstance pActiveChar) - { - activeChar = pActiveChar; - } - - @Override - public void run() - { - DeleteObject obj = null; - for (int i = 0; i < 8; i++) - { - obj = new DeleteObject(MonsterRace.getInstance().getMonsters()[i]); - activeChar.sendPacket(obj); - activeChar.broadcastPacket(obj); - } - state = -1; - } - } -} diff --git a/L2J_Mobius_4.0_GrandCrusade/java/org/l2jmobius/gameserver/GameServer.java b/L2J_Mobius_4.0_GrandCrusade/java/org/l2jmobius/gameserver/GameServer.java index f2d30a8583..73147542f4 100644 --- a/L2J_Mobius_4.0_GrandCrusade/java/org/l2jmobius/gameserver/GameServer.java +++ b/L2J_Mobius_4.0_GrandCrusade/java/org/l2jmobius/gameserver/GameServer.java @@ -144,6 +144,7 @@ import org.l2jmobius.gameserver.instancemanager.SiegeGuardManager; import org.l2jmobius.gameserver.instancemanager.SiegeManager; import org.l2jmobius.gameserver.instancemanager.WalkingManager; import org.l2jmobius.gameserver.instancemanager.ZoneManager; +import org.l2jmobius.gameserver.instancemanager.games.MonsterRace; import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.entity.Hero; import org.l2jmobius.gameserver.model.events.EventDispatcher; @@ -404,10 +405,8 @@ public class GameServer ItemsAutoDestroy.getInstance(); } - if (Config.ALLOW_RACE) - { - MonsterRace.getInstance(); - } + MonsterRace.getInstance(); + TaskManager.getInstance(); AntiFeedManager.getInstance().registerEvent(AntiFeedManager.GAME_ID); diff --git a/L2J_Mobius_4.0_GrandCrusade/java/org/l2jmobius/gameserver/MonsterRace.java b/L2J_Mobius_4.0_GrandCrusade/java/org/l2jmobius/gameserver/MonsterRace.java deleted file mode 100644 index cdd92a9a07..0000000000 --- a/L2J_Mobius_4.0_GrandCrusade/java/org/l2jmobius/gameserver/MonsterRace.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * This program 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. - * - * This program 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 . - */ -package org.l2jmobius.gameserver; - -import java.util.logging.Level; -import java.util.logging.Logger; - -import org.l2jmobius.commons.util.Rnd; -import org.l2jmobius.gameserver.data.xml.impl.NpcData; -import org.l2jmobius.gameserver.model.actor.Npc; -import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate; - -public class MonsterRace -{ - protected static final Logger LOGGER = Logger.getLogger(MonsterRace.class.getName()); - - private final Npc[] _monsters; - private int[][] _speeds; - private final int[] _first; - private final int[] _second; - - protected MonsterRace() - { - _monsters = new Npc[8]; - _speeds = new int[8][20]; - _first = new int[2]; - _second = new int[2]; - } - - public void newRace() - { - int random = 0; - - for (int i = 0; i < 8; i++) - { - final int id = 31003; - random = Rnd.get(24); - while (true) - { - for (int j = i - 1; j >= 0; j--) - { - if (_monsters[j].getTemplate().getId() == (id + random)) - { - random = Rnd.get(24); - } - } - break; - } - try - { - final NpcTemplate template = NpcData.getInstance().getTemplate(id + random); - _monsters[i] = (Npc) Class.forName("org.l2jmobius.gameserver.model.actor.instance." + template.getType() + "Instance").getConstructors()[0].newInstance(template); - } - catch (Exception e) - { - LOGGER.log(Level.WARNING, "Unable to create monster!", e); - } - } - newSpeeds(); - } - - public void newSpeeds() - { - _speeds = new int[8][20]; - int total = 0; - _first[1] = 0; - _second[1] = 0; - for (int i = 0; i < 8; i++) - { - total = 0; - for (int j = 0; j < 20; j++) - { - _speeds[i][j] = j == 19 ? 100 : Rnd.get(60) + 65; - total += _speeds[i][j]; - } - if (total >= _first[1]) - { - _second[0] = _first[0]; - _second[1] = _first[1]; - _first[0] = 8 - i; - _first[1] = total; - } - else if (total >= _second[1]) - { - _second[0] = 8 - i; - _second[1] = total; - } - } - } - - /** - * @return Returns the monsters. - */ - public Npc[] getMonsters() - { - return _monsters; - } - - /** - * @return Returns the speeds. - */ - public int[][] getSpeeds() - { - return _speeds; - } - - public int getFirstPlace() - { - return _first[0]; - } - - public int getSecondPlace() - { - return _second[0]; - } - - public static MonsterRace getInstance() - { - return SingletonHolder.INSTANCE; - } - - private static class SingletonHolder - { - protected static final MonsterRace INSTANCE = new MonsterRace(); - } -} diff --git a/L2J_Mobius_4.0_GrandCrusade/java/org/l2jmobius/gameserver/instancemanager/games/MonsterRace.java b/L2J_Mobius_4.0_GrandCrusade/java/org/l2jmobius/gameserver/instancemanager/games/MonsterRace.java new file mode 100644 index 0000000000..2fa4c519f6 --- /dev/null +++ b/L2J_Mobius_4.0_GrandCrusade/java/org/l2jmobius/gameserver/instancemanager/games/MonsterRace.java @@ -0,0 +1,623 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program 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. + * + * This program 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 . + */ +package org.l2jmobius.gameserver.instancemanager.games; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.Config; +import org.l2jmobius.commons.concurrent.ThreadPool; +import org.l2jmobius.commons.database.DatabaseFactory; +import org.l2jmobius.commons.util.Rnd; +import org.l2jmobius.gameserver.data.xml.impl.NpcData; +import org.l2jmobius.gameserver.model.actor.Npc; +import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.model.zone.type.DerbyTrackZone; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.serverpackets.DeleteObject; +import org.l2jmobius.gameserver.network.serverpackets.MonRaceInfo; +import org.l2jmobius.gameserver.network.serverpackets.PlaySound; +import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; +import org.l2jmobius.gameserver.util.Broadcast; + +public class MonsterRace +{ + protected static final Logger LOGGER = Logger.getLogger(MonsterRace.class.getName()); + + public static enum RaceState + { + ACCEPTING_BETS, + WAITING, + STARTING_RACE, + RACE_END + } + + protected static final PlaySound SOUND_1 = new PlaySound(1, "S_Race", 0, 0, 0, 0, 0); + protected static final PlaySound SOUND_2 = new PlaySound("ItemSound2.race_start"); + + protected static final int[][] CODES = + { + { + -1, + 0 + }, + { + 0, + 15322 + }, + { + 13765, + -1 + } + }; + + protected final List _npcTemplates = new ArrayList<>(); // List holding npc templates, shuffled on a new race. + protected final List _history = new ArrayList<>(); // List holding old race records. + protected final Map _betsPerLane = new ConcurrentHashMap<>(); // Map holding all bets for each lane ; values setted to 0 after every race. + protected final List _odds = new ArrayList<>(); // List holding sorted odds per lane ; cleared at new odds calculation. + + protected int _raceNumber = 1; + protected int _finalCountdown = 0; + protected RaceState _state = RaceState.RACE_END; + + protected MonRaceInfo _packet; + + private final Npc[] _monsters = new Npc[8]; + private int[][] _speeds = new int[8][20]; + private final int[] _first = new int[2]; + private final int[] _second = new int[2]; + + protected MonsterRace() + { + if (!Config.ALLOW_RACE) + { + return; + } + + // Feed _history with previous race results. + loadHistory(); + + // Feed _betsPerLane with stored informations on bets. + loadBets(); + + // Feed _npcTemplates, we will only have to shuffle it when needed. + for (int i = 31003; i < 31027; i++) + { + _npcTemplates.add(i); + } + + ThreadPool.scheduleAtFixedRate(new Announcement(), 0, 1000); + } + + public static class HistoryInfo + { + private final int _raceId; + private int _first; + private int _second; + private double _oddRate; + + public HistoryInfo(int raceId, int first, int second, double oddRate) + { + _raceId = raceId; + _first = first; + _second = second; + _oddRate = oddRate; + } + + public int getRaceId() + { + return _raceId; + } + + public int getFirst() + { + return _first; + } + + public int getSecond() + { + return _second; + } + + public double getOddRate() + { + return _oddRate; + } + + public void setFirst(int first) + { + _first = first; + } + + public void setSecond(int second) + { + _second = second; + } + + public void setOddRate(double oddRate) + { + _oddRate = oddRate; + } + } + + private class Announcement implements Runnable + { + public Announcement() + { + } + + @Override + public void run() + { + if (_finalCountdown > 1200) + { + _finalCountdown = 0; + } + + switch (_finalCountdown) + { + case 0: + { + newRace(); + newSpeeds(); + + _state = RaceState.ACCEPTING_BETS; + _packet = new MonRaceInfo(CODES[0][0], CODES[0][1], getMonsters(), getSpeeds()); + + final SystemMessage msg = new SystemMessage(SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, _packet, msg); + 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 + { + final SystemMessage msg = new SystemMessage(SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + break; + } + case 300: // 5 min + { + final SystemMessage msg = new SystemMessage(SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S); + msg2.addInt(10); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + break; + } + case 600: // 10 min + { + final SystemMessage msg = new SystemMessage(SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S); + msg2.addInt(5); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + break; + } + case 840: // 14 min + { + final SystemMessage msg = new SystemMessage(SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S); + msg2.addInt(1); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + break; + } + case 900: // 15 min + { + _state = RaceState.WAITING; + + calculateOdds(); + + final SystemMessage msg = new SystemMessage(SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.TICKETS_SALES_ARE_CLOSED_FOR_MONSTER_RACE_S1_ODDS_ARE_POSTED); + msg2.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + break; + } + case 960: // 16 min + case 1020: // 17 min + { + final int minutes = (_finalCountdown == 960) ? 2 : 1; + final SystemMessage msg = new SystemMessage(SystemMessageId.MONSTER_RACE_S2_WILL_BEGIN_IN_S1_MINUTE_S); + msg.addInt(minutes); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + break; + } + case 1050: // 17 min 30 sec + { + final SystemMessage msg = new SystemMessage(SystemMessageId.MONSTER_RACE_S1_WILL_BEGIN_IN_30_SECONDS); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + break; + } + case 1070: // 17 min 50 sec + { + final SystemMessage msg = new SystemMessage(SystemMessageId.MONSTER_RACE_S1_IS_ABOUT_TO_BEGIN_COUNTDOWN_IN_FIVE_SECONDS); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + 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; + final SystemMessage msg = new SystemMessage(SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SECOND_S); + msg.addInt(seconds); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + break; + } + case 1080: // 18 min + { + _state = RaceState.STARTING_RACE; + _packet = new MonRaceInfo(CODES[1][0], CODES[1][1], getMonsters(), getSpeeds()); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, new SystemMessage(SystemMessageId.THEY_RE_OFF), SOUND_1, SOUND_2, _packet); + break; + } + case 1085: // 18 min 5 sec + { + _packet = new MonRaceInfo(CODES[2][0], CODES[2][1], getMonsters(), getSpeeds()); + + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, _packet); + break; + } + case 1115: // 18 min 35 sec + { + _state = RaceState.RACE_END; + + // Populate history info with data, stores it in database. + final HistoryInfo info = _history.get(_history.size() - 1); + info.setFirst(getFirstPlace()); + info.setSecond(getSecondPlace()); + info.setOddRate(_odds.get(getFirstPlace() - 1)); + + saveHistory(info); + clearBets(); + + final SystemMessage msg = new SystemMessage(SystemMessageId.FIRST_PRIZE_GOES_TO_THE_PLAYER_IN_LANE_S1_SECOND_PRIZE_GOES_TO_THE_PLAYER_IN_LANE_S2); + msg.addInt(getFirstPlace()); + msg.addInt(getSecondPlace()); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.MONSTER_RACE_S1_IS_FINISHED); + msg2.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + _raceNumber++; + break; + } + case 1140: // 19 min + { + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, new DeleteObject(getMonsters()[0]), new DeleteObject(getMonsters()[1]), new DeleteObject(getMonsters()[2]), new DeleteObject(getMonsters()[3]), new DeleteObject(getMonsters()[4]), new DeleteObject(getMonsters()[5]), new DeleteObject(getMonsters()[6]), new DeleteObject(getMonsters()[7])); + break; + } + } + _finalCountdown += 1; + } + } + + public void newRace() + { + // Edit _history. + _history.add(new HistoryInfo(_raceNumber, 0, 0, 0)); + + // Randomize _npcTemplates. + Collections.shuffle(_npcTemplates); + + // Setup 8 new creatures ; pickup the first 8 from _npcTemplates. + for (int i = 0; i < 8; i++) + { + try + { + final NpcTemplate template = NpcData.getInstance().getTemplate(_npcTemplates.get(i)); + _monsters[i] = (Npc) Class.forName("org.l2jmobius.gameserver.model.actor.instance." + template.getType() + "Instance").getConstructors()[0].newInstance(template); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "", e); + } + } + } + + public void newSpeeds() + { + _speeds = new int[8][20]; + int total = 0; + _first[1] = 0; + _second[1] = 0; + + for (int i = 0; i < 8; i++) + { + total = 0; + for (int j = 0; j < 20; j++) + { + if (j == 19) + { + _speeds[i][j] = 100; + } + else + { + _speeds[i][j] = Rnd.get(60) + 65; + } + total += _speeds[i][j]; + } + + if (total >= _first[1]) + { + _second[0] = _first[0]; + _second[1] = _first[1]; + _first[0] = 8 - i; + _first[1] = total; + } + else if (total >= _second[1]) + { + _second[0] = 8 - i; + _second[1] = total; + } + } + } + + /** + * Load past races informations, feeding _history arrayList.
+ * Also sets _raceNumber, based on latest HistoryInfo loaded. + */ + protected void loadHistory() + { + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("SELECT * FROM mdt_history"); + ResultSet rset = statement.executeQuery(); + + while (rset.next()) + { + _history.add(new HistoryInfo(rset.getInt("race_id"), rset.getInt("first"), rset.getInt("second"), rset.getDouble("odd_rate"))); + _raceNumber++; + } + rset.close(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't load history: " + e.getMessage(), e); + } + LOGGER.info("MonsterRace: loaded " + _history.size() + " records, currently on race #" + _raceNumber); + } + + /** + * Save an history record into database. + * @param history The infos to store. + */ + protected void saveHistory(HistoryInfo history) + { + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("INSERT INTO mdt_history (race_id, first, second, odd_rate) VALUES (?,?,?,?)"); + statement.setInt(1, history.getRaceId()); + statement.setInt(2, history.getFirst()); + statement.setInt(3, history.getSecond()); + statement.setDouble(4, history.getOddRate()); + statement.execute(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't save history: " + e.getMessage(), e); + } + } + + /** + * Load current bets per lane ; initialize the map keys. + */ + protected void loadBets() + { + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("SELECT * FROM mdt_bets"); + ResultSet rset = statement.executeQuery(); + + while (rset.next()) + { + setBetOnLane(rset.getInt("lane_id"), rset.getLong("bet"), false); + } + + rset.close(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't load bets: " + e.getMessage(), e); + } + } + + /** + * Save the current lane bet into database. + * @param lane : The lane to affect. + * @param sum : The sum to set. + */ + protected void saveBet(int lane, long sum) + { + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("REPLACE INTO mdt_bets (lane_id, bet) VALUES (?,?)"); + statement.setInt(1, lane); + statement.setLong(2, sum); + statement.execute(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't save bet: " + e.getMessage(), e); + } + } + + /** + * Clear all lanes bets, either on database or Map. + */ + protected void clearBets() + { + for (int key : _betsPerLane.keySet()) + { + _betsPerLane.put(key, 0L); + } + + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("UPDATE mdt_bets SET bet = 0"); + statement.execute(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't clear bets: " + e.getMessage(), e); + } + } + + /** + * Setup lane bet, based on previous value (if any). + * @param lane : The lane to edit. + * @param amount : The amount to add. + * @param saveOnDb : Should it be saved on db or not. + */ + public void setBetOnLane(int lane, long amount, boolean saveOnDb) + { + final long sum = (_betsPerLane.containsKey(lane)) ? _betsPerLane.get(lane) + amount : amount; + + _betsPerLane.put(lane, sum); + + if (saveOnDb) + { + saveBet(lane, sum); + } + } + + /** + * Calculate odds for every lane, based on others lanes. + */ + protected void calculateOdds() + { + // Clear previous List holding old odds. + _odds.clear(); + + // Sort bets lanes per lane. + final Map 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; + } + + // 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)); + } + } + + public Npc[] getMonsters() + { + return _monsters; + } + + public int[][] getSpeeds() + { + return _speeds; + } + + public int getFirstPlace() + { + return _first[0]; + } + + public int getSecondPlace() + { + return _second[0]; + } + + public MonRaceInfo getRacePacket() + { + return _packet; + } + + public RaceState getCurrentRaceState() + { + return _state; + } + + public int getRaceNumber() + { + return _raceNumber; + } + + public List getHistory() + { + return _history; + } + + public List getOdds() + { + return _odds; + } + + public static MonsterRace getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final MonsterRace INSTANCE = new MonsterRace(); + } +} \ No newline at end of file diff --git a/L2J_Mobius_4.0_GrandCrusade/java/org/l2jmobius/gameserver/model/actor/instance/RaceManagerInstance.java b/L2J_Mobius_4.0_GrandCrusade/java/org/l2jmobius/gameserver/model/actor/instance/RaceManagerInstance.java index f893d4f5f2..2375543774 100644 --- a/L2J_Mobius_4.0_GrandCrusade/java/org/l2jmobius/gameserver/model/actor/instance/RaceManagerInstance.java +++ b/L2J_Mobius_4.0_GrandCrusade/java/org/l2jmobius/gameserver/model/actor/instance/RaceManagerInstance.java @@ -16,69 +16,26 @@ */ package org.l2jmobius.gameserver.model.actor.instance; -import java.util.Collection; -import java.util.concurrent.ConcurrentHashMap; +import java.util.List; +import java.util.Locale; import org.l2jmobius.Config; -import org.l2jmobius.commons.concurrent.ThreadPool; -import org.l2jmobius.gameserver.MonsterRace; -import org.l2jmobius.gameserver.enums.InstanceType; +import org.l2jmobius.commons.util.StringUtil; import org.l2jmobius.gameserver.idfactory.IdFactory; -import org.l2jmobius.gameserver.model.actor.Creature; +import org.l2jmobius.gameserver.instancemanager.games.MonsterRace; +import org.l2jmobius.gameserver.instancemanager.games.MonsterRace.HistoryInfo; +import org.l2jmobius.gameserver.instancemanager.games.MonsterRace.RaceState; import org.l2jmobius.gameserver.model.actor.Npc; import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate; -import org.l2jmobius.gameserver.model.itemcontainer.Inventory; import org.l2jmobius.gameserver.model.items.instance.ItemInstance; import org.l2jmobius.gameserver.network.SystemMessageId; import org.l2jmobius.gameserver.network.serverpackets.ActionFailed; -import org.l2jmobius.gameserver.network.serverpackets.DeleteObject; -import org.l2jmobius.gameserver.network.serverpackets.IClientOutgoingPacket; -import org.l2jmobius.gameserver.network.serverpackets.InventoryUpdate; -import org.l2jmobius.gameserver.network.serverpackets.MonRaceInfo; import org.l2jmobius.gameserver.network.serverpackets.NpcHtmlMessage; -import org.l2jmobius.gameserver.network.serverpackets.PlaySound; import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; -import org.l2jmobius.gameserver.util.Broadcast; public class RaceManagerInstance extends Npc { - public static final int LANES = 8; - public static final int WINDOW_START = 0; - - private static Collection _managers; - protected static int _raceNumber = 4; - - // Time Constants - private static final long SECOND = 1000; - private static final long MINUTE = 60 * SECOND; - - private static int _minutes = 5; - - // States - private static final int ACCEPTING_BETS = 0; - private static final int WAITING = 1; - private static final int STARTING_RACE = 2; - private static final int RACE_END = 3; - private static int _state = RACE_END; - - protected static final int[][] _codes = - { - { - -1, - 0 - }, - { - 0, - 15322 - }, - { - 13765, - -1 - } - }; - private static boolean _notInitialized = true; - protected static MonRaceInfo _packet; - protected static final int[] _cost = + protected static final int TICKET_PRICES[] = { 100, 500, @@ -93,223 +50,167 @@ public class RaceManagerInstance extends Npc public RaceManagerInstance(NpcTemplate template) { super(template); - setInstanceType(InstanceType.RaceManagerInstance); - - if (!Config.ALLOW_RACE) - { - return; - } - - if (_notInitialized) - { - _notInitialized = false; - - _managers = ConcurrentHashMap.newKeySet(); - - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1), 0, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1), 30 * SECOND, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1), MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1), MINUTE + (30 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S), 2 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S), 3 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S), 4 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S), 5 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S), 6 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKETS_SALES_ARE_CLOSED_FOR_MONSTER_RACE_S1_ODDS_ARE_POSTED), 7 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.MONSTER_RACE_S2_WILL_BEGIN_IN_S1_MINUTE_S), 7 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.MONSTER_RACE_S2_WILL_BEGIN_IN_S1_MINUTE_S), 8 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.MONSTER_RACE_S1_WILL_BEGIN_IN_30_SECONDS), (8 * MINUTE) + (30 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.MONSTER_RACE_S1_IS_ABOUT_TO_BEGIN_COUNTDOWN_IN_FIVE_SECONDS), (8 * MINUTE) + (50 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SECOND_S), (8 * MINUTE) + (55 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SECOND_S), (8 * MINUTE) + (56 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SECOND_S), (8 * MINUTE) + (57 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SECOND_S), (8 * MINUTE) + (58 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SECOND_S), (8 * MINUTE) + (59 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.THEY_RE_OFF), 9 * MINUTE, 10 * MINUTE); - } - _managers.add(this); - } - - @Override - public boolean isAutoAttackable(Creature attacker) - { - if (attacker.isMonster()) - { - return true; - } - - return super.isAutoAttackable(attacker); - } - - class Announcement implements Runnable - { - private final SystemMessageId _type; - - public Announcement(SystemMessageId pType) - { - _type = pType; - } - - @Override - public void run() - { - makeAnnouncement(_type); - } - } - - public void makeAnnouncement(SystemMessageId type) - { - final SystemMessage sm = new SystemMessage(type); - switch (type.getId()) - { - case 816: // SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1 - case 817: // SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1 - { - if (_state != ACCEPTING_BETS) - {// LOGGER.info("Race Initializing"); - _state = ACCEPTING_BETS; - startRace(); - } // else{LOGGER.info("Race open");} - sm.addInt(_raceNumber); - break; - } - case 818: // SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S - case 820: // SystemMessageId.MONSTER_RACE_S2_WILL_BEGIN_IN_S1_MINUTE_S - case 823: // SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SECOND_S - { - sm.addInt(_minutes); - if (type.getId() == 820) - { - sm.addInt(_raceNumber); - } - _minutes--; - break; - } - case 819: // SystemMessageId.TICKETS_SALES_ARE_CLOSED_FOR_MONSTER_RACE_S1_ODDS_ARE_POSTED - { - // LOGGER.info("Sales closed"); - sm.addInt(_raceNumber); - _state = WAITING; - _minutes = 2; - break; - } - case 821: // SystemMessageId.MONSTER_RACE_S1_WILL_BEGIN_IN_30_SECONDS - case 822: // SystemMessageId.MONSTER_RACE_S1_IS_ABOUT_TO_BEGIN_COUNTDOWN_IN_FIVE_SECONDS - case 825: // SystemMessageId.MONSTER_RACE_S1_IS_FINISHED - { - sm.addInt(_raceNumber); - _minutes = 5; - break; - } - case 826: // SystemMessageId.FIRST_PRIZE_GOES_TO_THE_PLAYER_IN_LANE_S1_SECOND_PRIZE_GOES_TO_THE_PLAYER_IN_LANE_S2 - { - // LOGGER.info("Placing"); - _state = RACE_END; - sm.addInt(MonsterRace.getInstance().getFirstPlace()); - sm.addInt(MonsterRace.getInstance().getSecondPlace()); - break; - } - } - // _logn.info("Counter: "+minutes); - // LOGGER.info("State: "+state); - broadcast(sm); - // LOGGER.info("Player's known: "+getKnownPlayers().size()); - - if (type == SystemMessageId.THEY_RE_OFF) - { - // LOGGER.info("Starting race"); - _state = STARTING_RACE; - startRace(); - _minutes = 5; - } - } - - protected void broadcast(IClientOutgoingPacket pkt) - { - for (RaceManagerInstance manager : _managers) - { - if (!manager.isDead()) - { - Broadcast.toKnownPlayers(manager, pkt); - } - } - } - - public void sendMonsterInfo() - { - broadcast(_packet); - } - - private void startRace() - { - final MonsterRace race = MonsterRace.getInstance(); - if (_state == STARTING_RACE) - { - // state++; - broadcast(new PlaySound(1, "S_Race", 0, 0, 0, 0, 0)); - broadcast(new PlaySound(0, "ItemSound2.race_start", 1, 121209259, 12125, 182487, -3559)); - _packet = new MonRaceInfo(_codes[1][0], _codes[1][1], race.getMonsters(), race.getSpeeds()); - sendMonsterInfo(); - - ThreadPool.schedule(new RunRace(), 5000); - } - else - { - // state++; - race.newRace(); - race.newSpeeds(); - _packet = new MonRaceInfo(_codes[0][0], _codes[0][1], race.getMonsters(), race.getSpeeds()); - sendMonsterInfo(); - } } @Override public void onBypassFeedback(PlayerInstance player, String command) { - if (command.startsWith("BuyTicket") && (_state != ACCEPTING_BETS)) - { - if (!Config.ALLOW_RACE) - { - return; - } - player.sendPacket(SystemMessageId.MONSTER_RACE_TICKETS_ARE_NO_LONGER_AVAILABLE); - command = "Chat 0"; - } - if (command.startsWith("ShowOdds") && (_state == ACCEPTING_BETS)) - { - if (!Config.ALLOW_RACE) - { - return; - } - player.sendPacket(SystemMessageId.MONSTER_RACE_PAYOUT_INFORMATION_IS_NOT_AVAILABLE_WHILE_TICKETS_ARE_BEING_SOLD); - command = "Chat 0"; - } - if (command.startsWith("BuyTicket")) { - if (!Config.ALLOW_RACE) + if (!Config.ALLOW_RACE || (MonsterRace.getInstance().getCurrentRaceState() != RaceState.ACCEPTING_BETS)) { + player.sendPacket(SystemMessageId.MONSTER_RACE_TICKETS_ARE_NO_LONGER_AVAILABLE); + super.onBypassFeedback(player, "Chat 0"); return; } + int 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))) { val = 0; } - showBuyTicket(player, val); + + String search, replace; + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + + if (val < 10) + { + html.setFile(player, getHtmlPath(getId(), 2, player)); + for (int i = 0; i < 8; i++) + { + int n = i + 1; + search = "Mob" + n; + html.replace(search, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); + } + search = "No1"; + if (val == 0) + { + html.replace(search, ""); + } + else + { + html.replace(search, val); + player.setRace(0, val); + } + } + else if (val < 20) + { + if (player.getRace(0) == 0) + { + return; + } + + html.setFile(player, getHtmlPath(getId(), 3, player)); + html.replace("0place", player.getRace(0)); + search = "Mob1"; + replace = MonsterRace.getInstance().getMonsters()[player.getRace(0) - 1].getTemplate().getName(); + html.replace(search, replace); + search = "0adena"; + + if (val == 10) + { + html.replace(search, ""); + } + else + { + html.replace(search, TICKET_PRICES[val - 11]); + player.setRace(1, val - 10); + } + } + else if (val == 20) + { + if ((player.getRace(0) == 0) || (player.getRace(1) == 0)) + { + return; + } + + html.setFile(player, getHtmlPath(getId(), 4, player)); + html.replace("0place", player.getRace(0)); + search = "Mob1"; + replace = MonsterRace.getInstance().getMonsters()[player.getRace(0) - 1].getTemplate().getName(); + 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); + } + else + { + if ((player.getRace(0) == 0) || (player.getRace(1) == 0)) + { + return; + } + + int ticket = player.getRace(0); + int priceId = player.getRace(1); + + if (!player.reduceAdena("Race", TICKET_PRICES[priceId - 1], this, true)) + { + return; + } + + player.setRace(0, 0); + player.setRace(1, 0); + + ItemInstance item = new ItemInstance(IdFactory.getInstance().getNextId(), 4443); + item.setCount(1); + item.setEnchantLevel(MonsterRace.getInstance().getRaceNumber()); + item.setCustomType1(ticket); + item.setCustomType2(TICKET_PRICES[priceId - 1] / 100); + + player.addItem("Race", item, player, false); + final SystemMessage msg = new SystemMessage(SystemMessageId.ACQUIRED_S1_S2); + msg.addInt(MonsterRace.getInstance().getRaceNumber()); + msg.addItemName(4443); + player.sendPacket(msg); + + // Refresh lane bet. + MonsterRace.getInstance().setBetOnLane(ticket, TICKET_PRICES[priceId - 1], true); + super.onBypassFeedback(player, "Chat 0"); + return; + } + html.replace("1race", MonsterRace.getInstance().getRaceNumber()); + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); } else if (command.equals("ShowOdds")) { - if (!Config.ALLOW_RACE) + if (!Config.ALLOW_RACE || (MonsterRace.getInstance().getCurrentRaceState() == RaceState.ACCEPTING_BETS)) { + player.sendPacket(SystemMessageId.MONSTER_RACE_PAYOUT_INFORMATION_IS_NOT_AVAILABLE_WHILE_TICKETS_ARE_BEING_SOLD); + super.onBypassFeedback(player, "Chat 0"); return; } - showOdds(player); + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 5, player)); + for (int i = 0; i < 8; i++) + { + final int n = i + 1; + + html.replace("Mob" + n, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); + + // Odd + final double odd = MonsterRace.getInstance().getOdds().get(i); + html.replace("Odd" + n, (odd > 0D) ? String.format(Locale.ENGLISH, "%.1f", odd) : "&$804;"); + } + html.replace("1race", MonsterRace.getInstance().getRaceNumber()); + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); } else if (command.equals("ShowInfo")) { @@ -317,269 +218,160 @@ public class RaceManagerInstance extends Npc { return; } - showMonsterInfo(player); + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 6, player)); + + for (int i = 0; i < 8; i++) + { + int n = i + 1; + String search = "Mob" + n; + html.replace(search, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); + } + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); } - else if (command.equals("calculateWin")) + else if (command.equals("ShowTickets")) { if (!Config.ALLOW_RACE) { + super.onBypassFeedback(player, "Chat 0"); return; } - // displayCalculateWinnings(player); + + // Generate data. + final StringBuilder sb = new StringBuilder(); + + // Retrieve player's tickets. + for (ItemInstance ticket : player.getInventory().getAllItemsByItemId(4443)) + { + // Don't list current race tickets. + if (ticket.getEnchantLevel() == MonsterRace.getInstance().getRaceNumber()) + { + continue; + } + + StringUtil.append(sb, "", "" + ticket.getEnchantLevel(), " Race Number", "" + ticket.getCustomType1(), " Number", "" + (ticket.getCustomType2() * 100), " Adena"); + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 7, player)); + html.replace("%tickets%", sb.toString()); + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); } - else if (command.equals("viewHistory")) + else if (command.startsWith("ShowTicket")) + { + // Retrieve ticket objectId. + final int val = Integer.parseInt(command.substring(11)); + if (!Config.ALLOW_RACE || (val == 0)) + { + super.onBypassFeedback(player, "Chat 0"); + return; + } + + // Retrieve ticket on player's inventory. + final ItemInstance 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; + + // Retrieve HistoryInfo for that race. + final HistoryInfo info = MonsterRace.getInstance().getHistory().get(raceId - 1); + if (info == null) + { + super.onBypassFeedback(player, "Chat 0"); + return; + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 8, player)); + html.replace("%raceId%", raceId); + html.replace("%lane%", lane); + html.replace("%bet%", bet); + html.replace("%firstLane%", info.getFirst()); + html.replace("%odd%", (lane == info.getFirst()) ? String.format(Locale.ENGLISH, "%.2f", info.getOddRate()) : "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 (!Config.ALLOW_RACE || (val == 0)) + { + super.onBypassFeedback(player, "Chat 0"); + return; + } + + // Delete ticket on player's inventory. + final ItemInstance 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; + + // Retrieve HistoryInfo for that race. + final HistoryInfo info = MonsterRace.getInstance().getHistory().get(raceId - 1); + if (info == 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()) ? info.getOddRate() : 0.01)), this, true); + } + + super.onBypassFeedback(player, "Chat 0"); + return; + } + else if (command.equals("ViewHistory")) { if (!Config.ALLOW_RACE) { + super.onBypassFeedback(player, "Chat 0"); return; } - // displayHistory(player); + + // Generate data. + final StringBuilder sb = new StringBuilder(); + + // Use whole history, pickup from 'last element' and stop at 'latest element - 7'. + final List history = MonsterRace.getInstance().getHistory(); + for (int i = history.size() - 1; i >= Math.max(0, history.size() - 7); i--) + { + final HistoryInfo info = history.get(i); + StringUtil.append(sb, "", "" + info.getRaceId(), " th", "" + info.getFirst(), " Lane ", "" + info.getSecond(), " Lane", String.format(Locale.ENGLISH, "%.2f", info.getOddRate()), " Times"); + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 9, player)); + html.replace("%infos%", sb.toString()); + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); } else { - // getKnownList().removeKnownObject(player); super.onBypassFeedback(player, command); } } - - public void showOdds(PlayerInstance player) - { - if (_state == ACCEPTING_BETS) - { - return; - } - final int npcId = getTemplate().getId(); - String filename; - String search; - final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); - filename = getHtmlPath(npcId, 5, player); - html.setFile(player, filename); - for (int i = 0; i < 8; i++) - { - final int n = i + 1; - search = "Mob" + n; - html.replace(search, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); - } - html.replace("1race", String.valueOf(_raceNumber)); - html.replace("%objectId%", String.valueOf(getObjectId())); - player.sendPacket(html); - player.sendPacket(ActionFailed.STATIC_PACKET); - } - - public void showMonsterInfo(PlayerInstance player) - { - final int npcId = getTemplate().getId(); - String filename; - String search; - final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); - filename = getHtmlPath(npcId, 6, player); - html.setFile(player, filename); - for (int i = 0; i < 8; i++) - { - final int n = i + 1; - search = "Mob" + n; - html.replace(search, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); - } - html.replace("%objectId%", String.valueOf(getObjectId())); - player.sendPacket(html); - player.sendPacket(ActionFailed.STATIC_PACKET); - } - - public void showBuyTicket(PlayerInstance player, int value) - { - if (_state != ACCEPTING_BETS) - { - return; - } - final int npcId = getTemplate().getId(); - SystemMessage sm; - String filename; - String search; - String replace; - final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); - if (value < 10) - { - filename = getHtmlPath(npcId, 2, player); - html.setFile(player, filename); - for (int i = 0; i < 8; i++) - { - final int n = i + 1; - search = "Mob" + n; - html.replace(search, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); - } - search = "No1"; - if (value == 0) - { - html.replace(search, ""); - } - else - { - html.replace(search, Integer.toString(value)); - player.setRace(0, value); - } - } - else if (value < 20) - { - if (player.getRace(0) == 0) - { - return; - } - filename = getHtmlPath(npcId, 3, player); - html.setFile(player, filename); - html.replace("0place", Integer.toString(player.getRace(0))); - search = "Mob1"; - replace = MonsterRace.getInstance().getMonsters()[player.getRace(0) - 1].getTemplate().getName(); - html.replace(search, replace); - search = "0adena"; - if (value == 10) - { - html.replace(search, ""); - } - else - { - html.replace(search, Integer.toString(_cost[value - 11])); - player.setRace(1, value - 10); - } - } - else if (value == 20) - { - if ((player.getRace(0) == 0) || (player.getRace(1) == 0)) - { - return; - } - filename = getHtmlPath(npcId, 4, player); - html.setFile(player, filename); - html.replace("0place", Integer.toString(player.getRace(0))); - search = "Mob1"; - replace = MonsterRace.getInstance().getMonsters()[player.getRace(0) - 1].getTemplate().getName(); - html.replace(search, replace); - search = "0adena"; - final int price = _cost[player.getRace(1) - 1]; - html.replace(search, Integer.toString(price)); - search = "0tax"; - final int tax = 0; - html.replace(search, Integer.toString(tax)); - search = "0total"; - final int total = price + tax; - html.replace(search, Integer.toString(total)); - } - else - { - if ((player.getRace(0) == 0) || (player.getRace(1) == 0)) - { - return; - } - final int ticket = player.getRace(0); - final int priceId = player.getRace(1); - if (!player.reduceAdena("Race", _cost[priceId - 1], this, true)) - { - return; - } - player.setRace(0, 0); - player.setRace(1, 0); - sm = new SystemMessage(SystemMessageId.ACQUIRED_S1_S2); - sm.addInt(_raceNumber); - sm.addItemName(4443); - player.sendPacket(sm); - final ItemInstance item = new ItemInstance(IdFactory.getInstance().getNextId(), 4443); - item.setCount(1); - item.setEnchantLevel(_raceNumber); - item.setCustomType1(ticket); - item.setCustomType2(_cost[priceId - 1] / 100); - player.getInventory().addItem("Race", item, player, this); - final InventoryUpdate iu = new InventoryUpdate(); - iu.addItem(item); - final ItemInstance adenaupdate = player.getInventory().getItemByItemId(Inventory.ADENA_ID); - iu.addModifiedItem(adenaupdate); - player.sendInventoryUpdate(iu); - return; - } - html.replace("1race", String.valueOf(_raceNumber)); - html.replace("%objectId%", String.valueOf(getObjectId())); - player.sendPacket(html); - player.sendPacket(ActionFailed.STATIC_PACKET); - } - - public static class Race - { - private final Info[] _info; - - public Race(Info[] pInfo) - { - _info = pInfo; - } - - public Info getLaneInfo(int lane) - { - return _info[lane]; - } - - public class Info - { - private final int _id; - private final int _place; - private final int _odds; - private final int _payout; - - public Info(int pId, int pPlace, int pOdds, int pPayout) - { - _id = pId; - _place = pPlace; - _odds = pOdds; - _payout = pPayout; - } - - public int getId() - { - return _id; - } - - public int getOdds() - { - return _odds; - } - - public int getPayout() - { - return _payout; - } - - public int getPlace() - { - return _place; - } - } - - } - - class RunRace implements Runnable - { - @Override - public void run() - { - _packet = new MonRaceInfo(_codes[2][0], _codes[2][1], MonsterRace.getInstance().getMonsters(), MonsterRace.getInstance().getSpeeds()); - sendMonsterInfo(); - ThreadPool.schedule(new RunEnd(), 30000); - } - } - - class RunEnd implements Runnable - { - @Override - public void run() - { - makeAnnouncement(SystemMessageId.FIRST_PRIZE_GOES_TO_THE_PLAYER_IN_LANE_S1_SECOND_PRIZE_GOES_TO_THE_PLAYER_IN_LANE_S2); - makeAnnouncement(SystemMessageId.MONSTER_RACE_S1_IS_FINISHED); - _raceNumber++; - - DeleteObject obj = null; - for (int i = 0; i < 8; i++) - { - obj = new DeleteObject(MonsterRace.getInstance().getMonsters()[i]); - broadcast(obj); - MonsterRace.getInstance().getMonsters()[i].deleteMe(); - } - } - } - } diff --git a/L2J_Mobius_4.0_GrandCrusade/java/org/l2jmobius/gameserver/util/Broadcast.java b/L2J_Mobius_4.0_GrandCrusade/java/org/l2jmobius/gameserver/util/Broadcast.java index 60eb0ff361..8d751afc53 100644 --- a/L2J_Mobius_4.0_GrandCrusade/java/org/l2jmobius/gameserver/util/Broadcast.java +++ b/L2J_Mobius_4.0_GrandCrusade/java/org/l2jmobius/gameserver/util/Broadcast.java @@ -20,10 +20,12 @@ import java.util.logging.Level; import java.util.logging.Logger; import org.l2jmobius.gameserver.enums.ChatType; +import org.l2jmobius.gameserver.instancemanager.ZoneManager; import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.actor.Creature; import org.l2jmobius.gameserver.model.actor.Summon; import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance; +import org.l2jmobius.gameserver.model.zone.ZoneType; import org.l2jmobius.gameserver.network.serverpackets.CharInfo; import org.l2jmobius.gameserver.network.serverpackets.CreatureSay; import org.l2jmobius.gameserver.network.serverpackets.ExShowScreenMessage; @@ -191,4 +193,29 @@ public class Broadcast { toAllOnlinePlayers(new ExShowScreenMessage(text, 10000)); } + + /** + * Send a packet to all players in a specific zone type. + * @param ZoneType. + * @param zoneType : The zone type to send packets. + * @param packets : The packets to send. + */ + public static void toAllPlayersInZoneType(Class zoneType, IClientOutgoingPacket... packets) + { + for (ZoneType zone : ZoneManager.getInstance().getAllZones(zoneType)) + { + for (Creature creature : zone.getCharactersInside()) + { + if (creature == null) + { + continue; + } + + for (IClientOutgoingPacket packet : packets) + { + creature.sendPacket(packet); + } + } + } + } } diff --git a/L2J_Mobius_5.0_Salvation/dist/db_installer/sql/game/mdt_bets.sql b/L2J_Mobius_5.0_Salvation/dist/db_installer/sql/game/mdt_bets.sql new file mode 100644 index 0000000000..8d7d4cb738 --- /dev/null +++ b/L2J_Mobius_5.0_Salvation/dist/db_installer/sql/game/mdt_bets.sql @@ -0,0 +1,15 @@ +CREATE TABLE IF NOT EXISTS `mdt_bets` ( + `lane_id` INT(1) DEFAULT 0, + `bet` INT DEFAULT 0, + PRIMARY KEY (`lane_id`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci; + +INSERT INTO `mdt_bets` VALUES +('1','0'), +('2','0'), +('3','0'), +('4','0'), +('5','0'), +('6','0'), +('7','0'), +('8','0'); \ No newline at end of file diff --git a/L2J_Mobius_5.0_Salvation/dist/db_installer/sql/game/mdt_history.sql b/L2J_Mobius_5.0_Salvation/dist/db_installer/sql/game/mdt_history.sql new file mode 100644 index 0000000000..06978f3377 --- /dev/null +++ b/L2J_Mobius_5.0_Salvation/dist/db_installer/sql/game/mdt_history.sql @@ -0,0 +1,7 @@ +CREATE TABLE IF NOT EXISTS `mdt_history` ( + `race_id` MEDIUMINT DEFAULT 0, + `first` INT(1) DEFAULT 0, + `second` INT(1) DEFAULT 0, + `odd_rate` DOUBLE(10,2) DEFAULT 0, + PRIMARY KEY (`race_id`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci; \ No newline at end of file diff --git a/L2J_Mobius_5.0_Salvation/dist/game/config/AdminCommands.xml b/L2J_Mobius_5.0_Salvation/dist/game/config/AdminCommands.xml index 36ab1240b5..215b7c679a 100644 --- a/L2J_Mobius_5.0_Salvation/dist/game/config/AdminCommands.xml +++ b/L2J_Mobius_5.0_Salvation/dist/game/config/AdminCommands.xml @@ -416,9 +416,6 @@ - - - diff --git a/L2J_Mobius_5.0_Salvation/dist/game/data/html/default/30995-1.htm b/L2J_Mobius_5.0_Salvation/dist/game/data/html/default/30995-1.htm index 4962bdf3a2..d38d518149 100644 --- a/L2J_Mobius_5.0_Salvation/dist/game/data/html/default/30995-1.htm +++ b/L2J_Mobius_5.0_Salvation/dist/game/data/html/default/30995-1.htm @@ -1,19 +1,22 @@ -

-
-In a monster race eight monsters race at the same time. If you guess which monster is expected to finish first and buy that monster's race ticket, based upon the odds to win you will receive an appropriate amount of Adena.
-How the Race WorksMonster races run every 20 minutes in real time.
-How to Participate
-You can purchase a monster race ticket through a race manager NPC. Only between 18 minutes before the start of a race until 3 minutes before the race begins can you buy a ticket for the applicable race. When you purchase a ticket through a race manager, you can find out the names of the participating monsters and their current statistics. Three minutes before a race starts, the race manager will stop ticket sales and announce the monsters' individual odds for the current race.
-Betting
-There are two types of race betting. The Win bet means that you back a particular monster to win 1st place. The Place bet means that you will have to guess the 1st and 2nd place monsters, regardless of who finishes first or second.
+ +
+ +
+In a monster race eight monsters race at the same time. If you guess which monster is expected to finish first and buy that monster's race ticket, based upon the odds to win you will receive an appropriate amount of adena.
+Racing Schedule
+Monster races every 20 minutes in real time.
+Method of Race Participation
+You can purchase a monster race ticket through a race manager NPC. Only between 18 minutes before the start of a race until 3 minutes before the race begins you can buy a ticket for the applicable race. When you purchase a ticket through a conversation with a race manager, you can find out the names of participating monsters in the race and their current statistics. Five minutes before a race starts, the race manager will stop ticket sales and announce monsters' individual odds rate for the current race.
+Types of Race
+There are two types of race betting. The Win type betting means that you back a monster that will win the first prize. Place type of betting means that you will have to guess the 1st and 2nd winning monsters, regardless of who finishes first or second.
Win - * Among the entire betting amount for Win betting, after subtracting the race track's own share of profit, the amount left will be your total winnings. Three minutes before the race, each monster's expected odds are announced. Each Win ticket's dividend amount is calculated by multiplying the odds by the price of a ticket. The odds are never less than 1. - * The prize money does not get transferred to the next race.
+* Among the entire betting amount for Win betting, after subtracting the race track's own share of profit, the amount left will be your total winnings. Three minutes before the race each monster's expected odds are announced. Each Win ticket's dividend amount is calculated by multiplying the odds by the price of a ticket. The odds rate is never less than 1. +* The prize money does not get transferred to the next race.
Place - * Place betting is a way of betting when you correctly guess the two monsters who will finish first and second regardless of their order. The method of calculating the dividend amount is the same as Win betting. - * Currently place betting is not available.
+* Place betting is a way of betting when you correctly guess the two monsters who will finish first and second regardless of their order. The method of calculating dividend amount is the same as Win betting.
+* Currently place betting is not offered.
End of Race
-When a race is over, the race manager will shout the results of the race. You can trade your ticket for Adena by speaking to a race manager NPC. Check the Winning Information Data to find out the types of bets and total winnings for the race you bet on, as well as your win status and prize amount. If you won, you can receive your prize money thorough a race manager NPC. If you did not win, a race manager will buy back your ticket for a small amount of money.
+When a race is over, the race manager will shout out the results of the race. Through a conversation with a race manager NPC, you can trade your ticket for adena. By looking up on the Winning Information Data, you can find out the types of tickets and quantity of the winnings in the race you betted on and your win status and prize amount you won. If you won, you can receive your prize money thorough a race manager NPC. If you did not win, a race manager will buy back your ticket by paying you a very small amount of money.
If a server goes down while tickets are being sold, or while a race is in progress, the race will be cancelled. You can receive a full refund for your ticket for a cancelled race through a race manager.
- + \ No newline at end of file diff --git a/L2J_Mobius_5.0_Salvation/dist/game/data/html/default/30995-3.htm b/L2J_Mobius_5.0_Salvation/dist/game/data/html/default/30995-3.htm index 5577c579a4..7ae65a37a4 100644 --- a/L2J_Mobius_5.0_Salvation/dist/game/data/html/default/30995-3.htm +++ b/L2J_Mobius_5.0_Salvation/dist/game/data/html/default/30995-3.htm @@ -3,6 +3,7 @@ + @@ -11,6 +12,7 @@
Lane SelectionOdds
+ @@ -20,18 +22,21 @@
13%
+
Select Bet Amount -100 Adena , 500 Adena -1,000 Adena , 5,000 Adena -10,000 Adena , 20,000 Adena -50,000 Adena , 100,000 Adena +100 Adena , 500 Adena +1,000 Adena , 5,000 Adena +10,000 Adena , 20,000 Adena +50,000 Adena , 100,000 Adena
+ + @@ -39,4 +44,5 @@
+ \ No newline at end of file diff --git a/L2J_Mobius_5.0_Salvation/dist/game/data/html/default/30995-4.htm b/L2J_Mobius_5.0_Salvation/dist/game/data/html/default/30995-4.htm index bcd1f0d520..30c508b9cb 100644 --- a/L2J_Mobius_5.0_Salvation/dist/game/data/html/default/30995-4.htm +++ b/L2J_Mobius_5.0_Salvation/dist/game/data/html/default/30995-4.htm @@ -11,6 +11,7 @@ Odds + @@ -20,6 +21,7 @@
13%
+ @@ -37,11 +39,13 @@ -
Bet Amount Total 0total Adena

+ +
If the information is correct, please click Confirm.

+ diff --git a/L2J_Mobius_5.0_Salvation/dist/game/data/html/default/30995-5.htm b/L2J_Mobius_5.0_Salvation/dist/game/data/html/default/30995-5.htm index b9729e01b0..59a05e14a0 100644 --- a/L2J_Mobius_5.0_Salvation/dist/game/data/html/default/30995-5.htm +++ b/L2J_Mobius_5.0_Salvation/dist/game/data/html/default/30995-5.htm @@ -15,49 +15,49 @@ - + - + - + - + - + - + - + - +
No.1 Mob1&$804; Odd1
No.2 Mob2&$804; Odd2
No.3 Mob3&$804; Odd3
No.4 Mob4&$804; Odd4
No.5 Mob5&$804; Odd5
No.6 Mob6&$804; Odd6
No.7 Mob7&$804; Odd7
No.8 Mob8&$804; Odd8
diff --git a/L2J_Mobius_5.0_Salvation/dist/game/data/html/default/30995-7.htm b/L2J_Mobius_5.0_Salvation/dist/game/data/html/default/30995-7.htm index a058e0e0b3..53eb213a52 100644 --- a/L2J_Mobius_5.0_Salvation/dist/game/data/html/default/30995-7.htm +++ b/L2J_Mobius_5.0_Salvation/dist/game/data/html/default/30995-7.htm @@ -3,22 +3,12 @@ - - + +
Winnings for a Race.LaneBet Amount
Winnings for a Race.LaneBet Amount
- - - - - +%tickets%
1 Number100 Adena
- - - - -
1 Page
- diff --git a/L2J_Mobius_5.0_Salvation/dist/game/data/html/default/30995-8.htm b/L2J_Mobius_5.0_Salvation/dist/game/data/html/default/30995-8.htm index ebbcf542b2..5f856a222f 100644 --- a/L2J_Mobius_5.0_Salvation/dist/game/data/html/default/30995-8.htm +++ b/L2J_Mobius_5.0_Salvation/dist/game/data/html/default/30995-8.htm @@ -4,11 +4,11 @@
-
Race #19090
-Lane 1
-Bet: 100 adena
-1st place: Lane [4]
-Odds:[0.01] +
Race #%raceId%
+Lane: %lane%
+Bet: %bet% Adena
+1st place: Lane [%firstLane%]
+Odds: [%odd%]
@@ -18,8 +18,8 @@ Odds:[0.01] - + - +
\ No newline at end of file diff --git a/L2J_Mobius_5.0_Salvation/dist/game/data/html/default/30995-9.htm b/L2J_Mobius_5.0_Salvation/dist/game/data/html/default/30995-9.htm index ac1201d6f2..c17ab4849a 100644 --- a/L2J_Mobius_5.0_Salvation/dist/game/data/html/default/30995-9.htm +++ b/L2J_Mobius_5.0_Salvation/dist/game/data/html/default/30995-9.htm @@ -7,41 +7,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +%infos%
7 th8 Lane 1 Lane0.00 Times
6 th4 Lane 8 Lane0.00 Times
5 th7 Lane 2 Lane0.00 Times
4 th6 Lane 8 Lane0.00 Times
3 th4 Lane 1 Lane0.00 Times
2 th1 Lane 8 Lane0.00 Times
1 th7 Lane 6 Lane0.00 Times
diff --git a/L2J_Mobius_5.0_Salvation/dist/game/data/html/default/30995.htm b/L2J_Mobius_5.0_Salvation/dist/game/data/html/default/30995.htm index 89f40742ae..271c4ee284 100644 --- a/L2J_Mobius_5.0_Salvation/dist/game/data/html/default/30995.htm +++ b/L2J_Mobius_5.0_Salvation/dist/game/data/html/default/30995.htm @@ -1,15 +1,15 @@ - -
Monster Race Manager
- - - - - - - - - -
- + + +
+ + + + + + + + + + \ No newline at end of file diff --git a/L2J_Mobius_5.0_Salvation/dist/game/data/scripts/handlers/MasterHandler.java b/L2J_Mobius_5.0_Salvation/dist/game/data/scripts/handlers/MasterHandler.java index 9ff84f140c..04132b481d 100644 --- a/L2J_Mobius_5.0_Salvation/dist/game/data/scripts/handlers/MasterHandler.java +++ b/L2J_Mobius_5.0_Salvation/dist/game/data/scripts/handlers/MasterHandler.java @@ -105,7 +105,6 @@ import handlers.admincommandhandlers.AdminMenu; import handlers.admincommandhandlers.AdminMessages; import handlers.admincommandhandlers.AdminMissingHtmls; import handlers.admincommandhandlers.AdminMobGroup; -import handlers.admincommandhandlers.AdminMonsterRace; import handlers.admincommandhandlers.AdminOlympiad; import handlers.admincommandhandlers.AdminOnline; import handlers.admincommandhandlers.AdminPForge; @@ -442,7 +441,6 @@ public class MasterHandler AdminMessages.class, AdminMissingHtmls.class, AdminMobGroup.class, - AdminMonsterRace.class, AdminOlympiad.class, AdminOnline.class, AdminPathNode.class, diff --git a/L2J_Mobius_5.0_Salvation/dist/game/data/scripts/handlers/admincommandhandlers/AdminMonsterRace.java b/L2J_Mobius_5.0_Salvation/dist/game/data/scripts/handlers/admincommandhandlers/AdminMonsterRace.java deleted file mode 100644 index e465186490..0000000000 --- a/L2J_Mobius_5.0_Salvation/dist/game/data/scripts/handlers/admincommandhandlers/AdminMonsterRace.java +++ /dev/null @@ -1,166 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * This program 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. - * - * This program 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 . - */ -package handlers.admincommandhandlers; - -import org.l2jmobius.commons.concurrent.ThreadPool; -import org.l2jmobius.gameserver.MonsterRace; -import org.l2jmobius.gameserver.handler.IAdminCommandHandler; -import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance; -import org.l2jmobius.gameserver.network.SystemMessageId; -import org.l2jmobius.gameserver.network.serverpackets.DeleteObject; -import org.l2jmobius.gameserver.network.serverpackets.MonRaceInfo; -import org.l2jmobius.gameserver.network.serverpackets.PlaySound; -import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; - -/** - * This class handles following admin commands: - invul = turns invulnerability on/off - * @version $Revision: 1.1.6.4 $ $Date: 2007/07/31 10:06:00 $ - */ -public class AdminMonsterRace implements IAdminCommandHandler -{ - private static final String[] ADMIN_COMMANDS = - { - "admin_mons" - }; - - static int state = -1; - - @Override - public boolean useAdminCommand(String command, PlayerInstance activeChar) - { - if (command.equalsIgnoreCase("admin_mons")) - { - handleSendPacket(activeChar); - } - return true; - } - - @Override - public String[] getAdminCommandList() - { - return ADMIN_COMMANDS; - } - - private void handleSendPacket(PlayerInstance activeChar) - { - /* - * -1 0 to initialize the race 0 15322 to start race 13765 -1 in middle of race -1 0 to end the race 8003 to 8027 - */ - - final int[][] codes = - { - { - -1, - 0 - }, - { - 0, - 15322 - }, - { - 13765, - -1 - }, - { - -1, - 0 - } - }; - final MonsterRace race = MonsterRace.getInstance(); - - if (state == -1) - { - state++; - race.newRace(); - race.newSpeeds(); - final MonRaceInfo spk = new MonRaceInfo(codes[state][0], codes[state][1], race.getMonsters(), race.getSpeeds()); - activeChar.sendPacket(spk); - activeChar.broadcastPacket(spk); - } - else if (state == 0) - { - state++; - final SystemMessage sm = new SystemMessage(SystemMessageId.THEY_RE_OFF); - sm.addInt(0); - activeChar.sendPacket(sm); - final PlaySound sRace = new PlaySound(1, "S_Race", 0, 0, 0, 0, 0); - activeChar.sendPacket(sRace); - activeChar.broadcastPacket(sRace); - final PlaySound sRace2 = new PlaySound(0, "ItemSound2.race_start", 1, 121209259, 12125, 182487, -3559); - activeChar.sendPacket(sRace2); - activeChar.broadcastPacket(sRace2); - final MonRaceInfo spk = new MonRaceInfo(codes[state][0], codes[state][1], race.getMonsters(), race.getSpeeds()); - activeChar.sendPacket(spk); - activeChar.broadcastPacket(spk); - - ThreadPool.schedule(new RunRace(codes, activeChar), 5000); - } - - } - - private class RunRace implements Runnable - { - - private final int[][] codes; - private final PlayerInstance activeChar; - - public RunRace(int[][] pCodes, PlayerInstance pActiveChar) - { - codes = pCodes; - activeChar = pActiveChar; - } - - @Override - public void run() - { - // int[][] speeds1 = MonsterRace.getInstance().getSpeeds(); - // MonsterRace.getInstance().newSpeeds(); - // int[][] speeds2 = MonsterRace.getInstance().getSpeeds(); - /* - * int[] speed = new int[8]; for (int i=0; i<8; i++) { for (int j=0; j<20; j++) { //LOGGER.info("Adding "+speeds1[i][j] +" and "+ speeds2[i][j]); speed[i] += (speeds1[i][j]*1); // + (speeds2[i][j]*1); } LOGGER.info("Total speed for "+(i+1)+" = "+speed[i]); } - */ - - final MonRaceInfo spk = new MonRaceInfo(codes[2][0], codes[2][1], MonsterRace.getInstance().getMonsters(), MonsterRace.getInstance().getSpeeds()); - activeChar.sendPacket(spk); - activeChar.broadcastPacket(spk); - ThreadPool.schedule(new RunEnd(activeChar), 30000); - } - } - - private static class RunEnd implements Runnable - { - private final PlayerInstance activeChar; - - public RunEnd(PlayerInstance pActiveChar) - { - activeChar = pActiveChar; - } - - @Override - public void run() - { - DeleteObject obj = null; - for (int i = 0; i < 8; i++) - { - obj = new DeleteObject(MonsterRace.getInstance().getMonsters()[i]); - activeChar.sendPacket(obj); - activeChar.broadcastPacket(obj); - } - state = -1; - } - } -} diff --git a/L2J_Mobius_5.0_Salvation/java/org/l2jmobius/gameserver/GameServer.java b/L2J_Mobius_5.0_Salvation/java/org/l2jmobius/gameserver/GameServer.java index bbea5daf0c..1bdba3bc2c 100644 --- a/L2J_Mobius_5.0_Salvation/java/org/l2jmobius/gameserver/GameServer.java +++ b/L2J_Mobius_5.0_Salvation/java/org/l2jmobius/gameserver/GameServer.java @@ -145,6 +145,7 @@ import org.l2jmobius.gameserver.instancemanager.SiegeGuardManager; import org.l2jmobius.gameserver.instancemanager.SiegeManager; import org.l2jmobius.gameserver.instancemanager.WalkingManager; import org.l2jmobius.gameserver.instancemanager.ZoneManager; +import org.l2jmobius.gameserver.instancemanager.games.MonsterRace; import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.entity.Hero; import org.l2jmobius.gameserver.model.events.EventDispatcher; @@ -406,10 +407,8 @@ public class GameServer ItemsAutoDestroy.getInstance(); } - if (Config.ALLOW_RACE) - { - MonsterRace.getInstance(); - } + MonsterRace.getInstance(); + TaskManager.getInstance(); AntiFeedManager.getInstance().registerEvent(AntiFeedManager.GAME_ID); diff --git a/L2J_Mobius_5.0_Salvation/java/org/l2jmobius/gameserver/MonsterRace.java b/L2J_Mobius_5.0_Salvation/java/org/l2jmobius/gameserver/MonsterRace.java deleted file mode 100644 index cdd92a9a07..0000000000 --- a/L2J_Mobius_5.0_Salvation/java/org/l2jmobius/gameserver/MonsterRace.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * This program 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. - * - * This program 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 . - */ -package org.l2jmobius.gameserver; - -import java.util.logging.Level; -import java.util.logging.Logger; - -import org.l2jmobius.commons.util.Rnd; -import org.l2jmobius.gameserver.data.xml.impl.NpcData; -import org.l2jmobius.gameserver.model.actor.Npc; -import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate; - -public class MonsterRace -{ - protected static final Logger LOGGER = Logger.getLogger(MonsterRace.class.getName()); - - private final Npc[] _monsters; - private int[][] _speeds; - private final int[] _first; - private final int[] _second; - - protected MonsterRace() - { - _monsters = new Npc[8]; - _speeds = new int[8][20]; - _first = new int[2]; - _second = new int[2]; - } - - public void newRace() - { - int random = 0; - - for (int i = 0; i < 8; i++) - { - final int id = 31003; - random = Rnd.get(24); - while (true) - { - for (int j = i - 1; j >= 0; j--) - { - if (_monsters[j].getTemplate().getId() == (id + random)) - { - random = Rnd.get(24); - } - } - break; - } - try - { - final NpcTemplate template = NpcData.getInstance().getTemplate(id + random); - _monsters[i] = (Npc) Class.forName("org.l2jmobius.gameserver.model.actor.instance." + template.getType() + "Instance").getConstructors()[0].newInstance(template); - } - catch (Exception e) - { - LOGGER.log(Level.WARNING, "Unable to create monster!", e); - } - } - newSpeeds(); - } - - public void newSpeeds() - { - _speeds = new int[8][20]; - int total = 0; - _first[1] = 0; - _second[1] = 0; - for (int i = 0; i < 8; i++) - { - total = 0; - for (int j = 0; j < 20; j++) - { - _speeds[i][j] = j == 19 ? 100 : Rnd.get(60) + 65; - total += _speeds[i][j]; - } - if (total >= _first[1]) - { - _second[0] = _first[0]; - _second[1] = _first[1]; - _first[0] = 8 - i; - _first[1] = total; - } - else if (total >= _second[1]) - { - _second[0] = 8 - i; - _second[1] = total; - } - } - } - - /** - * @return Returns the monsters. - */ - public Npc[] getMonsters() - { - return _monsters; - } - - /** - * @return Returns the speeds. - */ - public int[][] getSpeeds() - { - return _speeds; - } - - public int getFirstPlace() - { - return _first[0]; - } - - public int getSecondPlace() - { - return _second[0]; - } - - public static MonsterRace getInstance() - { - return SingletonHolder.INSTANCE; - } - - private static class SingletonHolder - { - protected static final MonsterRace INSTANCE = new MonsterRace(); - } -} diff --git a/L2J_Mobius_5.0_Salvation/java/org/l2jmobius/gameserver/instancemanager/games/MonsterRace.java b/L2J_Mobius_5.0_Salvation/java/org/l2jmobius/gameserver/instancemanager/games/MonsterRace.java new file mode 100644 index 0000000000..d4961f02c2 --- /dev/null +++ b/L2J_Mobius_5.0_Salvation/java/org/l2jmobius/gameserver/instancemanager/games/MonsterRace.java @@ -0,0 +1,623 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program 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. + * + * This program 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 . + */ +package org.l2jmobius.gameserver.instancemanager.games; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.Config; +import org.l2jmobius.commons.concurrent.ThreadPool; +import org.l2jmobius.commons.database.DatabaseFactory; +import org.l2jmobius.commons.util.Rnd; +import org.l2jmobius.gameserver.data.xml.impl.NpcData; +import org.l2jmobius.gameserver.model.actor.Npc; +import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.model.zone.type.DerbyTrackZone; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.serverpackets.DeleteObject; +import org.l2jmobius.gameserver.network.serverpackets.MonRaceInfo; +import org.l2jmobius.gameserver.network.serverpackets.PlaySound; +import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; +import org.l2jmobius.gameserver.util.Broadcast; + +public class MonsterRace +{ + protected static final Logger LOGGER = Logger.getLogger(MonsterRace.class.getName()); + + public static enum RaceState + { + ACCEPTING_BETS, + WAITING, + STARTING_RACE, + RACE_END + } + + protected static final PlaySound SOUND_1 = new PlaySound(1, "S_Race", 0, 0, 0, 0, 0); + protected static final PlaySound SOUND_2 = new PlaySound("ItemSound2.race_start"); + + protected static final int[][] CODES = + { + { + -1, + 0 + }, + { + 0, + 15322 + }, + { + 13765, + -1 + } + }; + + protected final List _npcTemplates = new ArrayList<>(); // List holding npc templates, shuffled on a new race. + protected final List _history = new ArrayList<>(); // List holding old race records. + protected final Map _betsPerLane = new ConcurrentHashMap<>(); // Map holding all bets for each lane ; values setted to 0 after every race. + protected final List _odds = new ArrayList<>(); // List holding sorted odds per lane ; cleared at new odds calculation. + + protected int _raceNumber = 1; + protected int _finalCountdown = 0; + protected RaceState _state = RaceState.RACE_END; + + protected MonRaceInfo _packet; + + private final Npc[] _monsters = new Npc[8]; + private int[][] _speeds = new int[8][20]; + private final int[] _first = new int[2]; + private final int[] _second = new int[2]; + + protected MonsterRace() + { + if (!Config.ALLOW_RACE) + { + return; + } + + // Feed _history with previous race results. + loadHistory(); + + // Feed _betsPerLane with stored informations on bets. + loadBets(); + + // Feed _npcTemplates, we will only have to shuffle it when needed. + for (int i = 31003; i < 31027; i++) + { + _npcTemplates.add(i); + } + + ThreadPool.scheduleAtFixedRate(new Announcement(), 0, 1000); + } + + public static class HistoryInfo + { + private final int _raceId; + private int _first; + private int _second; + private double _oddRate; + + public HistoryInfo(int raceId, int first, int second, double oddRate) + { + _raceId = raceId; + _first = first; + _second = second; + _oddRate = oddRate; + } + + public int getRaceId() + { + return _raceId; + } + + public int getFirst() + { + return _first; + } + + public int getSecond() + { + return _second; + } + + public double getOddRate() + { + return _oddRate; + } + + public void setFirst(int first) + { + _first = first; + } + + public void setSecond(int second) + { + _second = second; + } + + public void setOddRate(double oddRate) + { + _oddRate = oddRate; + } + } + + private class Announcement implements Runnable + { + public Announcement() + { + } + + @Override + public void run() + { + if (_finalCountdown > 1200) + { + _finalCountdown = 0; + } + + switch (_finalCountdown) + { + case 0: + { + newRace(); + newSpeeds(); + + _state = RaceState.ACCEPTING_BETS; + _packet = new MonRaceInfo(CODES[0][0], CODES[0][1], getMonsters(), getSpeeds()); + + final SystemMessage msg = new SystemMessage(SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, _packet, msg); + 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 + { + final SystemMessage msg = new SystemMessage(SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + break; + } + case 300: // 5 min + { + final SystemMessage msg = new SystemMessage(SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MIN); + msg2.addInt(10); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + break; + } + case 600: // 10 min + { + final SystemMessage msg = new SystemMessage(SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MIN); + msg2.addInt(5); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + break; + } + case 840: // 14 min + { + final SystemMessage msg = new SystemMessage(SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MIN); + msg2.addInt(1); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + break; + } + case 900: // 15 min + { + _state = RaceState.WAITING; + + calculateOdds(); + + final SystemMessage msg = new SystemMessage(SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.TICKETS_SALES_ARE_CLOSED_FOR_MONSTER_RACE_S1_ODDS_ARE_POSTED); + msg2.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + break; + } + case 960: // 16 min + case 1020: // 17 min + { + final int minutes = (_finalCountdown == 960) ? 2 : 1; + final SystemMessage msg = new SystemMessage(SystemMessageId.MONSTER_RACE_S2_WILL_BEGIN_IN_S1_MIN); + msg.addInt(minutes); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + break; + } + case 1050: // 17 min 30 sec + { + final SystemMessage msg = new SystemMessage(SystemMessageId.MONSTER_RACE_S1_WILL_BEGIN_IN_30_SEC); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + break; + } + case 1070: // 17 min 50 sec + { + final SystemMessage msg = new SystemMessage(SystemMessageId.MONSTER_RACE_S1_IS_ABOUT_TO_BEGIN_COUNTDOWN_IN_FIVE_SEC); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + 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; + final SystemMessage msg = new SystemMessage(SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SEC); + msg.addInt(seconds); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + break; + } + case 1080: // 18 min + { + _state = RaceState.STARTING_RACE; + _packet = new MonRaceInfo(CODES[1][0], CODES[1][1], getMonsters(), getSpeeds()); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, new SystemMessage(SystemMessageId.THEY_RE_OFF), SOUND_1, SOUND_2, _packet); + break; + } + case 1085: // 18 min 5 sec + { + _packet = new MonRaceInfo(CODES[2][0], CODES[2][1], getMonsters(), getSpeeds()); + + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, _packet); + break; + } + case 1115: // 18 min 35 sec + { + _state = RaceState.RACE_END; + + // Populate history info with data, stores it in database. + final HistoryInfo info = _history.get(_history.size() - 1); + info.setFirst(getFirstPlace()); + info.setSecond(getSecondPlace()); + info.setOddRate(_odds.get(getFirstPlace() - 1)); + + saveHistory(info); + clearBets(); + + final SystemMessage msg = new SystemMessage(SystemMessageId.FIRST_PRIZE_GOES_TO_THE_PLAYER_IN_LANE_S1_SECOND_PRIZE_GOES_TO_THE_PLAYER_IN_LANE_S2); + msg.addInt(getFirstPlace()); + msg.addInt(getSecondPlace()); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.MONSTER_RACE_S1_IS_FINISHED); + msg2.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + _raceNumber++; + break; + } + case 1140: // 19 min + { + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, new DeleteObject(getMonsters()[0]), new DeleteObject(getMonsters()[1]), new DeleteObject(getMonsters()[2]), new DeleteObject(getMonsters()[3]), new DeleteObject(getMonsters()[4]), new DeleteObject(getMonsters()[5]), new DeleteObject(getMonsters()[6]), new DeleteObject(getMonsters()[7])); + break; + } + } + _finalCountdown += 1; + } + } + + public void newRace() + { + // Edit _history. + _history.add(new HistoryInfo(_raceNumber, 0, 0, 0)); + + // Randomize _npcTemplates. + Collections.shuffle(_npcTemplates); + + // Setup 8 new creatures ; pickup the first 8 from _npcTemplates. + for (int i = 0; i < 8; i++) + { + try + { + final NpcTemplate template = NpcData.getInstance().getTemplate(_npcTemplates.get(i)); + _monsters[i] = (Npc) Class.forName("org.l2jmobius.gameserver.model.actor.instance." + template.getType() + "Instance").getConstructors()[0].newInstance(template); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "", e); + } + } + } + + public void newSpeeds() + { + _speeds = new int[8][20]; + int total = 0; + _first[1] = 0; + _second[1] = 0; + + for (int i = 0; i < 8; i++) + { + total = 0; + for (int j = 0; j < 20; j++) + { + if (j == 19) + { + _speeds[i][j] = 100; + } + else + { + _speeds[i][j] = Rnd.get(60) + 65; + } + total += _speeds[i][j]; + } + + if (total >= _first[1]) + { + _second[0] = _first[0]; + _second[1] = _first[1]; + _first[0] = 8 - i; + _first[1] = total; + } + else if (total >= _second[1]) + { + _second[0] = 8 - i; + _second[1] = total; + } + } + } + + /** + * Load past races informations, feeding _history arrayList.
+ * Also sets _raceNumber, based on latest HistoryInfo loaded. + */ + protected void loadHistory() + { + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("SELECT * FROM mdt_history"); + ResultSet rset = statement.executeQuery(); + + while (rset.next()) + { + _history.add(new HistoryInfo(rset.getInt("race_id"), rset.getInt("first"), rset.getInt("second"), rset.getDouble("odd_rate"))); + _raceNumber++; + } + rset.close(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't load history: " + e.getMessage(), e); + } + LOGGER.info("MonsterRace: loaded " + _history.size() + " records, currently on race #" + _raceNumber); + } + + /** + * Save an history record into database. + * @param history The infos to store. + */ + protected void saveHistory(HistoryInfo history) + { + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("INSERT INTO mdt_history (race_id, first, second, odd_rate) VALUES (?,?,?,?)"); + statement.setInt(1, history.getRaceId()); + statement.setInt(2, history.getFirst()); + statement.setInt(3, history.getSecond()); + statement.setDouble(4, history.getOddRate()); + statement.execute(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't save history: " + e.getMessage(), e); + } + } + + /** + * Load current bets per lane ; initialize the map keys. + */ + protected void loadBets() + { + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("SELECT * FROM mdt_bets"); + ResultSet rset = statement.executeQuery(); + + while (rset.next()) + { + setBetOnLane(rset.getInt("lane_id"), rset.getLong("bet"), false); + } + + rset.close(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't load bets: " + e.getMessage(), e); + } + } + + /** + * Save the current lane bet into database. + * @param lane : The lane to affect. + * @param sum : The sum to set. + */ + protected void saveBet(int lane, long sum) + { + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("REPLACE INTO mdt_bets (lane_id, bet) VALUES (?,?)"); + statement.setInt(1, lane); + statement.setLong(2, sum); + statement.execute(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't save bet: " + e.getMessage(), e); + } + } + + /** + * Clear all lanes bets, either on database or Map. + */ + protected void clearBets() + { + for (int key : _betsPerLane.keySet()) + { + _betsPerLane.put(key, 0L); + } + + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("UPDATE mdt_bets SET bet = 0"); + statement.execute(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't clear bets: " + e.getMessage(), e); + } + } + + /** + * Setup lane bet, based on previous value (if any). + * @param lane : The lane to edit. + * @param amount : The amount to add. + * @param saveOnDb : Should it be saved on db or not. + */ + public void setBetOnLane(int lane, long amount, boolean saveOnDb) + { + final long sum = (_betsPerLane.containsKey(lane)) ? _betsPerLane.get(lane) + amount : amount; + + _betsPerLane.put(lane, sum); + + if (saveOnDb) + { + saveBet(lane, sum); + } + } + + /** + * Calculate odds for every lane, based on others lanes. + */ + protected void calculateOdds() + { + // Clear previous List holding old odds. + _odds.clear(); + + // Sort bets lanes per lane. + final Map 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; + } + + // 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)); + } + } + + public Npc[] getMonsters() + { + return _monsters; + } + + public int[][] getSpeeds() + { + return _speeds; + } + + public int getFirstPlace() + { + return _first[0]; + } + + public int getSecondPlace() + { + return _second[0]; + } + + public MonRaceInfo getRacePacket() + { + return _packet; + } + + public RaceState getCurrentRaceState() + { + return _state; + } + + public int getRaceNumber() + { + return _raceNumber; + } + + public List getHistory() + { + return _history; + } + + public List getOdds() + { + return _odds; + } + + public static MonsterRace getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final MonsterRace INSTANCE = new MonsterRace(); + } +} \ No newline at end of file diff --git a/L2J_Mobius_5.0_Salvation/java/org/l2jmobius/gameserver/model/actor/instance/RaceManagerInstance.java b/L2J_Mobius_5.0_Salvation/java/org/l2jmobius/gameserver/model/actor/instance/RaceManagerInstance.java index a9798f6538..2375543774 100644 --- a/L2J_Mobius_5.0_Salvation/java/org/l2jmobius/gameserver/model/actor/instance/RaceManagerInstance.java +++ b/L2J_Mobius_5.0_Salvation/java/org/l2jmobius/gameserver/model/actor/instance/RaceManagerInstance.java @@ -16,69 +16,26 @@ */ package org.l2jmobius.gameserver.model.actor.instance; -import java.util.Collection; -import java.util.concurrent.ConcurrentHashMap; +import java.util.List; +import java.util.Locale; import org.l2jmobius.Config; -import org.l2jmobius.commons.concurrent.ThreadPool; -import org.l2jmobius.gameserver.MonsterRace; -import org.l2jmobius.gameserver.enums.InstanceType; +import org.l2jmobius.commons.util.StringUtil; import org.l2jmobius.gameserver.idfactory.IdFactory; -import org.l2jmobius.gameserver.model.actor.Creature; +import org.l2jmobius.gameserver.instancemanager.games.MonsterRace; +import org.l2jmobius.gameserver.instancemanager.games.MonsterRace.HistoryInfo; +import org.l2jmobius.gameserver.instancemanager.games.MonsterRace.RaceState; import org.l2jmobius.gameserver.model.actor.Npc; import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate; -import org.l2jmobius.gameserver.model.itemcontainer.Inventory; import org.l2jmobius.gameserver.model.items.instance.ItemInstance; import org.l2jmobius.gameserver.network.SystemMessageId; import org.l2jmobius.gameserver.network.serverpackets.ActionFailed; -import org.l2jmobius.gameserver.network.serverpackets.DeleteObject; -import org.l2jmobius.gameserver.network.serverpackets.IClientOutgoingPacket; -import org.l2jmobius.gameserver.network.serverpackets.InventoryUpdate; -import org.l2jmobius.gameserver.network.serverpackets.MonRaceInfo; import org.l2jmobius.gameserver.network.serverpackets.NpcHtmlMessage; -import org.l2jmobius.gameserver.network.serverpackets.PlaySound; import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; -import org.l2jmobius.gameserver.util.Broadcast; public class RaceManagerInstance extends Npc { - public static final int LANES = 8; - public static final int WINDOW_START = 0; - - private static Collection _managers; - protected static int _raceNumber = 4; - - // Time Constants - private static final long SECOND = 1000; - private static final long MINUTE = 60 * SECOND; - - private static int _minutes = 5; - - // States - private static final int ACCEPTING_BETS = 0; - private static final int WAITING = 1; - private static final int STARTING_RACE = 2; - private static final int RACE_END = 3; - private static int _state = RACE_END; - - protected static final int[][] _codes = - { - { - -1, - 0 - }, - { - 0, - 15322 - }, - { - 13765, - -1 - } - }; - private static boolean _notInitialized = true; - protected static MonRaceInfo _packet; - protected static final int[] _cost = + protected static final int TICKET_PRICES[] = { 100, 500, @@ -93,223 +50,167 @@ public class RaceManagerInstance extends Npc public RaceManagerInstance(NpcTemplate template) { super(template); - setInstanceType(InstanceType.RaceManagerInstance); - - if (!Config.ALLOW_RACE) - { - return; - } - - if (_notInitialized) - { - _notInitialized = false; - - _managers = ConcurrentHashMap.newKeySet(); - - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1), 0, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1), 30 * SECOND, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1), MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1), MINUTE + (30 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MIN), 2 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MIN), 3 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MIN), 4 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MIN), 5 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MIN), 6 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKETS_SALES_ARE_CLOSED_FOR_MONSTER_RACE_S1_ODDS_ARE_POSTED), 7 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.MONSTER_RACE_S2_WILL_BEGIN_IN_S1_MIN), 7 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.MONSTER_RACE_S2_WILL_BEGIN_IN_S1_MIN), 8 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.MONSTER_RACE_S1_WILL_BEGIN_IN_30_SEC), (8 * MINUTE) + (30 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.MONSTER_RACE_S1_IS_ABOUT_TO_BEGIN_COUNTDOWN_IN_FIVE_SEC), (8 * MINUTE) + (50 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SEC), (8 * MINUTE) + (55 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SEC), (8 * MINUTE) + (56 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SEC), (8 * MINUTE) + (57 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SEC), (8 * MINUTE) + (58 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SEC), (8 * MINUTE) + (59 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.THEY_RE_OFF), 9 * MINUTE, 10 * MINUTE); - } - _managers.add(this); - } - - @Override - public boolean isAutoAttackable(Creature attacker) - { - if (attacker.isMonster()) - { - return true; - } - - return super.isAutoAttackable(attacker); - } - - class Announcement implements Runnable - { - private final SystemMessageId _type; - - public Announcement(SystemMessageId pType) - { - _type = pType; - } - - @Override - public void run() - { - makeAnnouncement(_type); - } - } - - public void makeAnnouncement(SystemMessageId type) - { - final SystemMessage sm = new SystemMessage(type); - switch (type.getId()) - { - case 816: // SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1 - case 817: // SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1 - { - if (_state != ACCEPTING_BETS) - {// LOGGER.info("Race Initializing"); - _state = ACCEPTING_BETS; - startRace(); - } // else{LOGGER.info("Race open");} - sm.addInt(_raceNumber); - break; - } - case 818: // SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S - case 820: // SystemMessageId.MONSTER_RACE_S2_WILL_BEGIN_IN_S1_MINUTE_S - case 823: // SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SECOND_S - { - sm.addInt(_minutes); - if (type.getId() == 820) - { - sm.addInt(_raceNumber); - } - _minutes--; - break; - } - case 819: // SystemMessageId.TICKETS_SALES_ARE_CLOSED_FOR_MONSTER_RACE_S1_ODDS_ARE_POSTED - { - // LOGGER.info("Sales closed"); - sm.addInt(_raceNumber); - _state = WAITING; - _minutes = 2; - break; - } - case 821: // SystemMessageId.MONSTER_RACE_S1_WILL_BEGIN_IN_30_SECONDS - case 822: // SystemMessageId.MONSTER_RACE_S1_IS_ABOUT_TO_BEGIN_COUNTDOWN_IN_FIVE_SECONDS - case 825: // SystemMessageId.MONSTER_RACE_S1_IS_FINISHED - { - sm.addInt(_raceNumber); - _minutes = 5; - break; - } - case 826: // SystemMessageId.FIRST_PRIZE_GOES_TO_THE_PLAYER_IN_LANE_S1_SECOND_PRIZE_GOES_TO_THE_PLAYER_IN_LANE_S2 - { - // LOGGER.info("Placing"); - _state = RACE_END; - sm.addInt(MonsterRace.getInstance().getFirstPlace()); - sm.addInt(MonsterRace.getInstance().getSecondPlace()); - break; - } - } - // _logn.info("Counter: "+minutes); - // LOGGER.info("State: "+state); - broadcast(sm); - // LOGGER.info("Player's known: "+getKnownPlayers().size()); - - if (type == SystemMessageId.THEY_RE_OFF) - { - // LOGGER.info("Starting race"); - _state = STARTING_RACE; - startRace(); - _minutes = 5; - } - } - - protected void broadcast(IClientOutgoingPacket pkt) - { - for (RaceManagerInstance manager : _managers) - { - if (!manager.isDead()) - { - Broadcast.toKnownPlayers(manager, pkt); - } - } - } - - public void sendMonsterInfo() - { - broadcast(_packet); - } - - private void startRace() - { - final MonsterRace race = MonsterRace.getInstance(); - if (_state == STARTING_RACE) - { - // state++; - broadcast(new PlaySound(1, "S_Race", 0, 0, 0, 0, 0)); - broadcast(new PlaySound(0, "ItemSound2.race_start", 1, 121209259, 12125, 182487, -3559)); - _packet = new MonRaceInfo(_codes[1][0], _codes[1][1], race.getMonsters(), race.getSpeeds()); - sendMonsterInfo(); - - ThreadPool.schedule(new RunRace(), 5000); - } - else - { - // state++; - race.newRace(); - race.newSpeeds(); - _packet = new MonRaceInfo(_codes[0][0], _codes[0][1], race.getMonsters(), race.getSpeeds()); - sendMonsterInfo(); - } } @Override public void onBypassFeedback(PlayerInstance player, String command) { - if (command.startsWith("BuyTicket") && (_state != ACCEPTING_BETS)) - { - if (!Config.ALLOW_RACE) - { - return; - } - player.sendPacket(SystemMessageId.MONSTER_RACE_TICKETS_ARE_NO_LONGER_AVAILABLE); - command = "Chat 0"; - } - if (command.startsWith("ShowOdds") && (_state == ACCEPTING_BETS)) - { - if (!Config.ALLOW_RACE) - { - return; - } - player.sendPacket(SystemMessageId.MONSTER_RACE_PAYOUT_INFORMATION_IS_NOT_AVAILABLE_WHILE_TICKETS_ARE_BEING_SOLD); - command = "Chat 0"; - } - if (command.startsWith("BuyTicket")) { - if (!Config.ALLOW_RACE) + if (!Config.ALLOW_RACE || (MonsterRace.getInstance().getCurrentRaceState() != RaceState.ACCEPTING_BETS)) { + player.sendPacket(SystemMessageId.MONSTER_RACE_TICKETS_ARE_NO_LONGER_AVAILABLE); + super.onBypassFeedback(player, "Chat 0"); return; } + int 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))) { val = 0; } - showBuyTicket(player, val); + + String search, replace; + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + + if (val < 10) + { + html.setFile(player, getHtmlPath(getId(), 2, player)); + for (int i = 0; i < 8; i++) + { + int n = i + 1; + search = "Mob" + n; + html.replace(search, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); + } + search = "No1"; + if (val == 0) + { + html.replace(search, ""); + } + else + { + html.replace(search, val); + player.setRace(0, val); + } + } + else if (val < 20) + { + if (player.getRace(0) == 0) + { + return; + } + + html.setFile(player, getHtmlPath(getId(), 3, player)); + html.replace("0place", player.getRace(0)); + search = "Mob1"; + replace = MonsterRace.getInstance().getMonsters()[player.getRace(0) - 1].getTemplate().getName(); + html.replace(search, replace); + search = "0adena"; + + if (val == 10) + { + html.replace(search, ""); + } + else + { + html.replace(search, TICKET_PRICES[val - 11]); + player.setRace(1, val - 10); + } + } + else if (val == 20) + { + if ((player.getRace(0) == 0) || (player.getRace(1) == 0)) + { + return; + } + + html.setFile(player, getHtmlPath(getId(), 4, player)); + html.replace("0place", player.getRace(0)); + search = "Mob1"; + replace = MonsterRace.getInstance().getMonsters()[player.getRace(0) - 1].getTemplate().getName(); + 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); + } + else + { + if ((player.getRace(0) == 0) || (player.getRace(1) == 0)) + { + return; + } + + int ticket = player.getRace(0); + int priceId = player.getRace(1); + + if (!player.reduceAdena("Race", TICKET_PRICES[priceId - 1], this, true)) + { + return; + } + + player.setRace(0, 0); + player.setRace(1, 0); + + ItemInstance item = new ItemInstance(IdFactory.getInstance().getNextId(), 4443); + item.setCount(1); + item.setEnchantLevel(MonsterRace.getInstance().getRaceNumber()); + item.setCustomType1(ticket); + item.setCustomType2(TICKET_PRICES[priceId - 1] / 100); + + player.addItem("Race", item, player, false); + final SystemMessage msg = new SystemMessage(SystemMessageId.ACQUIRED_S1_S2); + msg.addInt(MonsterRace.getInstance().getRaceNumber()); + msg.addItemName(4443); + player.sendPacket(msg); + + // Refresh lane bet. + MonsterRace.getInstance().setBetOnLane(ticket, TICKET_PRICES[priceId - 1], true); + super.onBypassFeedback(player, "Chat 0"); + return; + } + html.replace("1race", MonsterRace.getInstance().getRaceNumber()); + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); } else if (command.equals("ShowOdds")) { - if (!Config.ALLOW_RACE) + if (!Config.ALLOW_RACE || (MonsterRace.getInstance().getCurrentRaceState() == RaceState.ACCEPTING_BETS)) { + player.sendPacket(SystemMessageId.MONSTER_RACE_PAYOUT_INFORMATION_IS_NOT_AVAILABLE_WHILE_TICKETS_ARE_BEING_SOLD); + super.onBypassFeedback(player, "Chat 0"); return; } - showOdds(player); + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 5, player)); + for (int i = 0; i < 8; i++) + { + final int n = i + 1; + + html.replace("Mob" + n, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); + + // Odd + final double odd = MonsterRace.getInstance().getOdds().get(i); + html.replace("Odd" + n, (odd > 0D) ? String.format(Locale.ENGLISH, "%.1f", odd) : "&$804;"); + } + html.replace("1race", MonsterRace.getInstance().getRaceNumber()); + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); } else if (command.equals("ShowInfo")) { @@ -317,269 +218,160 @@ public class RaceManagerInstance extends Npc { return; } - showMonsterInfo(player); + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 6, player)); + + for (int i = 0; i < 8; i++) + { + int n = i + 1; + String search = "Mob" + n; + html.replace(search, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); + } + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); } - else if (command.equals("calculateWin")) + else if (command.equals("ShowTickets")) { if (!Config.ALLOW_RACE) { + super.onBypassFeedback(player, "Chat 0"); return; } - // displayCalculateWinnings(player); + + // Generate data. + final StringBuilder sb = new StringBuilder(); + + // Retrieve player's tickets. + for (ItemInstance ticket : player.getInventory().getAllItemsByItemId(4443)) + { + // Don't list current race tickets. + if (ticket.getEnchantLevel() == MonsterRace.getInstance().getRaceNumber()) + { + continue; + } + + StringUtil.append(sb, "", "" + ticket.getEnchantLevel(), " Race Number", "" + ticket.getCustomType1(), " Number", "" + (ticket.getCustomType2() * 100), " Adena"); + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 7, player)); + html.replace("%tickets%", sb.toString()); + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); } - else if (command.equals("viewHistory")) + else if (command.startsWith("ShowTicket")) + { + // Retrieve ticket objectId. + final int val = Integer.parseInt(command.substring(11)); + if (!Config.ALLOW_RACE || (val == 0)) + { + super.onBypassFeedback(player, "Chat 0"); + return; + } + + // Retrieve ticket on player's inventory. + final ItemInstance 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; + + // Retrieve HistoryInfo for that race. + final HistoryInfo info = MonsterRace.getInstance().getHistory().get(raceId - 1); + if (info == null) + { + super.onBypassFeedback(player, "Chat 0"); + return; + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 8, player)); + html.replace("%raceId%", raceId); + html.replace("%lane%", lane); + html.replace("%bet%", bet); + html.replace("%firstLane%", info.getFirst()); + html.replace("%odd%", (lane == info.getFirst()) ? String.format(Locale.ENGLISH, "%.2f", info.getOddRate()) : "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 (!Config.ALLOW_RACE || (val == 0)) + { + super.onBypassFeedback(player, "Chat 0"); + return; + } + + // Delete ticket on player's inventory. + final ItemInstance 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; + + // Retrieve HistoryInfo for that race. + final HistoryInfo info = MonsterRace.getInstance().getHistory().get(raceId - 1); + if (info == 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()) ? info.getOddRate() : 0.01)), this, true); + } + + super.onBypassFeedback(player, "Chat 0"); + return; + } + else if (command.equals("ViewHistory")) { if (!Config.ALLOW_RACE) { + super.onBypassFeedback(player, "Chat 0"); return; } - // displayHistory(player); + + // Generate data. + final StringBuilder sb = new StringBuilder(); + + // Use whole history, pickup from 'last element' and stop at 'latest element - 7'. + final List history = MonsterRace.getInstance().getHistory(); + for (int i = history.size() - 1; i >= Math.max(0, history.size() - 7); i--) + { + final HistoryInfo info = history.get(i); + StringUtil.append(sb, "", "" + info.getRaceId(), " th", "" + info.getFirst(), " Lane ", "" + info.getSecond(), " Lane", String.format(Locale.ENGLISH, "%.2f", info.getOddRate()), " Times"); + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 9, player)); + html.replace("%infos%", sb.toString()); + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); } else { - // getKnownList().removeKnownObject(player); super.onBypassFeedback(player, command); } } - - public void showOdds(PlayerInstance player) - { - if (_state == ACCEPTING_BETS) - { - return; - } - final int npcId = getTemplate().getId(); - String filename; - String search; - final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); - filename = getHtmlPath(npcId, 5, player); - html.setFile(player, filename); - for (int i = 0; i < 8; i++) - { - final int n = i + 1; - search = "Mob" + n; - html.replace(search, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); - } - html.replace("1race", String.valueOf(_raceNumber)); - html.replace("%objectId%", String.valueOf(getObjectId())); - player.sendPacket(html); - player.sendPacket(ActionFailed.STATIC_PACKET); - } - - public void showMonsterInfo(PlayerInstance player) - { - final int npcId = getTemplate().getId(); - String filename; - String search; - final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); - filename = getHtmlPath(npcId, 6, player); - html.setFile(player, filename); - for (int i = 0; i < 8; i++) - { - final int n = i + 1; - search = "Mob" + n; - html.replace(search, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); - } - html.replace("%objectId%", String.valueOf(getObjectId())); - player.sendPacket(html); - player.sendPacket(ActionFailed.STATIC_PACKET); - } - - public void showBuyTicket(PlayerInstance player, int value) - { - if (_state != ACCEPTING_BETS) - { - return; - } - final int npcId = getTemplate().getId(); - SystemMessage sm; - String filename; - String search; - String replace; - final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); - if (value < 10) - { - filename = getHtmlPath(npcId, 2, player); - html.setFile(player, filename); - for (int i = 0; i < 8; i++) - { - final int n = i + 1; - search = "Mob" + n; - html.replace(search, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); - } - search = "No1"; - if (value == 0) - { - html.replace(search, ""); - } - else - { - html.replace(search, Integer.toString(value)); - player.setRace(0, value); - } - } - else if (value < 20) - { - if (player.getRace(0) == 0) - { - return; - } - filename = getHtmlPath(npcId, 3, player); - html.setFile(player, filename); - html.replace("0place", Integer.toString(player.getRace(0))); - search = "Mob1"; - replace = MonsterRace.getInstance().getMonsters()[player.getRace(0) - 1].getTemplate().getName(); - html.replace(search, replace); - search = "0adena"; - if (value == 10) - { - html.replace(search, ""); - } - else - { - html.replace(search, Integer.toString(_cost[value - 11])); - player.setRace(1, value - 10); - } - } - else if (value == 20) - { - if ((player.getRace(0) == 0) || (player.getRace(1) == 0)) - { - return; - } - filename = getHtmlPath(npcId, 4, player); - html.setFile(player, filename); - html.replace("0place", Integer.toString(player.getRace(0))); - search = "Mob1"; - replace = MonsterRace.getInstance().getMonsters()[player.getRace(0) - 1].getTemplate().getName(); - html.replace(search, replace); - search = "0adena"; - final int price = _cost[player.getRace(1) - 1]; - html.replace(search, Integer.toString(price)); - search = "0tax"; - final int tax = 0; - html.replace(search, Integer.toString(tax)); - search = "0total"; - final int total = price + tax; - html.replace(search, Integer.toString(total)); - } - else - { - if ((player.getRace(0) == 0) || (player.getRace(1) == 0)) - { - return; - } - final int ticket = player.getRace(0); - final int priceId = player.getRace(1); - if (!player.reduceAdena("Race", _cost[priceId - 1], this, true)) - { - return; - } - player.setRace(0, 0); - player.setRace(1, 0); - sm = new SystemMessage(SystemMessageId.ACQUIRED_S1_S2); - sm.addInt(_raceNumber); - sm.addItemName(4443); - player.sendPacket(sm); - final ItemInstance item = new ItemInstance(IdFactory.getInstance().getNextId(), 4443); - item.setCount(1); - item.setEnchantLevel(_raceNumber); - item.setCustomType1(ticket); - item.setCustomType2(_cost[priceId - 1] / 100); - player.getInventory().addItem("Race", item, player, this); - final InventoryUpdate iu = new InventoryUpdate(); - iu.addItem(item); - final ItemInstance adenaupdate = player.getInventory().getItemByItemId(Inventory.ADENA_ID); - iu.addModifiedItem(adenaupdate); - player.sendInventoryUpdate(iu); - return; - } - html.replace("1race", String.valueOf(_raceNumber)); - html.replace("%objectId%", String.valueOf(getObjectId())); - player.sendPacket(html); - player.sendPacket(ActionFailed.STATIC_PACKET); - } - - public static class Race - { - private final Info[] _info; - - public Race(Info[] pInfo) - { - _info = pInfo; - } - - public Info getLaneInfo(int lane) - { - return _info[lane]; - } - - public class Info - { - private final int _id; - private final int _place; - private final int _odds; - private final int _payout; - - public Info(int pId, int pPlace, int pOdds, int pPayout) - { - _id = pId; - _place = pPlace; - _odds = pOdds; - _payout = pPayout; - } - - public int getId() - { - return _id; - } - - public int getOdds() - { - return _odds; - } - - public int getPayout() - { - return _payout; - } - - public int getPlace() - { - return _place; - } - } - - } - - class RunRace implements Runnable - { - @Override - public void run() - { - _packet = new MonRaceInfo(_codes[2][0], _codes[2][1], MonsterRace.getInstance().getMonsters(), MonsterRace.getInstance().getSpeeds()); - sendMonsterInfo(); - ThreadPool.schedule(new RunEnd(), 30000); - } - } - - class RunEnd implements Runnable - { - @Override - public void run() - { - makeAnnouncement(SystemMessageId.FIRST_PRIZE_GOES_TO_THE_PLAYER_IN_LANE_S1_SECOND_PRIZE_GOES_TO_THE_PLAYER_IN_LANE_S2); - makeAnnouncement(SystemMessageId.MONSTER_RACE_S1_IS_FINISHED); - _raceNumber++; - - DeleteObject obj = null; - for (int i = 0; i < 8; i++) - { - obj = new DeleteObject(MonsterRace.getInstance().getMonsters()[i]); - broadcast(obj); - MonsterRace.getInstance().getMonsters()[i].deleteMe(); - } - } - } - } diff --git a/L2J_Mobius_5.0_Salvation/java/org/l2jmobius/gameserver/util/Broadcast.java b/L2J_Mobius_5.0_Salvation/java/org/l2jmobius/gameserver/util/Broadcast.java index 60eb0ff361..8d751afc53 100644 --- a/L2J_Mobius_5.0_Salvation/java/org/l2jmobius/gameserver/util/Broadcast.java +++ b/L2J_Mobius_5.0_Salvation/java/org/l2jmobius/gameserver/util/Broadcast.java @@ -20,10 +20,12 @@ import java.util.logging.Level; import java.util.logging.Logger; import org.l2jmobius.gameserver.enums.ChatType; +import org.l2jmobius.gameserver.instancemanager.ZoneManager; import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.actor.Creature; import org.l2jmobius.gameserver.model.actor.Summon; import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance; +import org.l2jmobius.gameserver.model.zone.ZoneType; import org.l2jmobius.gameserver.network.serverpackets.CharInfo; import org.l2jmobius.gameserver.network.serverpackets.CreatureSay; import org.l2jmobius.gameserver.network.serverpackets.ExShowScreenMessage; @@ -191,4 +193,29 @@ public class Broadcast { toAllOnlinePlayers(new ExShowScreenMessage(text, 10000)); } + + /** + * Send a packet to all players in a specific zone type. + * @param ZoneType. + * @param zoneType : The zone type to send packets. + * @param packets : The packets to send. + */ + public static void toAllPlayersInZoneType(Class zoneType, IClientOutgoingPacket... packets) + { + for (ZoneType zone : ZoneManager.getInstance().getAllZones(zoneType)) + { + for (Creature creature : zone.getCharactersInside()) + { + if (creature == null) + { + continue; + } + + for (IClientOutgoingPacket packet : packets) + { + creature.sendPacket(packet); + } + } + } + } } diff --git a/L2J_Mobius_5.5_EtinasFate/dist/db_installer/sql/game/mdt_bets.sql b/L2J_Mobius_5.5_EtinasFate/dist/db_installer/sql/game/mdt_bets.sql new file mode 100644 index 0000000000..8d7d4cb738 --- /dev/null +++ b/L2J_Mobius_5.5_EtinasFate/dist/db_installer/sql/game/mdt_bets.sql @@ -0,0 +1,15 @@ +CREATE TABLE IF NOT EXISTS `mdt_bets` ( + `lane_id` INT(1) DEFAULT 0, + `bet` INT DEFAULT 0, + PRIMARY KEY (`lane_id`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci; + +INSERT INTO `mdt_bets` VALUES +('1','0'), +('2','0'), +('3','0'), +('4','0'), +('5','0'), +('6','0'), +('7','0'), +('8','0'); \ No newline at end of file diff --git a/L2J_Mobius_5.5_EtinasFate/dist/db_installer/sql/game/mdt_history.sql b/L2J_Mobius_5.5_EtinasFate/dist/db_installer/sql/game/mdt_history.sql new file mode 100644 index 0000000000..06978f3377 --- /dev/null +++ b/L2J_Mobius_5.5_EtinasFate/dist/db_installer/sql/game/mdt_history.sql @@ -0,0 +1,7 @@ +CREATE TABLE IF NOT EXISTS `mdt_history` ( + `race_id` MEDIUMINT DEFAULT 0, + `first` INT(1) DEFAULT 0, + `second` INT(1) DEFAULT 0, + `odd_rate` DOUBLE(10,2) DEFAULT 0, + PRIMARY KEY (`race_id`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci; \ No newline at end of file diff --git a/L2J_Mobius_5.5_EtinasFate/dist/game/config/AdminCommands.xml b/L2J_Mobius_5.5_EtinasFate/dist/game/config/AdminCommands.xml index 36ab1240b5..215b7c679a 100644 --- a/L2J_Mobius_5.5_EtinasFate/dist/game/config/AdminCommands.xml +++ b/L2J_Mobius_5.5_EtinasFate/dist/game/config/AdminCommands.xml @@ -416,9 +416,6 @@ - - - diff --git a/L2J_Mobius_5.5_EtinasFate/dist/game/data/html/default/30995-1.htm b/L2J_Mobius_5.5_EtinasFate/dist/game/data/html/default/30995-1.htm index 4962bdf3a2..d38d518149 100644 --- a/L2J_Mobius_5.5_EtinasFate/dist/game/data/html/default/30995-1.htm +++ b/L2J_Mobius_5.5_EtinasFate/dist/game/data/html/default/30995-1.htm @@ -1,19 +1,22 @@ -

-
-In a monster race eight monsters race at the same time. If you guess which monster is expected to finish first and buy that monster's race ticket, based upon the odds to win you will receive an appropriate amount of Adena.
-How the Race WorksMonster races run every 20 minutes in real time.
-How to Participate
-You can purchase a monster race ticket through a race manager NPC. Only between 18 minutes before the start of a race until 3 minutes before the race begins can you buy a ticket for the applicable race. When you purchase a ticket through a race manager, you can find out the names of the participating monsters and their current statistics. Three minutes before a race starts, the race manager will stop ticket sales and announce the monsters' individual odds for the current race.
-Betting
-There are two types of race betting. The Win bet means that you back a particular monster to win 1st place. The Place bet means that you will have to guess the 1st and 2nd place monsters, regardless of who finishes first or second.
+ +
+ +
+In a monster race eight monsters race at the same time. If you guess which monster is expected to finish first and buy that monster's race ticket, based upon the odds to win you will receive an appropriate amount of adena.
+Racing Schedule
+Monster races every 20 minutes in real time.
+Method of Race Participation
+You can purchase a monster race ticket through a race manager NPC. Only between 18 minutes before the start of a race until 3 minutes before the race begins you can buy a ticket for the applicable race. When you purchase a ticket through a conversation with a race manager, you can find out the names of participating monsters in the race and their current statistics. Five minutes before a race starts, the race manager will stop ticket sales and announce monsters' individual odds rate for the current race.
+Types of Race
+There are two types of race betting. The Win type betting means that you back a monster that will win the first prize. Place type of betting means that you will have to guess the 1st and 2nd winning monsters, regardless of who finishes first or second.
Win - * Among the entire betting amount for Win betting, after subtracting the race track's own share of profit, the amount left will be your total winnings. Three minutes before the race, each monster's expected odds are announced. Each Win ticket's dividend amount is calculated by multiplying the odds by the price of a ticket. The odds are never less than 1. - * The prize money does not get transferred to the next race.
+* Among the entire betting amount for Win betting, after subtracting the race track's own share of profit, the amount left will be your total winnings. Three minutes before the race each monster's expected odds are announced. Each Win ticket's dividend amount is calculated by multiplying the odds by the price of a ticket. The odds rate is never less than 1. +* The prize money does not get transferred to the next race.
Place - * Place betting is a way of betting when you correctly guess the two monsters who will finish first and second regardless of their order. The method of calculating the dividend amount is the same as Win betting. - * Currently place betting is not available.
+* Place betting is a way of betting when you correctly guess the two monsters who will finish first and second regardless of their order. The method of calculating dividend amount is the same as Win betting.
+* Currently place betting is not offered.
End of Race
-When a race is over, the race manager will shout the results of the race. You can trade your ticket for Adena by speaking to a race manager NPC. Check the Winning Information Data to find out the types of bets and total winnings for the race you bet on, as well as your win status and prize amount. If you won, you can receive your prize money thorough a race manager NPC. If you did not win, a race manager will buy back your ticket for a small amount of money.
+When a race is over, the race manager will shout out the results of the race. Through a conversation with a race manager NPC, you can trade your ticket for adena. By looking up on the Winning Information Data, you can find out the types of tickets and quantity of the winnings in the race you betted on and your win status and prize amount you won. If you won, you can receive your prize money thorough a race manager NPC. If you did not win, a race manager will buy back your ticket by paying you a very small amount of money.
If a server goes down while tickets are being sold, or while a race is in progress, the race will be cancelled. You can receive a full refund for your ticket for a cancelled race through a race manager.
- + \ No newline at end of file diff --git a/L2J_Mobius_5.5_EtinasFate/dist/game/data/html/default/30995-3.htm b/L2J_Mobius_5.5_EtinasFate/dist/game/data/html/default/30995-3.htm index 5577c579a4..7ae65a37a4 100644 --- a/L2J_Mobius_5.5_EtinasFate/dist/game/data/html/default/30995-3.htm +++ b/L2J_Mobius_5.5_EtinasFate/dist/game/data/html/default/30995-3.htm @@ -3,6 +3,7 @@ + @@ -11,6 +12,7 @@
Lane SelectionOdds
+ @@ -20,18 +22,21 @@
13%
+
Select Bet Amount -100 Adena , 500 Adena -1,000 Adena , 5,000 Adena -10,000 Adena , 20,000 Adena -50,000 Adena , 100,000 Adena +100 Adena , 500 Adena +1,000 Adena , 5,000 Adena +10,000 Adena , 20,000 Adena +50,000 Adena , 100,000 Adena
+ + @@ -39,4 +44,5 @@
+ \ No newline at end of file diff --git a/L2J_Mobius_5.5_EtinasFate/dist/game/data/html/default/30995-4.htm b/L2J_Mobius_5.5_EtinasFate/dist/game/data/html/default/30995-4.htm index bcd1f0d520..30c508b9cb 100644 --- a/L2J_Mobius_5.5_EtinasFate/dist/game/data/html/default/30995-4.htm +++ b/L2J_Mobius_5.5_EtinasFate/dist/game/data/html/default/30995-4.htm @@ -11,6 +11,7 @@ Odds + @@ -20,6 +21,7 @@
13%
+ @@ -37,11 +39,13 @@ -
Bet Amount Total 0total Adena

+ +
If the information is correct, please click Confirm.

+ diff --git a/L2J_Mobius_5.5_EtinasFate/dist/game/data/html/default/30995-5.htm b/L2J_Mobius_5.5_EtinasFate/dist/game/data/html/default/30995-5.htm index b9729e01b0..59a05e14a0 100644 --- a/L2J_Mobius_5.5_EtinasFate/dist/game/data/html/default/30995-5.htm +++ b/L2J_Mobius_5.5_EtinasFate/dist/game/data/html/default/30995-5.htm @@ -15,49 +15,49 @@ - + - + - + - + - + - + - + - +
No.1 Mob1&$804; Odd1
No.2 Mob2&$804; Odd2
No.3 Mob3&$804; Odd3
No.4 Mob4&$804; Odd4
No.5 Mob5&$804; Odd5
No.6 Mob6&$804; Odd6
No.7 Mob7&$804; Odd7
No.8 Mob8&$804; Odd8
diff --git a/L2J_Mobius_5.5_EtinasFate/dist/game/data/html/default/30995-7.htm b/L2J_Mobius_5.5_EtinasFate/dist/game/data/html/default/30995-7.htm index a058e0e0b3..53eb213a52 100644 --- a/L2J_Mobius_5.5_EtinasFate/dist/game/data/html/default/30995-7.htm +++ b/L2J_Mobius_5.5_EtinasFate/dist/game/data/html/default/30995-7.htm @@ -3,22 +3,12 @@ - - + +
Winnings for a Race.LaneBet Amount
Winnings for a Race.LaneBet Amount
- - - - - +%tickets%
1 Number100 Adena
- - - - -
1 Page
- diff --git a/L2J_Mobius_5.5_EtinasFate/dist/game/data/html/default/30995-8.htm b/L2J_Mobius_5.5_EtinasFate/dist/game/data/html/default/30995-8.htm index ebbcf542b2..5f856a222f 100644 --- a/L2J_Mobius_5.5_EtinasFate/dist/game/data/html/default/30995-8.htm +++ b/L2J_Mobius_5.5_EtinasFate/dist/game/data/html/default/30995-8.htm @@ -4,11 +4,11 @@
-
Race #19090
-Lane 1
-Bet: 100 adena
-1st place: Lane [4]
-Odds:[0.01] +
Race #%raceId%
+Lane: %lane%
+Bet: %bet% Adena
+1st place: Lane [%firstLane%]
+Odds: [%odd%]
@@ -18,8 +18,8 @@ Odds:[0.01] - + - +
\ No newline at end of file diff --git a/L2J_Mobius_5.5_EtinasFate/dist/game/data/html/default/30995-9.htm b/L2J_Mobius_5.5_EtinasFate/dist/game/data/html/default/30995-9.htm index ac1201d6f2..c17ab4849a 100644 --- a/L2J_Mobius_5.5_EtinasFate/dist/game/data/html/default/30995-9.htm +++ b/L2J_Mobius_5.5_EtinasFate/dist/game/data/html/default/30995-9.htm @@ -7,41 +7,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +%infos%
7 th8 Lane 1 Lane0.00 Times
6 th4 Lane 8 Lane0.00 Times
5 th7 Lane 2 Lane0.00 Times
4 th6 Lane 8 Lane0.00 Times
3 th4 Lane 1 Lane0.00 Times
2 th1 Lane 8 Lane0.00 Times
1 th7 Lane 6 Lane0.00 Times
diff --git a/L2J_Mobius_5.5_EtinasFate/dist/game/data/html/default/30995.htm b/L2J_Mobius_5.5_EtinasFate/dist/game/data/html/default/30995.htm index 89f40742ae..271c4ee284 100644 --- a/L2J_Mobius_5.5_EtinasFate/dist/game/data/html/default/30995.htm +++ b/L2J_Mobius_5.5_EtinasFate/dist/game/data/html/default/30995.htm @@ -1,15 +1,15 @@ - -
Monster Race Manager
- - - - - - - - - -
- + + +
+ + + + + + + + + + \ No newline at end of file diff --git a/L2J_Mobius_5.5_EtinasFate/dist/game/data/scripts/handlers/MasterHandler.java b/L2J_Mobius_5.5_EtinasFate/dist/game/data/scripts/handlers/MasterHandler.java index 9ff84f140c..04132b481d 100644 --- a/L2J_Mobius_5.5_EtinasFate/dist/game/data/scripts/handlers/MasterHandler.java +++ b/L2J_Mobius_5.5_EtinasFate/dist/game/data/scripts/handlers/MasterHandler.java @@ -105,7 +105,6 @@ import handlers.admincommandhandlers.AdminMenu; import handlers.admincommandhandlers.AdminMessages; import handlers.admincommandhandlers.AdminMissingHtmls; import handlers.admincommandhandlers.AdminMobGroup; -import handlers.admincommandhandlers.AdminMonsterRace; import handlers.admincommandhandlers.AdminOlympiad; import handlers.admincommandhandlers.AdminOnline; import handlers.admincommandhandlers.AdminPForge; @@ -442,7 +441,6 @@ public class MasterHandler AdminMessages.class, AdminMissingHtmls.class, AdminMobGroup.class, - AdminMonsterRace.class, AdminOlympiad.class, AdminOnline.class, AdminPathNode.class, diff --git a/L2J_Mobius_5.5_EtinasFate/dist/game/data/scripts/handlers/admincommandhandlers/AdminMonsterRace.java b/L2J_Mobius_5.5_EtinasFate/dist/game/data/scripts/handlers/admincommandhandlers/AdminMonsterRace.java deleted file mode 100644 index e465186490..0000000000 --- a/L2J_Mobius_5.5_EtinasFate/dist/game/data/scripts/handlers/admincommandhandlers/AdminMonsterRace.java +++ /dev/null @@ -1,166 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * This program 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. - * - * This program 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 . - */ -package handlers.admincommandhandlers; - -import org.l2jmobius.commons.concurrent.ThreadPool; -import org.l2jmobius.gameserver.MonsterRace; -import org.l2jmobius.gameserver.handler.IAdminCommandHandler; -import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance; -import org.l2jmobius.gameserver.network.SystemMessageId; -import org.l2jmobius.gameserver.network.serverpackets.DeleteObject; -import org.l2jmobius.gameserver.network.serverpackets.MonRaceInfo; -import org.l2jmobius.gameserver.network.serverpackets.PlaySound; -import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; - -/** - * This class handles following admin commands: - invul = turns invulnerability on/off - * @version $Revision: 1.1.6.4 $ $Date: 2007/07/31 10:06:00 $ - */ -public class AdminMonsterRace implements IAdminCommandHandler -{ - private static final String[] ADMIN_COMMANDS = - { - "admin_mons" - }; - - static int state = -1; - - @Override - public boolean useAdminCommand(String command, PlayerInstance activeChar) - { - if (command.equalsIgnoreCase("admin_mons")) - { - handleSendPacket(activeChar); - } - return true; - } - - @Override - public String[] getAdminCommandList() - { - return ADMIN_COMMANDS; - } - - private void handleSendPacket(PlayerInstance activeChar) - { - /* - * -1 0 to initialize the race 0 15322 to start race 13765 -1 in middle of race -1 0 to end the race 8003 to 8027 - */ - - final int[][] codes = - { - { - -1, - 0 - }, - { - 0, - 15322 - }, - { - 13765, - -1 - }, - { - -1, - 0 - } - }; - final MonsterRace race = MonsterRace.getInstance(); - - if (state == -1) - { - state++; - race.newRace(); - race.newSpeeds(); - final MonRaceInfo spk = new MonRaceInfo(codes[state][0], codes[state][1], race.getMonsters(), race.getSpeeds()); - activeChar.sendPacket(spk); - activeChar.broadcastPacket(spk); - } - else if (state == 0) - { - state++; - final SystemMessage sm = new SystemMessage(SystemMessageId.THEY_RE_OFF); - sm.addInt(0); - activeChar.sendPacket(sm); - final PlaySound sRace = new PlaySound(1, "S_Race", 0, 0, 0, 0, 0); - activeChar.sendPacket(sRace); - activeChar.broadcastPacket(sRace); - final PlaySound sRace2 = new PlaySound(0, "ItemSound2.race_start", 1, 121209259, 12125, 182487, -3559); - activeChar.sendPacket(sRace2); - activeChar.broadcastPacket(sRace2); - final MonRaceInfo spk = new MonRaceInfo(codes[state][0], codes[state][1], race.getMonsters(), race.getSpeeds()); - activeChar.sendPacket(spk); - activeChar.broadcastPacket(spk); - - ThreadPool.schedule(new RunRace(codes, activeChar), 5000); - } - - } - - private class RunRace implements Runnable - { - - private final int[][] codes; - private final PlayerInstance activeChar; - - public RunRace(int[][] pCodes, PlayerInstance pActiveChar) - { - codes = pCodes; - activeChar = pActiveChar; - } - - @Override - public void run() - { - // int[][] speeds1 = MonsterRace.getInstance().getSpeeds(); - // MonsterRace.getInstance().newSpeeds(); - // int[][] speeds2 = MonsterRace.getInstance().getSpeeds(); - /* - * int[] speed = new int[8]; for (int i=0; i<8; i++) { for (int j=0; j<20; j++) { //LOGGER.info("Adding "+speeds1[i][j] +" and "+ speeds2[i][j]); speed[i] += (speeds1[i][j]*1); // + (speeds2[i][j]*1); } LOGGER.info("Total speed for "+(i+1)+" = "+speed[i]); } - */ - - final MonRaceInfo spk = new MonRaceInfo(codes[2][0], codes[2][1], MonsterRace.getInstance().getMonsters(), MonsterRace.getInstance().getSpeeds()); - activeChar.sendPacket(spk); - activeChar.broadcastPacket(spk); - ThreadPool.schedule(new RunEnd(activeChar), 30000); - } - } - - private static class RunEnd implements Runnable - { - private final PlayerInstance activeChar; - - public RunEnd(PlayerInstance pActiveChar) - { - activeChar = pActiveChar; - } - - @Override - public void run() - { - DeleteObject obj = null; - for (int i = 0; i < 8; i++) - { - obj = new DeleteObject(MonsterRace.getInstance().getMonsters()[i]); - activeChar.sendPacket(obj); - activeChar.broadcastPacket(obj); - } - state = -1; - } - } -} diff --git a/L2J_Mobius_5.5_EtinasFate/java/org/l2jmobius/gameserver/GameServer.java b/L2J_Mobius_5.5_EtinasFate/java/org/l2jmobius/gameserver/GameServer.java index bbea5daf0c..1bdba3bc2c 100644 --- a/L2J_Mobius_5.5_EtinasFate/java/org/l2jmobius/gameserver/GameServer.java +++ b/L2J_Mobius_5.5_EtinasFate/java/org/l2jmobius/gameserver/GameServer.java @@ -145,6 +145,7 @@ import org.l2jmobius.gameserver.instancemanager.SiegeGuardManager; import org.l2jmobius.gameserver.instancemanager.SiegeManager; import org.l2jmobius.gameserver.instancemanager.WalkingManager; import org.l2jmobius.gameserver.instancemanager.ZoneManager; +import org.l2jmobius.gameserver.instancemanager.games.MonsterRace; import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.entity.Hero; import org.l2jmobius.gameserver.model.events.EventDispatcher; @@ -406,10 +407,8 @@ public class GameServer ItemsAutoDestroy.getInstance(); } - if (Config.ALLOW_RACE) - { - MonsterRace.getInstance(); - } + MonsterRace.getInstance(); + TaskManager.getInstance(); AntiFeedManager.getInstance().registerEvent(AntiFeedManager.GAME_ID); diff --git a/L2J_Mobius_5.5_EtinasFate/java/org/l2jmobius/gameserver/MonsterRace.java b/L2J_Mobius_5.5_EtinasFate/java/org/l2jmobius/gameserver/MonsterRace.java deleted file mode 100644 index cdd92a9a07..0000000000 --- a/L2J_Mobius_5.5_EtinasFate/java/org/l2jmobius/gameserver/MonsterRace.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * This program 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. - * - * This program 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 . - */ -package org.l2jmobius.gameserver; - -import java.util.logging.Level; -import java.util.logging.Logger; - -import org.l2jmobius.commons.util.Rnd; -import org.l2jmobius.gameserver.data.xml.impl.NpcData; -import org.l2jmobius.gameserver.model.actor.Npc; -import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate; - -public class MonsterRace -{ - protected static final Logger LOGGER = Logger.getLogger(MonsterRace.class.getName()); - - private final Npc[] _monsters; - private int[][] _speeds; - private final int[] _first; - private final int[] _second; - - protected MonsterRace() - { - _monsters = new Npc[8]; - _speeds = new int[8][20]; - _first = new int[2]; - _second = new int[2]; - } - - public void newRace() - { - int random = 0; - - for (int i = 0; i < 8; i++) - { - final int id = 31003; - random = Rnd.get(24); - while (true) - { - for (int j = i - 1; j >= 0; j--) - { - if (_monsters[j].getTemplate().getId() == (id + random)) - { - random = Rnd.get(24); - } - } - break; - } - try - { - final NpcTemplate template = NpcData.getInstance().getTemplate(id + random); - _monsters[i] = (Npc) Class.forName("org.l2jmobius.gameserver.model.actor.instance." + template.getType() + "Instance").getConstructors()[0].newInstance(template); - } - catch (Exception e) - { - LOGGER.log(Level.WARNING, "Unable to create monster!", e); - } - } - newSpeeds(); - } - - public void newSpeeds() - { - _speeds = new int[8][20]; - int total = 0; - _first[1] = 0; - _second[1] = 0; - for (int i = 0; i < 8; i++) - { - total = 0; - for (int j = 0; j < 20; j++) - { - _speeds[i][j] = j == 19 ? 100 : Rnd.get(60) + 65; - total += _speeds[i][j]; - } - if (total >= _first[1]) - { - _second[0] = _first[0]; - _second[1] = _first[1]; - _first[0] = 8 - i; - _first[1] = total; - } - else if (total >= _second[1]) - { - _second[0] = 8 - i; - _second[1] = total; - } - } - } - - /** - * @return Returns the monsters. - */ - public Npc[] getMonsters() - { - return _monsters; - } - - /** - * @return Returns the speeds. - */ - public int[][] getSpeeds() - { - return _speeds; - } - - public int getFirstPlace() - { - return _first[0]; - } - - public int getSecondPlace() - { - return _second[0]; - } - - public static MonsterRace getInstance() - { - return SingletonHolder.INSTANCE; - } - - private static class SingletonHolder - { - protected static final MonsterRace INSTANCE = new MonsterRace(); - } -} diff --git a/L2J_Mobius_5.5_EtinasFate/java/org/l2jmobius/gameserver/instancemanager/games/MonsterRace.java b/L2J_Mobius_5.5_EtinasFate/java/org/l2jmobius/gameserver/instancemanager/games/MonsterRace.java new file mode 100644 index 0000000000..2fa4c519f6 --- /dev/null +++ b/L2J_Mobius_5.5_EtinasFate/java/org/l2jmobius/gameserver/instancemanager/games/MonsterRace.java @@ -0,0 +1,623 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program 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. + * + * This program 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 . + */ +package org.l2jmobius.gameserver.instancemanager.games; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.Config; +import org.l2jmobius.commons.concurrent.ThreadPool; +import org.l2jmobius.commons.database.DatabaseFactory; +import org.l2jmobius.commons.util.Rnd; +import org.l2jmobius.gameserver.data.xml.impl.NpcData; +import org.l2jmobius.gameserver.model.actor.Npc; +import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.model.zone.type.DerbyTrackZone; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.serverpackets.DeleteObject; +import org.l2jmobius.gameserver.network.serverpackets.MonRaceInfo; +import org.l2jmobius.gameserver.network.serverpackets.PlaySound; +import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; +import org.l2jmobius.gameserver.util.Broadcast; + +public class MonsterRace +{ + protected static final Logger LOGGER = Logger.getLogger(MonsterRace.class.getName()); + + public static enum RaceState + { + ACCEPTING_BETS, + WAITING, + STARTING_RACE, + RACE_END + } + + protected static final PlaySound SOUND_1 = new PlaySound(1, "S_Race", 0, 0, 0, 0, 0); + protected static final PlaySound SOUND_2 = new PlaySound("ItemSound2.race_start"); + + protected static final int[][] CODES = + { + { + -1, + 0 + }, + { + 0, + 15322 + }, + { + 13765, + -1 + } + }; + + protected final List _npcTemplates = new ArrayList<>(); // List holding npc templates, shuffled on a new race. + protected final List _history = new ArrayList<>(); // List holding old race records. + protected final Map _betsPerLane = new ConcurrentHashMap<>(); // Map holding all bets for each lane ; values setted to 0 after every race. + protected final List _odds = new ArrayList<>(); // List holding sorted odds per lane ; cleared at new odds calculation. + + protected int _raceNumber = 1; + protected int _finalCountdown = 0; + protected RaceState _state = RaceState.RACE_END; + + protected MonRaceInfo _packet; + + private final Npc[] _monsters = new Npc[8]; + private int[][] _speeds = new int[8][20]; + private final int[] _first = new int[2]; + private final int[] _second = new int[2]; + + protected MonsterRace() + { + if (!Config.ALLOW_RACE) + { + return; + } + + // Feed _history with previous race results. + loadHistory(); + + // Feed _betsPerLane with stored informations on bets. + loadBets(); + + // Feed _npcTemplates, we will only have to shuffle it when needed. + for (int i = 31003; i < 31027; i++) + { + _npcTemplates.add(i); + } + + ThreadPool.scheduleAtFixedRate(new Announcement(), 0, 1000); + } + + public static class HistoryInfo + { + private final int _raceId; + private int _first; + private int _second; + private double _oddRate; + + public HistoryInfo(int raceId, int first, int second, double oddRate) + { + _raceId = raceId; + _first = first; + _second = second; + _oddRate = oddRate; + } + + public int getRaceId() + { + return _raceId; + } + + public int getFirst() + { + return _first; + } + + public int getSecond() + { + return _second; + } + + public double getOddRate() + { + return _oddRate; + } + + public void setFirst(int first) + { + _first = first; + } + + public void setSecond(int second) + { + _second = second; + } + + public void setOddRate(double oddRate) + { + _oddRate = oddRate; + } + } + + private class Announcement implements Runnable + { + public Announcement() + { + } + + @Override + public void run() + { + if (_finalCountdown > 1200) + { + _finalCountdown = 0; + } + + switch (_finalCountdown) + { + case 0: + { + newRace(); + newSpeeds(); + + _state = RaceState.ACCEPTING_BETS; + _packet = new MonRaceInfo(CODES[0][0], CODES[0][1], getMonsters(), getSpeeds()); + + final SystemMessage msg = new SystemMessage(SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, _packet, msg); + 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 + { + final SystemMessage msg = new SystemMessage(SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + break; + } + case 300: // 5 min + { + final SystemMessage msg = new SystemMessage(SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S); + msg2.addInt(10); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + break; + } + case 600: // 10 min + { + final SystemMessage msg = new SystemMessage(SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S); + msg2.addInt(5); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + break; + } + case 840: // 14 min + { + final SystemMessage msg = new SystemMessage(SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S); + msg2.addInt(1); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + break; + } + case 900: // 15 min + { + _state = RaceState.WAITING; + + calculateOdds(); + + final SystemMessage msg = new SystemMessage(SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.TICKETS_SALES_ARE_CLOSED_FOR_MONSTER_RACE_S1_ODDS_ARE_POSTED); + msg2.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + break; + } + case 960: // 16 min + case 1020: // 17 min + { + final int minutes = (_finalCountdown == 960) ? 2 : 1; + final SystemMessage msg = new SystemMessage(SystemMessageId.MONSTER_RACE_S2_WILL_BEGIN_IN_S1_MINUTE_S); + msg.addInt(minutes); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + break; + } + case 1050: // 17 min 30 sec + { + final SystemMessage msg = new SystemMessage(SystemMessageId.MONSTER_RACE_S1_WILL_BEGIN_IN_30_SECONDS); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + break; + } + case 1070: // 17 min 50 sec + { + final SystemMessage msg = new SystemMessage(SystemMessageId.MONSTER_RACE_S1_IS_ABOUT_TO_BEGIN_COUNTDOWN_IN_FIVE_SECONDS); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + 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; + final SystemMessage msg = new SystemMessage(SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SECOND_S); + msg.addInt(seconds); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + break; + } + case 1080: // 18 min + { + _state = RaceState.STARTING_RACE; + _packet = new MonRaceInfo(CODES[1][0], CODES[1][1], getMonsters(), getSpeeds()); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, new SystemMessage(SystemMessageId.THEY_RE_OFF), SOUND_1, SOUND_2, _packet); + break; + } + case 1085: // 18 min 5 sec + { + _packet = new MonRaceInfo(CODES[2][0], CODES[2][1], getMonsters(), getSpeeds()); + + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, _packet); + break; + } + case 1115: // 18 min 35 sec + { + _state = RaceState.RACE_END; + + // Populate history info with data, stores it in database. + final HistoryInfo info = _history.get(_history.size() - 1); + info.setFirst(getFirstPlace()); + info.setSecond(getSecondPlace()); + info.setOddRate(_odds.get(getFirstPlace() - 1)); + + saveHistory(info); + clearBets(); + + final SystemMessage msg = new SystemMessage(SystemMessageId.FIRST_PRIZE_GOES_TO_THE_PLAYER_IN_LANE_S1_SECOND_PRIZE_GOES_TO_THE_PLAYER_IN_LANE_S2); + msg.addInt(getFirstPlace()); + msg.addInt(getSecondPlace()); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.MONSTER_RACE_S1_IS_FINISHED); + msg2.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + _raceNumber++; + break; + } + case 1140: // 19 min + { + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, new DeleteObject(getMonsters()[0]), new DeleteObject(getMonsters()[1]), new DeleteObject(getMonsters()[2]), new DeleteObject(getMonsters()[3]), new DeleteObject(getMonsters()[4]), new DeleteObject(getMonsters()[5]), new DeleteObject(getMonsters()[6]), new DeleteObject(getMonsters()[7])); + break; + } + } + _finalCountdown += 1; + } + } + + public void newRace() + { + // Edit _history. + _history.add(new HistoryInfo(_raceNumber, 0, 0, 0)); + + // Randomize _npcTemplates. + Collections.shuffle(_npcTemplates); + + // Setup 8 new creatures ; pickup the first 8 from _npcTemplates. + for (int i = 0; i < 8; i++) + { + try + { + final NpcTemplate template = NpcData.getInstance().getTemplate(_npcTemplates.get(i)); + _monsters[i] = (Npc) Class.forName("org.l2jmobius.gameserver.model.actor.instance." + template.getType() + "Instance").getConstructors()[0].newInstance(template); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "", e); + } + } + } + + public void newSpeeds() + { + _speeds = new int[8][20]; + int total = 0; + _first[1] = 0; + _second[1] = 0; + + for (int i = 0; i < 8; i++) + { + total = 0; + for (int j = 0; j < 20; j++) + { + if (j == 19) + { + _speeds[i][j] = 100; + } + else + { + _speeds[i][j] = Rnd.get(60) + 65; + } + total += _speeds[i][j]; + } + + if (total >= _first[1]) + { + _second[0] = _first[0]; + _second[1] = _first[1]; + _first[0] = 8 - i; + _first[1] = total; + } + else if (total >= _second[1]) + { + _second[0] = 8 - i; + _second[1] = total; + } + } + } + + /** + * Load past races informations, feeding _history arrayList.
+ * Also sets _raceNumber, based on latest HistoryInfo loaded. + */ + protected void loadHistory() + { + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("SELECT * FROM mdt_history"); + ResultSet rset = statement.executeQuery(); + + while (rset.next()) + { + _history.add(new HistoryInfo(rset.getInt("race_id"), rset.getInt("first"), rset.getInt("second"), rset.getDouble("odd_rate"))); + _raceNumber++; + } + rset.close(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't load history: " + e.getMessage(), e); + } + LOGGER.info("MonsterRace: loaded " + _history.size() + " records, currently on race #" + _raceNumber); + } + + /** + * Save an history record into database. + * @param history The infos to store. + */ + protected void saveHistory(HistoryInfo history) + { + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("INSERT INTO mdt_history (race_id, first, second, odd_rate) VALUES (?,?,?,?)"); + statement.setInt(1, history.getRaceId()); + statement.setInt(2, history.getFirst()); + statement.setInt(3, history.getSecond()); + statement.setDouble(4, history.getOddRate()); + statement.execute(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't save history: " + e.getMessage(), e); + } + } + + /** + * Load current bets per lane ; initialize the map keys. + */ + protected void loadBets() + { + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("SELECT * FROM mdt_bets"); + ResultSet rset = statement.executeQuery(); + + while (rset.next()) + { + setBetOnLane(rset.getInt("lane_id"), rset.getLong("bet"), false); + } + + rset.close(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't load bets: " + e.getMessage(), e); + } + } + + /** + * Save the current lane bet into database. + * @param lane : The lane to affect. + * @param sum : The sum to set. + */ + protected void saveBet(int lane, long sum) + { + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("REPLACE INTO mdt_bets (lane_id, bet) VALUES (?,?)"); + statement.setInt(1, lane); + statement.setLong(2, sum); + statement.execute(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't save bet: " + e.getMessage(), e); + } + } + + /** + * Clear all lanes bets, either on database or Map. + */ + protected void clearBets() + { + for (int key : _betsPerLane.keySet()) + { + _betsPerLane.put(key, 0L); + } + + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("UPDATE mdt_bets SET bet = 0"); + statement.execute(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't clear bets: " + e.getMessage(), e); + } + } + + /** + * Setup lane bet, based on previous value (if any). + * @param lane : The lane to edit. + * @param amount : The amount to add. + * @param saveOnDb : Should it be saved on db or not. + */ + public void setBetOnLane(int lane, long amount, boolean saveOnDb) + { + final long sum = (_betsPerLane.containsKey(lane)) ? _betsPerLane.get(lane) + amount : amount; + + _betsPerLane.put(lane, sum); + + if (saveOnDb) + { + saveBet(lane, sum); + } + } + + /** + * Calculate odds for every lane, based on others lanes. + */ + protected void calculateOdds() + { + // Clear previous List holding old odds. + _odds.clear(); + + // Sort bets lanes per lane. + final Map 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; + } + + // 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)); + } + } + + public Npc[] getMonsters() + { + return _monsters; + } + + public int[][] getSpeeds() + { + return _speeds; + } + + public int getFirstPlace() + { + return _first[0]; + } + + public int getSecondPlace() + { + return _second[0]; + } + + public MonRaceInfo getRacePacket() + { + return _packet; + } + + public RaceState getCurrentRaceState() + { + return _state; + } + + public int getRaceNumber() + { + return _raceNumber; + } + + public List getHistory() + { + return _history; + } + + public List getOdds() + { + return _odds; + } + + public static MonsterRace getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final MonsterRace INSTANCE = new MonsterRace(); + } +} \ No newline at end of file diff --git a/L2J_Mobius_5.5_EtinasFate/java/org/l2jmobius/gameserver/model/actor/instance/RaceManagerInstance.java b/L2J_Mobius_5.5_EtinasFate/java/org/l2jmobius/gameserver/model/actor/instance/RaceManagerInstance.java index f893d4f5f2..2375543774 100644 --- a/L2J_Mobius_5.5_EtinasFate/java/org/l2jmobius/gameserver/model/actor/instance/RaceManagerInstance.java +++ b/L2J_Mobius_5.5_EtinasFate/java/org/l2jmobius/gameserver/model/actor/instance/RaceManagerInstance.java @@ -16,69 +16,26 @@ */ package org.l2jmobius.gameserver.model.actor.instance; -import java.util.Collection; -import java.util.concurrent.ConcurrentHashMap; +import java.util.List; +import java.util.Locale; import org.l2jmobius.Config; -import org.l2jmobius.commons.concurrent.ThreadPool; -import org.l2jmobius.gameserver.MonsterRace; -import org.l2jmobius.gameserver.enums.InstanceType; +import org.l2jmobius.commons.util.StringUtil; import org.l2jmobius.gameserver.idfactory.IdFactory; -import org.l2jmobius.gameserver.model.actor.Creature; +import org.l2jmobius.gameserver.instancemanager.games.MonsterRace; +import org.l2jmobius.gameserver.instancemanager.games.MonsterRace.HistoryInfo; +import org.l2jmobius.gameserver.instancemanager.games.MonsterRace.RaceState; import org.l2jmobius.gameserver.model.actor.Npc; import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate; -import org.l2jmobius.gameserver.model.itemcontainer.Inventory; import org.l2jmobius.gameserver.model.items.instance.ItemInstance; import org.l2jmobius.gameserver.network.SystemMessageId; import org.l2jmobius.gameserver.network.serverpackets.ActionFailed; -import org.l2jmobius.gameserver.network.serverpackets.DeleteObject; -import org.l2jmobius.gameserver.network.serverpackets.IClientOutgoingPacket; -import org.l2jmobius.gameserver.network.serverpackets.InventoryUpdate; -import org.l2jmobius.gameserver.network.serverpackets.MonRaceInfo; import org.l2jmobius.gameserver.network.serverpackets.NpcHtmlMessage; -import org.l2jmobius.gameserver.network.serverpackets.PlaySound; import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; -import org.l2jmobius.gameserver.util.Broadcast; public class RaceManagerInstance extends Npc { - public static final int LANES = 8; - public static final int WINDOW_START = 0; - - private static Collection _managers; - protected static int _raceNumber = 4; - - // Time Constants - private static final long SECOND = 1000; - private static final long MINUTE = 60 * SECOND; - - private static int _minutes = 5; - - // States - private static final int ACCEPTING_BETS = 0; - private static final int WAITING = 1; - private static final int STARTING_RACE = 2; - private static final int RACE_END = 3; - private static int _state = RACE_END; - - protected static final int[][] _codes = - { - { - -1, - 0 - }, - { - 0, - 15322 - }, - { - 13765, - -1 - } - }; - private static boolean _notInitialized = true; - protected static MonRaceInfo _packet; - protected static final int[] _cost = + protected static final int TICKET_PRICES[] = { 100, 500, @@ -93,223 +50,167 @@ public class RaceManagerInstance extends Npc public RaceManagerInstance(NpcTemplate template) { super(template); - setInstanceType(InstanceType.RaceManagerInstance); - - if (!Config.ALLOW_RACE) - { - return; - } - - if (_notInitialized) - { - _notInitialized = false; - - _managers = ConcurrentHashMap.newKeySet(); - - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1), 0, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1), 30 * SECOND, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1), MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1), MINUTE + (30 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S), 2 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S), 3 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S), 4 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S), 5 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S), 6 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKETS_SALES_ARE_CLOSED_FOR_MONSTER_RACE_S1_ODDS_ARE_POSTED), 7 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.MONSTER_RACE_S2_WILL_BEGIN_IN_S1_MINUTE_S), 7 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.MONSTER_RACE_S2_WILL_BEGIN_IN_S1_MINUTE_S), 8 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.MONSTER_RACE_S1_WILL_BEGIN_IN_30_SECONDS), (8 * MINUTE) + (30 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.MONSTER_RACE_S1_IS_ABOUT_TO_BEGIN_COUNTDOWN_IN_FIVE_SECONDS), (8 * MINUTE) + (50 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SECOND_S), (8 * MINUTE) + (55 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SECOND_S), (8 * MINUTE) + (56 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SECOND_S), (8 * MINUTE) + (57 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SECOND_S), (8 * MINUTE) + (58 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SECOND_S), (8 * MINUTE) + (59 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.THEY_RE_OFF), 9 * MINUTE, 10 * MINUTE); - } - _managers.add(this); - } - - @Override - public boolean isAutoAttackable(Creature attacker) - { - if (attacker.isMonster()) - { - return true; - } - - return super.isAutoAttackable(attacker); - } - - class Announcement implements Runnable - { - private final SystemMessageId _type; - - public Announcement(SystemMessageId pType) - { - _type = pType; - } - - @Override - public void run() - { - makeAnnouncement(_type); - } - } - - public void makeAnnouncement(SystemMessageId type) - { - final SystemMessage sm = new SystemMessage(type); - switch (type.getId()) - { - case 816: // SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1 - case 817: // SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1 - { - if (_state != ACCEPTING_BETS) - {// LOGGER.info("Race Initializing"); - _state = ACCEPTING_BETS; - startRace(); - } // else{LOGGER.info("Race open");} - sm.addInt(_raceNumber); - break; - } - case 818: // SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S - case 820: // SystemMessageId.MONSTER_RACE_S2_WILL_BEGIN_IN_S1_MINUTE_S - case 823: // SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SECOND_S - { - sm.addInt(_minutes); - if (type.getId() == 820) - { - sm.addInt(_raceNumber); - } - _minutes--; - break; - } - case 819: // SystemMessageId.TICKETS_SALES_ARE_CLOSED_FOR_MONSTER_RACE_S1_ODDS_ARE_POSTED - { - // LOGGER.info("Sales closed"); - sm.addInt(_raceNumber); - _state = WAITING; - _minutes = 2; - break; - } - case 821: // SystemMessageId.MONSTER_RACE_S1_WILL_BEGIN_IN_30_SECONDS - case 822: // SystemMessageId.MONSTER_RACE_S1_IS_ABOUT_TO_BEGIN_COUNTDOWN_IN_FIVE_SECONDS - case 825: // SystemMessageId.MONSTER_RACE_S1_IS_FINISHED - { - sm.addInt(_raceNumber); - _minutes = 5; - break; - } - case 826: // SystemMessageId.FIRST_PRIZE_GOES_TO_THE_PLAYER_IN_LANE_S1_SECOND_PRIZE_GOES_TO_THE_PLAYER_IN_LANE_S2 - { - // LOGGER.info("Placing"); - _state = RACE_END; - sm.addInt(MonsterRace.getInstance().getFirstPlace()); - sm.addInt(MonsterRace.getInstance().getSecondPlace()); - break; - } - } - // _logn.info("Counter: "+minutes); - // LOGGER.info("State: "+state); - broadcast(sm); - // LOGGER.info("Player's known: "+getKnownPlayers().size()); - - if (type == SystemMessageId.THEY_RE_OFF) - { - // LOGGER.info("Starting race"); - _state = STARTING_RACE; - startRace(); - _minutes = 5; - } - } - - protected void broadcast(IClientOutgoingPacket pkt) - { - for (RaceManagerInstance manager : _managers) - { - if (!manager.isDead()) - { - Broadcast.toKnownPlayers(manager, pkt); - } - } - } - - public void sendMonsterInfo() - { - broadcast(_packet); - } - - private void startRace() - { - final MonsterRace race = MonsterRace.getInstance(); - if (_state == STARTING_RACE) - { - // state++; - broadcast(new PlaySound(1, "S_Race", 0, 0, 0, 0, 0)); - broadcast(new PlaySound(0, "ItemSound2.race_start", 1, 121209259, 12125, 182487, -3559)); - _packet = new MonRaceInfo(_codes[1][0], _codes[1][1], race.getMonsters(), race.getSpeeds()); - sendMonsterInfo(); - - ThreadPool.schedule(new RunRace(), 5000); - } - else - { - // state++; - race.newRace(); - race.newSpeeds(); - _packet = new MonRaceInfo(_codes[0][0], _codes[0][1], race.getMonsters(), race.getSpeeds()); - sendMonsterInfo(); - } } @Override public void onBypassFeedback(PlayerInstance player, String command) { - if (command.startsWith("BuyTicket") && (_state != ACCEPTING_BETS)) - { - if (!Config.ALLOW_RACE) - { - return; - } - player.sendPacket(SystemMessageId.MONSTER_RACE_TICKETS_ARE_NO_LONGER_AVAILABLE); - command = "Chat 0"; - } - if (command.startsWith("ShowOdds") && (_state == ACCEPTING_BETS)) - { - if (!Config.ALLOW_RACE) - { - return; - } - player.sendPacket(SystemMessageId.MONSTER_RACE_PAYOUT_INFORMATION_IS_NOT_AVAILABLE_WHILE_TICKETS_ARE_BEING_SOLD); - command = "Chat 0"; - } - if (command.startsWith("BuyTicket")) { - if (!Config.ALLOW_RACE) + if (!Config.ALLOW_RACE || (MonsterRace.getInstance().getCurrentRaceState() != RaceState.ACCEPTING_BETS)) { + player.sendPacket(SystemMessageId.MONSTER_RACE_TICKETS_ARE_NO_LONGER_AVAILABLE); + super.onBypassFeedback(player, "Chat 0"); return; } + int 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))) { val = 0; } - showBuyTicket(player, val); + + String search, replace; + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + + if (val < 10) + { + html.setFile(player, getHtmlPath(getId(), 2, player)); + for (int i = 0; i < 8; i++) + { + int n = i + 1; + search = "Mob" + n; + html.replace(search, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); + } + search = "No1"; + if (val == 0) + { + html.replace(search, ""); + } + else + { + html.replace(search, val); + player.setRace(0, val); + } + } + else if (val < 20) + { + if (player.getRace(0) == 0) + { + return; + } + + html.setFile(player, getHtmlPath(getId(), 3, player)); + html.replace("0place", player.getRace(0)); + search = "Mob1"; + replace = MonsterRace.getInstance().getMonsters()[player.getRace(0) - 1].getTemplate().getName(); + html.replace(search, replace); + search = "0adena"; + + if (val == 10) + { + html.replace(search, ""); + } + else + { + html.replace(search, TICKET_PRICES[val - 11]); + player.setRace(1, val - 10); + } + } + else if (val == 20) + { + if ((player.getRace(0) == 0) || (player.getRace(1) == 0)) + { + return; + } + + html.setFile(player, getHtmlPath(getId(), 4, player)); + html.replace("0place", player.getRace(0)); + search = "Mob1"; + replace = MonsterRace.getInstance().getMonsters()[player.getRace(0) - 1].getTemplate().getName(); + 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); + } + else + { + if ((player.getRace(0) == 0) || (player.getRace(1) == 0)) + { + return; + } + + int ticket = player.getRace(0); + int priceId = player.getRace(1); + + if (!player.reduceAdena("Race", TICKET_PRICES[priceId - 1], this, true)) + { + return; + } + + player.setRace(0, 0); + player.setRace(1, 0); + + ItemInstance item = new ItemInstance(IdFactory.getInstance().getNextId(), 4443); + item.setCount(1); + item.setEnchantLevel(MonsterRace.getInstance().getRaceNumber()); + item.setCustomType1(ticket); + item.setCustomType2(TICKET_PRICES[priceId - 1] / 100); + + player.addItem("Race", item, player, false); + final SystemMessage msg = new SystemMessage(SystemMessageId.ACQUIRED_S1_S2); + msg.addInt(MonsterRace.getInstance().getRaceNumber()); + msg.addItemName(4443); + player.sendPacket(msg); + + // Refresh lane bet. + MonsterRace.getInstance().setBetOnLane(ticket, TICKET_PRICES[priceId - 1], true); + super.onBypassFeedback(player, "Chat 0"); + return; + } + html.replace("1race", MonsterRace.getInstance().getRaceNumber()); + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); } else if (command.equals("ShowOdds")) { - if (!Config.ALLOW_RACE) + if (!Config.ALLOW_RACE || (MonsterRace.getInstance().getCurrentRaceState() == RaceState.ACCEPTING_BETS)) { + player.sendPacket(SystemMessageId.MONSTER_RACE_PAYOUT_INFORMATION_IS_NOT_AVAILABLE_WHILE_TICKETS_ARE_BEING_SOLD); + super.onBypassFeedback(player, "Chat 0"); return; } - showOdds(player); + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 5, player)); + for (int i = 0; i < 8; i++) + { + final int n = i + 1; + + html.replace("Mob" + n, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); + + // Odd + final double odd = MonsterRace.getInstance().getOdds().get(i); + html.replace("Odd" + n, (odd > 0D) ? String.format(Locale.ENGLISH, "%.1f", odd) : "&$804;"); + } + html.replace("1race", MonsterRace.getInstance().getRaceNumber()); + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); } else if (command.equals("ShowInfo")) { @@ -317,269 +218,160 @@ public class RaceManagerInstance extends Npc { return; } - showMonsterInfo(player); + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 6, player)); + + for (int i = 0; i < 8; i++) + { + int n = i + 1; + String search = "Mob" + n; + html.replace(search, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); + } + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); } - else if (command.equals("calculateWin")) + else if (command.equals("ShowTickets")) { if (!Config.ALLOW_RACE) { + super.onBypassFeedback(player, "Chat 0"); return; } - // displayCalculateWinnings(player); + + // Generate data. + final StringBuilder sb = new StringBuilder(); + + // Retrieve player's tickets. + for (ItemInstance ticket : player.getInventory().getAllItemsByItemId(4443)) + { + // Don't list current race tickets. + if (ticket.getEnchantLevel() == MonsterRace.getInstance().getRaceNumber()) + { + continue; + } + + StringUtil.append(sb, "", "" + ticket.getEnchantLevel(), " Race Number", "" + ticket.getCustomType1(), " Number", "" + (ticket.getCustomType2() * 100), " Adena"); + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 7, player)); + html.replace("%tickets%", sb.toString()); + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); } - else if (command.equals("viewHistory")) + else if (command.startsWith("ShowTicket")) + { + // Retrieve ticket objectId. + final int val = Integer.parseInt(command.substring(11)); + if (!Config.ALLOW_RACE || (val == 0)) + { + super.onBypassFeedback(player, "Chat 0"); + return; + } + + // Retrieve ticket on player's inventory. + final ItemInstance 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; + + // Retrieve HistoryInfo for that race. + final HistoryInfo info = MonsterRace.getInstance().getHistory().get(raceId - 1); + if (info == null) + { + super.onBypassFeedback(player, "Chat 0"); + return; + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 8, player)); + html.replace("%raceId%", raceId); + html.replace("%lane%", lane); + html.replace("%bet%", bet); + html.replace("%firstLane%", info.getFirst()); + html.replace("%odd%", (lane == info.getFirst()) ? String.format(Locale.ENGLISH, "%.2f", info.getOddRate()) : "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 (!Config.ALLOW_RACE || (val == 0)) + { + super.onBypassFeedback(player, "Chat 0"); + return; + } + + // Delete ticket on player's inventory. + final ItemInstance 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; + + // Retrieve HistoryInfo for that race. + final HistoryInfo info = MonsterRace.getInstance().getHistory().get(raceId - 1); + if (info == 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()) ? info.getOddRate() : 0.01)), this, true); + } + + super.onBypassFeedback(player, "Chat 0"); + return; + } + else if (command.equals("ViewHistory")) { if (!Config.ALLOW_RACE) { + super.onBypassFeedback(player, "Chat 0"); return; } - // displayHistory(player); + + // Generate data. + final StringBuilder sb = new StringBuilder(); + + // Use whole history, pickup from 'last element' and stop at 'latest element - 7'. + final List history = MonsterRace.getInstance().getHistory(); + for (int i = history.size() - 1; i >= Math.max(0, history.size() - 7); i--) + { + final HistoryInfo info = history.get(i); + StringUtil.append(sb, "", "" + info.getRaceId(), " th", "" + info.getFirst(), " Lane ", "" + info.getSecond(), " Lane", String.format(Locale.ENGLISH, "%.2f", info.getOddRate()), " Times"); + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 9, player)); + html.replace("%infos%", sb.toString()); + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); } else { - // getKnownList().removeKnownObject(player); super.onBypassFeedback(player, command); } } - - public void showOdds(PlayerInstance player) - { - if (_state == ACCEPTING_BETS) - { - return; - } - final int npcId = getTemplate().getId(); - String filename; - String search; - final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); - filename = getHtmlPath(npcId, 5, player); - html.setFile(player, filename); - for (int i = 0; i < 8; i++) - { - final int n = i + 1; - search = "Mob" + n; - html.replace(search, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); - } - html.replace("1race", String.valueOf(_raceNumber)); - html.replace("%objectId%", String.valueOf(getObjectId())); - player.sendPacket(html); - player.sendPacket(ActionFailed.STATIC_PACKET); - } - - public void showMonsterInfo(PlayerInstance player) - { - final int npcId = getTemplate().getId(); - String filename; - String search; - final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); - filename = getHtmlPath(npcId, 6, player); - html.setFile(player, filename); - for (int i = 0; i < 8; i++) - { - final int n = i + 1; - search = "Mob" + n; - html.replace(search, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); - } - html.replace("%objectId%", String.valueOf(getObjectId())); - player.sendPacket(html); - player.sendPacket(ActionFailed.STATIC_PACKET); - } - - public void showBuyTicket(PlayerInstance player, int value) - { - if (_state != ACCEPTING_BETS) - { - return; - } - final int npcId = getTemplate().getId(); - SystemMessage sm; - String filename; - String search; - String replace; - final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); - if (value < 10) - { - filename = getHtmlPath(npcId, 2, player); - html.setFile(player, filename); - for (int i = 0; i < 8; i++) - { - final int n = i + 1; - search = "Mob" + n; - html.replace(search, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); - } - search = "No1"; - if (value == 0) - { - html.replace(search, ""); - } - else - { - html.replace(search, Integer.toString(value)); - player.setRace(0, value); - } - } - else if (value < 20) - { - if (player.getRace(0) == 0) - { - return; - } - filename = getHtmlPath(npcId, 3, player); - html.setFile(player, filename); - html.replace("0place", Integer.toString(player.getRace(0))); - search = "Mob1"; - replace = MonsterRace.getInstance().getMonsters()[player.getRace(0) - 1].getTemplate().getName(); - html.replace(search, replace); - search = "0adena"; - if (value == 10) - { - html.replace(search, ""); - } - else - { - html.replace(search, Integer.toString(_cost[value - 11])); - player.setRace(1, value - 10); - } - } - else if (value == 20) - { - if ((player.getRace(0) == 0) || (player.getRace(1) == 0)) - { - return; - } - filename = getHtmlPath(npcId, 4, player); - html.setFile(player, filename); - html.replace("0place", Integer.toString(player.getRace(0))); - search = "Mob1"; - replace = MonsterRace.getInstance().getMonsters()[player.getRace(0) - 1].getTemplate().getName(); - html.replace(search, replace); - search = "0adena"; - final int price = _cost[player.getRace(1) - 1]; - html.replace(search, Integer.toString(price)); - search = "0tax"; - final int tax = 0; - html.replace(search, Integer.toString(tax)); - search = "0total"; - final int total = price + tax; - html.replace(search, Integer.toString(total)); - } - else - { - if ((player.getRace(0) == 0) || (player.getRace(1) == 0)) - { - return; - } - final int ticket = player.getRace(0); - final int priceId = player.getRace(1); - if (!player.reduceAdena("Race", _cost[priceId - 1], this, true)) - { - return; - } - player.setRace(0, 0); - player.setRace(1, 0); - sm = new SystemMessage(SystemMessageId.ACQUIRED_S1_S2); - sm.addInt(_raceNumber); - sm.addItemName(4443); - player.sendPacket(sm); - final ItemInstance item = new ItemInstance(IdFactory.getInstance().getNextId(), 4443); - item.setCount(1); - item.setEnchantLevel(_raceNumber); - item.setCustomType1(ticket); - item.setCustomType2(_cost[priceId - 1] / 100); - player.getInventory().addItem("Race", item, player, this); - final InventoryUpdate iu = new InventoryUpdate(); - iu.addItem(item); - final ItemInstance adenaupdate = player.getInventory().getItemByItemId(Inventory.ADENA_ID); - iu.addModifiedItem(adenaupdate); - player.sendInventoryUpdate(iu); - return; - } - html.replace("1race", String.valueOf(_raceNumber)); - html.replace("%objectId%", String.valueOf(getObjectId())); - player.sendPacket(html); - player.sendPacket(ActionFailed.STATIC_PACKET); - } - - public static class Race - { - private final Info[] _info; - - public Race(Info[] pInfo) - { - _info = pInfo; - } - - public Info getLaneInfo(int lane) - { - return _info[lane]; - } - - public class Info - { - private final int _id; - private final int _place; - private final int _odds; - private final int _payout; - - public Info(int pId, int pPlace, int pOdds, int pPayout) - { - _id = pId; - _place = pPlace; - _odds = pOdds; - _payout = pPayout; - } - - public int getId() - { - return _id; - } - - public int getOdds() - { - return _odds; - } - - public int getPayout() - { - return _payout; - } - - public int getPlace() - { - return _place; - } - } - - } - - class RunRace implements Runnable - { - @Override - public void run() - { - _packet = new MonRaceInfo(_codes[2][0], _codes[2][1], MonsterRace.getInstance().getMonsters(), MonsterRace.getInstance().getSpeeds()); - sendMonsterInfo(); - ThreadPool.schedule(new RunEnd(), 30000); - } - } - - class RunEnd implements Runnable - { - @Override - public void run() - { - makeAnnouncement(SystemMessageId.FIRST_PRIZE_GOES_TO_THE_PLAYER_IN_LANE_S1_SECOND_PRIZE_GOES_TO_THE_PLAYER_IN_LANE_S2); - makeAnnouncement(SystemMessageId.MONSTER_RACE_S1_IS_FINISHED); - _raceNumber++; - - DeleteObject obj = null; - for (int i = 0; i < 8; i++) - { - obj = new DeleteObject(MonsterRace.getInstance().getMonsters()[i]); - broadcast(obj); - MonsterRace.getInstance().getMonsters()[i].deleteMe(); - } - } - } - } diff --git a/L2J_Mobius_5.5_EtinasFate/java/org/l2jmobius/gameserver/util/Broadcast.java b/L2J_Mobius_5.5_EtinasFate/java/org/l2jmobius/gameserver/util/Broadcast.java index 60eb0ff361..8d751afc53 100644 --- a/L2J_Mobius_5.5_EtinasFate/java/org/l2jmobius/gameserver/util/Broadcast.java +++ b/L2J_Mobius_5.5_EtinasFate/java/org/l2jmobius/gameserver/util/Broadcast.java @@ -20,10 +20,12 @@ import java.util.logging.Level; import java.util.logging.Logger; import org.l2jmobius.gameserver.enums.ChatType; +import org.l2jmobius.gameserver.instancemanager.ZoneManager; import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.actor.Creature; import org.l2jmobius.gameserver.model.actor.Summon; import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance; +import org.l2jmobius.gameserver.model.zone.ZoneType; import org.l2jmobius.gameserver.network.serverpackets.CharInfo; import org.l2jmobius.gameserver.network.serverpackets.CreatureSay; import org.l2jmobius.gameserver.network.serverpackets.ExShowScreenMessage; @@ -191,4 +193,29 @@ public class Broadcast { toAllOnlinePlayers(new ExShowScreenMessage(text, 10000)); } + + /** + * Send a packet to all players in a specific zone type. + * @param ZoneType. + * @param zoneType : The zone type to send packets. + * @param packets : The packets to send. + */ + public static void toAllPlayersInZoneType(Class zoneType, IClientOutgoingPacket... packets) + { + for (ZoneType zone : ZoneManager.getInstance().getAllZones(zoneType)) + { + for (Creature creature : zone.getCharactersInside()) + { + if (creature == null) + { + continue; + } + + for (IClientOutgoingPacket packet : packets) + { + creature.sendPacket(packet); + } + } + } + } } diff --git a/L2J_Mobius_6.0_Fafurion/dist/db_installer/sql/game/mdt_bets.sql b/L2J_Mobius_6.0_Fafurion/dist/db_installer/sql/game/mdt_bets.sql new file mode 100644 index 0000000000..8d7d4cb738 --- /dev/null +++ b/L2J_Mobius_6.0_Fafurion/dist/db_installer/sql/game/mdt_bets.sql @@ -0,0 +1,15 @@ +CREATE TABLE IF NOT EXISTS `mdt_bets` ( + `lane_id` INT(1) DEFAULT 0, + `bet` INT DEFAULT 0, + PRIMARY KEY (`lane_id`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci; + +INSERT INTO `mdt_bets` VALUES +('1','0'), +('2','0'), +('3','0'), +('4','0'), +('5','0'), +('6','0'), +('7','0'), +('8','0'); \ No newline at end of file diff --git a/L2J_Mobius_6.0_Fafurion/dist/db_installer/sql/game/mdt_history.sql b/L2J_Mobius_6.0_Fafurion/dist/db_installer/sql/game/mdt_history.sql new file mode 100644 index 0000000000..06978f3377 --- /dev/null +++ b/L2J_Mobius_6.0_Fafurion/dist/db_installer/sql/game/mdt_history.sql @@ -0,0 +1,7 @@ +CREATE TABLE IF NOT EXISTS `mdt_history` ( + `race_id` MEDIUMINT DEFAULT 0, + `first` INT(1) DEFAULT 0, + `second` INT(1) DEFAULT 0, + `odd_rate` DOUBLE(10,2) DEFAULT 0, + PRIMARY KEY (`race_id`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci; \ No newline at end of file diff --git a/L2J_Mobius_6.0_Fafurion/dist/game/config/AdminCommands.xml b/L2J_Mobius_6.0_Fafurion/dist/game/config/AdminCommands.xml index 36ab1240b5..215b7c679a 100644 --- a/L2J_Mobius_6.0_Fafurion/dist/game/config/AdminCommands.xml +++ b/L2J_Mobius_6.0_Fafurion/dist/game/config/AdminCommands.xml @@ -416,9 +416,6 @@ - - - diff --git a/L2J_Mobius_6.0_Fafurion/dist/game/data/html/default/30995-1.htm b/L2J_Mobius_6.0_Fafurion/dist/game/data/html/default/30995-1.htm index 4962bdf3a2..d38d518149 100644 --- a/L2J_Mobius_6.0_Fafurion/dist/game/data/html/default/30995-1.htm +++ b/L2J_Mobius_6.0_Fafurion/dist/game/data/html/default/30995-1.htm @@ -1,19 +1,22 @@ -

-
-In a monster race eight monsters race at the same time. If you guess which monster is expected to finish first and buy that monster's race ticket, based upon the odds to win you will receive an appropriate amount of Adena.
-How the Race WorksMonster races run every 20 minutes in real time.
-How to Participate
-You can purchase a monster race ticket through a race manager NPC. Only between 18 minutes before the start of a race until 3 minutes before the race begins can you buy a ticket for the applicable race. When you purchase a ticket through a race manager, you can find out the names of the participating monsters and their current statistics. Three minutes before a race starts, the race manager will stop ticket sales and announce the monsters' individual odds for the current race.
-Betting
-There are two types of race betting. The Win bet means that you back a particular monster to win 1st place. The Place bet means that you will have to guess the 1st and 2nd place monsters, regardless of who finishes first or second.
+ +
+ +
+In a monster race eight monsters race at the same time. If you guess which monster is expected to finish first and buy that monster's race ticket, based upon the odds to win you will receive an appropriate amount of adena.
+Racing Schedule
+Monster races every 20 minutes in real time.
+Method of Race Participation
+You can purchase a monster race ticket through a race manager NPC. Only between 18 minutes before the start of a race until 3 minutes before the race begins you can buy a ticket for the applicable race. When you purchase a ticket through a conversation with a race manager, you can find out the names of participating monsters in the race and their current statistics. Five minutes before a race starts, the race manager will stop ticket sales and announce monsters' individual odds rate for the current race.
+Types of Race
+There are two types of race betting. The Win type betting means that you back a monster that will win the first prize. Place type of betting means that you will have to guess the 1st and 2nd winning monsters, regardless of who finishes first or second.
Win - * Among the entire betting amount for Win betting, after subtracting the race track's own share of profit, the amount left will be your total winnings. Three minutes before the race, each monster's expected odds are announced. Each Win ticket's dividend amount is calculated by multiplying the odds by the price of a ticket. The odds are never less than 1. - * The prize money does not get transferred to the next race.
+* Among the entire betting amount for Win betting, after subtracting the race track's own share of profit, the amount left will be your total winnings. Three minutes before the race each monster's expected odds are announced. Each Win ticket's dividend amount is calculated by multiplying the odds by the price of a ticket. The odds rate is never less than 1. +* The prize money does not get transferred to the next race.
Place - * Place betting is a way of betting when you correctly guess the two monsters who will finish first and second regardless of their order. The method of calculating the dividend amount is the same as Win betting. - * Currently place betting is not available.
+* Place betting is a way of betting when you correctly guess the two monsters who will finish first and second regardless of their order. The method of calculating dividend amount is the same as Win betting.
+* Currently place betting is not offered.
End of Race
-When a race is over, the race manager will shout the results of the race. You can trade your ticket for Adena by speaking to a race manager NPC. Check the Winning Information Data to find out the types of bets and total winnings for the race you bet on, as well as your win status and prize amount. If you won, you can receive your prize money thorough a race manager NPC. If you did not win, a race manager will buy back your ticket for a small amount of money.
+When a race is over, the race manager will shout out the results of the race. Through a conversation with a race manager NPC, you can trade your ticket for adena. By looking up on the Winning Information Data, you can find out the types of tickets and quantity of the winnings in the race you betted on and your win status and prize amount you won. If you won, you can receive your prize money thorough a race manager NPC. If you did not win, a race manager will buy back your ticket by paying you a very small amount of money.
If a server goes down while tickets are being sold, or while a race is in progress, the race will be cancelled. You can receive a full refund for your ticket for a cancelled race through a race manager.
- + \ No newline at end of file diff --git a/L2J_Mobius_6.0_Fafurion/dist/game/data/html/default/30995-3.htm b/L2J_Mobius_6.0_Fafurion/dist/game/data/html/default/30995-3.htm index 5577c579a4..7ae65a37a4 100644 --- a/L2J_Mobius_6.0_Fafurion/dist/game/data/html/default/30995-3.htm +++ b/L2J_Mobius_6.0_Fafurion/dist/game/data/html/default/30995-3.htm @@ -3,6 +3,7 @@ + @@ -11,6 +12,7 @@
Lane SelectionOdds
+ @@ -20,18 +22,21 @@
13%
+
Select Bet Amount -100 Adena , 500 Adena -1,000 Adena , 5,000 Adena -10,000 Adena , 20,000 Adena -50,000 Adena , 100,000 Adena +100 Adena , 500 Adena +1,000 Adena , 5,000 Adena +10,000 Adena , 20,000 Adena +50,000 Adena , 100,000 Adena
+ + @@ -39,4 +44,5 @@
+ \ No newline at end of file diff --git a/L2J_Mobius_6.0_Fafurion/dist/game/data/html/default/30995-4.htm b/L2J_Mobius_6.0_Fafurion/dist/game/data/html/default/30995-4.htm index bcd1f0d520..30c508b9cb 100644 --- a/L2J_Mobius_6.0_Fafurion/dist/game/data/html/default/30995-4.htm +++ b/L2J_Mobius_6.0_Fafurion/dist/game/data/html/default/30995-4.htm @@ -11,6 +11,7 @@ Odds + @@ -20,6 +21,7 @@
13%
+ @@ -37,11 +39,13 @@ -
Bet Amount Total 0total Adena

+ +
If the information is correct, please click Confirm.

+ diff --git a/L2J_Mobius_6.0_Fafurion/dist/game/data/html/default/30995-5.htm b/L2J_Mobius_6.0_Fafurion/dist/game/data/html/default/30995-5.htm index b9729e01b0..59a05e14a0 100644 --- a/L2J_Mobius_6.0_Fafurion/dist/game/data/html/default/30995-5.htm +++ b/L2J_Mobius_6.0_Fafurion/dist/game/data/html/default/30995-5.htm @@ -15,49 +15,49 @@ - + - + - + - + - + - + - + - +
No.1 Mob1&$804; Odd1
No.2 Mob2&$804; Odd2
No.3 Mob3&$804; Odd3
No.4 Mob4&$804; Odd4
No.5 Mob5&$804; Odd5
No.6 Mob6&$804; Odd6
No.7 Mob7&$804; Odd7
No.8 Mob8&$804; Odd8
diff --git a/L2J_Mobius_6.0_Fafurion/dist/game/data/html/default/30995-7.htm b/L2J_Mobius_6.0_Fafurion/dist/game/data/html/default/30995-7.htm index a058e0e0b3..53eb213a52 100644 --- a/L2J_Mobius_6.0_Fafurion/dist/game/data/html/default/30995-7.htm +++ b/L2J_Mobius_6.0_Fafurion/dist/game/data/html/default/30995-7.htm @@ -3,22 +3,12 @@ - - + +
Winnings for a Race.LaneBet Amount
Winnings for a Race.LaneBet Amount
- - - - - +%tickets%
1 Number100 Adena
- - - - -
1 Page
- diff --git a/L2J_Mobius_6.0_Fafurion/dist/game/data/html/default/30995-8.htm b/L2J_Mobius_6.0_Fafurion/dist/game/data/html/default/30995-8.htm index ebbcf542b2..5f856a222f 100644 --- a/L2J_Mobius_6.0_Fafurion/dist/game/data/html/default/30995-8.htm +++ b/L2J_Mobius_6.0_Fafurion/dist/game/data/html/default/30995-8.htm @@ -4,11 +4,11 @@
-
Race #19090
-Lane 1
-Bet: 100 adena
-1st place: Lane [4]
-Odds:[0.01] +
Race #%raceId%
+Lane: %lane%
+Bet: %bet% Adena
+1st place: Lane [%firstLane%]
+Odds: [%odd%]
@@ -18,8 +18,8 @@ Odds:[0.01] - + - +
\ No newline at end of file diff --git a/L2J_Mobius_6.0_Fafurion/dist/game/data/html/default/30995-9.htm b/L2J_Mobius_6.0_Fafurion/dist/game/data/html/default/30995-9.htm index ac1201d6f2..c17ab4849a 100644 --- a/L2J_Mobius_6.0_Fafurion/dist/game/data/html/default/30995-9.htm +++ b/L2J_Mobius_6.0_Fafurion/dist/game/data/html/default/30995-9.htm @@ -7,41 +7,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +%infos%
7 th8 Lane 1 Lane0.00 Times
6 th4 Lane 8 Lane0.00 Times
5 th7 Lane 2 Lane0.00 Times
4 th6 Lane 8 Lane0.00 Times
3 th4 Lane 1 Lane0.00 Times
2 th1 Lane 8 Lane0.00 Times
1 th7 Lane 6 Lane0.00 Times
diff --git a/L2J_Mobius_6.0_Fafurion/dist/game/data/html/default/30995.htm b/L2J_Mobius_6.0_Fafurion/dist/game/data/html/default/30995.htm index 89f40742ae..271c4ee284 100644 --- a/L2J_Mobius_6.0_Fafurion/dist/game/data/html/default/30995.htm +++ b/L2J_Mobius_6.0_Fafurion/dist/game/data/html/default/30995.htm @@ -1,15 +1,15 @@ - -
Monster Race Manager
- - - - - - - - - -
- + + +
+ + + + + + + + + + \ No newline at end of file diff --git a/L2J_Mobius_6.0_Fafurion/dist/game/data/scripts/handlers/MasterHandler.java b/L2J_Mobius_6.0_Fafurion/dist/game/data/scripts/handlers/MasterHandler.java index da3d6d3bee..3b4bba89f4 100644 --- a/L2J_Mobius_6.0_Fafurion/dist/game/data/scripts/handlers/MasterHandler.java +++ b/L2J_Mobius_6.0_Fafurion/dist/game/data/scripts/handlers/MasterHandler.java @@ -105,7 +105,6 @@ import handlers.admincommandhandlers.AdminMenu; import handlers.admincommandhandlers.AdminMessages; import handlers.admincommandhandlers.AdminMissingHtmls; import handlers.admincommandhandlers.AdminMobGroup; -import handlers.admincommandhandlers.AdminMonsterRace; import handlers.admincommandhandlers.AdminOlympiad; import handlers.admincommandhandlers.AdminOnline; import handlers.admincommandhandlers.AdminPForge; @@ -443,7 +442,6 @@ public class MasterHandler AdminMessages.class, AdminMissingHtmls.class, AdminMobGroup.class, - AdminMonsterRace.class, AdminOlympiad.class, AdminOnline.class, AdminPathNode.class, diff --git a/L2J_Mobius_6.0_Fafurion/dist/game/data/scripts/handlers/admincommandhandlers/AdminMonsterRace.java b/L2J_Mobius_6.0_Fafurion/dist/game/data/scripts/handlers/admincommandhandlers/AdminMonsterRace.java deleted file mode 100644 index e465186490..0000000000 --- a/L2J_Mobius_6.0_Fafurion/dist/game/data/scripts/handlers/admincommandhandlers/AdminMonsterRace.java +++ /dev/null @@ -1,166 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * This program 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. - * - * This program 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 . - */ -package handlers.admincommandhandlers; - -import org.l2jmobius.commons.concurrent.ThreadPool; -import org.l2jmobius.gameserver.MonsterRace; -import org.l2jmobius.gameserver.handler.IAdminCommandHandler; -import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance; -import org.l2jmobius.gameserver.network.SystemMessageId; -import org.l2jmobius.gameserver.network.serverpackets.DeleteObject; -import org.l2jmobius.gameserver.network.serverpackets.MonRaceInfo; -import org.l2jmobius.gameserver.network.serverpackets.PlaySound; -import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; - -/** - * This class handles following admin commands: - invul = turns invulnerability on/off - * @version $Revision: 1.1.6.4 $ $Date: 2007/07/31 10:06:00 $ - */ -public class AdminMonsterRace implements IAdminCommandHandler -{ - private static final String[] ADMIN_COMMANDS = - { - "admin_mons" - }; - - static int state = -1; - - @Override - public boolean useAdminCommand(String command, PlayerInstance activeChar) - { - if (command.equalsIgnoreCase("admin_mons")) - { - handleSendPacket(activeChar); - } - return true; - } - - @Override - public String[] getAdminCommandList() - { - return ADMIN_COMMANDS; - } - - private void handleSendPacket(PlayerInstance activeChar) - { - /* - * -1 0 to initialize the race 0 15322 to start race 13765 -1 in middle of race -1 0 to end the race 8003 to 8027 - */ - - final int[][] codes = - { - { - -1, - 0 - }, - { - 0, - 15322 - }, - { - 13765, - -1 - }, - { - -1, - 0 - } - }; - final MonsterRace race = MonsterRace.getInstance(); - - if (state == -1) - { - state++; - race.newRace(); - race.newSpeeds(); - final MonRaceInfo spk = new MonRaceInfo(codes[state][0], codes[state][1], race.getMonsters(), race.getSpeeds()); - activeChar.sendPacket(spk); - activeChar.broadcastPacket(spk); - } - else if (state == 0) - { - state++; - final SystemMessage sm = new SystemMessage(SystemMessageId.THEY_RE_OFF); - sm.addInt(0); - activeChar.sendPacket(sm); - final PlaySound sRace = new PlaySound(1, "S_Race", 0, 0, 0, 0, 0); - activeChar.sendPacket(sRace); - activeChar.broadcastPacket(sRace); - final PlaySound sRace2 = new PlaySound(0, "ItemSound2.race_start", 1, 121209259, 12125, 182487, -3559); - activeChar.sendPacket(sRace2); - activeChar.broadcastPacket(sRace2); - final MonRaceInfo spk = new MonRaceInfo(codes[state][0], codes[state][1], race.getMonsters(), race.getSpeeds()); - activeChar.sendPacket(spk); - activeChar.broadcastPacket(spk); - - ThreadPool.schedule(new RunRace(codes, activeChar), 5000); - } - - } - - private class RunRace implements Runnable - { - - private final int[][] codes; - private final PlayerInstance activeChar; - - public RunRace(int[][] pCodes, PlayerInstance pActiveChar) - { - codes = pCodes; - activeChar = pActiveChar; - } - - @Override - public void run() - { - // int[][] speeds1 = MonsterRace.getInstance().getSpeeds(); - // MonsterRace.getInstance().newSpeeds(); - // int[][] speeds2 = MonsterRace.getInstance().getSpeeds(); - /* - * int[] speed = new int[8]; for (int i=0; i<8; i++) { for (int j=0; j<20; j++) { //LOGGER.info("Adding "+speeds1[i][j] +" and "+ speeds2[i][j]); speed[i] += (speeds1[i][j]*1); // + (speeds2[i][j]*1); } LOGGER.info("Total speed for "+(i+1)+" = "+speed[i]); } - */ - - final MonRaceInfo spk = new MonRaceInfo(codes[2][0], codes[2][1], MonsterRace.getInstance().getMonsters(), MonsterRace.getInstance().getSpeeds()); - activeChar.sendPacket(spk); - activeChar.broadcastPacket(spk); - ThreadPool.schedule(new RunEnd(activeChar), 30000); - } - } - - private static class RunEnd implements Runnable - { - private final PlayerInstance activeChar; - - public RunEnd(PlayerInstance pActiveChar) - { - activeChar = pActiveChar; - } - - @Override - public void run() - { - DeleteObject obj = null; - for (int i = 0; i < 8; i++) - { - obj = new DeleteObject(MonsterRace.getInstance().getMonsters()[i]); - activeChar.sendPacket(obj); - activeChar.broadcastPacket(obj); - } - state = -1; - } - } -} diff --git a/L2J_Mobius_6.0_Fafurion/java/org/l2jmobius/gameserver/GameServer.java b/L2J_Mobius_6.0_Fafurion/java/org/l2jmobius/gameserver/GameServer.java index a4744832ed..4251cf75b7 100644 --- a/L2J_Mobius_6.0_Fafurion/java/org/l2jmobius/gameserver/GameServer.java +++ b/L2J_Mobius_6.0_Fafurion/java/org/l2jmobius/gameserver/GameServer.java @@ -146,6 +146,7 @@ import org.l2jmobius.gameserver.instancemanager.SiegeGuardManager; import org.l2jmobius.gameserver.instancemanager.SiegeManager; import org.l2jmobius.gameserver.instancemanager.WalkingManager; import org.l2jmobius.gameserver.instancemanager.ZoneManager; +import org.l2jmobius.gameserver.instancemanager.games.MonsterRace; import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.entity.Hero; import org.l2jmobius.gameserver.model.events.EventDispatcher; @@ -408,10 +409,8 @@ public class GameServer ItemsAutoDestroy.getInstance(); } - if (Config.ALLOW_RACE) - { - MonsterRace.getInstance(); - } + MonsterRace.getInstance(); + TaskManager.getInstance(); AntiFeedManager.getInstance().registerEvent(AntiFeedManager.GAME_ID); diff --git a/L2J_Mobius_6.0_Fafurion/java/org/l2jmobius/gameserver/MonsterRace.java b/L2J_Mobius_6.0_Fafurion/java/org/l2jmobius/gameserver/MonsterRace.java deleted file mode 100644 index cdd92a9a07..0000000000 --- a/L2J_Mobius_6.0_Fafurion/java/org/l2jmobius/gameserver/MonsterRace.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * This program 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. - * - * This program 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 . - */ -package org.l2jmobius.gameserver; - -import java.util.logging.Level; -import java.util.logging.Logger; - -import org.l2jmobius.commons.util.Rnd; -import org.l2jmobius.gameserver.data.xml.impl.NpcData; -import org.l2jmobius.gameserver.model.actor.Npc; -import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate; - -public class MonsterRace -{ - protected static final Logger LOGGER = Logger.getLogger(MonsterRace.class.getName()); - - private final Npc[] _monsters; - private int[][] _speeds; - private final int[] _first; - private final int[] _second; - - protected MonsterRace() - { - _monsters = new Npc[8]; - _speeds = new int[8][20]; - _first = new int[2]; - _second = new int[2]; - } - - public void newRace() - { - int random = 0; - - for (int i = 0; i < 8; i++) - { - final int id = 31003; - random = Rnd.get(24); - while (true) - { - for (int j = i - 1; j >= 0; j--) - { - if (_monsters[j].getTemplate().getId() == (id + random)) - { - random = Rnd.get(24); - } - } - break; - } - try - { - final NpcTemplate template = NpcData.getInstance().getTemplate(id + random); - _monsters[i] = (Npc) Class.forName("org.l2jmobius.gameserver.model.actor.instance." + template.getType() + "Instance").getConstructors()[0].newInstance(template); - } - catch (Exception e) - { - LOGGER.log(Level.WARNING, "Unable to create monster!", e); - } - } - newSpeeds(); - } - - public void newSpeeds() - { - _speeds = new int[8][20]; - int total = 0; - _first[1] = 0; - _second[1] = 0; - for (int i = 0; i < 8; i++) - { - total = 0; - for (int j = 0; j < 20; j++) - { - _speeds[i][j] = j == 19 ? 100 : Rnd.get(60) + 65; - total += _speeds[i][j]; - } - if (total >= _first[1]) - { - _second[0] = _first[0]; - _second[1] = _first[1]; - _first[0] = 8 - i; - _first[1] = total; - } - else if (total >= _second[1]) - { - _second[0] = 8 - i; - _second[1] = total; - } - } - } - - /** - * @return Returns the monsters. - */ - public Npc[] getMonsters() - { - return _monsters; - } - - /** - * @return Returns the speeds. - */ - public int[][] getSpeeds() - { - return _speeds; - } - - public int getFirstPlace() - { - return _first[0]; - } - - public int getSecondPlace() - { - return _second[0]; - } - - public static MonsterRace getInstance() - { - return SingletonHolder.INSTANCE; - } - - private static class SingletonHolder - { - protected static final MonsterRace INSTANCE = new MonsterRace(); - } -} diff --git a/L2J_Mobius_6.0_Fafurion/java/org/l2jmobius/gameserver/instancemanager/games/MonsterRace.java b/L2J_Mobius_6.0_Fafurion/java/org/l2jmobius/gameserver/instancemanager/games/MonsterRace.java new file mode 100644 index 0000000000..2fa4c519f6 --- /dev/null +++ b/L2J_Mobius_6.0_Fafurion/java/org/l2jmobius/gameserver/instancemanager/games/MonsterRace.java @@ -0,0 +1,623 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program 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. + * + * This program 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 . + */ +package org.l2jmobius.gameserver.instancemanager.games; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.Config; +import org.l2jmobius.commons.concurrent.ThreadPool; +import org.l2jmobius.commons.database.DatabaseFactory; +import org.l2jmobius.commons.util.Rnd; +import org.l2jmobius.gameserver.data.xml.impl.NpcData; +import org.l2jmobius.gameserver.model.actor.Npc; +import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.model.zone.type.DerbyTrackZone; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.serverpackets.DeleteObject; +import org.l2jmobius.gameserver.network.serverpackets.MonRaceInfo; +import org.l2jmobius.gameserver.network.serverpackets.PlaySound; +import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; +import org.l2jmobius.gameserver.util.Broadcast; + +public class MonsterRace +{ + protected static final Logger LOGGER = Logger.getLogger(MonsterRace.class.getName()); + + public static enum RaceState + { + ACCEPTING_BETS, + WAITING, + STARTING_RACE, + RACE_END + } + + protected static final PlaySound SOUND_1 = new PlaySound(1, "S_Race", 0, 0, 0, 0, 0); + protected static final PlaySound SOUND_2 = new PlaySound("ItemSound2.race_start"); + + protected static final int[][] CODES = + { + { + -1, + 0 + }, + { + 0, + 15322 + }, + { + 13765, + -1 + } + }; + + protected final List _npcTemplates = new ArrayList<>(); // List holding npc templates, shuffled on a new race. + protected final List _history = new ArrayList<>(); // List holding old race records. + protected final Map _betsPerLane = new ConcurrentHashMap<>(); // Map holding all bets for each lane ; values setted to 0 after every race. + protected final List _odds = new ArrayList<>(); // List holding sorted odds per lane ; cleared at new odds calculation. + + protected int _raceNumber = 1; + protected int _finalCountdown = 0; + protected RaceState _state = RaceState.RACE_END; + + protected MonRaceInfo _packet; + + private final Npc[] _monsters = new Npc[8]; + private int[][] _speeds = new int[8][20]; + private final int[] _first = new int[2]; + private final int[] _second = new int[2]; + + protected MonsterRace() + { + if (!Config.ALLOW_RACE) + { + return; + } + + // Feed _history with previous race results. + loadHistory(); + + // Feed _betsPerLane with stored informations on bets. + loadBets(); + + // Feed _npcTemplates, we will only have to shuffle it when needed. + for (int i = 31003; i < 31027; i++) + { + _npcTemplates.add(i); + } + + ThreadPool.scheduleAtFixedRate(new Announcement(), 0, 1000); + } + + public static class HistoryInfo + { + private final int _raceId; + private int _first; + private int _second; + private double _oddRate; + + public HistoryInfo(int raceId, int first, int second, double oddRate) + { + _raceId = raceId; + _first = first; + _second = second; + _oddRate = oddRate; + } + + public int getRaceId() + { + return _raceId; + } + + public int getFirst() + { + return _first; + } + + public int getSecond() + { + return _second; + } + + public double getOddRate() + { + return _oddRate; + } + + public void setFirst(int first) + { + _first = first; + } + + public void setSecond(int second) + { + _second = second; + } + + public void setOddRate(double oddRate) + { + _oddRate = oddRate; + } + } + + private class Announcement implements Runnable + { + public Announcement() + { + } + + @Override + public void run() + { + if (_finalCountdown > 1200) + { + _finalCountdown = 0; + } + + switch (_finalCountdown) + { + case 0: + { + newRace(); + newSpeeds(); + + _state = RaceState.ACCEPTING_BETS; + _packet = new MonRaceInfo(CODES[0][0], CODES[0][1], getMonsters(), getSpeeds()); + + final SystemMessage msg = new SystemMessage(SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, _packet, msg); + 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 + { + final SystemMessage msg = new SystemMessage(SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + break; + } + case 300: // 5 min + { + final SystemMessage msg = new SystemMessage(SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S); + msg2.addInt(10); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + break; + } + case 600: // 10 min + { + final SystemMessage msg = new SystemMessage(SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S); + msg2.addInt(5); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + break; + } + case 840: // 14 min + { + final SystemMessage msg = new SystemMessage(SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S); + msg2.addInt(1); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + break; + } + case 900: // 15 min + { + _state = RaceState.WAITING; + + calculateOdds(); + + final SystemMessage msg = new SystemMessage(SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.TICKETS_SALES_ARE_CLOSED_FOR_MONSTER_RACE_S1_ODDS_ARE_POSTED); + msg2.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + break; + } + case 960: // 16 min + case 1020: // 17 min + { + final int minutes = (_finalCountdown == 960) ? 2 : 1; + final SystemMessage msg = new SystemMessage(SystemMessageId.MONSTER_RACE_S2_WILL_BEGIN_IN_S1_MINUTE_S); + msg.addInt(minutes); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + break; + } + case 1050: // 17 min 30 sec + { + final SystemMessage msg = new SystemMessage(SystemMessageId.MONSTER_RACE_S1_WILL_BEGIN_IN_30_SECONDS); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + break; + } + case 1070: // 17 min 50 sec + { + final SystemMessage msg = new SystemMessage(SystemMessageId.MONSTER_RACE_S1_IS_ABOUT_TO_BEGIN_COUNTDOWN_IN_FIVE_SECONDS); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + 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; + final SystemMessage msg = new SystemMessage(SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SECOND_S); + msg.addInt(seconds); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + break; + } + case 1080: // 18 min + { + _state = RaceState.STARTING_RACE; + _packet = new MonRaceInfo(CODES[1][0], CODES[1][1], getMonsters(), getSpeeds()); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, new SystemMessage(SystemMessageId.THEY_RE_OFF), SOUND_1, SOUND_2, _packet); + break; + } + case 1085: // 18 min 5 sec + { + _packet = new MonRaceInfo(CODES[2][0], CODES[2][1], getMonsters(), getSpeeds()); + + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, _packet); + break; + } + case 1115: // 18 min 35 sec + { + _state = RaceState.RACE_END; + + // Populate history info with data, stores it in database. + final HistoryInfo info = _history.get(_history.size() - 1); + info.setFirst(getFirstPlace()); + info.setSecond(getSecondPlace()); + info.setOddRate(_odds.get(getFirstPlace() - 1)); + + saveHistory(info); + clearBets(); + + final SystemMessage msg = new SystemMessage(SystemMessageId.FIRST_PRIZE_GOES_TO_THE_PLAYER_IN_LANE_S1_SECOND_PRIZE_GOES_TO_THE_PLAYER_IN_LANE_S2); + msg.addInt(getFirstPlace()); + msg.addInt(getSecondPlace()); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.MONSTER_RACE_S1_IS_FINISHED); + msg2.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + _raceNumber++; + break; + } + case 1140: // 19 min + { + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, new DeleteObject(getMonsters()[0]), new DeleteObject(getMonsters()[1]), new DeleteObject(getMonsters()[2]), new DeleteObject(getMonsters()[3]), new DeleteObject(getMonsters()[4]), new DeleteObject(getMonsters()[5]), new DeleteObject(getMonsters()[6]), new DeleteObject(getMonsters()[7])); + break; + } + } + _finalCountdown += 1; + } + } + + public void newRace() + { + // Edit _history. + _history.add(new HistoryInfo(_raceNumber, 0, 0, 0)); + + // Randomize _npcTemplates. + Collections.shuffle(_npcTemplates); + + // Setup 8 new creatures ; pickup the first 8 from _npcTemplates. + for (int i = 0; i < 8; i++) + { + try + { + final NpcTemplate template = NpcData.getInstance().getTemplate(_npcTemplates.get(i)); + _monsters[i] = (Npc) Class.forName("org.l2jmobius.gameserver.model.actor.instance." + template.getType() + "Instance").getConstructors()[0].newInstance(template); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "", e); + } + } + } + + public void newSpeeds() + { + _speeds = new int[8][20]; + int total = 0; + _first[1] = 0; + _second[1] = 0; + + for (int i = 0; i < 8; i++) + { + total = 0; + for (int j = 0; j < 20; j++) + { + if (j == 19) + { + _speeds[i][j] = 100; + } + else + { + _speeds[i][j] = Rnd.get(60) + 65; + } + total += _speeds[i][j]; + } + + if (total >= _first[1]) + { + _second[0] = _first[0]; + _second[1] = _first[1]; + _first[0] = 8 - i; + _first[1] = total; + } + else if (total >= _second[1]) + { + _second[0] = 8 - i; + _second[1] = total; + } + } + } + + /** + * Load past races informations, feeding _history arrayList.
+ * Also sets _raceNumber, based on latest HistoryInfo loaded. + */ + protected void loadHistory() + { + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("SELECT * FROM mdt_history"); + ResultSet rset = statement.executeQuery(); + + while (rset.next()) + { + _history.add(new HistoryInfo(rset.getInt("race_id"), rset.getInt("first"), rset.getInt("second"), rset.getDouble("odd_rate"))); + _raceNumber++; + } + rset.close(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't load history: " + e.getMessage(), e); + } + LOGGER.info("MonsterRace: loaded " + _history.size() + " records, currently on race #" + _raceNumber); + } + + /** + * Save an history record into database. + * @param history The infos to store. + */ + protected void saveHistory(HistoryInfo history) + { + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("INSERT INTO mdt_history (race_id, first, second, odd_rate) VALUES (?,?,?,?)"); + statement.setInt(1, history.getRaceId()); + statement.setInt(2, history.getFirst()); + statement.setInt(3, history.getSecond()); + statement.setDouble(4, history.getOddRate()); + statement.execute(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't save history: " + e.getMessage(), e); + } + } + + /** + * Load current bets per lane ; initialize the map keys. + */ + protected void loadBets() + { + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("SELECT * FROM mdt_bets"); + ResultSet rset = statement.executeQuery(); + + while (rset.next()) + { + setBetOnLane(rset.getInt("lane_id"), rset.getLong("bet"), false); + } + + rset.close(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't load bets: " + e.getMessage(), e); + } + } + + /** + * Save the current lane bet into database. + * @param lane : The lane to affect. + * @param sum : The sum to set. + */ + protected void saveBet(int lane, long sum) + { + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("REPLACE INTO mdt_bets (lane_id, bet) VALUES (?,?)"); + statement.setInt(1, lane); + statement.setLong(2, sum); + statement.execute(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't save bet: " + e.getMessage(), e); + } + } + + /** + * Clear all lanes bets, either on database or Map. + */ + protected void clearBets() + { + for (int key : _betsPerLane.keySet()) + { + _betsPerLane.put(key, 0L); + } + + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("UPDATE mdt_bets SET bet = 0"); + statement.execute(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't clear bets: " + e.getMessage(), e); + } + } + + /** + * Setup lane bet, based on previous value (if any). + * @param lane : The lane to edit. + * @param amount : The amount to add. + * @param saveOnDb : Should it be saved on db or not. + */ + public void setBetOnLane(int lane, long amount, boolean saveOnDb) + { + final long sum = (_betsPerLane.containsKey(lane)) ? _betsPerLane.get(lane) + amount : amount; + + _betsPerLane.put(lane, sum); + + if (saveOnDb) + { + saveBet(lane, sum); + } + } + + /** + * Calculate odds for every lane, based on others lanes. + */ + protected void calculateOdds() + { + // Clear previous List holding old odds. + _odds.clear(); + + // Sort bets lanes per lane. + final Map 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; + } + + // 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)); + } + } + + public Npc[] getMonsters() + { + return _monsters; + } + + public int[][] getSpeeds() + { + return _speeds; + } + + public int getFirstPlace() + { + return _first[0]; + } + + public int getSecondPlace() + { + return _second[0]; + } + + public MonRaceInfo getRacePacket() + { + return _packet; + } + + public RaceState getCurrentRaceState() + { + return _state; + } + + public int getRaceNumber() + { + return _raceNumber; + } + + public List getHistory() + { + return _history; + } + + public List getOdds() + { + return _odds; + } + + public static MonsterRace getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final MonsterRace INSTANCE = new MonsterRace(); + } +} \ No newline at end of file diff --git a/L2J_Mobius_6.0_Fafurion/java/org/l2jmobius/gameserver/model/actor/instance/RaceManagerInstance.java b/L2J_Mobius_6.0_Fafurion/java/org/l2jmobius/gameserver/model/actor/instance/RaceManagerInstance.java index f893d4f5f2..2375543774 100644 --- a/L2J_Mobius_6.0_Fafurion/java/org/l2jmobius/gameserver/model/actor/instance/RaceManagerInstance.java +++ b/L2J_Mobius_6.0_Fafurion/java/org/l2jmobius/gameserver/model/actor/instance/RaceManagerInstance.java @@ -16,69 +16,26 @@ */ package org.l2jmobius.gameserver.model.actor.instance; -import java.util.Collection; -import java.util.concurrent.ConcurrentHashMap; +import java.util.List; +import java.util.Locale; import org.l2jmobius.Config; -import org.l2jmobius.commons.concurrent.ThreadPool; -import org.l2jmobius.gameserver.MonsterRace; -import org.l2jmobius.gameserver.enums.InstanceType; +import org.l2jmobius.commons.util.StringUtil; import org.l2jmobius.gameserver.idfactory.IdFactory; -import org.l2jmobius.gameserver.model.actor.Creature; +import org.l2jmobius.gameserver.instancemanager.games.MonsterRace; +import org.l2jmobius.gameserver.instancemanager.games.MonsterRace.HistoryInfo; +import org.l2jmobius.gameserver.instancemanager.games.MonsterRace.RaceState; import org.l2jmobius.gameserver.model.actor.Npc; import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate; -import org.l2jmobius.gameserver.model.itemcontainer.Inventory; import org.l2jmobius.gameserver.model.items.instance.ItemInstance; import org.l2jmobius.gameserver.network.SystemMessageId; import org.l2jmobius.gameserver.network.serverpackets.ActionFailed; -import org.l2jmobius.gameserver.network.serverpackets.DeleteObject; -import org.l2jmobius.gameserver.network.serverpackets.IClientOutgoingPacket; -import org.l2jmobius.gameserver.network.serverpackets.InventoryUpdate; -import org.l2jmobius.gameserver.network.serverpackets.MonRaceInfo; import org.l2jmobius.gameserver.network.serverpackets.NpcHtmlMessage; -import org.l2jmobius.gameserver.network.serverpackets.PlaySound; import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; -import org.l2jmobius.gameserver.util.Broadcast; public class RaceManagerInstance extends Npc { - public static final int LANES = 8; - public static final int WINDOW_START = 0; - - private static Collection _managers; - protected static int _raceNumber = 4; - - // Time Constants - private static final long SECOND = 1000; - private static final long MINUTE = 60 * SECOND; - - private static int _minutes = 5; - - // States - private static final int ACCEPTING_BETS = 0; - private static final int WAITING = 1; - private static final int STARTING_RACE = 2; - private static final int RACE_END = 3; - private static int _state = RACE_END; - - protected static final int[][] _codes = - { - { - -1, - 0 - }, - { - 0, - 15322 - }, - { - 13765, - -1 - } - }; - private static boolean _notInitialized = true; - protected static MonRaceInfo _packet; - protected static final int[] _cost = + protected static final int TICKET_PRICES[] = { 100, 500, @@ -93,223 +50,167 @@ public class RaceManagerInstance extends Npc public RaceManagerInstance(NpcTemplate template) { super(template); - setInstanceType(InstanceType.RaceManagerInstance); - - if (!Config.ALLOW_RACE) - { - return; - } - - if (_notInitialized) - { - _notInitialized = false; - - _managers = ConcurrentHashMap.newKeySet(); - - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1), 0, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1), 30 * SECOND, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1), MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1), MINUTE + (30 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S), 2 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S), 3 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S), 4 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S), 5 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S), 6 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKETS_SALES_ARE_CLOSED_FOR_MONSTER_RACE_S1_ODDS_ARE_POSTED), 7 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.MONSTER_RACE_S2_WILL_BEGIN_IN_S1_MINUTE_S), 7 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.MONSTER_RACE_S2_WILL_BEGIN_IN_S1_MINUTE_S), 8 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.MONSTER_RACE_S1_WILL_BEGIN_IN_30_SECONDS), (8 * MINUTE) + (30 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.MONSTER_RACE_S1_IS_ABOUT_TO_BEGIN_COUNTDOWN_IN_FIVE_SECONDS), (8 * MINUTE) + (50 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SECOND_S), (8 * MINUTE) + (55 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SECOND_S), (8 * MINUTE) + (56 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SECOND_S), (8 * MINUTE) + (57 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SECOND_S), (8 * MINUTE) + (58 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SECOND_S), (8 * MINUTE) + (59 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.THEY_RE_OFF), 9 * MINUTE, 10 * MINUTE); - } - _managers.add(this); - } - - @Override - public boolean isAutoAttackable(Creature attacker) - { - if (attacker.isMonster()) - { - return true; - } - - return super.isAutoAttackable(attacker); - } - - class Announcement implements Runnable - { - private final SystemMessageId _type; - - public Announcement(SystemMessageId pType) - { - _type = pType; - } - - @Override - public void run() - { - makeAnnouncement(_type); - } - } - - public void makeAnnouncement(SystemMessageId type) - { - final SystemMessage sm = new SystemMessage(type); - switch (type.getId()) - { - case 816: // SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1 - case 817: // SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1 - { - if (_state != ACCEPTING_BETS) - {// LOGGER.info("Race Initializing"); - _state = ACCEPTING_BETS; - startRace(); - } // else{LOGGER.info("Race open");} - sm.addInt(_raceNumber); - break; - } - case 818: // SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S - case 820: // SystemMessageId.MONSTER_RACE_S2_WILL_BEGIN_IN_S1_MINUTE_S - case 823: // SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SECOND_S - { - sm.addInt(_minutes); - if (type.getId() == 820) - { - sm.addInt(_raceNumber); - } - _minutes--; - break; - } - case 819: // SystemMessageId.TICKETS_SALES_ARE_CLOSED_FOR_MONSTER_RACE_S1_ODDS_ARE_POSTED - { - // LOGGER.info("Sales closed"); - sm.addInt(_raceNumber); - _state = WAITING; - _minutes = 2; - break; - } - case 821: // SystemMessageId.MONSTER_RACE_S1_WILL_BEGIN_IN_30_SECONDS - case 822: // SystemMessageId.MONSTER_RACE_S1_IS_ABOUT_TO_BEGIN_COUNTDOWN_IN_FIVE_SECONDS - case 825: // SystemMessageId.MONSTER_RACE_S1_IS_FINISHED - { - sm.addInt(_raceNumber); - _minutes = 5; - break; - } - case 826: // SystemMessageId.FIRST_PRIZE_GOES_TO_THE_PLAYER_IN_LANE_S1_SECOND_PRIZE_GOES_TO_THE_PLAYER_IN_LANE_S2 - { - // LOGGER.info("Placing"); - _state = RACE_END; - sm.addInt(MonsterRace.getInstance().getFirstPlace()); - sm.addInt(MonsterRace.getInstance().getSecondPlace()); - break; - } - } - // _logn.info("Counter: "+minutes); - // LOGGER.info("State: "+state); - broadcast(sm); - // LOGGER.info("Player's known: "+getKnownPlayers().size()); - - if (type == SystemMessageId.THEY_RE_OFF) - { - // LOGGER.info("Starting race"); - _state = STARTING_RACE; - startRace(); - _minutes = 5; - } - } - - protected void broadcast(IClientOutgoingPacket pkt) - { - for (RaceManagerInstance manager : _managers) - { - if (!manager.isDead()) - { - Broadcast.toKnownPlayers(manager, pkt); - } - } - } - - public void sendMonsterInfo() - { - broadcast(_packet); - } - - private void startRace() - { - final MonsterRace race = MonsterRace.getInstance(); - if (_state == STARTING_RACE) - { - // state++; - broadcast(new PlaySound(1, "S_Race", 0, 0, 0, 0, 0)); - broadcast(new PlaySound(0, "ItemSound2.race_start", 1, 121209259, 12125, 182487, -3559)); - _packet = new MonRaceInfo(_codes[1][0], _codes[1][1], race.getMonsters(), race.getSpeeds()); - sendMonsterInfo(); - - ThreadPool.schedule(new RunRace(), 5000); - } - else - { - // state++; - race.newRace(); - race.newSpeeds(); - _packet = new MonRaceInfo(_codes[0][0], _codes[0][1], race.getMonsters(), race.getSpeeds()); - sendMonsterInfo(); - } } @Override public void onBypassFeedback(PlayerInstance player, String command) { - if (command.startsWith("BuyTicket") && (_state != ACCEPTING_BETS)) - { - if (!Config.ALLOW_RACE) - { - return; - } - player.sendPacket(SystemMessageId.MONSTER_RACE_TICKETS_ARE_NO_LONGER_AVAILABLE); - command = "Chat 0"; - } - if (command.startsWith("ShowOdds") && (_state == ACCEPTING_BETS)) - { - if (!Config.ALLOW_RACE) - { - return; - } - player.sendPacket(SystemMessageId.MONSTER_RACE_PAYOUT_INFORMATION_IS_NOT_AVAILABLE_WHILE_TICKETS_ARE_BEING_SOLD); - command = "Chat 0"; - } - if (command.startsWith("BuyTicket")) { - if (!Config.ALLOW_RACE) + if (!Config.ALLOW_RACE || (MonsterRace.getInstance().getCurrentRaceState() != RaceState.ACCEPTING_BETS)) { + player.sendPacket(SystemMessageId.MONSTER_RACE_TICKETS_ARE_NO_LONGER_AVAILABLE); + super.onBypassFeedback(player, "Chat 0"); return; } + int 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))) { val = 0; } - showBuyTicket(player, val); + + String search, replace; + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + + if (val < 10) + { + html.setFile(player, getHtmlPath(getId(), 2, player)); + for (int i = 0; i < 8; i++) + { + int n = i + 1; + search = "Mob" + n; + html.replace(search, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); + } + search = "No1"; + if (val == 0) + { + html.replace(search, ""); + } + else + { + html.replace(search, val); + player.setRace(0, val); + } + } + else if (val < 20) + { + if (player.getRace(0) == 0) + { + return; + } + + html.setFile(player, getHtmlPath(getId(), 3, player)); + html.replace("0place", player.getRace(0)); + search = "Mob1"; + replace = MonsterRace.getInstance().getMonsters()[player.getRace(0) - 1].getTemplate().getName(); + html.replace(search, replace); + search = "0adena"; + + if (val == 10) + { + html.replace(search, ""); + } + else + { + html.replace(search, TICKET_PRICES[val - 11]); + player.setRace(1, val - 10); + } + } + else if (val == 20) + { + if ((player.getRace(0) == 0) || (player.getRace(1) == 0)) + { + return; + } + + html.setFile(player, getHtmlPath(getId(), 4, player)); + html.replace("0place", player.getRace(0)); + search = "Mob1"; + replace = MonsterRace.getInstance().getMonsters()[player.getRace(0) - 1].getTemplate().getName(); + 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); + } + else + { + if ((player.getRace(0) == 0) || (player.getRace(1) == 0)) + { + return; + } + + int ticket = player.getRace(0); + int priceId = player.getRace(1); + + if (!player.reduceAdena("Race", TICKET_PRICES[priceId - 1], this, true)) + { + return; + } + + player.setRace(0, 0); + player.setRace(1, 0); + + ItemInstance item = new ItemInstance(IdFactory.getInstance().getNextId(), 4443); + item.setCount(1); + item.setEnchantLevel(MonsterRace.getInstance().getRaceNumber()); + item.setCustomType1(ticket); + item.setCustomType2(TICKET_PRICES[priceId - 1] / 100); + + player.addItem("Race", item, player, false); + final SystemMessage msg = new SystemMessage(SystemMessageId.ACQUIRED_S1_S2); + msg.addInt(MonsterRace.getInstance().getRaceNumber()); + msg.addItemName(4443); + player.sendPacket(msg); + + // Refresh lane bet. + MonsterRace.getInstance().setBetOnLane(ticket, TICKET_PRICES[priceId - 1], true); + super.onBypassFeedback(player, "Chat 0"); + return; + } + html.replace("1race", MonsterRace.getInstance().getRaceNumber()); + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); } else if (command.equals("ShowOdds")) { - if (!Config.ALLOW_RACE) + if (!Config.ALLOW_RACE || (MonsterRace.getInstance().getCurrentRaceState() == RaceState.ACCEPTING_BETS)) { + player.sendPacket(SystemMessageId.MONSTER_RACE_PAYOUT_INFORMATION_IS_NOT_AVAILABLE_WHILE_TICKETS_ARE_BEING_SOLD); + super.onBypassFeedback(player, "Chat 0"); return; } - showOdds(player); + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 5, player)); + for (int i = 0; i < 8; i++) + { + final int n = i + 1; + + html.replace("Mob" + n, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); + + // Odd + final double odd = MonsterRace.getInstance().getOdds().get(i); + html.replace("Odd" + n, (odd > 0D) ? String.format(Locale.ENGLISH, "%.1f", odd) : "&$804;"); + } + html.replace("1race", MonsterRace.getInstance().getRaceNumber()); + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); } else if (command.equals("ShowInfo")) { @@ -317,269 +218,160 @@ public class RaceManagerInstance extends Npc { return; } - showMonsterInfo(player); + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 6, player)); + + for (int i = 0; i < 8; i++) + { + int n = i + 1; + String search = "Mob" + n; + html.replace(search, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); + } + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); } - else if (command.equals("calculateWin")) + else if (command.equals("ShowTickets")) { if (!Config.ALLOW_RACE) { + super.onBypassFeedback(player, "Chat 0"); return; } - // displayCalculateWinnings(player); + + // Generate data. + final StringBuilder sb = new StringBuilder(); + + // Retrieve player's tickets. + for (ItemInstance ticket : player.getInventory().getAllItemsByItemId(4443)) + { + // Don't list current race tickets. + if (ticket.getEnchantLevel() == MonsterRace.getInstance().getRaceNumber()) + { + continue; + } + + StringUtil.append(sb, "", "" + ticket.getEnchantLevel(), " Race Number", "" + ticket.getCustomType1(), " Number", "" + (ticket.getCustomType2() * 100), " Adena"); + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 7, player)); + html.replace("%tickets%", sb.toString()); + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); } - else if (command.equals("viewHistory")) + else if (command.startsWith("ShowTicket")) + { + // Retrieve ticket objectId. + final int val = Integer.parseInt(command.substring(11)); + if (!Config.ALLOW_RACE || (val == 0)) + { + super.onBypassFeedback(player, "Chat 0"); + return; + } + + // Retrieve ticket on player's inventory. + final ItemInstance 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; + + // Retrieve HistoryInfo for that race. + final HistoryInfo info = MonsterRace.getInstance().getHistory().get(raceId - 1); + if (info == null) + { + super.onBypassFeedback(player, "Chat 0"); + return; + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 8, player)); + html.replace("%raceId%", raceId); + html.replace("%lane%", lane); + html.replace("%bet%", bet); + html.replace("%firstLane%", info.getFirst()); + html.replace("%odd%", (lane == info.getFirst()) ? String.format(Locale.ENGLISH, "%.2f", info.getOddRate()) : "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 (!Config.ALLOW_RACE || (val == 0)) + { + super.onBypassFeedback(player, "Chat 0"); + return; + } + + // Delete ticket on player's inventory. + final ItemInstance 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; + + // Retrieve HistoryInfo for that race. + final HistoryInfo info = MonsterRace.getInstance().getHistory().get(raceId - 1); + if (info == 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()) ? info.getOddRate() : 0.01)), this, true); + } + + super.onBypassFeedback(player, "Chat 0"); + return; + } + else if (command.equals("ViewHistory")) { if (!Config.ALLOW_RACE) { + super.onBypassFeedback(player, "Chat 0"); return; } - // displayHistory(player); + + // Generate data. + final StringBuilder sb = new StringBuilder(); + + // Use whole history, pickup from 'last element' and stop at 'latest element - 7'. + final List history = MonsterRace.getInstance().getHistory(); + for (int i = history.size() - 1; i >= Math.max(0, history.size() - 7); i--) + { + final HistoryInfo info = history.get(i); + StringUtil.append(sb, "", "" + info.getRaceId(), " th", "" + info.getFirst(), " Lane ", "" + info.getSecond(), " Lane", String.format(Locale.ENGLISH, "%.2f", info.getOddRate()), " Times"); + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 9, player)); + html.replace("%infos%", sb.toString()); + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); } else { - // getKnownList().removeKnownObject(player); super.onBypassFeedback(player, command); } } - - public void showOdds(PlayerInstance player) - { - if (_state == ACCEPTING_BETS) - { - return; - } - final int npcId = getTemplate().getId(); - String filename; - String search; - final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); - filename = getHtmlPath(npcId, 5, player); - html.setFile(player, filename); - for (int i = 0; i < 8; i++) - { - final int n = i + 1; - search = "Mob" + n; - html.replace(search, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); - } - html.replace("1race", String.valueOf(_raceNumber)); - html.replace("%objectId%", String.valueOf(getObjectId())); - player.sendPacket(html); - player.sendPacket(ActionFailed.STATIC_PACKET); - } - - public void showMonsterInfo(PlayerInstance player) - { - final int npcId = getTemplate().getId(); - String filename; - String search; - final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); - filename = getHtmlPath(npcId, 6, player); - html.setFile(player, filename); - for (int i = 0; i < 8; i++) - { - final int n = i + 1; - search = "Mob" + n; - html.replace(search, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); - } - html.replace("%objectId%", String.valueOf(getObjectId())); - player.sendPacket(html); - player.sendPacket(ActionFailed.STATIC_PACKET); - } - - public void showBuyTicket(PlayerInstance player, int value) - { - if (_state != ACCEPTING_BETS) - { - return; - } - final int npcId = getTemplate().getId(); - SystemMessage sm; - String filename; - String search; - String replace; - final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); - if (value < 10) - { - filename = getHtmlPath(npcId, 2, player); - html.setFile(player, filename); - for (int i = 0; i < 8; i++) - { - final int n = i + 1; - search = "Mob" + n; - html.replace(search, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); - } - search = "No1"; - if (value == 0) - { - html.replace(search, ""); - } - else - { - html.replace(search, Integer.toString(value)); - player.setRace(0, value); - } - } - else if (value < 20) - { - if (player.getRace(0) == 0) - { - return; - } - filename = getHtmlPath(npcId, 3, player); - html.setFile(player, filename); - html.replace("0place", Integer.toString(player.getRace(0))); - search = "Mob1"; - replace = MonsterRace.getInstance().getMonsters()[player.getRace(0) - 1].getTemplate().getName(); - html.replace(search, replace); - search = "0adena"; - if (value == 10) - { - html.replace(search, ""); - } - else - { - html.replace(search, Integer.toString(_cost[value - 11])); - player.setRace(1, value - 10); - } - } - else if (value == 20) - { - if ((player.getRace(0) == 0) || (player.getRace(1) == 0)) - { - return; - } - filename = getHtmlPath(npcId, 4, player); - html.setFile(player, filename); - html.replace("0place", Integer.toString(player.getRace(0))); - search = "Mob1"; - replace = MonsterRace.getInstance().getMonsters()[player.getRace(0) - 1].getTemplate().getName(); - html.replace(search, replace); - search = "0adena"; - final int price = _cost[player.getRace(1) - 1]; - html.replace(search, Integer.toString(price)); - search = "0tax"; - final int tax = 0; - html.replace(search, Integer.toString(tax)); - search = "0total"; - final int total = price + tax; - html.replace(search, Integer.toString(total)); - } - else - { - if ((player.getRace(0) == 0) || (player.getRace(1) == 0)) - { - return; - } - final int ticket = player.getRace(0); - final int priceId = player.getRace(1); - if (!player.reduceAdena("Race", _cost[priceId - 1], this, true)) - { - return; - } - player.setRace(0, 0); - player.setRace(1, 0); - sm = new SystemMessage(SystemMessageId.ACQUIRED_S1_S2); - sm.addInt(_raceNumber); - sm.addItemName(4443); - player.sendPacket(sm); - final ItemInstance item = new ItemInstance(IdFactory.getInstance().getNextId(), 4443); - item.setCount(1); - item.setEnchantLevel(_raceNumber); - item.setCustomType1(ticket); - item.setCustomType2(_cost[priceId - 1] / 100); - player.getInventory().addItem("Race", item, player, this); - final InventoryUpdate iu = new InventoryUpdate(); - iu.addItem(item); - final ItemInstance adenaupdate = player.getInventory().getItemByItemId(Inventory.ADENA_ID); - iu.addModifiedItem(adenaupdate); - player.sendInventoryUpdate(iu); - return; - } - html.replace("1race", String.valueOf(_raceNumber)); - html.replace("%objectId%", String.valueOf(getObjectId())); - player.sendPacket(html); - player.sendPacket(ActionFailed.STATIC_PACKET); - } - - public static class Race - { - private final Info[] _info; - - public Race(Info[] pInfo) - { - _info = pInfo; - } - - public Info getLaneInfo(int lane) - { - return _info[lane]; - } - - public class Info - { - private final int _id; - private final int _place; - private final int _odds; - private final int _payout; - - public Info(int pId, int pPlace, int pOdds, int pPayout) - { - _id = pId; - _place = pPlace; - _odds = pOdds; - _payout = pPayout; - } - - public int getId() - { - return _id; - } - - public int getOdds() - { - return _odds; - } - - public int getPayout() - { - return _payout; - } - - public int getPlace() - { - return _place; - } - } - - } - - class RunRace implements Runnable - { - @Override - public void run() - { - _packet = new MonRaceInfo(_codes[2][0], _codes[2][1], MonsterRace.getInstance().getMonsters(), MonsterRace.getInstance().getSpeeds()); - sendMonsterInfo(); - ThreadPool.schedule(new RunEnd(), 30000); - } - } - - class RunEnd implements Runnable - { - @Override - public void run() - { - makeAnnouncement(SystemMessageId.FIRST_PRIZE_GOES_TO_THE_PLAYER_IN_LANE_S1_SECOND_PRIZE_GOES_TO_THE_PLAYER_IN_LANE_S2); - makeAnnouncement(SystemMessageId.MONSTER_RACE_S1_IS_FINISHED); - _raceNumber++; - - DeleteObject obj = null; - for (int i = 0; i < 8; i++) - { - obj = new DeleteObject(MonsterRace.getInstance().getMonsters()[i]); - broadcast(obj); - MonsterRace.getInstance().getMonsters()[i].deleteMe(); - } - } - } - } diff --git a/L2J_Mobius_6.0_Fafurion/java/org/l2jmobius/gameserver/util/Broadcast.java b/L2J_Mobius_6.0_Fafurion/java/org/l2jmobius/gameserver/util/Broadcast.java index 60eb0ff361..8d751afc53 100644 --- a/L2J_Mobius_6.0_Fafurion/java/org/l2jmobius/gameserver/util/Broadcast.java +++ b/L2J_Mobius_6.0_Fafurion/java/org/l2jmobius/gameserver/util/Broadcast.java @@ -20,10 +20,12 @@ import java.util.logging.Level; import java.util.logging.Logger; import org.l2jmobius.gameserver.enums.ChatType; +import org.l2jmobius.gameserver.instancemanager.ZoneManager; import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.actor.Creature; import org.l2jmobius.gameserver.model.actor.Summon; import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance; +import org.l2jmobius.gameserver.model.zone.ZoneType; import org.l2jmobius.gameserver.network.serverpackets.CharInfo; import org.l2jmobius.gameserver.network.serverpackets.CreatureSay; import org.l2jmobius.gameserver.network.serverpackets.ExShowScreenMessage; @@ -191,4 +193,29 @@ public class Broadcast { toAllOnlinePlayers(new ExShowScreenMessage(text, 10000)); } + + /** + * Send a packet to all players in a specific zone type. + * @param ZoneType. + * @param zoneType : The zone type to send packets. + * @param packets : The packets to send. + */ + public static void toAllPlayersInZoneType(Class zoneType, IClientOutgoingPacket... packets) + { + for (ZoneType zone : ZoneManager.getInstance().getAllZones(zoneType)) + { + for (Creature creature : zone.getCharactersInside()) + { + if (creature == null) + { + continue; + } + + for (IClientOutgoingPacket packet : packets) + { + creature.sendPacket(packet); + } + } + } + } } diff --git a/L2J_Mobius_7.0_PreludeOfWar/dist/db_installer/sql/game/mdt_bets.sql b/L2J_Mobius_7.0_PreludeOfWar/dist/db_installer/sql/game/mdt_bets.sql new file mode 100644 index 0000000000..8d7d4cb738 --- /dev/null +++ b/L2J_Mobius_7.0_PreludeOfWar/dist/db_installer/sql/game/mdt_bets.sql @@ -0,0 +1,15 @@ +CREATE TABLE IF NOT EXISTS `mdt_bets` ( + `lane_id` INT(1) DEFAULT 0, + `bet` INT DEFAULT 0, + PRIMARY KEY (`lane_id`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci; + +INSERT INTO `mdt_bets` VALUES +('1','0'), +('2','0'), +('3','0'), +('4','0'), +('5','0'), +('6','0'), +('7','0'), +('8','0'); \ No newline at end of file diff --git a/L2J_Mobius_7.0_PreludeOfWar/dist/db_installer/sql/game/mdt_history.sql b/L2J_Mobius_7.0_PreludeOfWar/dist/db_installer/sql/game/mdt_history.sql new file mode 100644 index 0000000000..06978f3377 --- /dev/null +++ b/L2J_Mobius_7.0_PreludeOfWar/dist/db_installer/sql/game/mdt_history.sql @@ -0,0 +1,7 @@ +CREATE TABLE IF NOT EXISTS `mdt_history` ( + `race_id` MEDIUMINT DEFAULT 0, + `first` INT(1) DEFAULT 0, + `second` INT(1) DEFAULT 0, + `odd_rate` DOUBLE(10,2) DEFAULT 0, + PRIMARY KEY (`race_id`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci; \ No newline at end of file diff --git a/L2J_Mobius_7.0_PreludeOfWar/dist/game/config/AdminCommands.xml b/L2J_Mobius_7.0_PreludeOfWar/dist/game/config/AdminCommands.xml index 36ab1240b5..215b7c679a 100644 --- a/L2J_Mobius_7.0_PreludeOfWar/dist/game/config/AdminCommands.xml +++ b/L2J_Mobius_7.0_PreludeOfWar/dist/game/config/AdminCommands.xml @@ -416,9 +416,6 @@ - - - diff --git a/L2J_Mobius_7.0_PreludeOfWar/dist/game/data/html/default/30995-1.htm b/L2J_Mobius_7.0_PreludeOfWar/dist/game/data/html/default/30995-1.htm index 4962bdf3a2..d38d518149 100644 --- a/L2J_Mobius_7.0_PreludeOfWar/dist/game/data/html/default/30995-1.htm +++ b/L2J_Mobius_7.0_PreludeOfWar/dist/game/data/html/default/30995-1.htm @@ -1,19 +1,22 @@ -

-
-In a monster race eight monsters race at the same time. If you guess which monster is expected to finish first and buy that monster's race ticket, based upon the odds to win you will receive an appropriate amount of Adena.
-How the Race WorksMonster races run every 20 minutes in real time.
-How to Participate
-You can purchase a monster race ticket through a race manager NPC. Only between 18 minutes before the start of a race until 3 minutes before the race begins can you buy a ticket for the applicable race. When you purchase a ticket through a race manager, you can find out the names of the participating monsters and their current statistics. Three minutes before a race starts, the race manager will stop ticket sales and announce the monsters' individual odds for the current race.
-Betting
-There are two types of race betting. The Win bet means that you back a particular monster to win 1st place. The Place bet means that you will have to guess the 1st and 2nd place monsters, regardless of who finishes first or second.
+ +
+ +
+In a monster race eight monsters race at the same time. If you guess which monster is expected to finish first and buy that monster's race ticket, based upon the odds to win you will receive an appropriate amount of adena.
+Racing Schedule
+Monster races every 20 minutes in real time.
+Method of Race Participation
+You can purchase a monster race ticket through a race manager NPC. Only between 18 minutes before the start of a race until 3 minutes before the race begins you can buy a ticket for the applicable race. When you purchase a ticket through a conversation with a race manager, you can find out the names of participating monsters in the race and their current statistics. Five minutes before a race starts, the race manager will stop ticket sales and announce monsters' individual odds rate for the current race.
+Types of Race
+There are two types of race betting. The Win type betting means that you back a monster that will win the first prize. Place type of betting means that you will have to guess the 1st and 2nd winning monsters, regardless of who finishes first or second.
Win - * Among the entire betting amount for Win betting, after subtracting the race track's own share of profit, the amount left will be your total winnings. Three minutes before the race, each monster's expected odds are announced. Each Win ticket's dividend amount is calculated by multiplying the odds by the price of a ticket. The odds are never less than 1. - * The prize money does not get transferred to the next race.
+* Among the entire betting amount for Win betting, after subtracting the race track's own share of profit, the amount left will be your total winnings. Three minutes before the race each monster's expected odds are announced. Each Win ticket's dividend amount is calculated by multiplying the odds by the price of a ticket. The odds rate is never less than 1. +* The prize money does not get transferred to the next race.
Place - * Place betting is a way of betting when you correctly guess the two monsters who will finish first and second regardless of their order. The method of calculating the dividend amount is the same as Win betting. - * Currently place betting is not available.
+* Place betting is a way of betting when you correctly guess the two monsters who will finish first and second regardless of their order. The method of calculating dividend amount is the same as Win betting.
+* Currently place betting is not offered.
End of Race
-When a race is over, the race manager will shout the results of the race. You can trade your ticket for Adena by speaking to a race manager NPC. Check the Winning Information Data to find out the types of bets and total winnings for the race you bet on, as well as your win status and prize amount. If you won, you can receive your prize money thorough a race manager NPC. If you did not win, a race manager will buy back your ticket for a small amount of money.
+When a race is over, the race manager will shout out the results of the race. Through a conversation with a race manager NPC, you can trade your ticket for adena. By looking up on the Winning Information Data, you can find out the types of tickets and quantity of the winnings in the race you betted on and your win status and prize amount you won. If you won, you can receive your prize money thorough a race manager NPC. If you did not win, a race manager will buy back your ticket by paying you a very small amount of money.
If a server goes down while tickets are being sold, or while a race is in progress, the race will be cancelled. You can receive a full refund for your ticket for a cancelled race through a race manager.
- + \ No newline at end of file diff --git a/L2J_Mobius_7.0_PreludeOfWar/dist/game/data/html/default/30995-3.htm b/L2J_Mobius_7.0_PreludeOfWar/dist/game/data/html/default/30995-3.htm index 5577c579a4..7ae65a37a4 100644 --- a/L2J_Mobius_7.0_PreludeOfWar/dist/game/data/html/default/30995-3.htm +++ b/L2J_Mobius_7.0_PreludeOfWar/dist/game/data/html/default/30995-3.htm @@ -3,6 +3,7 @@ + @@ -11,6 +12,7 @@
Lane SelectionOdds
+ @@ -20,18 +22,21 @@
13%
+
Select Bet Amount -100 Adena , 500 Adena -1,000 Adena , 5,000 Adena -10,000 Adena , 20,000 Adena -50,000 Adena , 100,000 Adena +100 Adena , 500 Adena +1,000 Adena , 5,000 Adena +10,000 Adena , 20,000 Adena +50,000 Adena , 100,000 Adena
+ + @@ -39,4 +44,5 @@
+ \ No newline at end of file diff --git a/L2J_Mobius_7.0_PreludeOfWar/dist/game/data/html/default/30995-4.htm b/L2J_Mobius_7.0_PreludeOfWar/dist/game/data/html/default/30995-4.htm index bcd1f0d520..30c508b9cb 100644 --- a/L2J_Mobius_7.0_PreludeOfWar/dist/game/data/html/default/30995-4.htm +++ b/L2J_Mobius_7.0_PreludeOfWar/dist/game/data/html/default/30995-4.htm @@ -11,6 +11,7 @@ Odds + @@ -20,6 +21,7 @@
13%
+ @@ -37,11 +39,13 @@ -
Bet Amount Total 0total Adena

+ +
If the information is correct, please click Confirm.

+ diff --git a/L2J_Mobius_7.0_PreludeOfWar/dist/game/data/html/default/30995-5.htm b/L2J_Mobius_7.0_PreludeOfWar/dist/game/data/html/default/30995-5.htm index b9729e01b0..59a05e14a0 100644 --- a/L2J_Mobius_7.0_PreludeOfWar/dist/game/data/html/default/30995-5.htm +++ b/L2J_Mobius_7.0_PreludeOfWar/dist/game/data/html/default/30995-5.htm @@ -15,49 +15,49 @@ - + - + - + - + - + - + - + - +
No.1 Mob1&$804; Odd1
No.2 Mob2&$804; Odd2
No.3 Mob3&$804; Odd3
No.4 Mob4&$804; Odd4
No.5 Mob5&$804; Odd5
No.6 Mob6&$804; Odd6
No.7 Mob7&$804; Odd7
No.8 Mob8&$804; Odd8
diff --git a/L2J_Mobius_7.0_PreludeOfWar/dist/game/data/html/default/30995-7.htm b/L2J_Mobius_7.0_PreludeOfWar/dist/game/data/html/default/30995-7.htm index a058e0e0b3..53eb213a52 100644 --- a/L2J_Mobius_7.0_PreludeOfWar/dist/game/data/html/default/30995-7.htm +++ b/L2J_Mobius_7.0_PreludeOfWar/dist/game/data/html/default/30995-7.htm @@ -3,22 +3,12 @@ - - + +
Winnings for a Race.LaneBet Amount
Winnings for a Race.LaneBet Amount
- - - - - +%tickets%
1 Number100 Adena
- - - - -
1 Page
- diff --git a/L2J_Mobius_7.0_PreludeOfWar/dist/game/data/html/default/30995-8.htm b/L2J_Mobius_7.0_PreludeOfWar/dist/game/data/html/default/30995-8.htm index ebbcf542b2..5f856a222f 100644 --- a/L2J_Mobius_7.0_PreludeOfWar/dist/game/data/html/default/30995-8.htm +++ b/L2J_Mobius_7.0_PreludeOfWar/dist/game/data/html/default/30995-8.htm @@ -4,11 +4,11 @@
-
Race #19090
-Lane 1
-Bet: 100 adena
-1st place: Lane [4]
-Odds:[0.01] +
Race #%raceId%
+Lane: %lane%
+Bet: %bet% Adena
+1st place: Lane [%firstLane%]
+Odds: [%odd%]
@@ -18,8 +18,8 @@ Odds:[0.01] - + - +
\ No newline at end of file diff --git a/L2J_Mobius_7.0_PreludeOfWar/dist/game/data/html/default/30995-9.htm b/L2J_Mobius_7.0_PreludeOfWar/dist/game/data/html/default/30995-9.htm index ac1201d6f2..c17ab4849a 100644 --- a/L2J_Mobius_7.0_PreludeOfWar/dist/game/data/html/default/30995-9.htm +++ b/L2J_Mobius_7.0_PreludeOfWar/dist/game/data/html/default/30995-9.htm @@ -7,41 +7,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +%infos%
7 th8 Lane 1 Lane0.00 Times
6 th4 Lane 8 Lane0.00 Times
5 th7 Lane 2 Lane0.00 Times
4 th6 Lane 8 Lane0.00 Times
3 th4 Lane 1 Lane0.00 Times
2 th1 Lane 8 Lane0.00 Times
1 th7 Lane 6 Lane0.00 Times
diff --git a/L2J_Mobius_7.0_PreludeOfWar/dist/game/data/html/default/30995.htm b/L2J_Mobius_7.0_PreludeOfWar/dist/game/data/html/default/30995.htm index 89f40742ae..271c4ee284 100644 --- a/L2J_Mobius_7.0_PreludeOfWar/dist/game/data/html/default/30995.htm +++ b/L2J_Mobius_7.0_PreludeOfWar/dist/game/data/html/default/30995.htm @@ -1,15 +1,15 @@ - -
Monster Race Manager
- - - - - - - - - -
- + + +
+ + + + + + + + + + \ No newline at end of file diff --git a/L2J_Mobius_7.0_PreludeOfWar/dist/game/data/scripts/handlers/MasterHandler.java b/L2J_Mobius_7.0_PreludeOfWar/dist/game/data/scripts/handlers/MasterHandler.java index da3d6d3bee..3b4bba89f4 100644 --- a/L2J_Mobius_7.0_PreludeOfWar/dist/game/data/scripts/handlers/MasterHandler.java +++ b/L2J_Mobius_7.0_PreludeOfWar/dist/game/data/scripts/handlers/MasterHandler.java @@ -105,7 +105,6 @@ import handlers.admincommandhandlers.AdminMenu; import handlers.admincommandhandlers.AdminMessages; import handlers.admincommandhandlers.AdminMissingHtmls; import handlers.admincommandhandlers.AdminMobGroup; -import handlers.admincommandhandlers.AdminMonsterRace; import handlers.admincommandhandlers.AdminOlympiad; import handlers.admincommandhandlers.AdminOnline; import handlers.admincommandhandlers.AdminPForge; @@ -443,7 +442,6 @@ public class MasterHandler AdminMessages.class, AdminMissingHtmls.class, AdminMobGroup.class, - AdminMonsterRace.class, AdminOlympiad.class, AdminOnline.class, AdminPathNode.class, diff --git a/L2J_Mobius_7.0_PreludeOfWar/dist/game/data/scripts/handlers/admincommandhandlers/AdminMonsterRace.java b/L2J_Mobius_7.0_PreludeOfWar/dist/game/data/scripts/handlers/admincommandhandlers/AdminMonsterRace.java deleted file mode 100644 index e465186490..0000000000 --- a/L2J_Mobius_7.0_PreludeOfWar/dist/game/data/scripts/handlers/admincommandhandlers/AdminMonsterRace.java +++ /dev/null @@ -1,166 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * This program 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. - * - * This program 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 . - */ -package handlers.admincommandhandlers; - -import org.l2jmobius.commons.concurrent.ThreadPool; -import org.l2jmobius.gameserver.MonsterRace; -import org.l2jmobius.gameserver.handler.IAdminCommandHandler; -import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance; -import org.l2jmobius.gameserver.network.SystemMessageId; -import org.l2jmobius.gameserver.network.serverpackets.DeleteObject; -import org.l2jmobius.gameserver.network.serverpackets.MonRaceInfo; -import org.l2jmobius.gameserver.network.serverpackets.PlaySound; -import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; - -/** - * This class handles following admin commands: - invul = turns invulnerability on/off - * @version $Revision: 1.1.6.4 $ $Date: 2007/07/31 10:06:00 $ - */ -public class AdminMonsterRace implements IAdminCommandHandler -{ - private static final String[] ADMIN_COMMANDS = - { - "admin_mons" - }; - - static int state = -1; - - @Override - public boolean useAdminCommand(String command, PlayerInstance activeChar) - { - if (command.equalsIgnoreCase("admin_mons")) - { - handleSendPacket(activeChar); - } - return true; - } - - @Override - public String[] getAdminCommandList() - { - return ADMIN_COMMANDS; - } - - private void handleSendPacket(PlayerInstance activeChar) - { - /* - * -1 0 to initialize the race 0 15322 to start race 13765 -1 in middle of race -1 0 to end the race 8003 to 8027 - */ - - final int[][] codes = - { - { - -1, - 0 - }, - { - 0, - 15322 - }, - { - 13765, - -1 - }, - { - -1, - 0 - } - }; - final MonsterRace race = MonsterRace.getInstance(); - - if (state == -1) - { - state++; - race.newRace(); - race.newSpeeds(); - final MonRaceInfo spk = new MonRaceInfo(codes[state][0], codes[state][1], race.getMonsters(), race.getSpeeds()); - activeChar.sendPacket(spk); - activeChar.broadcastPacket(spk); - } - else if (state == 0) - { - state++; - final SystemMessage sm = new SystemMessage(SystemMessageId.THEY_RE_OFF); - sm.addInt(0); - activeChar.sendPacket(sm); - final PlaySound sRace = new PlaySound(1, "S_Race", 0, 0, 0, 0, 0); - activeChar.sendPacket(sRace); - activeChar.broadcastPacket(sRace); - final PlaySound sRace2 = new PlaySound(0, "ItemSound2.race_start", 1, 121209259, 12125, 182487, -3559); - activeChar.sendPacket(sRace2); - activeChar.broadcastPacket(sRace2); - final MonRaceInfo spk = new MonRaceInfo(codes[state][0], codes[state][1], race.getMonsters(), race.getSpeeds()); - activeChar.sendPacket(spk); - activeChar.broadcastPacket(spk); - - ThreadPool.schedule(new RunRace(codes, activeChar), 5000); - } - - } - - private class RunRace implements Runnable - { - - private final int[][] codes; - private final PlayerInstance activeChar; - - public RunRace(int[][] pCodes, PlayerInstance pActiveChar) - { - codes = pCodes; - activeChar = pActiveChar; - } - - @Override - public void run() - { - // int[][] speeds1 = MonsterRace.getInstance().getSpeeds(); - // MonsterRace.getInstance().newSpeeds(); - // int[][] speeds2 = MonsterRace.getInstance().getSpeeds(); - /* - * int[] speed = new int[8]; for (int i=0; i<8; i++) { for (int j=0; j<20; j++) { //LOGGER.info("Adding "+speeds1[i][j] +" and "+ speeds2[i][j]); speed[i] += (speeds1[i][j]*1); // + (speeds2[i][j]*1); } LOGGER.info("Total speed for "+(i+1)+" = "+speed[i]); } - */ - - final MonRaceInfo spk = new MonRaceInfo(codes[2][0], codes[2][1], MonsterRace.getInstance().getMonsters(), MonsterRace.getInstance().getSpeeds()); - activeChar.sendPacket(spk); - activeChar.broadcastPacket(spk); - ThreadPool.schedule(new RunEnd(activeChar), 30000); - } - } - - private static class RunEnd implements Runnable - { - private final PlayerInstance activeChar; - - public RunEnd(PlayerInstance pActiveChar) - { - activeChar = pActiveChar; - } - - @Override - public void run() - { - DeleteObject obj = null; - for (int i = 0; i < 8; i++) - { - obj = new DeleteObject(MonsterRace.getInstance().getMonsters()[i]); - activeChar.sendPacket(obj); - activeChar.broadcastPacket(obj); - } - state = -1; - } - } -} diff --git a/L2J_Mobius_7.0_PreludeOfWar/java/org/l2jmobius/gameserver/GameServer.java b/L2J_Mobius_7.0_PreludeOfWar/java/org/l2jmobius/gameserver/GameServer.java index 2119ea2d40..2bb5681d30 100644 --- a/L2J_Mobius_7.0_PreludeOfWar/java/org/l2jmobius/gameserver/GameServer.java +++ b/L2J_Mobius_7.0_PreludeOfWar/java/org/l2jmobius/gameserver/GameServer.java @@ -148,6 +148,7 @@ import org.l2jmobius.gameserver.instancemanager.SiegeGuardManager; import org.l2jmobius.gameserver.instancemanager.SiegeManager; import org.l2jmobius.gameserver.instancemanager.WalkingManager; import org.l2jmobius.gameserver.instancemanager.ZoneManager; +import org.l2jmobius.gameserver.instancemanager.games.MonsterRace; import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.entity.Hero; import org.l2jmobius.gameserver.model.events.EventDispatcher; @@ -412,10 +413,8 @@ public class GameServer ItemsAutoDestroy.getInstance(); } - if (Config.ALLOW_RACE) - { - MonsterRace.getInstance(); - } + MonsterRace.getInstance(); + TaskManager.getInstance(); AntiFeedManager.getInstance().registerEvent(AntiFeedManager.GAME_ID); diff --git a/L2J_Mobius_7.0_PreludeOfWar/java/org/l2jmobius/gameserver/MonsterRace.java b/L2J_Mobius_7.0_PreludeOfWar/java/org/l2jmobius/gameserver/MonsterRace.java deleted file mode 100644 index cdd92a9a07..0000000000 --- a/L2J_Mobius_7.0_PreludeOfWar/java/org/l2jmobius/gameserver/MonsterRace.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * This program 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. - * - * This program 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 . - */ -package org.l2jmobius.gameserver; - -import java.util.logging.Level; -import java.util.logging.Logger; - -import org.l2jmobius.commons.util.Rnd; -import org.l2jmobius.gameserver.data.xml.impl.NpcData; -import org.l2jmobius.gameserver.model.actor.Npc; -import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate; - -public class MonsterRace -{ - protected static final Logger LOGGER = Logger.getLogger(MonsterRace.class.getName()); - - private final Npc[] _monsters; - private int[][] _speeds; - private final int[] _first; - private final int[] _second; - - protected MonsterRace() - { - _monsters = new Npc[8]; - _speeds = new int[8][20]; - _first = new int[2]; - _second = new int[2]; - } - - public void newRace() - { - int random = 0; - - for (int i = 0; i < 8; i++) - { - final int id = 31003; - random = Rnd.get(24); - while (true) - { - for (int j = i - 1; j >= 0; j--) - { - if (_monsters[j].getTemplate().getId() == (id + random)) - { - random = Rnd.get(24); - } - } - break; - } - try - { - final NpcTemplate template = NpcData.getInstance().getTemplate(id + random); - _monsters[i] = (Npc) Class.forName("org.l2jmobius.gameserver.model.actor.instance." + template.getType() + "Instance").getConstructors()[0].newInstance(template); - } - catch (Exception e) - { - LOGGER.log(Level.WARNING, "Unable to create monster!", e); - } - } - newSpeeds(); - } - - public void newSpeeds() - { - _speeds = new int[8][20]; - int total = 0; - _first[1] = 0; - _second[1] = 0; - for (int i = 0; i < 8; i++) - { - total = 0; - for (int j = 0; j < 20; j++) - { - _speeds[i][j] = j == 19 ? 100 : Rnd.get(60) + 65; - total += _speeds[i][j]; - } - if (total >= _first[1]) - { - _second[0] = _first[0]; - _second[1] = _first[1]; - _first[0] = 8 - i; - _first[1] = total; - } - else if (total >= _second[1]) - { - _second[0] = 8 - i; - _second[1] = total; - } - } - } - - /** - * @return Returns the monsters. - */ - public Npc[] getMonsters() - { - return _monsters; - } - - /** - * @return Returns the speeds. - */ - public int[][] getSpeeds() - { - return _speeds; - } - - public int getFirstPlace() - { - return _first[0]; - } - - public int getSecondPlace() - { - return _second[0]; - } - - public static MonsterRace getInstance() - { - return SingletonHolder.INSTANCE; - } - - private static class SingletonHolder - { - protected static final MonsterRace INSTANCE = new MonsterRace(); - } -} diff --git a/L2J_Mobius_7.0_PreludeOfWar/java/org/l2jmobius/gameserver/instancemanager/games/MonsterRace.java b/L2J_Mobius_7.0_PreludeOfWar/java/org/l2jmobius/gameserver/instancemanager/games/MonsterRace.java new file mode 100644 index 0000000000..a11147c9b0 --- /dev/null +++ b/L2J_Mobius_7.0_PreludeOfWar/java/org/l2jmobius/gameserver/instancemanager/games/MonsterRace.java @@ -0,0 +1,623 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program 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. + * + * This program 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 . + */ +package org.l2jmobius.gameserver.instancemanager.games; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.Config; +import org.l2jmobius.commons.concurrent.ThreadPool; +import org.l2jmobius.commons.database.DatabaseFactory; +import org.l2jmobius.commons.util.Rnd; +import org.l2jmobius.gameserver.data.xml.impl.NpcData; +import org.l2jmobius.gameserver.model.actor.Npc; +import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.model.zone.type.DerbyTrackZone; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.serverpackets.DeleteObject; +import org.l2jmobius.gameserver.network.serverpackets.MonRaceInfo; +import org.l2jmobius.gameserver.network.serverpackets.PlaySound; +import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; +import org.l2jmobius.gameserver.util.Broadcast; + +public class MonsterRace +{ + protected static final Logger LOGGER = Logger.getLogger(MonsterRace.class.getName()); + + public static enum RaceState + { + ACCEPTING_BETS, + WAITING, + STARTING_RACE, + RACE_END + } + + protected static final PlaySound SOUND_1 = new PlaySound(1, "S_Race", 0, 0, 0, 0, 0); + protected static final PlaySound SOUND_2 = new PlaySound("ItemSound2.race_start"); + + protected static final int[][] CODES = + { + { + -1, + 0 + }, + { + 0, + 15322 + }, + { + 13765, + -1 + } + }; + + protected final List _npcTemplates = new ArrayList<>(); // List holding npc templates, shuffled on a new race. + protected final List _history = new ArrayList<>(); // List holding old race records. + protected final Map _betsPerLane = new ConcurrentHashMap<>(); // Map holding all bets for each lane ; values setted to 0 after every race. + protected final List _odds = new ArrayList<>(); // List holding sorted odds per lane ; cleared at new odds calculation. + + protected int _raceNumber = 1; + protected int _finalCountdown = 0; + protected RaceState _state = RaceState.RACE_END; + + protected MonRaceInfo _packet; + + private final Npc[] _monsters = new Npc[8]; + private int[][] _speeds = new int[8][20]; + private final int[] _first = new int[2]; + private final int[] _second = new int[2]; + + protected MonsterRace() + { + if (!Config.ALLOW_RACE) + { + return; + } + + // Feed _history with previous race results. + loadHistory(); + + // Feed _betsPerLane with stored informations on bets. + loadBets(); + + // Feed _npcTemplates, we will only have to shuffle it when needed. + for (int i = 31003; i < 31027; i++) + { + _npcTemplates.add(i); + } + + ThreadPool.scheduleAtFixedRate(new Announcement(), 0, 1000); + } + + public static class HistoryInfo + { + private final int _raceId; + private int _first; + private int _second; + private double _oddRate; + + public HistoryInfo(int raceId, int first, int second, double oddRate) + { + _raceId = raceId; + _first = first; + _second = second; + _oddRate = oddRate; + } + + public int getRaceId() + { + return _raceId; + } + + public int getFirst() + { + return _first; + } + + public int getSecond() + { + return _second; + } + + public double getOddRate() + { + return _oddRate; + } + + public void setFirst(int first) + { + _first = first; + } + + public void setSecond(int second) + { + _second = second; + } + + public void setOddRate(double oddRate) + { + _oddRate = oddRate; + } + } + + private class Announcement implements Runnable + { + public Announcement() + { + } + + @Override + public void run() + { + if (_finalCountdown > 1200) + { + _finalCountdown = 0; + } + + switch (_finalCountdown) + { + case 0: + { + newRace(); + newSpeeds(); + + _state = RaceState.ACCEPTING_BETS; + _packet = new MonRaceInfo(CODES[0][0], CODES[0][1], getMonsters(), getSpeeds()); + + final SystemMessage msg = new SystemMessage(SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, _packet, msg); + 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 + { + final SystemMessage msg = new SystemMessage(SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + break; + } + case 300: // 5 min + { + final SystemMessage msg = new SystemMessage(SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + // final SystemMessage msg2 = new SystemMessage(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S); + // msg2.addInt(10); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + break; + } + case 600: // 10 min + { + final SystemMessage msg = new SystemMessage(SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + // final SystemMessage msg2 = new SystemMessage(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S); + // msg2.addInt(5); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + break; + } + case 840: // 14 min + { + final SystemMessage msg = new SystemMessage(SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + // final SystemMessage msg2 = new SystemMessage(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S); + // msg2.addInt(1); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + break; + } + case 900: // 15 min + { + _state = RaceState.WAITING; + + calculateOdds(); + + final SystemMessage msg = new SystemMessage(SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.TICKETS_SALES_ARE_CLOSED_FOR_MONSTER_RACE_S1_YOU_CAN_SEE_THE_AMOUNT_OF_WIN); + msg2.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + break; + } + case 960: // 16 min + case 1020: // 17 min + { + final int minutes = (_finalCountdown == 960) ? 2 : 1; + final SystemMessage msg = new SystemMessage(SystemMessageId.MONSTER_RACE_S2_WILL_BEGIN_IN_S1_MINUTE_S); + msg.addInt(minutes); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + break; + } + case 1050: // 17 min 30 sec + { + final SystemMessage msg = new SystemMessage(SystemMessageId.MONSTER_RACE_S2_WILL_BEGIN_IN_30_SECONDS); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + break; + } + case 1070: // 17 min 50 sec + { + final SystemMessage msg = new SystemMessage(SystemMessageId.MONSTER_RACE_S1_IS_ABOUT_TO_BEGIN_COUNTDOWN_IN_FIVE_SECONDS); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + 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; + final SystemMessage msg = new SystemMessage(SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SECOND_S); + msg.addInt(seconds); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + break; + } + case 1080: // 18 min + { + _state = RaceState.STARTING_RACE; + _packet = new MonRaceInfo(CODES[1][0], CODES[1][1], getMonsters(), getSpeeds()); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, new SystemMessage(SystemMessageId.THEY_RE_OFF), SOUND_1, SOUND_2, _packet); + break; + } + case 1085: // 18 min 5 sec + { + _packet = new MonRaceInfo(CODES[2][0], CODES[2][1], getMonsters(), getSpeeds()); + + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, _packet); + break; + } + case 1115: // 18 min 35 sec + { + _state = RaceState.RACE_END; + + // Populate history info with data, stores it in database. + final HistoryInfo info = _history.get(_history.size() - 1); + info.setFirst(getFirstPlace()); + info.setSecond(getSecondPlace()); + info.setOddRate(_odds.get(getFirstPlace() - 1)); + + saveHistory(info); + clearBets(); + + final SystemMessage msg = new SystemMessage(SystemMessageId.FIRST_PRIZE_GOES_TO_THE_PLAYER_IN_LANE_S1_SECOND_PRIZE_GOES_TO_THE_PLAYER_IN_LANE_S2); + msg.addInt(getFirstPlace()); + msg.addInt(getSecondPlace()); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.MONSTER_RACE_S1_IS_FINISHED); + msg2.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + _raceNumber++; + break; + } + case 1140: // 19 min + { + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, new DeleteObject(getMonsters()[0]), new DeleteObject(getMonsters()[1]), new DeleteObject(getMonsters()[2]), new DeleteObject(getMonsters()[3]), new DeleteObject(getMonsters()[4]), new DeleteObject(getMonsters()[5]), new DeleteObject(getMonsters()[6]), new DeleteObject(getMonsters()[7])); + break; + } + } + _finalCountdown += 1; + } + } + + public void newRace() + { + // Edit _history. + _history.add(new HistoryInfo(_raceNumber, 0, 0, 0)); + + // Randomize _npcTemplates. + Collections.shuffle(_npcTemplates); + + // Setup 8 new creatures ; pickup the first 8 from _npcTemplates. + for (int i = 0; i < 8; i++) + { + try + { + final NpcTemplate template = NpcData.getInstance().getTemplate(_npcTemplates.get(i)); + _monsters[i] = (Npc) Class.forName("org.l2jmobius.gameserver.model.actor.instance." + template.getType() + "Instance").getConstructors()[0].newInstance(template); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "", e); + } + } + } + + public void newSpeeds() + { + _speeds = new int[8][20]; + int total = 0; + _first[1] = 0; + _second[1] = 0; + + for (int i = 0; i < 8; i++) + { + total = 0; + for (int j = 0; j < 20; j++) + { + if (j == 19) + { + _speeds[i][j] = 100; + } + else + { + _speeds[i][j] = Rnd.get(60) + 65; + } + total += _speeds[i][j]; + } + + if (total >= _first[1]) + { + _second[0] = _first[0]; + _second[1] = _first[1]; + _first[0] = 8 - i; + _first[1] = total; + } + else if (total >= _second[1]) + { + _second[0] = 8 - i; + _second[1] = total; + } + } + } + + /** + * Load past races informations, feeding _history arrayList.
+ * Also sets _raceNumber, based on latest HistoryInfo loaded. + */ + protected void loadHistory() + { + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("SELECT * FROM mdt_history"); + ResultSet rset = statement.executeQuery(); + + while (rset.next()) + { + _history.add(new HistoryInfo(rset.getInt("race_id"), rset.getInt("first"), rset.getInt("second"), rset.getDouble("odd_rate"))); + _raceNumber++; + } + rset.close(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't load history: " + e.getMessage(), e); + } + LOGGER.info("MonsterRace: loaded " + _history.size() + " records, currently on race #" + _raceNumber); + } + + /** + * Save an history record into database. + * @param history The infos to store. + */ + protected void saveHistory(HistoryInfo history) + { + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("INSERT INTO mdt_history (race_id, first, second, odd_rate) VALUES (?,?,?,?)"); + statement.setInt(1, history.getRaceId()); + statement.setInt(2, history.getFirst()); + statement.setInt(3, history.getSecond()); + statement.setDouble(4, history.getOddRate()); + statement.execute(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't save history: " + e.getMessage(), e); + } + } + + /** + * Load current bets per lane ; initialize the map keys. + */ + protected void loadBets() + { + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("SELECT * FROM mdt_bets"); + ResultSet rset = statement.executeQuery(); + + while (rset.next()) + { + setBetOnLane(rset.getInt("lane_id"), rset.getLong("bet"), false); + } + + rset.close(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't load bets: " + e.getMessage(), e); + } + } + + /** + * Save the current lane bet into database. + * @param lane : The lane to affect. + * @param sum : The sum to set. + */ + protected void saveBet(int lane, long sum) + { + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("REPLACE INTO mdt_bets (lane_id, bet) VALUES (?,?)"); + statement.setInt(1, lane); + statement.setLong(2, sum); + statement.execute(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't save bet: " + e.getMessage(), e); + } + } + + /** + * Clear all lanes bets, either on database or Map. + */ + protected void clearBets() + { + for (int key : _betsPerLane.keySet()) + { + _betsPerLane.put(key, 0L); + } + + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("UPDATE mdt_bets SET bet = 0"); + statement.execute(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't clear bets: " + e.getMessage(), e); + } + } + + /** + * Setup lane bet, based on previous value (if any). + * @param lane : The lane to edit. + * @param amount : The amount to add. + * @param saveOnDb : Should it be saved on db or not. + */ + public void setBetOnLane(int lane, long amount, boolean saveOnDb) + { + final long sum = (_betsPerLane.containsKey(lane)) ? _betsPerLane.get(lane) + amount : amount; + + _betsPerLane.put(lane, sum); + + if (saveOnDb) + { + saveBet(lane, sum); + } + } + + /** + * Calculate odds for every lane, based on others lanes. + */ + protected void calculateOdds() + { + // Clear previous List holding old odds. + _odds.clear(); + + // Sort bets lanes per lane. + final Map 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; + } + + // 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)); + } + } + + public Npc[] getMonsters() + { + return _monsters; + } + + public int[][] getSpeeds() + { + return _speeds; + } + + public int getFirstPlace() + { + return _first[0]; + } + + public int getSecondPlace() + { + return _second[0]; + } + + public MonRaceInfo getRacePacket() + { + return _packet; + } + + public RaceState getCurrentRaceState() + { + return _state; + } + + public int getRaceNumber() + { + return _raceNumber; + } + + public List getHistory() + { + return _history; + } + + public List getOdds() + { + return _odds; + } + + public static MonsterRace getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final MonsterRace INSTANCE = new MonsterRace(); + } +} \ No newline at end of file diff --git a/L2J_Mobius_7.0_PreludeOfWar/java/org/l2jmobius/gameserver/model/actor/instance/RaceManagerInstance.java b/L2J_Mobius_7.0_PreludeOfWar/java/org/l2jmobius/gameserver/model/actor/instance/RaceManagerInstance.java index 04cce0cb41..2375543774 100644 --- a/L2J_Mobius_7.0_PreludeOfWar/java/org/l2jmobius/gameserver/model/actor/instance/RaceManagerInstance.java +++ b/L2J_Mobius_7.0_PreludeOfWar/java/org/l2jmobius/gameserver/model/actor/instance/RaceManagerInstance.java @@ -16,69 +16,26 @@ */ package org.l2jmobius.gameserver.model.actor.instance; -import java.util.Collection; -import java.util.concurrent.ConcurrentHashMap; +import java.util.List; +import java.util.Locale; import org.l2jmobius.Config; -import org.l2jmobius.commons.concurrent.ThreadPool; -import org.l2jmobius.gameserver.MonsterRace; -import org.l2jmobius.gameserver.enums.InstanceType; +import org.l2jmobius.commons.util.StringUtil; import org.l2jmobius.gameserver.idfactory.IdFactory; -import org.l2jmobius.gameserver.model.actor.Creature; +import org.l2jmobius.gameserver.instancemanager.games.MonsterRace; +import org.l2jmobius.gameserver.instancemanager.games.MonsterRace.HistoryInfo; +import org.l2jmobius.gameserver.instancemanager.games.MonsterRace.RaceState; import org.l2jmobius.gameserver.model.actor.Npc; import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate; -import org.l2jmobius.gameserver.model.itemcontainer.Inventory; import org.l2jmobius.gameserver.model.items.instance.ItemInstance; import org.l2jmobius.gameserver.network.SystemMessageId; import org.l2jmobius.gameserver.network.serverpackets.ActionFailed; -import org.l2jmobius.gameserver.network.serverpackets.DeleteObject; -import org.l2jmobius.gameserver.network.serverpackets.IClientOutgoingPacket; -import org.l2jmobius.gameserver.network.serverpackets.InventoryUpdate; -import org.l2jmobius.gameserver.network.serverpackets.MonRaceInfo; import org.l2jmobius.gameserver.network.serverpackets.NpcHtmlMessage; -import org.l2jmobius.gameserver.network.serverpackets.PlaySound; import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; -import org.l2jmobius.gameserver.util.Broadcast; public class RaceManagerInstance extends Npc { - public static final int LANES = 8; - public static final int WINDOW_START = 0; - - private static Collection _managers; - protected static int _raceNumber = 4; - - // Time Constants - private static final long SECOND = 1000; - private static final long MINUTE = 60 * SECOND; - - private static int _minutes = 5; - - // States - private static final int ACCEPTING_BETS = 0; - private static final int WAITING = 1; - private static final int STARTING_RACE = 2; - private static final int RACE_END = 3; - private static int _state = RACE_END; - - protected static final int[][] _codes = - { - { - -1, - 0 - }, - { - 0, - 15322 - }, - { - 13765, - -1 - } - }; - private static boolean _notInitialized = true; - protected static MonRaceInfo _packet; - protected static final int[] _cost = + protected static final int TICKET_PRICES[] = { 100, 500, @@ -93,223 +50,167 @@ public class RaceManagerInstance extends Npc public RaceManagerInstance(NpcTemplate template) { super(template); - setInstanceType(InstanceType.RaceManagerInstance); - - if (!Config.ALLOW_RACE) - { - return; - } - - if (_notInitialized) - { - _notInitialized = false; - - _managers = ConcurrentHashMap.newKeySet(); - - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1), 0, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1), 30 * SECOND, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1), MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1), MINUTE + (30 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKET_SALES_FOR_MONSTER_RACE_S1_ARE_CLOSED), 2 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKET_SALES_FOR_MONSTER_RACE_S1_ARE_CLOSED), 3 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKET_SALES_FOR_MONSTER_RACE_S1_ARE_CLOSED), 4 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKET_SALES_FOR_MONSTER_RACE_S1_ARE_CLOSED), 5 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKET_SALES_FOR_MONSTER_RACE_S1_ARE_CLOSED), 6 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKETS_SALES_ARE_CLOSED_FOR_MONSTER_RACE_S1_YOU_CAN_SEE_THE_AMOUNT_OF_WIN), 7 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.MONSTER_RACE_S2_WILL_BEGIN_IN_S1_MINUTE_S), 7 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.MONSTER_RACE_S2_WILL_BEGIN_IN_S1_MINUTE_S), 8 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.MONSTER_RACE_S2_WILL_BEGIN_IN_30_SECONDS), (8 * MINUTE) + (30 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.MONSTER_RACE_S1_IS_ABOUT_TO_BEGIN_COUNTDOWN_IN_FIVE_SECONDS), (8 * MINUTE) + (50 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SECOND_S), (8 * MINUTE) + (55 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SECOND_S), (8 * MINUTE) + (56 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SECOND_S), (8 * MINUTE) + (57 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SECOND_S), (8 * MINUTE) + (58 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SECOND_S), (8 * MINUTE) + (59 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.THEY_RE_OFF), 9 * MINUTE, 10 * MINUTE); - } - _managers.add(this); - } - - @Override - public boolean isAutoAttackable(Creature attacker) - { - if (attacker.isMonster()) - { - return true; - } - - return super.isAutoAttackable(attacker); - } - - class Announcement implements Runnable - { - private final SystemMessageId _type; - - public Announcement(SystemMessageId pType) - { - _type = pType; - } - - @Override - public void run() - { - makeAnnouncement(_type); - } - } - - public void makeAnnouncement(SystemMessageId type) - { - final SystemMessage sm = new SystemMessage(type); - switch (type.getId()) - { - case 816: // SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1 - case 817: // SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1 - { - if (_state != ACCEPTING_BETS) - {// LOGGER.info("Race Initializing"); - _state = ACCEPTING_BETS; - startRace(); - } // else{LOGGER.info("Race open");} - sm.addInt(_raceNumber); - break; - } - case 818: // SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S - case 820: // SystemMessageId.MONSTER_RACE_S2_WILL_BEGIN_IN_S1_MINUTE_S - case 823: // SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SECOND_S - { - sm.addInt(_minutes); - if (type.getId() == 820) - { - sm.addInt(_raceNumber); - } - _minutes--; - break; - } - case 819: // SystemMessageId.TICKETS_SALES_ARE_CLOSED_FOR_MONSTER_RACE_S1_ODDS_ARE_POSTED - { - // LOGGER.info("Sales closed"); - sm.addInt(_raceNumber); - _state = WAITING; - _minutes = 2; - break; - } - case 821: // SystemMessageId.MONSTER_RACE_S1_WILL_BEGIN_IN_30_SECONDS - case 822: // SystemMessageId.MONSTER_RACE_S1_IS_ABOUT_TO_BEGIN_COUNTDOWN_IN_FIVE_SECONDS - case 825: // SystemMessageId.MONSTER_RACE_S1_IS_FINISHED - { - sm.addInt(_raceNumber); - _minutes = 5; - break; - } - case 826: // SystemMessageId.FIRST_PRIZE_GOES_TO_THE_PLAYER_IN_LANE_S1_SECOND_PRIZE_GOES_TO_THE_PLAYER_IN_LANE_S2 - { - // LOGGER.info("Placing"); - _state = RACE_END; - sm.addInt(MonsterRace.getInstance().getFirstPlace()); - sm.addInt(MonsterRace.getInstance().getSecondPlace()); - break; - } - } - // _logn.info("Counter: "+minutes); - // LOGGER.info("State: "+state); - broadcast(sm); - // LOGGER.info("Player's known: "+getKnownPlayers().size()); - - if (type == SystemMessageId.THEY_RE_OFF) - { - // LOGGER.info("Starting race"); - _state = STARTING_RACE; - startRace(); - _minutes = 5; - } - } - - protected void broadcast(IClientOutgoingPacket pkt) - { - for (RaceManagerInstance manager : _managers) - { - if (!manager.isDead()) - { - Broadcast.toKnownPlayers(manager, pkt); - } - } - } - - public void sendMonsterInfo() - { - broadcast(_packet); - } - - private void startRace() - { - final MonsterRace race = MonsterRace.getInstance(); - if (_state == STARTING_RACE) - { - // state++; - broadcast(new PlaySound(1, "S_Race", 0, 0, 0, 0, 0)); - broadcast(new PlaySound(0, "ItemSound2.race_start", 1, 121209259, 12125, 182487, -3559)); - _packet = new MonRaceInfo(_codes[1][0], _codes[1][1], race.getMonsters(), race.getSpeeds()); - sendMonsterInfo(); - - ThreadPool.schedule(new RunRace(), 5000); - } - else - { - // state++; - race.newRace(); - race.newSpeeds(); - _packet = new MonRaceInfo(_codes[0][0], _codes[0][1], race.getMonsters(), race.getSpeeds()); - sendMonsterInfo(); - } } @Override public void onBypassFeedback(PlayerInstance player, String command) { - if (command.startsWith("BuyTicket") && (_state != ACCEPTING_BETS)) - { - if (!Config.ALLOW_RACE) - { - return; - } - player.sendPacket(SystemMessageId.MONSTER_RACE_TICKETS_ARE_NO_LONGER_AVAILABLE); - command = "Chat 0"; - } - if (command.startsWith("ShowOdds") && (_state == ACCEPTING_BETS)) - { - if (!Config.ALLOW_RACE) - { - return; - } - player.sendPacket(SystemMessageId.MONSTER_RACE_PAYOUT_INFORMATION_IS_NOT_AVAILABLE_WHILE_TICKETS_ARE_BEING_SOLD); - command = "Chat 0"; - } - if (command.startsWith("BuyTicket")) { - if (!Config.ALLOW_RACE) + if (!Config.ALLOW_RACE || (MonsterRace.getInstance().getCurrentRaceState() != RaceState.ACCEPTING_BETS)) { + player.sendPacket(SystemMessageId.MONSTER_RACE_TICKETS_ARE_NO_LONGER_AVAILABLE); + super.onBypassFeedback(player, "Chat 0"); return; } + int 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))) { val = 0; } - showBuyTicket(player, val); + + String search, replace; + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + + if (val < 10) + { + html.setFile(player, getHtmlPath(getId(), 2, player)); + for (int i = 0; i < 8; i++) + { + int n = i + 1; + search = "Mob" + n; + html.replace(search, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); + } + search = "No1"; + if (val == 0) + { + html.replace(search, ""); + } + else + { + html.replace(search, val); + player.setRace(0, val); + } + } + else if (val < 20) + { + if (player.getRace(0) == 0) + { + return; + } + + html.setFile(player, getHtmlPath(getId(), 3, player)); + html.replace("0place", player.getRace(0)); + search = "Mob1"; + replace = MonsterRace.getInstance().getMonsters()[player.getRace(0) - 1].getTemplate().getName(); + html.replace(search, replace); + search = "0adena"; + + if (val == 10) + { + html.replace(search, ""); + } + else + { + html.replace(search, TICKET_PRICES[val - 11]); + player.setRace(1, val - 10); + } + } + else if (val == 20) + { + if ((player.getRace(0) == 0) || (player.getRace(1) == 0)) + { + return; + } + + html.setFile(player, getHtmlPath(getId(), 4, player)); + html.replace("0place", player.getRace(0)); + search = "Mob1"; + replace = MonsterRace.getInstance().getMonsters()[player.getRace(0) - 1].getTemplate().getName(); + 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); + } + else + { + if ((player.getRace(0) == 0) || (player.getRace(1) == 0)) + { + return; + } + + int ticket = player.getRace(0); + int priceId = player.getRace(1); + + if (!player.reduceAdena("Race", TICKET_PRICES[priceId - 1], this, true)) + { + return; + } + + player.setRace(0, 0); + player.setRace(1, 0); + + ItemInstance item = new ItemInstance(IdFactory.getInstance().getNextId(), 4443); + item.setCount(1); + item.setEnchantLevel(MonsterRace.getInstance().getRaceNumber()); + item.setCustomType1(ticket); + item.setCustomType2(TICKET_PRICES[priceId - 1] / 100); + + player.addItem("Race", item, player, false); + final SystemMessage msg = new SystemMessage(SystemMessageId.ACQUIRED_S1_S2); + msg.addInt(MonsterRace.getInstance().getRaceNumber()); + msg.addItemName(4443); + player.sendPacket(msg); + + // Refresh lane bet. + MonsterRace.getInstance().setBetOnLane(ticket, TICKET_PRICES[priceId - 1], true); + super.onBypassFeedback(player, "Chat 0"); + return; + } + html.replace("1race", MonsterRace.getInstance().getRaceNumber()); + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); } else if (command.equals("ShowOdds")) { - if (!Config.ALLOW_RACE) + if (!Config.ALLOW_RACE || (MonsterRace.getInstance().getCurrentRaceState() == RaceState.ACCEPTING_BETS)) { + player.sendPacket(SystemMessageId.MONSTER_RACE_PAYOUT_INFORMATION_IS_NOT_AVAILABLE_WHILE_TICKETS_ARE_BEING_SOLD); + super.onBypassFeedback(player, "Chat 0"); return; } - showOdds(player); + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 5, player)); + for (int i = 0; i < 8; i++) + { + final int n = i + 1; + + html.replace("Mob" + n, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); + + // Odd + final double odd = MonsterRace.getInstance().getOdds().get(i); + html.replace("Odd" + n, (odd > 0D) ? String.format(Locale.ENGLISH, "%.1f", odd) : "&$804;"); + } + html.replace("1race", MonsterRace.getInstance().getRaceNumber()); + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); } else if (command.equals("ShowInfo")) { @@ -317,269 +218,160 @@ public class RaceManagerInstance extends Npc { return; } - showMonsterInfo(player); + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 6, player)); + + for (int i = 0; i < 8; i++) + { + int n = i + 1; + String search = "Mob" + n; + html.replace(search, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); + } + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); } - else if (command.equals("calculateWin")) + else if (command.equals("ShowTickets")) { if (!Config.ALLOW_RACE) { + super.onBypassFeedback(player, "Chat 0"); return; } - // displayCalculateWinnings(player); + + // Generate data. + final StringBuilder sb = new StringBuilder(); + + // Retrieve player's tickets. + for (ItemInstance ticket : player.getInventory().getAllItemsByItemId(4443)) + { + // Don't list current race tickets. + if (ticket.getEnchantLevel() == MonsterRace.getInstance().getRaceNumber()) + { + continue; + } + + StringUtil.append(sb, "", "" + ticket.getEnchantLevel(), " Race Number", "" + ticket.getCustomType1(), " Number", "" + (ticket.getCustomType2() * 100), " Adena"); + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 7, player)); + html.replace("%tickets%", sb.toString()); + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); } - else if (command.equals("viewHistory")) + else if (command.startsWith("ShowTicket")) + { + // Retrieve ticket objectId. + final int val = Integer.parseInt(command.substring(11)); + if (!Config.ALLOW_RACE || (val == 0)) + { + super.onBypassFeedback(player, "Chat 0"); + return; + } + + // Retrieve ticket on player's inventory. + final ItemInstance 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; + + // Retrieve HistoryInfo for that race. + final HistoryInfo info = MonsterRace.getInstance().getHistory().get(raceId - 1); + if (info == null) + { + super.onBypassFeedback(player, "Chat 0"); + return; + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 8, player)); + html.replace("%raceId%", raceId); + html.replace("%lane%", lane); + html.replace("%bet%", bet); + html.replace("%firstLane%", info.getFirst()); + html.replace("%odd%", (lane == info.getFirst()) ? String.format(Locale.ENGLISH, "%.2f", info.getOddRate()) : "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 (!Config.ALLOW_RACE || (val == 0)) + { + super.onBypassFeedback(player, "Chat 0"); + return; + } + + // Delete ticket on player's inventory. + final ItemInstance 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; + + // Retrieve HistoryInfo for that race. + final HistoryInfo info = MonsterRace.getInstance().getHistory().get(raceId - 1); + if (info == 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()) ? info.getOddRate() : 0.01)), this, true); + } + + super.onBypassFeedback(player, "Chat 0"); + return; + } + else if (command.equals("ViewHistory")) { if (!Config.ALLOW_RACE) { + super.onBypassFeedback(player, "Chat 0"); return; } - // displayHistory(player); + + // Generate data. + final StringBuilder sb = new StringBuilder(); + + // Use whole history, pickup from 'last element' and stop at 'latest element - 7'. + final List history = MonsterRace.getInstance().getHistory(); + for (int i = history.size() - 1; i >= Math.max(0, history.size() - 7); i--) + { + final HistoryInfo info = history.get(i); + StringUtil.append(sb, "", "" + info.getRaceId(), " th", "" + info.getFirst(), " Lane ", "" + info.getSecond(), " Lane", String.format(Locale.ENGLISH, "%.2f", info.getOddRate()), " Times"); + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 9, player)); + html.replace("%infos%", sb.toString()); + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); } else { - // getKnownList().removeKnownObject(player); super.onBypassFeedback(player, command); } } - - public void showOdds(PlayerInstance player) - { - if (_state == ACCEPTING_BETS) - { - return; - } - final int npcId = getTemplate().getId(); - String filename; - String search; - final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); - filename = getHtmlPath(npcId, 5, player); - html.setFile(player, filename); - for (int i = 0; i < 8; i++) - { - final int n = i + 1; - search = "Mob" + n; - html.replace(search, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); - } - html.replace("1race", String.valueOf(_raceNumber)); - html.replace("%objectId%", String.valueOf(getObjectId())); - player.sendPacket(html); - player.sendPacket(ActionFailed.STATIC_PACKET); - } - - public void showMonsterInfo(PlayerInstance player) - { - final int npcId = getTemplate().getId(); - String filename; - String search; - final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); - filename = getHtmlPath(npcId, 6, player); - html.setFile(player, filename); - for (int i = 0; i < 8; i++) - { - final int n = i + 1; - search = "Mob" + n; - html.replace(search, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); - } - html.replace("%objectId%", String.valueOf(getObjectId())); - player.sendPacket(html); - player.sendPacket(ActionFailed.STATIC_PACKET); - } - - public void showBuyTicket(PlayerInstance player, int value) - { - if (_state != ACCEPTING_BETS) - { - return; - } - final int npcId = getTemplate().getId(); - SystemMessage sm; - String filename; - String search; - String replace; - final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); - if (value < 10) - { - filename = getHtmlPath(npcId, 2, player); - html.setFile(player, filename); - for (int i = 0; i < 8; i++) - { - final int n = i + 1; - search = "Mob" + n; - html.replace(search, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); - } - search = "No1"; - if (value == 0) - { - html.replace(search, ""); - } - else - { - html.replace(search, Integer.toString(value)); - player.setRace(0, value); - } - } - else if (value < 20) - { - if (player.getRace(0) == 0) - { - return; - } - filename = getHtmlPath(npcId, 3, player); - html.setFile(player, filename); - html.replace("0place", Integer.toString(player.getRace(0))); - search = "Mob1"; - replace = MonsterRace.getInstance().getMonsters()[player.getRace(0) - 1].getTemplate().getName(); - html.replace(search, replace); - search = "0adena"; - if (value == 10) - { - html.replace(search, ""); - } - else - { - html.replace(search, Integer.toString(_cost[value - 11])); - player.setRace(1, value - 10); - } - } - else if (value == 20) - { - if ((player.getRace(0) == 0) || (player.getRace(1) == 0)) - { - return; - } - filename = getHtmlPath(npcId, 4, player); - html.setFile(player, filename); - html.replace("0place", Integer.toString(player.getRace(0))); - search = "Mob1"; - replace = MonsterRace.getInstance().getMonsters()[player.getRace(0) - 1].getTemplate().getName(); - html.replace(search, replace); - search = "0adena"; - final int price = _cost[player.getRace(1) - 1]; - html.replace(search, Integer.toString(price)); - search = "0tax"; - final int tax = 0; - html.replace(search, Integer.toString(tax)); - search = "0total"; - final int total = price + tax; - html.replace(search, Integer.toString(total)); - } - else - { - if ((player.getRace(0) == 0) || (player.getRace(1) == 0)) - { - return; - } - final int ticket = player.getRace(0); - final int priceId = player.getRace(1); - if (!player.reduceAdena("Race", _cost[priceId - 1], this, true)) - { - return; - } - player.setRace(0, 0); - player.setRace(1, 0); - sm = new SystemMessage(SystemMessageId.ACQUIRED_S1_S2); - sm.addInt(_raceNumber); - sm.addItemName(4443); - player.sendPacket(sm); - final ItemInstance item = new ItemInstance(IdFactory.getInstance().getNextId(), 4443); - item.setCount(1); - item.setEnchantLevel(_raceNumber); - item.setCustomType1(ticket); - item.setCustomType2(_cost[priceId - 1] / 100); - player.getInventory().addItem("Race", item, player, this); - final InventoryUpdate iu = new InventoryUpdate(); - iu.addItem(item); - final ItemInstance adenaupdate = player.getInventory().getItemByItemId(Inventory.ADENA_ID); - iu.addModifiedItem(adenaupdate); - player.sendInventoryUpdate(iu); - return; - } - html.replace("1race", String.valueOf(_raceNumber)); - html.replace("%objectId%", String.valueOf(getObjectId())); - player.sendPacket(html); - player.sendPacket(ActionFailed.STATIC_PACKET); - } - - public static class Race - { - private final Info[] _info; - - public Race(Info[] pInfo) - { - _info = pInfo; - } - - public Info getLaneInfo(int lane) - { - return _info[lane]; - } - - public class Info - { - private final int _id; - private final int _place; - private final int _odds; - private final int _payout; - - public Info(int pId, int pPlace, int pOdds, int pPayout) - { - _id = pId; - _place = pPlace; - _odds = pOdds; - _payout = pPayout; - } - - public int getId() - { - return _id; - } - - public int getOdds() - { - return _odds; - } - - public int getPayout() - { - return _payout; - } - - public int getPlace() - { - return _place; - } - } - - } - - class RunRace implements Runnable - { - @Override - public void run() - { - _packet = new MonRaceInfo(_codes[2][0], _codes[2][1], MonsterRace.getInstance().getMonsters(), MonsterRace.getInstance().getSpeeds()); - sendMonsterInfo(); - ThreadPool.schedule(new RunEnd(), 30000); - } - } - - class RunEnd implements Runnable - { - @Override - public void run() - { - makeAnnouncement(SystemMessageId.FIRST_PRIZE_GOES_TO_THE_PLAYER_IN_LANE_S1_SECOND_PRIZE_GOES_TO_THE_PLAYER_IN_LANE_S2); - makeAnnouncement(SystemMessageId.MONSTER_RACE_S1_IS_FINISHED); - _raceNumber++; - - DeleteObject obj = null; - for (int i = 0; i < 8; i++) - { - obj = new DeleteObject(MonsterRace.getInstance().getMonsters()[i]); - broadcast(obj); - MonsterRace.getInstance().getMonsters()[i].deleteMe(); - } - } - } - } diff --git a/L2J_Mobius_7.0_PreludeOfWar/java/org/l2jmobius/gameserver/util/Broadcast.java b/L2J_Mobius_7.0_PreludeOfWar/java/org/l2jmobius/gameserver/util/Broadcast.java index 60eb0ff361..8d751afc53 100644 --- a/L2J_Mobius_7.0_PreludeOfWar/java/org/l2jmobius/gameserver/util/Broadcast.java +++ b/L2J_Mobius_7.0_PreludeOfWar/java/org/l2jmobius/gameserver/util/Broadcast.java @@ -20,10 +20,12 @@ import java.util.logging.Level; import java.util.logging.Logger; import org.l2jmobius.gameserver.enums.ChatType; +import org.l2jmobius.gameserver.instancemanager.ZoneManager; import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.actor.Creature; import org.l2jmobius.gameserver.model.actor.Summon; import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance; +import org.l2jmobius.gameserver.model.zone.ZoneType; import org.l2jmobius.gameserver.network.serverpackets.CharInfo; import org.l2jmobius.gameserver.network.serverpackets.CreatureSay; import org.l2jmobius.gameserver.network.serverpackets.ExShowScreenMessage; @@ -191,4 +193,29 @@ public class Broadcast { toAllOnlinePlayers(new ExShowScreenMessage(text, 10000)); } + + /** + * Send a packet to all players in a specific zone type. + * @param ZoneType. + * @param zoneType : The zone type to send packets. + * @param packets : The packets to send. + */ + public static void toAllPlayersInZoneType(Class zoneType, IClientOutgoingPacket... packets) + { + for (ZoneType zone : ZoneManager.getInstance().getAllZones(zoneType)) + { + for (Creature creature : zone.getCharactersInside()) + { + if (creature == null) + { + continue; + } + + for (IClientOutgoingPacket packet : packets) + { + creature.sendPacket(packet); + } + } + } + } } diff --git a/L2J_Mobius_C6_Interlude/dist/db_installer/sql/game/mdt_bets.sql b/L2J_Mobius_C6_Interlude/dist/db_installer/sql/game/mdt_bets.sql new file mode 100644 index 0000000000..8d7d4cb738 --- /dev/null +++ b/L2J_Mobius_C6_Interlude/dist/db_installer/sql/game/mdt_bets.sql @@ -0,0 +1,15 @@ +CREATE TABLE IF NOT EXISTS `mdt_bets` ( + `lane_id` INT(1) DEFAULT 0, + `bet` INT DEFAULT 0, + PRIMARY KEY (`lane_id`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci; + +INSERT INTO `mdt_bets` VALUES +('1','0'), +('2','0'), +('3','0'), +('4','0'), +('5','0'), +('6','0'), +('7','0'), +('8','0'); \ No newline at end of file diff --git a/L2J_Mobius_C6_Interlude/dist/db_installer/sql/game/mdt_history.sql b/L2J_Mobius_C6_Interlude/dist/db_installer/sql/game/mdt_history.sql new file mode 100644 index 0000000000..06978f3377 --- /dev/null +++ b/L2J_Mobius_C6_Interlude/dist/db_installer/sql/game/mdt_history.sql @@ -0,0 +1,7 @@ +CREATE TABLE IF NOT EXISTS `mdt_history` ( + `race_id` MEDIUMINT DEFAULT 0, + `first` INT(1) DEFAULT 0, + `second` INT(1) DEFAULT 0, + `odd_rate` DOUBLE(10,2) DEFAULT 0, + PRIMARY KEY (`race_id`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci; \ No newline at end of file diff --git a/L2J_Mobius_C6_Interlude/dist/game/config/AdminCommands.xml b/L2J_Mobius_C6_Interlude/dist/game/config/AdminCommands.xml index e9c3ba4931..481a0a1e5f 100644 --- a/L2J_Mobius_C6_Interlude/dist/game/config/AdminCommands.xml +++ b/L2J_Mobius_C6_Interlude/dist/game/config/AdminCommands.xml @@ -412,9 +412,6 @@ - - - diff --git a/L2J_Mobius_C6_Interlude/dist/game/data/html/default/30995-1.htm b/L2J_Mobius_C6_Interlude/dist/game/data/html/default/30995-1.htm index cfb5b4d150..60aab8916f 100644 --- a/L2J_Mobius_C6_Interlude/dist/game/data/html/default/30995-1.htm +++ b/L2J_Mobius_C6_Interlude/dist/game/data/html/default/30995-1.htm @@ -4,7 +4,7 @@
In a monster race eight monsters race at the same time. If you guess which monster is expected to finish first and buy that monster's race ticket, based upon the odds to win you will receive an appropriate amount of adena.
Racing Schedule
-Monster races every 20 minutes in real time. +Monster races every 20 minutes in real time.
Method of Race Participation
You can purchase a monster race ticket through a race manager NPC. Only between 18 minutes before the start of a race until 3 minutes before the race begins you can buy a ticket for the applicable race. When you purchase a ticket through a conversation with a race manager, you can find out the names of participating monsters in the race and their current statistics. Five minutes before a race starts, the race manager will stop ticket sales and announce monsters' individual odds rate for the current race.
Types of Race
diff --git a/L2J_Mobius_C6_Interlude/dist/game/data/html/default/30995-5.htm b/L2J_Mobius_C6_Interlude/dist/game/data/html/default/30995-5.htm index b9729e01b0..59a05e14a0 100644 --- a/L2J_Mobius_C6_Interlude/dist/game/data/html/default/30995-5.htm +++ b/L2J_Mobius_C6_Interlude/dist/game/data/html/default/30995-5.htm @@ -15,49 +15,49 @@ No.1 Mob1 -&$804; +Odd1 No.2 Mob2 -&$804; +Odd2 No.3 Mob3 -&$804; +Odd3 No.4 Mob4 -&$804; +Odd4 No.5 Mob5 -&$804; +Odd5 No.6 Mob6 -&$804; +Odd6 No.7 Mob7 -&$804; +Odd7 No.8 Mob8 -&$804; +Odd8 diff --git a/L2J_Mobius_C6_Interlude/dist/game/data/html/default/30995-7.htm b/L2J_Mobius_C6_Interlude/dist/game/data/html/default/30995-7.htm index 0d0755784b..53eb213a52 100644 --- a/L2J_Mobius_C6_Interlude/dist/game/data/html/default/30995-7.htm +++ b/L2J_Mobius_C6_Interlude/dist/game/data/html/default/30995-7.htm @@ -3,22 +3,12 @@ - - + +
Winnings for a Race.LaneBet Amount
Winnings for a Race.LaneBet Amount
- - - - - +%tickets%
1 Race Number1 Number100 Adena
- - - - -
Previous List1 Page Next List
- diff --git a/L2J_Mobius_C6_Interlude/dist/game/data/html/default/30995-8.htm b/L2J_Mobius_C6_Interlude/dist/game/data/html/default/30995-8.htm index ebbcf542b2..5f856a222f 100644 --- a/L2J_Mobius_C6_Interlude/dist/game/data/html/default/30995-8.htm +++ b/L2J_Mobius_C6_Interlude/dist/game/data/html/default/30995-8.htm @@ -4,11 +4,11 @@
-
Race #19090
-Lane 1
-Bet: 100 adena
-1st place: Lane [4]
-Odds:[0.01] +
Race #%raceId%
+Lane: %lane%
+Bet: %bet% Adena
+1st place: Lane [%firstLane%]
+Odds: [%odd%]
@@ -18,8 +18,8 @@ Odds:[0.01] - + - +
\ No newline at end of file diff --git a/L2J_Mobius_C6_Interlude/dist/game/data/html/default/30995-9.htm b/L2J_Mobius_C6_Interlude/dist/game/data/html/default/30995-9.htm index ac1201d6f2..c17ab4849a 100644 --- a/L2J_Mobius_C6_Interlude/dist/game/data/html/default/30995-9.htm +++ b/L2J_Mobius_C6_Interlude/dist/game/data/html/default/30995-9.htm @@ -7,41 +7,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +%infos%
7 th8 Lane 1 Lane0.00 Times
6 th4 Lane 8 Lane0.00 Times
5 th7 Lane 2 Lane0.00 Times
4 th6 Lane 8 Lane0.00 Times
3 th4 Lane 1 Lane0.00 Times
2 th1 Lane 8 Lane0.00 Times
1 th7 Lane 6 Lane0.00 Times
diff --git a/L2J_Mobius_C6_Interlude/dist/game/data/html/default/30995.htm b/L2J_Mobius_C6_Interlude/dist/game/data/html/default/30995.htm index d50c5d8b1d..0e48d2422f 100644 --- a/L2J_Mobius_C6_Interlude/dist/game/data/html/default/30995.htm +++ b/L2J_Mobius_C6_Interlude/dist/game/data/html/default/30995.htm @@ -7,8 +7,8 @@ View Odds View Monster Information Purchase Ticket -Calculate Winnings -View Past Results +Calculate Winnings +View Past Results Exit the monster race track. diff --git a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/GameServer.java b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/GameServer.java index 297a8f1c92..5d06160374 100644 --- a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/GameServer.java +++ b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/GameServer.java @@ -111,8 +111,8 @@ import org.l2jmobius.gameserver.instancemanager.SiegeManager; import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.entity.Announcements; import org.l2jmobius.gameserver.model.entity.Hero; -import org.l2jmobius.gameserver.model.entity.MonsterRace; import org.l2jmobius.gameserver.model.entity.event.Lottery; +import org.l2jmobius.gameserver.model.entity.event.MonsterRace; import org.l2jmobius.gameserver.model.entity.event.PcPoint; import org.l2jmobius.gameserver.model.entity.event.manager.EventManager; import org.l2jmobius.gameserver.model.entity.olympiad.Olympiad; diff --git a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/handler/AdminCommandHandler.java b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/handler/AdminCommandHandler.java index 09454b83ed..6ee0563a4c 100644 --- a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/handler/AdminCommandHandler.java +++ b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/handler/AdminCommandHandler.java @@ -63,7 +63,6 @@ import org.l2jmobius.gameserver.handler.admincommandhandlers.AdminMassControl; import org.l2jmobius.gameserver.handler.admincommandhandlers.AdminMassRecall; import org.l2jmobius.gameserver.handler.admincommandhandlers.AdminMenu; import org.l2jmobius.gameserver.handler.admincommandhandlers.AdminMobGroup; -import org.l2jmobius.gameserver.handler.admincommandhandlers.AdminMonsterRace; import org.l2jmobius.gameserver.handler.admincommandhandlers.AdminNoble; import org.l2jmobius.gameserver.handler.admincommandhandlers.AdminOnline; import org.l2jmobius.gameserver.handler.admincommandhandlers.AdminPForge; @@ -146,7 +145,6 @@ public class AdminCommandHandler registerAdminCommandHandler(new AdminMassRecall()); registerAdminCommandHandler(new AdminMenu()); registerAdminCommandHandler(new AdminMobGroup()); - registerAdminCommandHandler(new AdminMonsterRace()); registerAdminCommandHandler(new AdminNoble()); registerAdminCommandHandler(new AdminOnline()); registerAdminCommandHandler(new AdminPetition()); diff --git a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/handler/admincommandhandlers/AdminMonsterRace.java b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/handler/admincommandhandlers/AdminMonsterRace.java deleted file mode 100644 index 5b1af28152..0000000000 --- a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/handler/admincommandhandlers/AdminMonsterRace.java +++ /dev/null @@ -1,163 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * This program 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. - * - * This program 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 . - */ -package org.l2jmobius.gameserver.handler.admincommandhandlers; - -import org.l2jmobius.commons.concurrent.ThreadPool; -import org.l2jmobius.gameserver.handler.IAdminCommandHandler; -import org.l2jmobius.gameserver.model.Location; -import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance; -import org.l2jmobius.gameserver.model.entity.MonsterRace; -import org.l2jmobius.gameserver.network.SystemMessageId; -import org.l2jmobius.gameserver.network.serverpackets.DeleteObject; -import org.l2jmobius.gameserver.network.serverpackets.MonRaceInfo; -import org.l2jmobius.gameserver.network.serverpackets.PlaySound; -import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; - -/** - * This class handles following admin commands: - invul = turns invulnerability on/off - * @version $Revision: 1.1.6.4 $ $Date: 2007/07/31 10:06:00 $ - */ -public class AdminMonsterRace implements IAdminCommandHandler -{ - private static final String[] ADMIN_COMMANDS = - { - "admin_mons" - }; - - protected static int state = -1; - - @Override - public boolean useAdminCommand(String command, PlayerInstance activeChar) - { - if (command.equalsIgnoreCase("admin_mons")) - { - handleSendPacket(activeChar); - } - - return true; - } - - @Override - public String[] getAdminCommandList() - { - return ADMIN_COMMANDS; - } - - private void handleSendPacket(PlayerInstance activeChar) - { - /* - * -1 0 to initialize the race 0 15322 to start race 13765 -1 in middle of race -1 0 to end the race 8003 to 8027 - */ - final int[][] codes = - { - { - -1, - 0 - }, - { - 0, - 15322 - }, - { - 13765, - -1 - }, - { - -1, - 0 - } - }; - final MonsterRace race = MonsterRace.getInstance(); - - if (state == -1) - { - state++; - race.newRace(); - race.newSpeeds(); - final MonRaceInfo spk = new MonRaceInfo(codes[state][0], codes[state][1], race.getMonsters(), race.getSpeeds()); - activeChar.sendPacket(spk); - activeChar.broadcastPacket(spk); - } - else if (state == 0) - { - state++; - - final SystemMessage sm = new SystemMessage(SystemMessageId.MONSRACE_RACE_START); - sm.addNumber(0); - activeChar.sendPacket(sm); - - final PlaySound sRace = new PlaySound(1, "S_Race"); - activeChar.sendPacket(sRace); - activeChar.broadcastPacket(sRace); - - final PlaySound sRace2 = new PlaySound(0, "ItemSound2.race_start", true, 121209259, new Location(12125, 182487, -3559), 0); - activeChar.sendPacket(sRace2); - activeChar.broadcastPacket(sRace2); - - final MonRaceInfo spk = new MonRaceInfo(codes[state][0], codes[state][1], race.getMonsters(), race.getSpeeds()); - activeChar.sendPacket(spk); - activeChar.broadcastPacket(spk); - - ThreadPool.schedule(new RunRace(codes, activeChar), 5000); - } - } - - class RunRace implements Runnable - { - private final int[][] codes; - private final PlayerInstance activeChar; - - public RunRace(int[][] pCodes, PlayerInstance pActiveChar) - { - codes = pCodes; - activeChar = pActiveChar; - } - - @Override - public void run() - { - final MonRaceInfo spk = new MonRaceInfo(codes[2][0], codes[2][1], MonsterRace.getInstance().getMonsters(), MonsterRace.getInstance().getSpeeds()); - activeChar.sendPacket(spk); - activeChar.broadcastPacket(spk); - ThreadPool.schedule(new RunEnd(activeChar), 30000); - } - } - - class RunEnd implements Runnable - { - private final PlayerInstance activeChar; - - public RunEnd(PlayerInstance pActiveChar) - { - activeChar = pActiveChar; - } - - @Override - public void run() - { - DeleteObject obj = null; - - for (int i = 0; i < 8; i++) - { - obj = new DeleteObject(MonsterRace.getInstance().getMonsters()[i]); - activeChar.sendPacket(obj); - activeChar.broadcastPacket(obj); - } - state = -1; - } - } -} diff --git a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/model/actor/instance/RaceManagerInstance.java b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/model/actor/instance/RaceManagerInstance.java index 6efac6f4a2..af5d6b6bfc 100644 --- a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/model/actor/instance/RaceManagerInstance.java +++ b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/model/actor/instance/RaceManagerInstance.java @@ -1,530 +1,374 @@ -/* - * This file is part of the L2J Mobius project. - * - * This program 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. - * - * This program 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 . - */ -package org.l2jmobius.gameserver.model.actor.instance; - -import java.util.ArrayList; -import java.util.List; - -import org.l2jmobius.commons.concurrent.ThreadPool; -import org.l2jmobius.gameserver.idfactory.IdFactory; -import org.l2jmobius.gameserver.model.Location; -import org.l2jmobius.gameserver.model.actor.knownlist.RaceManagerKnownList; -import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate; -import org.l2jmobius.gameserver.model.entity.MonsterRace; -import org.l2jmobius.gameserver.model.items.instance.ItemInstance; -import org.l2jmobius.gameserver.network.SystemMessageId; -import org.l2jmobius.gameserver.network.serverpackets.ActionFailed; -import org.l2jmobius.gameserver.network.serverpackets.DeleteObject; -import org.l2jmobius.gameserver.network.serverpackets.GameServerPacket; -import org.l2jmobius.gameserver.network.serverpackets.InventoryUpdate; -import org.l2jmobius.gameserver.network.serverpackets.MonRaceInfo; -import org.l2jmobius.gameserver.network.serverpackets.NpcHtmlMessage; -import org.l2jmobius.gameserver.network.serverpackets.PlaySound; -import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; -import org.l2jmobius.gameserver.util.Broadcast; - -public class RaceManagerInstance extends NpcInstance -{ - public static final int LANES = 8; - public static final int WINDOW_START = 0; - - private static List _managers; - protected static int _raceNumber = 4; - - // Time Constants - private static final long SECOND = 1000; - private static final long MINUTE = 60 * SECOND; - - private static int _minutes = 5; - - // States - private static final int ACCEPTING_BETS = 0; - private static final int WAITING = 1; - private static final int STARTING_RACE = 2; - private static final int RACE_END = 3; - private static int _state = RACE_END; - - protected static final int[][] _codes = - { - { - -1, - 0 - }, - { - 0, - 15322 - }, - { - 13765, - -1 - } - }; - private static boolean _notInitialized = true; - protected static MonRaceInfo _packet; - protected static final int[] _cost = - { - 100, - 500, - 1000, - 5000, - 10000, - 20000, - 50000, - 100000 - }; - - public RaceManagerInstance(int objectId, NpcTemplate template) - { - super(objectId, template); - getKnownList(); // init knownlist - if (_notInitialized) - { - _notInitialized = false; - _managers = new ArrayList<>(); - - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.MONSRACE_TICKETS_AVAILABLE_FOR_S1_RACE), 0, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.MONSRACE_TICKETS_NOW_AVAILABLE_FOR_S1_RACE), 30 * SECOND, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.MONSRACE_TICKETS_AVAILABLE_FOR_S1_RACE), MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.MONSRACE_TICKETS_NOW_AVAILABLE_FOR_S1_RACE), MINUTE + (30 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.MONSRACE_TICKETS_STOP_IN_S1_MINUTES), 2 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.MONSRACE_TICKETS_STOP_IN_S1_MINUTES), 3 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.MONSRACE_TICKETS_STOP_IN_S1_MINUTES), 4 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.MONSRACE_TICKETS_STOP_IN_S1_MINUTES), 5 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.MONSRACE_TICKETS_STOP_IN_S1_MINUTES), 6 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.MONSRACE_TICKET_SALES_CLOSED), 7 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.MONSRACE_BEGINS_IN_S1_MINUTES), 7 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.MONSRACE_BEGINS_IN_S1_MINUTES), 8 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.MONSRACE_BEGINS_IN_30_SECONDS), (8 * MINUTE) + (30 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.MONSRACE_COUNTDOWN_IN_FIVE_SECONDS), (8 * MINUTE) + (50 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.MONSRACE_BEGINS_IN_S1_SECONDS), (8 * MINUTE) + (55 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.MONSRACE_BEGINS_IN_S1_SECONDS), (8 * MINUTE) + (56 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.MONSRACE_BEGINS_IN_S1_SECONDS), (8 * MINUTE) + (57 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.MONSRACE_BEGINS_IN_S1_SECONDS), (8 * MINUTE) + (58 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.MONSRACE_BEGINS_IN_S1_SECONDS), (8 * MINUTE) + (59 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.MONSRACE_RACE_START), 9 * MINUTE, 10 * MINUTE); - } - _managers.add(this); - } - - @Override - public RaceManagerKnownList getKnownList() - { - if (!(super.getKnownList() instanceof RaceManagerKnownList)) - { - setKnownList(new RaceManagerKnownList(this)); - } - return (RaceManagerKnownList) super.getKnownList(); - } - - class Announcement implements Runnable - { - private final SystemMessageId _type; - - public Announcement(SystemMessageId pType) - { - _type = pType; - } - - @Override - public void run() - { - makeAnnouncement(_type); - } - } - - public void makeAnnouncement(SystemMessageId type) - { - final SystemMessage sm = new SystemMessage(type); - switch (type.getId()) - { - case 816: // SystemMessageId.MONSRACE_TICKETS_AVAILABLE_FOR_S1_RACE - case 817: // SystemMessageId.MONSRACE_TICKETS_NOW_AVAILABLE_FOR_S1_RACE - { - if (_state != ACCEPTING_BETS) - { - _state = ACCEPTING_BETS; - startRace(); - } - sm.addNumber(_raceNumber); - break; - } - case 818: // SystemMessageId.MONSRACE_TICKETS_STOP_IN_S1_MINUTES - case 820: // SystemMessageId.MONSRACE_BEGINS_IN_S1_MINUTES - case 823: // SystemMessageId.MONSRACE_BEGINS_IN_S1_SECONDS - { - sm.addNumber(_minutes); - sm.addNumber(_raceNumber); - _minutes--; - break; - } - case 819: // SystemMessageId.MONSRACE_TICKET_SALES_CLOSED - { - sm.addNumber(_raceNumber); - _state = WAITING; - _minutes = 2; - break; - } - case 822: // SystemMessageId.MONSRACE_COUNTDOWN_IN_FIVE_SECONDS - case 825: // SystemMessageId.MONSRACE_RACE_END - { - sm.addNumber(_raceNumber); - _minutes = 5; - break; - } - case 826: // SystemMessageId.MONSRACE_FIRST_PLACE_S1_SECOND_S2 - { - _state = RACE_END; - sm.addNumber(MonsterRace.getInstance().getFirstPlace()); - sm.addNumber(MonsterRace.getInstance().getSecondPlace()); - break; - } - } - broadcast(sm); - - if (type == SystemMessageId.MONSRACE_RACE_START) - { - _state = STARTING_RACE; - startRace(); - _minutes = 5; - } - } - - protected void broadcast(GameServerPacket pkt) - { - for (RaceManagerInstance manager : _managers) - { - if (!manager.isDead()) - { - Broadcast.toKnownPlayers(manager, pkt); - } - } - } - - public void sendMonsterInfo() - { - broadcast(_packet); - } - - private void startRace() - { - final MonsterRace race = MonsterRace.getInstance(); - if (_state == STARTING_RACE) - { - broadcast(new PlaySound(1, "S_Race")); - broadcast(new PlaySound(0, "ItemSound2.race_start", true, 121209259, new Location(12125, 182487, -3559), 0)); - _packet = new MonRaceInfo(_codes[1][0], _codes[1][1], race.getMonsters(), race.getSpeeds()); - sendMonsterInfo(); - - ThreadPool.schedule(new RunRace(), 5000); - } - else - { - race.newRace(); - race.newSpeeds(); - _packet = new MonRaceInfo(_codes[0][0], _codes[0][1], race.getMonsters(), race.getSpeeds()); - sendMonsterInfo(); - } - } - - @Override - public void onBypassFeedback(PlayerInstance player, String command) - { - if (command.startsWith("BuyTicket") && (_state != ACCEPTING_BETS)) - { - player.sendPacket(SystemMessageId.MONSRACE_TICKETS_NOT_AVAILABLE); - command = "Chat 0"; - } - if (command.startsWith("ShowOdds") && (_state == ACCEPTING_BETS)) - { - player.sendPacket(SystemMessageId.MONSRACE_NO_PAYOUT_INFO); - command = "Chat 0"; - } - - if (command.startsWith("BuyTicket")) - { - int 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))) - { - val = 0; - } - showBuyTicket(player, val); - } - else if (command.equals("ShowOdds")) - { - showOdds(player); - } - else if (command.equals("ShowInfo")) - { - showMonsterInfo(player); - } - else if (command.equals("calculateWin")) - { - // displayCalculateWinnings(player); - } - else if (command.equals("viewHistory")) - { - // displayHistory(player); - } - else - { - super.onBypassFeedback(player, command); - } - } - - public void showOdds(PlayerInstance player) - { - if (_state == ACCEPTING_BETS) - { - return; - } - final int npcId = getTemplate().getNpcId(); - String filename; - String search; - final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); - filename = getHtmlPath(npcId, 5); - html.setFile(filename); - for (int i = 0; i < 8; i++) - { - final int n = i + 1; - search = "Mob" + n; - html.replace(search, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); - } - html.replace("1race", String.valueOf(_raceNumber)); - html.replace("%objectId%", String.valueOf(getObjectId())); - player.sendPacket(html); - player.sendPacket(ActionFailed.STATIC_PACKET); - } - - public void showMonsterInfo(PlayerInstance player) - { - final int npcId = getTemplate().getNpcId(); - String filename; - String search; - final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); - filename = getHtmlPath(npcId, 6); - html.setFile(filename); - for (int i = 0; i < 8; i++) - { - final int n = i + 1; - search = "Mob" + n; - html.replace(search, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); - } - html.replace("%objectId%", String.valueOf(getObjectId())); - player.sendPacket(html); - player.sendPacket(ActionFailed.STATIC_PACKET); - } - - public void showBuyTicket(PlayerInstance player, int value) - { - if (_state != ACCEPTING_BETS) - { - return; - } - final int npcId = getTemplate().getNpcId(); - SystemMessage sm; - String filename; - String search; - String replace; - final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); - if (value < 10) - { - filename = getHtmlPath(npcId, 2); - html.setFile(filename); - for (int i = 0; i < 8; i++) - { - final int n = i + 1; - search = "Mob" + n; - html.replace(search, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); - } - search = "No1"; - if (value == 0) - { - html.replace(search, ""); - } - else - { - html.replace(search, "" + value); - player.setRace(0, value); - } - } - else if (value < 20) - { - if (player.getRace(0) == 0) - { - return; - } - filename = getHtmlPath(npcId, 3); - html.setFile(filename); - html.replace("0place", "" + player.getRace(0)); - search = "Mob1"; - replace = MonsterRace.getInstance().getMonsters()[player.getRace(0) - 1].getTemplate().getName(); - html.replace(search, replace); - search = "0adena"; - if (value == 10) - { - html.replace(search, ""); - } - else - { - html.replace(search, "" + _cost[value - 11]); - player.setRace(1, value - 10); - } - } - else if (value == 20) - { - if ((player.getRace(0) == 0) || (player.getRace(1) == 0)) - { - return; - } - filename = getHtmlPath(npcId, 4); - html.setFile(filename); - html.replace("0place", "" + player.getRace(0)); - search = "Mob1"; - replace = MonsterRace.getInstance().getMonsters()[player.getRace(0) - 1].getTemplate().getName(); - html.replace(search, replace); - search = "0adena"; - final int price = _cost[player.getRace(1) - 1]; - html.replace(search, "" + price); - search = "0tax"; - final int tax = 0; - html.replace(search, "" + tax); - search = "0total"; - final int total = price + tax; - html.replace(search, "" + total); - } - else - { - if ((player.getRace(0) == 0) || (player.getRace(1) == 0)) - { - return; - } - final int ticket = player.getRace(0); - final int priceId = player.getRace(1); - if (!player.reduceAdena("Race", _cost[priceId - 1], this, true)) - { - return; - } - player.setRace(0, 0); - player.setRace(1, 0); - sm = new SystemMessage(SystemMessageId.ACQUIRED); - sm.addNumber(_raceNumber); - sm.addItemName(4443); - player.sendPacket(sm); - final ItemInstance item = new ItemInstance(IdFactory.getInstance().getNextId(), 4443); - item.setCount(1); - item.setEnchantLevel(_raceNumber); - item.setCustomType1(ticket); - item.setCustomType2(_cost[priceId - 1] / 100); - player.getInventory().addItem("Race", item, player, this); - final InventoryUpdate iu = new InventoryUpdate(); - iu.addItem(item); - final ItemInstance adenaupdate = player.getInventory().getItemByItemId(57); - iu.addModifiedItem(adenaupdate); - player.sendPacket(iu); - return; - } - html.replace("1race", String.valueOf(_raceNumber)); - html.replace("%objectId%", String.valueOf(getObjectId())); - player.sendPacket(html); - player.sendPacket(ActionFailed.STATIC_PACKET); - } - - public class Race - { - private final Info[] _info; - - public Race(Info[] pInfo) - { - _info = pInfo; - } - - public Info getLaneInfo(int lane) - { - return _info[lane]; - } - - public class Info - { - private final int _id; - private final int _place; - private final int _odds; - private final int _payout; - - public Info(int pId, int pPlace, int pOdds, int pPayout) - { - _id = pId; - _place = pPlace; - _odds = pOdds; - _payout = pPayout; - } - - public int getId() - { - return _id; - } - - public int getOdds() - { - return _odds; - } - - public int getPayout() - { - return _payout; - } - - public int getPlace() - { - return _place; - } - } - } - - class RunRace implements Runnable - { - @Override - public void run() - { - _packet = new MonRaceInfo(_codes[2][0], _codes[2][1], MonsterRace.getInstance().getMonsters(), MonsterRace.getInstance().getSpeeds()); - sendMonsterInfo(); - ThreadPool.schedule(new RunEnd(), 30000); - } - } - - class RunEnd implements Runnable - { - @Override - public void run() - { - makeAnnouncement(SystemMessageId.MONSRACE_FIRST_PLACE_S1_SECOND_S2); - makeAnnouncement(SystemMessageId.MONSRACE_RACE_END); - _raceNumber++; - - DeleteObject obj = null; - for (int i = 0; i < 8; i++) - { - obj = new DeleteObject(MonsterRace.getInstance().getMonsters()[i]); - broadcast(obj); - } - } - } -} +/* + * This file is part of the L2J Mobius project. + * + * This program 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. + * + * This program 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 . + */ +package org.l2jmobius.gameserver.model.actor.instance; + +import java.util.List; +import java.util.Locale; + +import org.l2jmobius.Config; +import org.l2jmobius.commons.util.StringUtil; +import org.l2jmobius.gameserver.idfactory.IdFactory; +import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.model.entity.event.MonsterRace; +import org.l2jmobius.gameserver.model.entity.event.MonsterRace.HistoryInfo; +import org.l2jmobius.gameserver.model.entity.event.MonsterRace.RaceState; +import org.l2jmobius.gameserver.model.items.instance.ItemInstance; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.serverpackets.ActionFailed; +import org.l2jmobius.gameserver.network.serverpackets.NpcHtmlMessage; +import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; + +public class RaceManagerInstance extends FolkInstance +{ + protected static final int TICKET_PRICES[] = + { + 100, + 500, + 1000, + 5000, + 10000, + 20000, + 50000, + 100000 + }; + + public RaceManagerInstance(int objectId, NpcTemplate template) + { + super(objectId, template); + } + + @Override + public void onBypassFeedback(PlayerInstance player, String command) + { + if (command.startsWith("BuyTicket")) + { + if (!Config.ALLOW_RACE || (MonsterRace.getInstance().getCurrentRaceState() != RaceState.ACCEPTING_BETS)) + { + player.sendPacket(SystemMessageId.MONSRACE_TICKETS_NOT_AVAILABLE); + super.onBypassFeedback(player, "Chat 0"); + return; + } + + int 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))) + { + val = 0; + } + + int npcId = getTemplate().getNpcId(); + String search, replace; + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + + if (val < 10) + { + html.setFile(getHtmlPath(npcId, 2)); + for (int i = 0; i < 8; i++) + { + int n = i + 1; + search = "Mob" + n; + html.replace(search, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); + } + search = "No1"; + if (val == 0) + { + html.replace(search, ""); + } + else + { + html.replace(search, val); + player.setRace(0, val); + } + } + else if (val < 20) + { + if (player.getRace(0) == 0) + { + return; + } + + html.setFile(getHtmlPath(npcId, 3)); + html.replace("0place", player.getRace(0)); + search = "Mob1"; + replace = MonsterRace.getInstance().getMonsters()[player.getRace(0) - 1].getTemplate().getName(); + html.replace(search, replace); + search = "0adena"; + + if (val == 10) + { + html.replace(search, ""); + } + else + { + html.replace(search, TICKET_PRICES[val - 11]); + player.setRace(1, val - 10); + } + } + else if (val == 20) + { + if ((player.getRace(0) == 0) || (player.getRace(1) == 0)) + { + return; + } + + html.setFile(getHtmlPath(npcId, 4)); + html.replace("0place", player.getRace(0)); + search = "Mob1"; + replace = MonsterRace.getInstance().getMonsters()[player.getRace(0) - 1].getTemplate().getName(); + 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); + } + else + { + if ((player.getRace(0) == 0) || (player.getRace(1) == 0)) + { + return; + } + + int ticket = player.getRace(0); + int priceId = player.getRace(1); + + if (!player.reduceAdena("Race", TICKET_PRICES[priceId - 1], this, true)) + { + return; + } + + player.setRace(0, 0); + player.setRace(1, 0); + + ItemInstance item = new ItemInstance(IdFactory.getInstance().getNextId(), 4443); + item.setCount(1); + item.setEnchantLevel(MonsterRace.getInstance().getRaceNumber()); + item.setCustomType1(ticket); + item.setCustomType2(TICKET_PRICES[priceId - 1] / 100); + + player.addItem("Race", item, player, false); + player.sendPacket(SystemMessage.getSystemMessage(SystemMessageId.ACQUIRED).addNumber(MonsterRace.getInstance().getRaceNumber()).addItemName(4443)); + + // Refresh lane bet. + MonsterRace.getInstance().setBetOnLane(ticket, TICKET_PRICES[priceId - 1], true); + super.onBypassFeedback(player, "Chat 0"); + return; + } + html.replace("1race", MonsterRace.getInstance().getRaceNumber()); + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); + } + else if (command.equals("ShowOdds")) + { + if (!Config.ALLOW_RACE || (MonsterRace.getInstance().getCurrentRaceState() == RaceState.ACCEPTING_BETS)) + { + player.sendPacket(SystemMessageId.MONSRACE_NO_PAYOUT_INFO); + super.onBypassFeedback(player, "Chat 0"); + return; + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(getHtmlPath(getTemplate().getNpcId(), 5)); + for (int i = 0; i < 8; i++) + { + final int n = i + 1; + + html.replace("Mob" + n, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); + + // Odd + final double odd = MonsterRace.getInstance().getOdds().get(i); + html.replace("Odd" + n, (odd > 0D) ? String.format(Locale.ENGLISH, "%.1f", odd) : "&$804;"); + } + html.replace("1race", MonsterRace.getInstance().getRaceNumber()); + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); + } + else if (command.equals("ShowInfo")) + { + if (!Config.ALLOW_RACE) + { + return; + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(getHtmlPath(getTemplate().getNpcId(), 6)); + + for (int i = 0; i < 8; i++) + { + int n = i + 1; + String search = "Mob" + n; + html.replace(search, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); + } + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); + } + else if (command.equals("ShowTickets")) + { + if (!Config.ALLOW_RACE) + { + super.onBypassFeedback(player, "Chat 0"); + return; + } + + // Generate data. + final StringBuilder sb = new StringBuilder(); + + // Retrieve player's tickets. + for (ItemInstance ticket : player.getInventory().getAllItemsByItemId(4443)) + { + // Don't list current race tickets. + if (ticket.getEnchantLevel() == MonsterRace.getInstance().getRaceNumber()) + { + continue; + } + + StringUtil.append(sb, "", ticket.getEnchantLevel(), " Race Number", ticket.getCustomType1(), " Number", ticket.getCustomType2() * 100, " Adena"); + } + + final NpcHtmlMessage 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)); + if (!Config.ALLOW_RACE || (val == 0)) + { + super.onBypassFeedback(player, "Chat 0"); + return; + } + + // Retrieve ticket on player's inventory. + final ItemInstance 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; + + // Retrieve HistoryInfo for that race. + final HistoryInfo info = MonsterRace.getInstance().getHistory().get(raceId - 1); + if (info == null) + { + super.onBypassFeedback(player, "Chat 0"); + return; + } + + final NpcHtmlMessage 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()); + html.replace("%odd%", (lane == info.getFirst()) ? String.format(Locale.ENGLISH, "%.2f", info.getOddRate()) : "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 (!Config.ALLOW_RACE || (val == 0)) + { + super.onBypassFeedback(player, "Chat 0"); + return; + } + + // Delete ticket on player's inventory. + final ItemInstance 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; + + // Retrieve HistoryInfo for that race. + final HistoryInfo info = MonsterRace.getInstance().getHistory().get(raceId - 1); + if (info == 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()) ? info.getOddRate() : 0.01)), this, true); + } + + super.onBypassFeedback(player, "Chat 0"); + return; + } + else if (command.equals("ViewHistory")) + { + if (!Config.ALLOW_RACE) + { + super.onBypassFeedback(player, "Chat 0"); + return; + } + + // Generate data. + final StringBuilder sb = new StringBuilder(); + + // Use whole history, pickup from 'last element' and stop at 'latest element - 7'. + final List history = MonsterRace.getInstance().getHistory(); + for (int i = history.size() - 1; i >= Math.max(0, history.size() - 7); i--) + { + final HistoryInfo info = history.get(i); + StringUtil.append(sb, "", info.getRaceId(), " th", info.getFirst(), " Lane ", info.getSecond(), " Lane", String.format(Locale.ENGLISH, "%.2f", info.getOddRate()), " Times"); + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(getHtmlPath(getTemplate().getNpcId(), 9)); + html.replace("%infos%", sb.toString()); + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); + } + else + { + super.onBypassFeedback(player, command); + } + } +} \ No newline at end of file diff --git a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/model/actor/knownlist/RaceManagerKnownList.java b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/model/actor/knownlist/RaceManagerKnownList.java index 20bc9893a8..4c5c289309 100644 --- a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/model/actor/knownlist/RaceManagerKnownList.java +++ b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/model/actor/knownlist/RaceManagerKnownList.java @@ -19,7 +19,7 @@ package org.l2jmobius.gameserver.model.actor.knownlist; import org.l2jmobius.gameserver.model.WorldObject; import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance; import org.l2jmobius.gameserver.model.actor.instance.RaceManagerInstance; -import org.l2jmobius.gameserver.model.entity.MonsterRace; +import org.l2jmobius.gameserver.model.entity.event.MonsterRace; import org.l2jmobius.gameserver.network.serverpackets.DeleteObject; public class RaceManagerKnownList extends NpcKnownList diff --git a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/model/entity/MonsterRace.java b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/model/entity/MonsterRace.java deleted file mode 100644 index 44556f2a9a..0000000000 --- a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/model/entity/MonsterRace.java +++ /dev/null @@ -1,148 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * This program 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. - * - * This program 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 . - */ -package org.l2jmobius.gameserver.model.entity; - -import java.lang.reflect.Constructor; - -import org.l2jmobius.commons.util.Rnd; -import org.l2jmobius.gameserver.datatables.sql.NpcTable; -import org.l2jmobius.gameserver.idfactory.IdFactory; -import org.l2jmobius.gameserver.model.actor.instance.NpcInstance; -import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate; - -public class MonsterRace -{ - private final NpcInstance[] _monsters; - private int[][] _speeds; - private final int[] _first; - private final int[] _second; - - private MonsterRace() - { - _monsters = new NpcInstance[8]; - _speeds = new int[8][20]; - _first = new int[2]; - _second = new int[2]; - } - - public void newRace() - { - int random = 0; - - for (int i = 0; i < 8; i++) - { - final int id = 31003; - random = Rnd.get(24); - while (true) - { - for (int j = i - 1; j >= 0; j--) - { - if (_monsters[j].getTemplate().getNpcId() == (id + random)) - { - random = Rnd.get(24); - } - } - break; - } - try - { - final NpcTemplate template = NpcTable.getInstance().getTemplate(id + random); - final Constructor constructor = Class.forName("org.l2jmobius.gameserver.model.actor.instance." + template.getType() + "Instance").getConstructors()[0]; - final int objectId = IdFactory.getInstance().getNextId(); - _monsters[i] = (NpcInstance) constructor.newInstance(objectId, template); - } - catch (Exception e) - { - e.printStackTrace(); - } - } - newSpeeds(); - } - - public void newSpeeds() - { - _speeds = new int[8][20]; - int total = 0; - _first[1] = 0; - _second[1] = 0; - for (int i = 0; i < 8; i++) - { - total = 0; - for (int j = 0; j < 20; j++) - { - if (j == 19) - { - _speeds[i][j] = 100; - } - else - { - _speeds[i][j] = Rnd.get(60) + 65; - } - total += _speeds[i][j]; - } - - if (total >= _first[1]) - { - _second[0] = _first[0]; - _second[1] = _first[1]; - _first[0] = 8 - i; - _first[1] = total; - } - else if (total >= _second[1]) - { - _second[0] = 8 - i; - _second[1] = total; - } - } - } - - /** - * @return Returns the monsters. - */ - public NpcInstance[] getMonsters() - { - return _monsters; - } - - /** - * @return Returns the speeds. - */ - public int[][] getSpeeds() - { - return _speeds; - } - - public int getFirstPlace() - { - return _first[0]; - } - - public int getSecondPlace() - { - return _second[0]; - } - - public static MonsterRace getInstance() - { - return SingletonHolder.INSTANCE; - } - - private static class SingletonHolder - { - protected static final MonsterRace INSTANCE = new MonsterRace(); - } -} diff --git a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/model/entity/event/MonsterRace.java b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/model/entity/event/MonsterRace.java new file mode 100644 index 0000000000..e788d783c1 --- /dev/null +++ b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/model/entity/event/MonsterRace.java @@ -0,0 +1,595 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program 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. + * + * This program 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 . + */ +package org.l2jmobius.gameserver.model.entity.event; + +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.Collections; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.Config; +import org.l2jmobius.commons.concurrent.ThreadPool; +import org.l2jmobius.commons.database.DatabaseFactory; +import org.l2jmobius.commons.util.Rnd; +import org.l2jmobius.gameserver.datatables.sql.NpcTable; +import org.l2jmobius.gameserver.idfactory.IdFactory; +import org.l2jmobius.gameserver.model.actor.instance.NpcInstance; +import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.model.zone.type.DerbyTrackZone; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.serverpackets.DeleteObject; +import org.l2jmobius.gameserver.network.serverpackets.MonRaceInfo; +import org.l2jmobius.gameserver.network.serverpackets.PlaySound; +import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; +import org.l2jmobius.gameserver.util.Broadcast; + +public class MonsterRace +{ + protected static final Logger LOGGER = Logger.getLogger(MonsterRace.class.getName()); + + public static enum RaceState + { + ACCEPTING_BETS, + WAITING, + STARTING_RACE, + RACE_END + } + + protected static final PlaySound SOUND_1 = new PlaySound(1, "S_Race"); + protected static final PlaySound SOUND_2 = new PlaySound("ItemSound2.race_start"); + + protected static final int[][] CODES = + { + { + -1, + 0 + }, + { + 0, + 15322 + }, + { + 13765, + -1 + } + }; + + protected final List _npcTemplates = new ArrayList<>(); // List holding npc templates, shuffled on a new race. + protected final List _history = new ArrayList<>(); // List holding old race records. + protected final Map _betsPerLane = new ConcurrentHashMap<>(); // Map holding all bets for each lane ; values setted to 0 after every race. + protected final List _odds = new ArrayList<>(); // List holding sorted odds per lane ; cleared at new odds calculation. + + protected int _raceNumber = 1; + protected int _finalCountdown = 0; + protected RaceState _state = RaceState.RACE_END; + + protected MonRaceInfo _packet; + + private Constructor _constructor; + private final NpcInstance[] _monsters = new NpcInstance[8]; + private int[][] _speeds = new int[8][20]; + private final int[] _first = new int[2]; + private final int[] _second = new int[2]; + + protected MonsterRace() + { + if (!Config.ALLOW_RACE) + { + return; + } + + // Feed _history with previous race results. + loadHistory(); + + // Feed _betsPerLane with stored informations on bets. + loadBets(); + + // Feed _npcTemplates, we will only have to shuffle it when needed. + for (int i = 31003; i < 31027; i++) + { + _npcTemplates.add(i); + } + + ThreadPool.scheduleAtFixedRate(new Announcement(), 0, 1000); + } + + public static class HistoryInfo + { + private final int _raceId; + private int _first; + private int _second; + private double _oddRate; + + public HistoryInfo(int raceId, int first, int second, double oddRate) + { + _raceId = raceId; + _first = first; + _second = second; + _oddRate = oddRate; + } + + public int getRaceId() + { + return _raceId; + } + + public int getFirst() + { + return _first; + } + + public int getSecond() + { + return _second; + } + + public double getOddRate() + { + return _oddRate; + } + + public void setFirst(int first) + { + _first = first; + } + + public void setSecond(int second) + { + _second = second; + } + + public void setOddRate(double oddRate) + { + _oddRate = oddRate; + } + } + + private class Announcement implements Runnable + { + public Announcement() + { + } + + @Override + public void run() + { + if (_finalCountdown > 1200) + { + _finalCountdown = 0; + } + + switch (_finalCountdown) + { + case 0: + { + newRace(); + newSpeeds(); + + _state = RaceState.ACCEPTING_BETS; + _packet = new MonRaceInfo(CODES[0][0], CODES[0][1], getMonsters(), getSpeeds()); + + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, _packet, SystemMessage.getSystemMessage(SystemMessageId.MONSRACE_TICKETS_AVAILABLE_FOR_S1_RACE).addNumber(_raceNumber)); + 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 + { + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, SystemMessage.getSystemMessage(SystemMessageId.MONSRACE_TICKETS_NOW_AVAILABLE_FOR_S1_RACE).addNumber(_raceNumber)); + break; + } + case 300: // 5 min + { + Broadcast.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)); + break; + } + case 600: // 10 min + { + Broadcast.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)); + break; + } + case 840: // 14 min + { + Broadcast.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 + { + _state = RaceState.WAITING; + + calculateOdds(); + + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, SystemMessage.getSystemMessage(SystemMessageId.MONSRACE_TICKETS_NOW_AVAILABLE_FOR_S1_RACE).addNumber(_raceNumber), SystemMessage.getSystemMessage(SystemMessageId.MONSRACE_S1_TICKET_SALES_CLOSED).addNumber(_raceNumber)); + break; + } + case 960: // 16 min + case 1020: // 17 min + { + final int minutes = (_finalCountdown == 960) ? 2 : 1; + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, SystemMessage.getSystemMessage(SystemMessageId.MONSRACE_S2_BEGINS_IN_S1_MINUTES).addNumber(minutes).addNumber(_raceNumber)); + break; + } + case 1050: // 17 min 30 sec + { + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, SystemMessage.getSystemMessage(SystemMessageId.MONSRACE_S1_BEGINS_IN_30_SECONDS)); + break; + } + case 1070: // 17 min 50 sec + { + Broadcast.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; + Broadcast.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], getMonsters(), getSpeeds()); + + Broadcast.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], getMonsters(), getSpeeds()); + + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, _packet); + break; + } + case 1115: // 18 min 35 sec + { + _state = RaceState.RACE_END; + + // Populate history info with data, stores it in database. + final HistoryInfo info = _history.get(_history.size() - 1); + info.setFirst(getFirstPlace()); + info.setSecond(getSecondPlace()); + info.setOddRate(_odds.get(getFirstPlace() - 1)); + + saveHistory(info); + clearBets(); + + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, SystemMessage.getSystemMessage(SystemMessageId.MONSRACE_FIRST_PLACE_S1_SECOND_S2).addNumber(getFirstPlace()).addNumber(getSecondPlace()), SystemMessage.getSystemMessage(SystemMessageId.MONSRACE_S1_RACE_END).addNumber(_raceNumber)); + _raceNumber++; + break; + } + case 1140: // 19 min + { + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, new DeleteObject(getMonsters()[0]), new DeleteObject(getMonsters()[1]), new DeleteObject(getMonsters()[2]), new DeleteObject(getMonsters()[3]), new DeleteObject(getMonsters()[4]), new DeleteObject(getMonsters()[5]), new DeleteObject(getMonsters()[6]), new DeleteObject(getMonsters()[7])); + break; + } + } + _finalCountdown += 1; + } + } + + public void newRace() + { + // Edit _history. + _history.add(new HistoryInfo(_raceNumber, 0, 0, 0)); + + // Randomize _npcTemplates. + Collections.shuffle(_npcTemplates); + + // Setup 8 new creatures ; pickup the first 8 from _npcTemplates. + for (int i = 0; i < 8; i++) + { + try + { + final NpcTemplate template = NpcTable.getInstance().getTemplate(_npcTemplates.get(i)); + _constructor = Class.forName("org.l2jmobius.gameserver.model.actor.instance." + template.getType() + "Instance").getConstructors()[0]; + final int objectId = IdFactory.getInstance().getNextId(); + _monsters[i] = (NpcInstance) _constructor.newInstance(objectId, template); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "", e); + } + } + } + + public void newSpeeds() + { + _speeds = new int[8][20]; + int total = 0; + _first[1] = 0; + _second[1] = 0; + + for (int i = 0; i < 8; i++) + { + total = 0; + for (int j = 0; j < 20; j++) + { + if (j == 19) + { + _speeds[i][j] = 100; + } + else + { + _speeds[i][j] = Rnd.get(60) + 65; + } + total += _speeds[i][j]; + } + + if (total >= _first[1]) + { + _second[0] = _first[0]; + _second[1] = _first[1]; + _first[0] = 8 - i; + _first[1] = total; + } + else if (total >= _second[1]) + { + _second[0] = 8 - i; + _second[1] = total; + } + } + } + + /** + * Load past races informations, feeding _history arrayList.
+ * Also sets _raceNumber, based on latest HistoryInfo loaded. + */ + protected void loadHistory() + { + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("SELECT * FROM mdt_history"); + ResultSet rset = statement.executeQuery(); + + while (rset.next()) + { + _history.add(new HistoryInfo(rset.getInt("race_id"), rset.getInt("first"), rset.getInt("second"), rset.getDouble("odd_rate"))); + _raceNumber++; + } + rset.close(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't load history: " + e.getMessage(), e); + } + LOGGER.info("MonsterRace: loaded " + _history.size() + " records, currently on race #" + _raceNumber); + } + + /** + * Save an history record into database. + * @param history The infos to store. + */ + protected void saveHistory(HistoryInfo history) + { + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("INSERT INTO mdt_history (race_id, first, second, odd_rate) VALUES (?,?,?,?)"); + statement.setInt(1, history.getRaceId()); + statement.setInt(2, history.getFirst()); + statement.setInt(3, history.getSecond()); + statement.setDouble(4, history.getOddRate()); + statement.execute(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't save history: " + e.getMessage(), e); + } + } + + /** + * Load current bets per lane ; initialize the map keys. + */ + protected void loadBets() + { + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("SELECT * FROM mdt_bets"); + ResultSet rset = statement.executeQuery(); + + while (rset.next()) + { + setBetOnLane(rset.getInt("lane_id"), rset.getLong("bet"), false); + } + + rset.close(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't load bets: " + e.getMessage(), e); + } + } + + /** + * Save the current lane bet into database. + * @param lane : The lane to affect. + * @param sum : The sum to set. + */ + protected void saveBet(int lane, long sum) + { + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("REPLACE INTO mdt_bets (lane_id, bet) VALUES (?,?)"); + statement.setInt(1, lane); + statement.setLong(2, sum); + statement.execute(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't save bet: " + e.getMessage(), e); + } + } + + /** + * Clear all lanes bets, either on database or Map. + */ + protected void clearBets() + { + for (int key : _betsPerLane.keySet()) + { + _betsPerLane.put(key, 0L); + } + + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("UPDATE mdt_bets SET bet = 0"); + statement.execute(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't clear bets: " + e.getMessage(), e); + } + } + + /** + * Setup lane bet, based on previous value (if any). + * @param lane : The lane to edit. + * @param amount : The amount to add. + * @param saveOnDb : Should it be saved on db or not. + */ + public void setBetOnLane(int lane, long amount, boolean saveOnDb) + { + final long sum = (_betsPerLane.containsKey(lane)) ? _betsPerLane.get(lane) + amount : amount; + + _betsPerLane.put(lane, sum); + + if (saveOnDb) + { + saveBet(lane, sum); + } + } + + /** + * Calculate odds for every lane, based on others lanes. + */ + protected void calculateOdds() + { + // Clear previous List holding old odds. + _odds.clear(); + + // Sort bets lanes per lane. + final Map 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; + } + + // 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)); + } + } + + public NpcInstance[] getMonsters() + { + return _monsters; + } + + public int[][] getSpeeds() + { + return _speeds; + } + + public int getFirstPlace() + { + return _first[0]; + } + + public int getSecondPlace() + { + return _second[0]; + } + + public MonRaceInfo getRacePacket() + { + return _packet; + } + + public RaceState getCurrentRaceState() + { + return _state; + } + + public int getRaceNumber() + { + return _raceNumber; + } + + public List getHistory() + { + return _history; + } + + public List getOdds() + { + return _odds; + } + + public static MonsterRace getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final MonsterRace INSTANCE = new MonsterRace(); + } +} \ No newline at end of file diff --git a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/network/SystemMessageId.java b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/network/SystemMessageId.java index f898732312..50520e41f5 100644 --- a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/network/SystemMessageId.java +++ b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/network/SystemMessageId.java @@ -2177,25 +2177,25 @@ public enum SystemMessageId * ID: 819
* Message: Tickets sales are closed for Monster Race $s1. Odds are posted. */ - MONSRACE_TICKET_SALES_CLOSED(819), + MONSRACE_S1_TICKET_SALES_CLOSED(819), /** * ID: 820
* Message: Monster Race $s2 will begin in $s1 minute(s)! */ - MONSRACE_BEGINS_IN_S1_MINUTES(820), + MONSRACE_S2_BEGINS_IN_S1_MINUTES(820), /** * ID: 821
* Message: Monster Race $s1 will begin in 30 seconds! */ - MONSRACE_BEGINS_IN_30_SECONDS(821), + MONSRACE_S1_BEGINS_IN_30_SECONDS(821), /** * ID: 822
* Message: Monster Race $s1 is about to begin! Countdown in five seconds! */ - MONSRACE_COUNTDOWN_IN_FIVE_SECONDS(822), + MONSRACE_S1_COUNTDOWN_IN_FIVE_SECONDS(822), /** * ID: 823
@@ -2213,7 +2213,7 @@ public enum SystemMessageId * ID: 825
* Message: Monster Race $s1 is finished! */ - MONSRACE_RACE_END(825), + MONSRACE_S1_RACE_END(825), /** * ID: 826
diff --git a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/util/Broadcast.java b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/util/Broadcast.java index edc3a9ad13..bd07222f5a 100644 --- a/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/util/Broadcast.java +++ b/L2J_Mobius_C6_Interlude/java/org/l2jmobius/gameserver/util/Broadcast.java @@ -16,9 +16,11 @@ */ package org.l2jmobius.gameserver.util; +import org.l2jmobius.gameserver.datatables.xml.ZoneData; import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.actor.Creature; import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance; +import org.l2jmobius.gameserver.model.zone.ZoneType; import org.l2jmobius.gameserver.network.serverpackets.CharInfo; import org.l2jmobius.gameserver.network.serverpackets.GameServerPacket; import org.l2jmobius.gameserver.network.serverpackets.RelationChanged; @@ -180,4 +182,29 @@ public class Broadcast onlinePlayer.sendPacket(packet); } } + + /** + * Send a packet to all players in a specific zone type. + * @param ZoneType. + * @param zoneType : The zone type to send packets. + * @param packets : The packets to send. + */ + public static void toAllPlayersInZoneType(Class zoneType, GameServerPacket... packets) + { + for (ZoneType zone : ZoneData.getInstance().getAllZones(zoneType)) + { + for (Creature creature : zone.getCharactersInside().values()) + { + if (creature == null) + { + continue; + } + + for (GameServerPacket packet : packets) + { + creature.sendPacket(packet); + } + } + } + } } diff --git a/L2J_Mobius_CT_2.4_Epilogue/dist/db_installer/sql/game/mdt_bets.sql b/L2J_Mobius_CT_2.4_Epilogue/dist/db_installer/sql/game/mdt_bets.sql new file mode 100644 index 0000000000..8d7d4cb738 --- /dev/null +++ b/L2J_Mobius_CT_2.4_Epilogue/dist/db_installer/sql/game/mdt_bets.sql @@ -0,0 +1,15 @@ +CREATE TABLE IF NOT EXISTS `mdt_bets` ( + `lane_id` INT(1) DEFAULT 0, + `bet` INT DEFAULT 0, + PRIMARY KEY (`lane_id`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci; + +INSERT INTO `mdt_bets` VALUES +('1','0'), +('2','0'), +('3','0'), +('4','0'), +('5','0'), +('6','0'), +('7','0'), +('8','0'); \ No newline at end of file diff --git a/L2J_Mobius_CT_2.4_Epilogue/dist/db_installer/sql/game/mdt_history.sql b/L2J_Mobius_CT_2.4_Epilogue/dist/db_installer/sql/game/mdt_history.sql new file mode 100644 index 0000000000..06978f3377 --- /dev/null +++ b/L2J_Mobius_CT_2.4_Epilogue/dist/db_installer/sql/game/mdt_history.sql @@ -0,0 +1,7 @@ +CREATE TABLE IF NOT EXISTS `mdt_history` ( + `race_id` MEDIUMINT DEFAULT 0, + `first` INT(1) DEFAULT 0, + `second` INT(1) DEFAULT 0, + `odd_rate` DOUBLE(10,2) DEFAULT 0, + PRIMARY KEY (`race_id`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci; \ No newline at end of file diff --git a/L2J_Mobius_CT_2.4_Epilogue/dist/game/config/AdminCommands.xml b/L2J_Mobius_CT_2.4_Epilogue/dist/game/config/AdminCommands.xml index 266edd3190..62cab6489a 100644 --- a/L2J_Mobius_CT_2.4_Epilogue/dist/game/config/AdminCommands.xml +++ b/L2J_Mobius_CT_2.4_Epilogue/dist/game/config/AdminCommands.xml @@ -403,9 +403,6 @@ - - - diff --git a/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/html/default/30995-1.htm b/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/html/default/30995-1.htm index 01783586be..60aab8916f 100644 --- a/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/html/default/30995-1.htm +++ b/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/html/default/30995-1.htm @@ -1,20 +1,22 @@ -
-Go Back
-In a monster race eight monsters race at the same time. If you guess which monster is expected to finish first and buy that monster's race ticket, based upon the odds to win you will receive an appropriate amount of adena.
-Racing Schedule
-Monster races every 20 minutes in real time. -Method of Race Participation
-You can purchase a monster race ticket through a race manager NPC. Only between 18 minutes before the start of a race until 3 minutes before the race begins you can buy a ticket for the applicable race. When you purchase a ticket through a conversation with a race manager, you can find out the names of participating monsters in the race and their current statistics. Five minutes before a race starts, the race manager will stop ticket sales and announce monsters' individual odds rate for the current race.
-Types of Race
-There are two types of race betting. The Win type betting means that you back a monster that will win the first prize. Place type of betting means that you will have to guess the 1st and 2nd winning monsters, regardless of who finishes first or second.
-Win -* Among the entire betting amount for Win betting, after subtracting the race track's own share of profit, the amount left will be your total winnings. Three minutes before the race each monster's expected odds are announced. Each Win ticket's dividend amount is calculated by multiplying the odds by the price of a ticket. The odds rate is never less than 1. -* The prize money does not get transferred to the next race.
-Place -* Place betting is a way of betting when you correctly guess the two monsters who will finish first and second regardless of their order. The method of calculating dividend amount is the same as Win betting.
-* Currently place betting is not offered.
-End of Race
-When a race is over, the race manager will shout out the results of the race. Through a conversation with a race manager NPC, you can trade your ticket for adena. By looking up on the Winning Information Data, you can find out the types of tickets and quantity of the winnings in the race you betted on and your win status and prize amount you won. If you won, you can receive your prize money thorough a race manager NPC. If you did not win, a race manager will buy back your ticket by paying you a very small amount of money.
-If a server goes down while tickets are being sold, or while a race is in progress, the race will be cancelled. You can receive a full refund for your ticket for a cancelled race through a race manager.
-Go Back + +
+Go Back +
+In a monster race eight monsters race at the same time. If you guess which monster is expected to finish first and buy that monster's race ticket, based upon the odds to win you will receive an appropriate amount of adena.
+Racing Schedule
+Monster races every 20 minutes in real time.
+Method of Race Participation
+You can purchase a monster race ticket through a race manager NPC. Only between 18 minutes before the start of a race until 3 minutes before the race begins you can buy a ticket for the applicable race. When you purchase a ticket through a conversation with a race manager, you can find out the names of participating monsters in the race and their current statistics. Five minutes before a race starts, the race manager will stop ticket sales and announce monsters' individual odds rate for the current race.
+Types of Race
+There are two types of race betting. The Win type betting means that you back a monster that will win the first prize. Place type of betting means that you will have to guess the 1st and 2nd winning monsters, regardless of who finishes first or second.
+Win +* Among the entire betting amount for Win betting, after subtracting the race track's own share of profit, the amount left will be your total winnings. Three minutes before the race each monster's expected odds are announced. Each Win ticket's dividend amount is calculated by multiplying the odds by the price of a ticket. The odds rate is never less than 1. +* The prize money does not get transferred to the next race.
+Place +* Place betting is a way of betting when you correctly guess the two monsters who will finish first and second regardless of their order. The method of calculating dividend amount is the same as Win betting.
+* Currently place betting is not offered.
+End of Race
+When a race is over, the race manager will shout out the results of the race. Through a conversation with a race manager NPC, you can trade your ticket for adena. By looking up on the Winning Information Data, you can find out the types of tickets and quantity of the winnings in the race you betted on and your win status and prize amount you won. If you won, you can receive your prize money thorough a race manager NPC. If you did not win, a race manager will buy back your ticket by paying you a very small amount of money.
+If a server goes down while tickets are being sold, or while a race is in progress, the race will be cancelled. You can receive a full refund for your ticket for a cancelled race through a race manager.
+Go Back \ No newline at end of file diff --git a/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/html/default/30995-2.htm b/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/html/default/30995-2.htm index 15dc426652..f7d606a36d 100644 --- a/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/html/default/30995-2.htm +++ b/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/html/default/30995-2.htm @@ -1,94 +1,94 @@ - -
Monster Race Betting To Win (Race # 1race) - - -
- - - - - - - - -
Lane SelectionName of RacerRatingOdds
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1Mob1&$745;13%
2Mob2&$745;6%
3Mob3&$747;4%
4Mob4&$745;12%
5Mob5&$745;14%
6Mob6&$745;13%
7Mob7&$745;13%
8Mob8&$746;14%
- - - - - - - - - - -
Select Lane : No1
- + +
Monster Race Betting To Win (Race # 1race) + + +
+ + + + + + + + +
Lane SelectionName of RacerRatingOdds
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
1Mob1&$745;13%
2Mob2&$745;6%
3Mob3&$747;4%
4Mob4&$745;12%
5Mob5&$745;14%
6Mob6&$745;13%
7Mob7&$745;13%
8Mob8&$746;14%
+ + + + + + + + + + +
Select Lane : No1
+ \ No newline at end of file diff --git a/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/html/default/30995-3.htm b/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/html/default/30995-3.htm index 861fc97450..7ae65a37a4 100644 --- a/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/html/default/30995-3.htm +++ b/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/html/default/30995-3.htm @@ -1,42 +1,48 @@ - -
Monster Race Betting To Win (Race # 1race) - - -
- - - - - - - -
Lane SelectionRacer's Name RatingOdds
- - - - - - - - -
0placeMob1&$745;13%
- - - -
Select Bet Amount -100 Adena , 500 Adena -1,000 Adena , 5,000 Adena -10,000 Adena , 20,000 Adena -50,000 Adena , 100,000 Adena -
- - - - - - - - - -
Purchase Price : 0adena
+ +
Monster Race Betting To Win (Race # 1race) + + +
+ + + + + + + + +
Lane SelectionRacer's Name RatingOdds
+ + + + + + + + + +
0placeMob1&$745;13%
+ + + + +
Select Bet Amount +100 Adena , 500 Adena +1,000 Adena , 5,000 Adena +10,000 Adena , 20,000 Adena +50,000 Adena , 100,000 Adena +
+ + + + + + + + + + + +
Purchase Price : 0adena
+ \ No newline at end of file diff --git a/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/html/default/30995-4.htm b/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/html/default/30995-4.htm index 0493fee9b6..30c508b9cb 100644 --- a/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/html/default/30995-4.htm +++ b/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/html/default/30995-4.htm @@ -1,52 +1,56 @@ - -
Monster Race Single Ticket Purchase (Race # 1race) - - -
- - - - - - - -
Lane SelectionRacer's NameRatingOdds
- - - - - - - - -
0placeMob1&$745;13%
- - - - - - - - - - - - - - - - - -
Bet Amount
Bet Amount 0adena Adena
Tax 0tax Adena
Total 0total Adena

-
If the information is correct, please click Confirm.

- - - - - - - - - -
+ +
Monster Race Single Ticket Purchase (Race # 1race) + + +
+ + + + + + + +
Lane SelectionRacer's NameRatingOdds
+ + + + + + + + + +
0placeMob1&$745;13%
+ + + + + + + + + + + + + + + + + + +
Bet Amount
Bet Amount 0adena Adena
Tax 0tax Adena
Total 0total Adena
+
+
If the information is correct, please click Confirm.

+ + + + + + + + + + +
\ No newline at end of file diff --git a/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/html/default/30995-5.htm b/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/html/default/30995-5.htm index f050b6a964..59a05e14a0 100644 --- a/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/html/default/30995-5.htm +++ b/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/html/default/30995-5.htm @@ -1,74 +1,74 @@ - -
Monster Race Betting To Win View Odds (Race # 1race ) - - -
- - - - - - -
LaneRacer's NameOdds
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
No.1Mob1&$804;
No.2Mob2&$804;
No.3Mob3&$804;
No.4Mob4&$804;
No.5Mob5&$804;
No.6Mob6&$804;
No.7Mob7&$804;
No.8Mob8&$804;
- - - - - - - - - - -
+ +
Monster Race Betting To Win View Odds (Race # 1race ) + + +
+ + + + + + +
LaneRacer's NameOdds
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
No.1Mob1Odd1
No.2Mob2Odd2
No.3Mob3Odd3
No.4Mob4Odd4
No.5Mob5Odd5
No.6Mob6Odd6
No.7Mob7Odd7
No.8Mob8Odd8
+ + + + + + + + + + +
\ No newline at end of file diff --git a/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/html/default/30995-6.htm b/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/html/default/30995-6.htm index dfb8449693..0d845cb665 100644 --- a/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/html/default/30995-6.htm +++ b/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/html/default/30995-6.htm @@ -1,80 +1,80 @@ - -
Participating Monsters - - -
- - - - - - - -
LaneRacer's NameRatingOdds
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1Mob1&$747;12%
2Mob2&$745;6%
3Mob3&$746;3%
4Mob4&$747;7%
5Mob5&$747;4%
6Mob6&$747;14%
7Mob7&$747;26%
8Mob8&$745;5%
- - - - - - - - + +
Participating Monsters + + +
+
+ + + + + + +
LaneRacer's NameRatingOdds
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
1Mob1&$747;12%
2Mob2&$745;6%
3Mob3&$746;3%
4Mob4&$747;7%
5Mob5&$747;4%
6Mob6&$747;14%
7Mob7&$747;26%
8Mob8&$745;5%
+ + + + + + + +
\ No newline at end of file diff --git a/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/html/default/30995-7.htm b/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/html/default/30995-7.htm index 31f36f0a72..53eb213a52 100644 --- a/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/html/default/30995-7.htm +++ b/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/html/default/30995-7.htm @@ -1,29 +1,19 @@ - -
Confirm Monster Race Bet / Calculate Winnings
- - - - - -
Winnings for a Race.LaneBet Amount
- - - - - - -
1 Race Number1 Number100 Adena
- - - - -
Previous List1 Page Next List
- - - - - - - - + +
Confirm Monster Race Bet / Calculate Winnings
+ + +
+ + +
Winnings for a Race.LaneBet Amount
+ +%tickets% +
+ + + + + + +
\ No newline at end of file diff --git a/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/html/default/30995-8.htm b/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/html/default/30995-8.htm index d198a9d1ac..5f856a222f 100644 --- a/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/html/default/30995-8.htm +++ b/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/html/default/30995-8.htm @@ -1,25 +1,25 @@ - -
Confirm Monster Race Bet / Calculate Winnings
- - - - - - -
Race #19090
-Lane 1
-Bet: 100 adena
-1st place: Lane [4]
-Odds:[0.01] -
- - - - - - - - - -
+ +
Confirm Monster Race Bet / Calculate Winnings
+ + + + + + +
Race #%raceId%
+Lane: %lane%
+Bet: %bet% Adena
+1st place: Lane [%firstLane%]
+Odds: [%odd%] +
+ + + + + + + + + +
\ No newline at end of file diff --git a/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/html/default/30995-9.htm b/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/html/default/30995-9.htm index 6d8775492d..c17ab4849a 100644 --- a/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/html/default/30995-9.htm +++ b/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/html/default/30995-9.htm @@ -1,56 +1,22 @@ - -
View Monster Race Results
- - - - -
Race Number1st Finish2nd FinishOdds Rate for Win Bet
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
7 th8 Lane 1 Lane0.00 Times
6 th4 Lane 8 Lane0.00 Times
5 th7 Lane 2 Lane0.00 Times
4 th6 Lane 8 Lane0.00 Times
3 th4 Lane 1 Lane0.00 Times
2 th1 Lane 8 Lane0.00 Times
1 th7 Lane 6 Lane0.00 Times
- - - - - - - - - -
+ +
View Monster Race Results
+ + + + +
Race Number1st Finish2nd FinishOdds Rate for Win Bet
+ + +%infos% +
+ + + + + + + + + +
\ No newline at end of file diff --git a/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/html/default/30995.htm b/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/html/default/30995.htm index 6ed69be1b8..1117d6ecde 100644 --- a/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/html/default/30995.htm +++ b/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/html/default/30995.htm @@ -1,17 +1,18 @@ - -
Monster Race Manager
- - - - - - - - - - -
Monster Race Help
View Odds
View Monster Information
Purchase Ticket
Calculate Winnings
View Past Results
Exit the monster race track.
- - - + +
Monster Race Manager
+ + + + + + + + + + +
Monster Race Help
View Odds
View Monster Information
Purchase Ticket
Calculate Winnings
View Past Results
Exit the monster race track.
+ + + + \ No newline at end of file diff --git a/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/scripts/handlers/MasterHandler.java b/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/scripts/handlers/MasterHandler.java index e6620779f6..ea01008c6d 100644 --- a/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/scripts/handlers/MasterHandler.java +++ b/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/scripts/handlers/MasterHandler.java @@ -101,7 +101,6 @@ import handlers.admincommandhandlers.AdminManor; import handlers.admincommandhandlers.AdminMenu; import handlers.admincommandhandlers.AdminMessages; import handlers.admincommandhandlers.AdminMobGroup; -import handlers.admincommandhandlers.AdminMonsterRace; import handlers.admincommandhandlers.AdminOnline; import handlers.admincommandhandlers.AdminPForge; import handlers.admincommandhandlers.AdminPathNode; @@ -400,7 +399,6 @@ public class MasterHandler AdminMenu.class, AdminMessages.class, AdminMobGroup.class, - AdminMonsterRace.class, AdminOnline.class, AdminPathNode.class, AdminPetition.class, diff --git a/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/scripts/handlers/admincommandhandlers/AdminMonsterRace.java b/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/scripts/handlers/admincommandhandlers/AdminMonsterRace.java deleted file mode 100644 index bf504a5032..0000000000 --- a/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/scripts/handlers/admincommandhandlers/AdminMonsterRace.java +++ /dev/null @@ -1,168 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * This program 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. - * - * This program 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 . - */ -package handlers.admincommandhandlers; - -import org.l2jmobius.commons.concurrent.ThreadPool; -import org.l2jmobius.gameserver.MonsterRace; -import org.l2jmobius.gameserver.handler.IAdminCommandHandler; -import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance; -import org.l2jmobius.gameserver.network.SystemMessageId; -import org.l2jmobius.gameserver.network.serverpackets.DeleteObject; -import org.l2jmobius.gameserver.network.serverpackets.MonRaceInfo; -import org.l2jmobius.gameserver.network.serverpackets.PlaySound; -import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; - -/** - * This class handles following admin commands: - invul = turns invulnerability on/off - * @version $Revision: 1.1.6.4 $ $Date: 2007/07/31 10:06:00 $ - */ -public class AdminMonsterRace implements IAdminCommandHandler -{ - // private static final Logger LOGGER = Logger.getLogger(AdminMonsterRace.class.getName()); - - private static final String[] ADMIN_COMMANDS = - { - "admin_mons" - }; - - protected static int state = -1; - - @Override - public boolean useAdminCommand(String command, PlayerInstance activeChar) - { - if (command.equalsIgnoreCase("admin_mons")) - { - handleSendPacket(activeChar); - } - return true; - } - - @Override - public String[] getAdminCommandList() - { - return ADMIN_COMMANDS; - } - - private void handleSendPacket(PlayerInstance activeChar) - { - /* - * -1 0 to initialize the race 0 15322 to start race 13765 -1 in middle of race -1 0 to end the race 8003 to 8027 - */ - - final int[][] codes = - { - { - -1, - 0 - }, - { - 0, - 15322 - }, - { - 13765, - -1 - }, - { - -1, - 0 - } - }; - final MonsterRace race = MonsterRace.getInstance(); - - if (state == -1) - { - state++; - race.newRace(); - race.newSpeeds(); - final MonRaceInfo spk = new MonRaceInfo(codes[state][0], codes[state][1], race.getMonsters(), race.getSpeeds()); - activeChar.sendPacket(spk); - activeChar.broadcastPacket(spk); - } - else if (state == 0) - { - state++; - final SystemMessage sm = new SystemMessage(SystemMessageId.THEY_RE_OFF); - sm.addInt(0); - activeChar.sendPacket(sm); - final PlaySound sRace = new PlaySound(1, "S_Race", 0, 0, 0, 0, 0); - activeChar.sendPacket(sRace); - activeChar.broadcastPacket(sRace); - final PlaySound sRace2 = new PlaySound(0, "ItemSound2.race_start", 1, 121209259, 12125, 182487, -3559); - activeChar.sendPacket(sRace2); - activeChar.broadcastPacket(sRace2); - final MonRaceInfo spk = new MonRaceInfo(codes[state][0], codes[state][1], race.getMonsters(), race.getSpeeds()); - activeChar.sendPacket(spk); - activeChar.broadcastPacket(spk); - - ThreadPool.schedule(new RunRace(codes, activeChar), 5000); - } - - } - - class RunRace implements Runnable - { - - private final int[][] codes; - private final PlayerInstance activeChar; - - public RunRace(int[][] pCodes, PlayerInstance pActiveChar) - { - codes = pCodes; - activeChar = pActiveChar; - } - - @Override - public void run() - { - // int[][] speeds1 = MonsterRace.getInstance().getSpeeds(); - // MonsterRace.getInstance().newSpeeds(); - // int[][] speeds2 = MonsterRace.getInstance().getSpeeds(); - /* - * int[] speed = new int[8]; for (int i=0; i<8; i++) { for (int j=0; j<20; j++) { //_log.info("Adding "+speeds1[i][j] +" and "+ speeds2[i][j]); speed[i] += (speeds1[i][j]*1); // + (speeds2[i][j]*1); } LOGGER.info("Total speed for "+(i+1)+" = "+speed[i]); } - */ - - final MonRaceInfo spk = new MonRaceInfo(codes[2][0], codes[2][1], MonsterRace.getInstance().getMonsters(), MonsterRace.getInstance().getSpeeds()); - activeChar.sendPacket(spk); - activeChar.broadcastPacket(spk); - ThreadPool.schedule(new RunEnd(activeChar), 30000); - } - } - - private static class RunEnd implements Runnable - { - private final PlayerInstance activeChar; - - public RunEnd(PlayerInstance pActiveChar) - { - activeChar = pActiveChar; - } - - @Override - public void run() - { - DeleteObject obj = null; - for (int i = 0; i < 8; i++) - { - obj = new DeleteObject(MonsterRace.getInstance().getMonsters()[i]); - activeChar.sendPacket(obj); - activeChar.broadcastPacket(obj); - } - state = -1; - } - } -} diff --git a/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/GameServer.java b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/GameServer.java index f13a479dd7..86195b9ffd 100644 --- a/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/GameServer.java +++ b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/GameServer.java @@ -133,6 +133,7 @@ import org.l2jmobius.gameserver.instancemanager.TerritoryWarManager; import org.l2jmobius.gameserver.instancemanager.WalkingManager; import org.l2jmobius.gameserver.instancemanager.ZoneManager; import org.l2jmobius.gameserver.instancemanager.games.Lottery; +import org.l2jmobius.gameserver.instancemanager.games.MonsterRace; import org.l2jmobius.gameserver.model.AutoSpawnHandler; import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.entity.Hero; diff --git a/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/MonsterRace.java b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/MonsterRace.java deleted file mode 100644 index da1a3f8676..0000000000 --- a/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/MonsterRace.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * This program 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. - * - * This program 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 . - */ -package org.l2jmobius.gameserver; - -import java.util.logging.Level; -import java.util.logging.Logger; - -import org.l2jmobius.commons.util.Rnd; -import org.l2jmobius.gameserver.data.xml.impl.NpcData; -import org.l2jmobius.gameserver.model.actor.Npc; -import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate; - -public class MonsterRace -{ - protected static final Logger LOGGER = Logger.getLogger(MonsterRace.class.getName()); - - private final Npc[] _monsters; - private int[][] _speeds; - private final int[] _first; - - private final int[] _second; - - protected MonsterRace() - { - _monsters = new Npc[8]; - _speeds = new int[8][20]; - _first = new int[2]; - _second = new int[2]; - } - - public void newRace() - { - int random = 0; - - for (int i = 0; i < 8; i++) - { - final int id = 31003; - random = Rnd.get(24); - while (true) - { - for (int j = i - 1; j >= 0; j--) - { - if (_monsters[j].getTemplate().getId() == (id + random)) - { - random = Rnd.get(24); - } - } - break; - } - try - { - final NpcTemplate template = NpcData.getInstance().getTemplate(id + random); - _monsters[i] = (Npc) Class.forName("org.l2jmobius.gameserver.model.actor.instance." + template.getType() + "Instance").getConstructors()[0].newInstance(template); - } - catch (Exception e) - { - LOGGER.log(Level.WARNING, "Unable to create monster!", e); - } - } - newSpeeds(); - } - - public void newSpeeds() - { - _speeds = new int[8][20]; - int total = 0; - _first[1] = 0; - _second[1] = 0; - for (int i = 0; i < 8; i++) - { - total = 0; - for (int j = 0; j < 20; j++) - { - _speeds[i][j] = j == 19 ? 100 : Rnd.get(60) + 65; - total += _speeds[i][j]; - } - if (total >= _first[1]) - { - _second[0] = _first[0]; - _second[1] = _first[1]; - _first[0] = 8 - i; - _first[1] = total; - } - else if (total >= _second[1]) - { - _second[0] = 8 - i; - _second[1] = total; - } - } - } - - /** - * @return Returns the monsters. - */ - public Npc[] getMonsters() - { - return _monsters; - } - - /** - * @return Returns the speeds. - */ - public int[][] getSpeeds() - { - return _speeds; - } - - public int getFirstPlace() - { - return _first[0]; - } - - public int getSecondPlace() - { - return _second[0]; - } - - public static MonsterRace getInstance() - { - return SingletonHolder.INSTANCE; - } - - private static class SingletonHolder - { - protected static final MonsterRace INSTANCE = new MonsterRace(); - } -} diff --git a/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/instancemanager/games/MonsterRace.java b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/instancemanager/games/MonsterRace.java new file mode 100644 index 0000000000..2fa4c519f6 --- /dev/null +++ b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/instancemanager/games/MonsterRace.java @@ -0,0 +1,623 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program 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. + * + * This program 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 . + */ +package org.l2jmobius.gameserver.instancemanager.games; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.Config; +import org.l2jmobius.commons.concurrent.ThreadPool; +import org.l2jmobius.commons.database.DatabaseFactory; +import org.l2jmobius.commons.util.Rnd; +import org.l2jmobius.gameserver.data.xml.impl.NpcData; +import org.l2jmobius.gameserver.model.actor.Npc; +import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.model.zone.type.DerbyTrackZone; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.serverpackets.DeleteObject; +import org.l2jmobius.gameserver.network.serverpackets.MonRaceInfo; +import org.l2jmobius.gameserver.network.serverpackets.PlaySound; +import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; +import org.l2jmobius.gameserver.util.Broadcast; + +public class MonsterRace +{ + protected static final Logger LOGGER = Logger.getLogger(MonsterRace.class.getName()); + + public static enum RaceState + { + ACCEPTING_BETS, + WAITING, + STARTING_RACE, + RACE_END + } + + protected static final PlaySound SOUND_1 = new PlaySound(1, "S_Race", 0, 0, 0, 0, 0); + protected static final PlaySound SOUND_2 = new PlaySound("ItemSound2.race_start"); + + protected static final int[][] CODES = + { + { + -1, + 0 + }, + { + 0, + 15322 + }, + { + 13765, + -1 + } + }; + + protected final List _npcTemplates = new ArrayList<>(); // List holding npc templates, shuffled on a new race. + protected final List _history = new ArrayList<>(); // List holding old race records. + protected final Map _betsPerLane = new ConcurrentHashMap<>(); // Map holding all bets for each lane ; values setted to 0 after every race. + protected final List _odds = new ArrayList<>(); // List holding sorted odds per lane ; cleared at new odds calculation. + + protected int _raceNumber = 1; + protected int _finalCountdown = 0; + protected RaceState _state = RaceState.RACE_END; + + protected MonRaceInfo _packet; + + private final Npc[] _monsters = new Npc[8]; + private int[][] _speeds = new int[8][20]; + private final int[] _first = new int[2]; + private final int[] _second = new int[2]; + + protected MonsterRace() + { + if (!Config.ALLOW_RACE) + { + return; + } + + // Feed _history with previous race results. + loadHistory(); + + // Feed _betsPerLane with stored informations on bets. + loadBets(); + + // Feed _npcTemplates, we will only have to shuffle it when needed. + for (int i = 31003; i < 31027; i++) + { + _npcTemplates.add(i); + } + + ThreadPool.scheduleAtFixedRate(new Announcement(), 0, 1000); + } + + public static class HistoryInfo + { + private final int _raceId; + private int _first; + private int _second; + private double _oddRate; + + public HistoryInfo(int raceId, int first, int second, double oddRate) + { + _raceId = raceId; + _first = first; + _second = second; + _oddRate = oddRate; + } + + public int getRaceId() + { + return _raceId; + } + + public int getFirst() + { + return _first; + } + + public int getSecond() + { + return _second; + } + + public double getOddRate() + { + return _oddRate; + } + + public void setFirst(int first) + { + _first = first; + } + + public void setSecond(int second) + { + _second = second; + } + + public void setOddRate(double oddRate) + { + _oddRate = oddRate; + } + } + + private class Announcement implements Runnable + { + public Announcement() + { + } + + @Override + public void run() + { + if (_finalCountdown > 1200) + { + _finalCountdown = 0; + } + + switch (_finalCountdown) + { + case 0: + { + newRace(); + newSpeeds(); + + _state = RaceState.ACCEPTING_BETS; + _packet = new MonRaceInfo(CODES[0][0], CODES[0][1], getMonsters(), getSpeeds()); + + final SystemMessage msg = new SystemMessage(SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, _packet, msg); + 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 + { + final SystemMessage msg = new SystemMessage(SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + break; + } + case 300: // 5 min + { + final SystemMessage msg = new SystemMessage(SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S); + msg2.addInt(10); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + break; + } + case 600: // 10 min + { + final SystemMessage msg = new SystemMessage(SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S); + msg2.addInt(5); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + break; + } + case 840: // 14 min + { + final SystemMessage msg = new SystemMessage(SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S); + msg2.addInt(1); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + break; + } + case 900: // 15 min + { + _state = RaceState.WAITING; + + calculateOdds(); + + final SystemMessage msg = new SystemMessage(SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.TICKETS_SALES_ARE_CLOSED_FOR_MONSTER_RACE_S1_ODDS_ARE_POSTED); + msg2.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + break; + } + case 960: // 16 min + case 1020: // 17 min + { + final int minutes = (_finalCountdown == 960) ? 2 : 1; + final SystemMessage msg = new SystemMessage(SystemMessageId.MONSTER_RACE_S2_WILL_BEGIN_IN_S1_MINUTE_S); + msg.addInt(minutes); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + break; + } + case 1050: // 17 min 30 sec + { + final SystemMessage msg = new SystemMessage(SystemMessageId.MONSTER_RACE_S1_WILL_BEGIN_IN_30_SECONDS); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + break; + } + case 1070: // 17 min 50 sec + { + final SystemMessage msg = new SystemMessage(SystemMessageId.MONSTER_RACE_S1_IS_ABOUT_TO_BEGIN_COUNTDOWN_IN_FIVE_SECONDS); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + 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; + final SystemMessage msg = new SystemMessage(SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SECOND_S); + msg.addInt(seconds); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + break; + } + case 1080: // 18 min + { + _state = RaceState.STARTING_RACE; + _packet = new MonRaceInfo(CODES[1][0], CODES[1][1], getMonsters(), getSpeeds()); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, new SystemMessage(SystemMessageId.THEY_RE_OFF), SOUND_1, SOUND_2, _packet); + break; + } + case 1085: // 18 min 5 sec + { + _packet = new MonRaceInfo(CODES[2][0], CODES[2][1], getMonsters(), getSpeeds()); + + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, _packet); + break; + } + case 1115: // 18 min 35 sec + { + _state = RaceState.RACE_END; + + // Populate history info with data, stores it in database. + final HistoryInfo info = _history.get(_history.size() - 1); + info.setFirst(getFirstPlace()); + info.setSecond(getSecondPlace()); + info.setOddRate(_odds.get(getFirstPlace() - 1)); + + saveHistory(info); + clearBets(); + + final SystemMessage msg = new SystemMessage(SystemMessageId.FIRST_PRIZE_GOES_TO_THE_PLAYER_IN_LANE_S1_SECOND_PRIZE_GOES_TO_THE_PLAYER_IN_LANE_S2); + msg.addInt(getFirstPlace()); + msg.addInt(getSecondPlace()); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.MONSTER_RACE_S1_IS_FINISHED); + msg2.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + _raceNumber++; + break; + } + case 1140: // 19 min + { + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, new DeleteObject(getMonsters()[0]), new DeleteObject(getMonsters()[1]), new DeleteObject(getMonsters()[2]), new DeleteObject(getMonsters()[3]), new DeleteObject(getMonsters()[4]), new DeleteObject(getMonsters()[5]), new DeleteObject(getMonsters()[6]), new DeleteObject(getMonsters()[7])); + break; + } + } + _finalCountdown += 1; + } + } + + public void newRace() + { + // Edit _history. + _history.add(new HistoryInfo(_raceNumber, 0, 0, 0)); + + // Randomize _npcTemplates. + Collections.shuffle(_npcTemplates); + + // Setup 8 new creatures ; pickup the first 8 from _npcTemplates. + for (int i = 0; i < 8; i++) + { + try + { + final NpcTemplate template = NpcData.getInstance().getTemplate(_npcTemplates.get(i)); + _monsters[i] = (Npc) Class.forName("org.l2jmobius.gameserver.model.actor.instance." + template.getType() + "Instance").getConstructors()[0].newInstance(template); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "", e); + } + } + } + + public void newSpeeds() + { + _speeds = new int[8][20]; + int total = 0; + _first[1] = 0; + _second[1] = 0; + + for (int i = 0; i < 8; i++) + { + total = 0; + for (int j = 0; j < 20; j++) + { + if (j == 19) + { + _speeds[i][j] = 100; + } + else + { + _speeds[i][j] = Rnd.get(60) + 65; + } + total += _speeds[i][j]; + } + + if (total >= _first[1]) + { + _second[0] = _first[0]; + _second[1] = _first[1]; + _first[0] = 8 - i; + _first[1] = total; + } + else if (total >= _second[1]) + { + _second[0] = 8 - i; + _second[1] = total; + } + } + } + + /** + * Load past races informations, feeding _history arrayList.
+ * Also sets _raceNumber, based on latest HistoryInfo loaded. + */ + protected void loadHistory() + { + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("SELECT * FROM mdt_history"); + ResultSet rset = statement.executeQuery(); + + while (rset.next()) + { + _history.add(new HistoryInfo(rset.getInt("race_id"), rset.getInt("first"), rset.getInt("second"), rset.getDouble("odd_rate"))); + _raceNumber++; + } + rset.close(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't load history: " + e.getMessage(), e); + } + LOGGER.info("MonsterRace: loaded " + _history.size() + " records, currently on race #" + _raceNumber); + } + + /** + * Save an history record into database. + * @param history The infos to store. + */ + protected void saveHistory(HistoryInfo history) + { + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("INSERT INTO mdt_history (race_id, first, second, odd_rate) VALUES (?,?,?,?)"); + statement.setInt(1, history.getRaceId()); + statement.setInt(2, history.getFirst()); + statement.setInt(3, history.getSecond()); + statement.setDouble(4, history.getOddRate()); + statement.execute(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't save history: " + e.getMessage(), e); + } + } + + /** + * Load current bets per lane ; initialize the map keys. + */ + protected void loadBets() + { + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("SELECT * FROM mdt_bets"); + ResultSet rset = statement.executeQuery(); + + while (rset.next()) + { + setBetOnLane(rset.getInt("lane_id"), rset.getLong("bet"), false); + } + + rset.close(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't load bets: " + e.getMessage(), e); + } + } + + /** + * Save the current lane bet into database. + * @param lane : The lane to affect. + * @param sum : The sum to set. + */ + protected void saveBet(int lane, long sum) + { + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("REPLACE INTO mdt_bets (lane_id, bet) VALUES (?,?)"); + statement.setInt(1, lane); + statement.setLong(2, sum); + statement.execute(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't save bet: " + e.getMessage(), e); + } + } + + /** + * Clear all lanes bets, either on database or Map. + */ + protected void clearBets() + { + for (int key : _betsPerLane.keySet()) + { + _betsPerLane.put(key, 0L); + } + + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("UPDATE mdt_bets SET bet = 0"); + statement.execute(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't clear bets: " + e.getMessage(), e); + } + } + + /** + * Setup lane bet, based on previous value (if any). + * @param lane : The lane to edit. + * @param amount : The amount to add. + * @param saveOnDb : Should it be saved on db or not. + */ + public void setBetOnLane(int lane, long amount, boolean saveOnDb) + { + final long sum = (_betsPerLane.containsKey(lane)) ? _betsPerLane.get(lane) + amount : amount; + + _betsPerLane.put(lane, sum); + + if (saveOnDb) + { + saveBet(lane, sum); + } + } + + /** + * Calculate odds for every lane, based on others lanes. + */ + protected void calculateOdds() + { + // Clear previous List holding old odds. + _odds.clear(); + + // Sort bets lanes per lane. + final Map 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; + } + + // 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)); + } + } + + public Npc[] getMonsters() + { + return _monsters; + } + + public int[][] getSpeeds() + { + return _speeds; + } + + public int getFirstPlace() + { + return _first[0]; + } + + public int getSecondPlace() + { + return _second[0]; + } + + public MonRaceInfo getRacePacket() + { + return _packet; + } + + public RaceState getCurrentRaceState() + { + return _state; + } + + public int getRaceNumber() + { + return _raceNumber; + } + + public List getHistory() + { + return _history; + } + + public List getOdds() + { + return _odds; + } + + public static MonsterRace getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final MonsterRace INSTANCE = new MonsterRace(); + } +} \ No newline at end of file diff --git a/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/model/actor/instance/RaceManagerInstance.java b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/model/actor/instance/RaceManagerInstance.java index 62fa858adf..e9aa717369 100644 --- a/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/model/actor/instance/RaceManagerInstance.java +++ b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/model/actor/instance/RaceManagerInstance.java @@ -16,67 +16,26 @@ */ package org.l2jmobius.gameserver.model.actor.instance; -import java.util.ArrayList; import java.util.List; +import java.util.Locale; -import org.l2jmobius.commons.concurrent.ThreadPool; -import org.l2jmobius.gameserver.MonsterRace; -import org.l2jmobius.gameserver.enums.InstanceType; +import org.l2jmobius.Config; +import org.l2jmobius.commons.util.StringUtil; import org.l2jmobius.gameserver.idfactory.IdFactory; +import org.l2jmobius.gameserver.instancemanager.games.MonsterRace; +import org.l2jmobius.gameserver.instancemanager.games.MonsterRace.HistoryInfo; +import org.l2jmobius.gameserver.instancemanager.games.MonsterRace.RaceState; import org.l2jmobius.gameserver.model.actor.Npc; import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate; -import org.l2jmobius.gameserver.model.itemcontainer.Inventory; import org.l2jmobius.gameserver.model.items.instance.ItemInstance; import org.l2jmobius.gameserver.network.SystemMessageId; import org.l2jmobius.gameserver.network.serverpackets.ActionFailed; -import org.l2jmobius.gameserver.network.serverpackets.DeleteObject; -import org.l2jmobius.gameserver.network.serverpackets.IClientOutgoingPacket; -import org.l2jmobius.gameserver.network.serverpackets.InventoryUpdate; -import org.l2jmobius.gameserver.network.serverpackets.MonRaceInfo; import org.l2jmobius.gameserver.network.serverpackets.NpcHtmlMessage; -import org.l2jmobius.gameserver.network.serverpackets.PlaySound; import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; -import org.l2jmobius.gameserver.util.Broadcast; public class RaceManagerInstance extends Npc { - public static final int LANES = 8; - public static final int WINDOW_START = 0; - - private static List _managers; - protected static int _raceNumber = 4; - - // Time Constants - private static final long SECOND = 1000; - private static final long MINUTE = 60 * SECOND; - - private static int _minutes = 5; - - // States - private static final int ACCEPTING_BETS = 0; - private static final int WAITING = 1; - private static final int STARTING_RACE = 2; - private static final int RACE_END = 3; - private static int _state = RACE_END; - - protected static final int[][] _codes = - { - { - -1, - 0 - }, - { - 0, - 15322 - }, - { - 13765, - -1 - } - }; - private static boolean _notInitialized = true; - protected static MonRaceInfo _packet; - protected static final int[] _cost = + protected static final int TICKET_PRICES[] = { 100, 500, @@ -88,453 +47,331 @@ public class RaceManagerInstance extends Npc 100000 }; - /** - * Creates a race manager. - * @param template the race manager NPC template - */ public RaceManagerInstance(NpcTemplate template) { super(template); - setInstanceType(InstanceType.RaceManagerInstance); - if (_notInitialized) - { - _notInitialized = false; - - _managers = new ArrayList<>(); - - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1), 0, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1), 30 * SECOND, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1), MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1), MINUTE + (30 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S), 2 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S), 3 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S), 4 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S), 5 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S), 6 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKETS_SALES_ARE_CLOSED_FOR_MONSTER_RACE_S1_ODDS_ARE_POSTED), 7 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.MONSTER_RACE_S2_WILL_BEGIN_IN_S1_MINUTE_S), 7 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.MONSTER_RACE_S2_WILL_BEGIN_IN_S1_MINUTE_S), 8 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.MONSTER_RACE_S1_WILL_BEGIN_IN_30_SECONDS), (8 * MINUTE) + (30 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.MONSTER_RACE_S1_IS_ABOUT_TO_BEGIN_COUNTDOWN_IN_FIVE_SECONDS), (8 * MINUTE) + (50 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SECOND_S), (8 * MINUTE) + (55 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SECOND_S), (8 * MINUTE) + (56 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SECOND_S), (8 * MINUTE) + (57 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SECOND_S), (8 * MINUTE) + (58 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SECOND_S), (8 * MINUTE) + (59 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.THEY_RE_OFF), 9 * MINUTE, 10 * MINUTE); - } - _managers.add(this); - } - - class Announcement implements Runnable - { - private final SystemMessageId _type; - - public Announcement(SystemMessageId pType) - { - _type = pType; - } - - @Override - public void run() - { - makeAnnouncement(_type); - } - } - - public void makeAnnouncement(SystemMessageId type) - { - final SystemMessage sm = new SystemMessage(type); - switch (type.getId()) - { - case 816: // SystemMessageId.MONSRACE_TICKETS_AVAILABLE_FOR_S1_RACE - case 817: // SystemMessageId.MONSRACE_TICKETS_NOW_AVAILABLE_FOR_S1_RACE - { - if (_state != ACCEPTING_BETS) - {// LOGGER.info("Race Initializing"); - _state = ACCEPTING_BETS; - startRace(); - } // else{_log.info("Race open");} - sm.addInt(_raceNumber); - break; - } - case 818: // SystemMessageId.MONSRACE_TICKETS_STOP_IN_S1_MINUTES - case 820: // SystemMessageId.MONSRACE_S2_BEGINS_IN_S1_MINUTES - case 823: // SystemMessageId.MONSRACE_BEGINS_IN_S1_SECONDS - { - sm.addInt(_minutes); - if (type.getId() == 820) - { - sm.addInt(_raceNumber); - } - _minutes--; - break; - } - case 819: // SystemMessageId.MONSRACE_S1_TICKET_SALES_CLOSED - { - // LOGGER.info("Sales closed"); - sm.addInt(_raceNumber); - _state = WAITING; - _minutes = 2; - break; - } - case 821: // SystemMessageId.MONSRACE_S1_BEGINS_IN_30_SECONDS - case 822: // SystemMessageId.MONSRACE_S1_COUNTDOWN_IN_FIVE_SECONDS - case 825: // SystemMessageId.MONSRACE_S1_RACE_END - { - sm.addInt(_raceNumber); - _minutes = 5; - break; - } - case 826: // SystemMessageId.MONSRACE_FIRST_PLACE_S1_SECOND_S2 - { - // LOGGER.info("Placing"); - _state = RACE_END; - sm.addInt(MonsterRace.getInstance().getFirstPlace()); - sm.addInt(MonsterRace.getInstance().getSecondPlace()); - break; - } - } - // _logn.info("Counter: "+minutes); - // LOGGER.info("State: "+state); - broadcast(sm); - // LOGGER.info("Player's known: "+getKnownPlayers().size()); - - if (type == SystemMessageId.THEY_RE_OFF) - { - // LOGGER.info("Starting race"); - _state = STARTING_RACE; - startRace(); - _minutes = 5; - } - } - - protected void broadcast(IClientOutgoingPacket pkt) - { - for (RaceManagerInstance manager : _managers) - { - if (!manager.isDead()) - { - Broadcast.toKnownPlayers(manager, pkt); - } - } - } - - public void sendMonsterInfo() - { - broadcast(_packet); - } - - private void startRace() - { - final MonsterRace race = MonsterRace.getInstance(); - if (_state == STARTING_RACE) - { - // state++; - broadcast(new PlaySound(1, "S_Race", 0, 0, 0, 0, 0)); - broadcast(new PlaySound(0, "ItemSound2.race_start", 1, 121209259, 12125, 182487, -3559)); - _packet = new MonRaceInfo(_codes[1][0], _codes[1][1], race.getMonsters(), race.getSpeeds()); - sendMonsterInfo(); - - ThreadPool.schedule(new RunRace(), 5000); - } - else - { - // state++; - race.newRace(); - race.newSpeeds(); - _packet = new MonRaceInfo(_codes[0][0], _codes[0][1], race.getMonsters(), race.getSpeeds()); - sendMonsterInfo(); - } } @Override public void onBypassFeedback(PlayerInstance player, String command) { - if (command.startsWith("BuyTicket") && (_state != ACCEPTING_BETS)) - { - player.sendPacket(SystemMessageId.MONSTER_RACE_TICKETS_ARE_NO_LONGER_AVAILABLE); - command = "Chat 0"; - } - if (command.startsWith("ShowOdds") && (_state == ACCEPTING_BETS)) - { - player.sendPacket(SystemMessageId.MONSTER_RACE_PAYOUT_INFORMATION_IS_NOT_AVAILABLE_WHILE_TICKETS_ARE_BEING_SOLD); - command = "Chat 0"; - } - if (command.startsWith("BuyTicket")) { + if (!Config.ALLOW_RACE || (MonsterRace.getInstance().getCurrentRaceState() != RaceState.ACCEPTING_BETS)) + { + player.sendPacket(SystemMessageId.MONSTER_RACE_TICKETS_ARE_NO_LONGER_AVAILABLE); + super.onBypassFeedback(player, "Chat 0"); + return; + } + int 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))) { val = 0; } - showBuyTicket(player, val); + + String search, replace; + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + + if (val < 10) + { + html.setFile(player, getHtmlPath(getId(), 2)); + for (int i = 0; i < 8; i++) + { + int n = i + 1; + search = "Mob" + n; + html.replace(search, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); + } + search = "No1"; + if (val == 0) + { + html.replace(search, ""); + } + else + { + html.replace(search, val); + player.setRace(0, val); + } + } + else if (val < 20) + { + if (player.getRace(0) == 0) + { + return; + } + + html.setFile(player, getHtmlPath(getId(), 3)); + html.replace("0place", player.getRace(0)); + search = "Mob1"; + replace = MonsterRace.getInstance().getMonsters()[player.getRace(0) - 1].getTemplate().getName(); + html.replace(search, replace); + search = "0adena"; + + if (val == 10) + { + html.replace(search, ""); + } + else + { + html.replace(search, TICKET_PRICES[val - 11]); + player.setRace(1, val - 10); + } + } + else if (val == 20) + { + if ((player.getRace(0) == 0) || (player.getRace(1) == 0)) + { + return; + } + + html.setFile(player, getHtmlPath(getId(), 4)); + html.replace("0place", player.getRace(0)); + search = "Mob1"; + replace = MonsterRace.getInstance().getMonsters()[player.getRace(0) - 1].getTemplate().getName(); + 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); + } + else + { + if ((player.getRace(0) == 0) || (player.getRace(1) == 0)) + { + return; + } + + int ticket = player.getRace(0); + int priceId = player.getRace(1); + + if (!player.reduceAdena("Race", TICKET_PRICES[priceId - 1], this, true)) + { + return; + } + + player.setRace(0, 0); + player.setRace(1, 0); + + ItemInstance item = new ItemInstance(IdFactory.getInstance().getNextId(), 4443); + item.setCount(1); + item.setEnchantLevel(MonsterRace.getInstance().getRaceNumber()); + item.setCustomType1(ticket); + item.setCustomType2(TICKET_PRICES[priceId - 1] / 100); + + player.addItem("Race", item, player, false); + final SystemMessage msg = new SystemMessage(SystemMessageId.ACQUIRED_S1_S2); + msg.addInt(MonsterRace.getInstance().getRaceNumber()); + msg.addItemName(4443); + player.sendPacket(msg); + + // Refresh lane bet. + MonsterRace.getInstance().setBetOnLane(ticket, TICKET_PRICES[priceId - 1], true); + super.onBypassFeedback(player, "Chat 0"); + return; + } + html.replace("1race", MonsterRace.getInstance().getRaceNumber()); + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); } else if (command.equals("ShowOdds")) { - showOdds(player); - } - else if (command.equals("ShowInfo")) - { - showMonsterInfo(player); - } - else if (command.equals("calculateWin")) - { - // displayCalculateWinnings(player); - } - else if (command.equals("viewHistory")) - { - // displayHistory(player); - } - else - { - // getKnownList().removeKnownObject(player); - super.onBypassFeedback(player, command); - } - } - - public void showOdds(PlayerInstance player) - { - if (_state == ACCEPTING_BETS) - { - return; - } - final int npcId = getTemplate().getId(); - String filename; - String search; - final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); - filename = getHtmlPath(npcId, 5); - html.setFile(player, filename); - for (int i = 0; i < 8; i++) - { - final int n = i + 1; - search = "Mob" + n; - html.replace(search, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); - } - html.replace("1race", String.valueOf(_raceNumber)); - html.replace("%objectId%", String.valueOf(getObjectId())); - player.sendPacket(html); - player.sendPacket(ActionFailed.STATIC_PACKET); - } - - public void showMonsterInfo(PlayerInstance player) - { - final int npcId = getTemplate().getId(); - String filename; - String search; - final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); - filename = getHtmlPath(npcId, 6); - html.setFile(player, filename); - for (int i = 0; i < 8; i++) - { - final int n = i + 1; - search = "Mob" + n; - html.replace(search, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); - } - html.replace("%objectId%", String.valueOf(getObjectId())); - player.sendPacket(html); - player.sendPacket(ActionFailed.STATIC_PACKET); - } - - public void showBuyTicket(PlayerInstance player, int value) - { - if (_state != ACCEPTING_BETS) - { - return; - } - final int npcId = getTemplate().getId(); - SystemMessage sm; - String filename; - String search; - String replace; - final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); - if (value < 10) - { - filename = getHtmlPath(npcId, 2); - html.setFile(player, filename); + if (!Config.ALLOW_RACE || (MonsterRace.getInstance().getCurrentRaceState() == RaceState.ACCEPTING_BETS)) + { + player.sendPacket(SystemMessageId.MONSTER_RACE_PAYOUT_INFORMATION_IS_NOT_AVAILABLE_WHILE_TICKETS_ARE_BEING_SOLD); + super.onBypassFeedback(player, "Chat 0"); + return; + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 5)); for (int i = 0; i < 8; i++) { final int n = i + 1; - search = "Mob" + n; + + html.replace("Mob" + n, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); + + // Odd + final double odd = MonsterRace.getInstance().getOdds().get(i); + html.replace("Odd" + n, (odd > 0D) ? String.format(Locale.ENGLISH, "%.1f", odd) : "&$804;"); + } + html.replace("1race", MonsterRace.getInstance().getRaceNumber()); + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); + } + else if (command.equals("ShowInfo")) + { + if (!Config.ALLOW_RACE) + { + return; + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 6)); + + for (int i = 0; i < 8; i++) + { + int n = i + 1; + String search = "Mob" + n; html.replace(search, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); } - search = "No1"; - if (value == 0) - { - html.replace(search, ""); - } - else - { - html.replace(search, Integer.toString(value)); - player.setRace(0, value); - } + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); } - else if (value < 20) + else if (command.equals("ShowTickets")) { - if (player.getRace(0) == 0) + if (!Config.ALLOW_RACE) { + super.onBypassFeedback(player, "Chat 0"); return; } - filename = getHtmlPath(npcId, 3); - html.setFile(player, filename); - html.replace("0place", Integer.toString(player.getRace(0))); - search = "Mob1"; - replace = MonsterRace.getInstance().getMonsters()[player.getRace(0) - 1].getTemplate().getName(); - html.replace(search, replace); - search = "0adena"; - if (value == 10) + + // Generate data. + final StringBuilder sb = new StringBuilder(); + + // Retrieve player's tickets. + for (ItemInstance ticket : player.getInventory().getAllItemsByItemId(4443)) { - html.replace(search, ""); - } - else - { - html.replace(search, Integer.toString(_cost[value - 11])); - player.setRace(1, value - 10); + // Don't list current race tickets. + if (ticket.getEnchantLevel() == MonsterRace.getInstance().getRaceNumber()) + { + continue; + } + + StringUtil.append(sb, "", "" + ticket.getEnchantLevel(), " Race Number", "" + ticket.getCustomType1(), " Number", "" + (ticket.getCustomType2() * 100), " Adena"); } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 7)); + html.replace("%tickets%", sb.toString()); + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); } - else if (value == 20) + else if (command.startsWith("ShowTicket")) { - if ((player.getRace(0) == 0) || (player.getRace(1) == 0)) + // Retrieve ticket objectId. + final int val = Integer.parseInt(command.substring(11)); + if (!Config.ALLOW_RACE || (val == 0)) { + super.onBypassFeedback(player, "Chat 0"); return; } - filename = getHtmlPath(npcId, 4); - html.setFile(player, filename); - html.replace("0place", Integer.toString(player.getRace(0))); - search = "Mob1"; - replace = MonsterRace.getInstance().getMonsters()[player.getRace(0) - 1].getTemplate().getName(); - html.replace(search, replace); - search = "0adena"; - final int price = _cost[player.getRace(1) - 1]; - html.replace(search, Integer.toString(price)); - search = "0tax"; - final int tax = 0; - html.replace(search, Integer.toString(tax)); - search = "0total"; - final int total = price + tax; - html.replace(search, Integer.toString(total)); + + // Retrieve ticket on player's inventory. + final ItemInstance 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; + + // Retrieve HistoryInfo for that race. + final HistoryInfo info = MonsterRace.getInstance().getHistory().get(raceId - 1); + if (info == null) + { + super.onBypassFeedback(player, "Chat 0"); + return; + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 8)); + html.replace("%raceId%", raceId); + html.replace("%lane%", lane); + html.replace("%bet%", bet); + html.replace("%firstLane%", info.getFirst()); + html.replace("%odd%", (lane == info.getFirst()) ? String.format(Locale.ENGLISH, "%.2f", info.getOddRate()) : "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 (!Config.ALLOW_RACE || (val == 0)) + { + super.onBypassFeedback(player, "Chat 0"); + return; + } + + // Delete ticket on player's inventory. + final ItemInstance 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; + + // Retrieve HistoryInfo for that race. + final HistoryInfo info = MonsterRace.getInstance().getHistory().get(raceId - 1); + if (info == 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()) ? info.getOddRate() : 0.01)), this, true); + } + + super.onBypassFeedback(player, "Chat 0"); + return; + } + else if (command.equals("ViewHistory")) + { + if (!Config.ALLOW_RACE) + { + super.onBypassFeedback(player, "Chat 0"); + return; + } + + // Generate data. + final StringBuilder sb = new StringBuilder(); + + // Use whole history, pickup from 'last element' and stop at 'latest element - 7'. + final List history = MonsterRace.getInstance().getHistory(); + for (int i = history.size() - 1; i >= Math.max(0, history.size() - 7); i--) + { + final HistoryInfo info = history.get(i); + StringUtil.append(sb, "", "" + info.getRaceId(), " th", "" + info.getFirst(), " Lane ", "" + info.getSecond(), " Lane", String.format(Locale.ENGLISH, "%.2f", info.getOddRate()), " Times"); + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 9)); + html.replace("%infos%", sb.toString()); + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); } else { - if ((player.getRace(0) == 0) || (player.getRace(1) == 0)) - { - return; - } - final int ticket = player.getRace(0); - final int priceId = player.getRace(1); - if (!player.reduceAdena("Race", _cost[priceId - 1], this, true)) - { - return; - } - player.setRace(0, 0); - player.setRace(1, 0); - sm = new SystemMessage(SystemMessageId.ACQUIRED_S1_S2); - sm.addInt(_raceNumber); - sm.addItemName(4443); - player.sendPacket(sm); - final ItemInstance item = new ItemInstance(IdFactory.getInstance().getNextId(), 4443); - item.setCount(1); - item.setEnchantLevel(_raceNumber); - item.setCustomType1(ticket); - item.setCustomType2(_cost[priceId - 1] / 100); - player.getInventory().addItem("Race", item, player, this); - final InventoryUpdate iu = new InventoryUpdate(); - iu.addItem(item); - final ItemInstance adenaupdate = player.getInventory().getItemByItemId(Inventory.ADENA_ID); - iu.addModifiedItem(adenaupdate); - player.sendPacket(iu); - return; - } - html.replace("1race", String.valueOf(_raceNumber)); - html.replace("%objectId%", String.valueOf(getObjectId())); - player.sendPacket(html); - player.sendPacket(ActionFailed.STATIC_PACKET); - } - - public static class Race - { - private final Info[] _info; - - public Race(Info[] pInfo) - { - _info = pInfo; - } - - public Info getLaneInfo(int lane) - { - return _info[lane]; - } - - public class Info - { - private final int _id; - private final int _place; - private final int _odds; - private final int _payout; - - public Info(int pId, int pPlace, int pOdds, int pPayout) - { - _id = pId; - _place = pPlace; - _odds = pOdds; - _payout = pPayout; - } - - public int getId() - { - return _id; - } - - public int getOdds() - { - return _odds; - } - - public int getPayout() - { - return _payout; - } - - public int getPlace() - { - return _place; - } - } - - } - - class RunRace implements Runnable - { - @Override - public void run() - { - _packet = new MonRaceInfo(_codes[2][0], _codes[2][1], MonsterRace.getInstance().getMonsters(), MonsterRace.getInstance().getSpeeds()); - sendMonsterInfo(); - ThreadPool.schedule(new RunEnd(), 30000); - } - } - - class RunEnd implements Runnable - { - @Override - public void run() - { - makeAnnouncement(SystemMessageId.FIRST_PRIZE_GOES_TO_THE_PLAYER_IN_LANE_S1_SECOND_PRIZE_GOES_TO_THE_PLAYER_IN_LANE_S2); - makeAnnouncement(SystemMessageId.MONSTER_RACE_S1_IS_FINISHED); - _raceNumber++; - - DeleteObject obj = null; - for (int i = 0; i < 8; i++) - { - obj = new DeleteObject(MonsterRace.getInstance().getMonsters()[i]); - broadcast(obj); - } + super.onBypassFeedback(player, command); } } } diff --git a/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/util/Broadcast.java b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/util/Broadcast.java index b6216d939a..eaf0c4afb8 100644 --- a/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/util/Broadcast.java +++ b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/util/Broadcast.java @@ -21,9 +21,11 @@ import java.util.logging.Logger; import org.l2jmobius.gameserver.cache.RelationCache; import org.l2jmobius.gameserver.enums.ChatType; +import org.l2jmobius.gameserver.instancemanager.ZoneManager; import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.actor.Creature; import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance; +import org.l2jmobius.gameserver.model.zone.ZoneType; import org.l2jmobius.gameserver.network.serverpackets.CharInfo; import org.l2jmobius.gameserver.network.serverpackets.CreatureSay; import org.l2jmobius.gameserver.network.serverpackets.ExShowScreenMessage; @@ -193,4 +195,29 @@ public class Broadcast { toAllOnlinePlayers(new ExShowScreenMessage(text, 10000)); } + + /** + * Send a packet to all players in a specific zone type. + * @param ZoneType. + * @param zoneType : The zone type to send packets. + * @param packets : The packets to send. + */ + public static void toAllPlayersInZoneType(Class zoneType, IClientOutgoingPacket... packets) + { + for (ZoneType zone : ZoneManager.getInstance().getAllZones(zoneType)) + { + for (Creature creature : zone.getCharactersInside()) + { + if (creature == null) + { + continue; + } + + for (IClientOutgoingPacket packet : packets) + { + creature.sendPacket(packet); + } + } + } + } } diff --git a/L2J_Mobius_CT_2.6_HighFive/dist/db_installer/sql/game/mdt_bets.sql b/L2J_Mobius_CT_2.6_HighFive/dist/db_installer/sql/game/mdt_bets.sql new file mode 100644 index 0000000000..8d7d4cb738 --- /dev/null +++ b/L2J_Mobius_CT_2.6_HighFive/dist/db_installer/sql/game/mdt_bets.sql @@ -0,0 +1,15 @@ +CREATE TABLE IF NOT EXISTS `mdt_bets` ( + `lane_id` INT(1) DEFAULT 0, + `bet` INT DEFAULT 0, + PRIMARY KEY (`lane_id`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci; + +INSERT INTO `mdt_bets` VALUES +('1','0'), +('2','0'), +('3','0'), +('4','0'), +('5','0'), +('6','0'), +('7','0'), +('8','0'); \ No newline at end of file diff --git a/L2J_Mobius_CT_2.6_HighFive/dist/db_installer/sql/game/mdt_history.sql b/L2J_Mobius_CT_2.6_HighFive/dist/db_installer/sql/game/mdt_history.sql new file mode 100644 index 0000000000..06978f3377 --- /dev/null +++ b/L2J_Mobius_CT_2.6_HighFive/dist/db_installer/sql/game/mdt_history.sql @@ -0,0 +1,7 @@ +CREATE TABLE IF NOT EXISTS `mdt_history` ( + `race_id` MEDIUMINT DEFAULT 0, + `first` INT(1) DEFAULT 0, + `second` INT(1) DEFAULT 0, + `odd_rate` DOUBLE(10,2) DEFAULT 0, + PRIMARY KEY (`race_id`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci; \ No newline at end of file diff --git a/L2J_Mobius_CT_2.6_HighFive/dist/game/config/AdminCommands.xml b/L2J_Mobius_CT_2.6_HighFive/dist/game/config/AdminCommands.xml index 266edd3190..62cab6489a 100644 --- a/L2J_Mobius_CT_2.6_HighFive/dist/game/config/AdminCommands.xml +++ b/L2J_Mobius_CT_2.6_HighFive/dist/game/config/AdminCommands.xml @@ -403,9 +403,6 @@ - - - diff --git a/L2J_Mobius_CT_2.6_HighFive/dist/game/data/html/default/30995-1.htm b/L2J_Mobius_CT_2.6_HighFive/dist/game/data/html/default/30995-1.htm index 01783586be..2cb460e567 100644 --- a/L2J_Mobius_CT_2.6_HighFive/dist/game/data/html/default/30995-1.htm +++ b/L2J_Mobius_CT_2.6_HighFive/dist/game/data/html/default/30995-1.htm @@ -1,8 +1,10 @@ -
-Go Back
+ +
+Go Back +
In a monster race eight monsters race at the same time. If you guess which monster is expected to finish first and buy that monster's race ticket, based upon the odds to win you will receive an appropriate amount of adena.
Racing Schedule
-Monster races every 20 minutes in real time. +Monster races every 20 minutes in real time.
Method of Race Participation
You can purchase a monster race ticket through a race manager NPC. Only between 18 minutes before the start of a race until 3 minutes before the race begins you can buy a ticket for the applicable race. When you purchase a ticket through a conversation with a race manager, you can find out the names of participating monsters in the race and their current statistics. Five minutes before a race starts, the race manager will stop ticket sales and announce monsters' individual odds rate for the current race.
Types of Race
diff --git a/L2J_Mobius_CT_2.6_HighFive/dist/game/data/html/default/30995-3.htm b/L2J_Mobius_CT_2.6_HighFive/dist/game/data/html/default/30995-3.htm index 861fc97450..ae367419b2 100644 --- a/L2J_Mobius_CT_2.6_HighFive/dist/game/data/html/default/30995-3.htm +++ b/L2J_Mobius_CT_2.6_HighFive/dist/game/data/html/default/30995-3.htm @@ -3,6 +3,7 @@ + @@ -11,6 +12,7 @@
Lane SelectionOdds
+ @@ -20,18 +22,21 @@
13%
+
Select Bet Amount -100 Adena , 500 Adena -1,000 Adena , 5,000 Adena -10,000 Adena , 20,000 Adena -50,000 Adena , 100,000 Adena +100 Adena , 500 Adena +1,000 Adena , 5,000 Adena +10,000 Adena , 20,000 Adena +50,000 Adena , 100,000 Adena
+ + @@ -39,4 +44,5 @@
+ \ No newline at end of file diff --git a/L2J_Mobius_CT_2.6_HighFive/dist/game/data/html/default/30995-4.htm b/L2J_Mobius_CT_2.6_HighFive/dist/game/data/html/default/30995-4.htm index 0493fee9b6..27cd4cd6e0 100644 --- a/L2J_Mobius_CT_2.6_HighFive/dist/game/data/html/default/30995-4.htm +++ b/L2J_Mobius_CT_2.6_HighFive/dist/game/data/html/default/30995-4.htm @@ -11,6 +11,7 @@ Odds + @@ -20,6 +21,7 @@
13%
+ @@ -37,11 +39,13 @@ -
Bet Amount Total 0total Adena

+ +
If the information is correct, please click Confirm.

+ diff --git a/L2J_Mobius_CT_2.6_HighFive/dist/game/data/html/default/30995-5.htm b/L2J_Mobius_CT_2.6_HighFive/dist/game/data/html/default/30995-5.htm index f050b6a964..3fcc40dc31 100644 --- a/L2J_Mobius_CT_2.6_HighFive/dist/game/data/html/default/30995-5.htm +++ b/L2J_Mobius_CT_2.6_HighFive/dist/game/data/html/default/30995-5.htm @@ -15,49 +15,49 @@ - + - + - + - + - + - + - + - +
No.1 Mob1&$804; Odd1
No.2 Mob2&$804; Odd2
No.3 Mob3&$804; Odd3
No.4 Mob4&$804; Odd4
No.5 Mob5&$804; Odd5
No.6 Mob6&$804; Odd6
No.7 Mob7&$804; Odd7
No.8 Mob8&$804; Odd8
diff --git a/L2J_Mobius_CT_2.6_HighFive/dist/game/data/html/default/30995-7.htm b/L2J_Mobius_CT_2.6_HighFive/dist/game/data/html/default/30995-7.htm index 31f36f0a72..f7b0807b0b 100644 --- a/L2J_Mobius_CT_2.6_HighFive/dist/game/data/html/default/30995-7.htm +++ b/L2J_Mobius_CT_2.6_HighFive/dist/game/data/html/default/30995-7.htm @@ -3,22 +3,12 @@ - - + +
Winnings for a Race.LaneBet Amount
Winnings for a Race.LaneBet Amount
- - - - - +%tickets%
1 Race Number1 Number100 Adena
- - - - -
Previous List1 Page Next List
- diff --git a/L2J_Mobius_CT_2.6_HighFive/dist/game/data/html/default/30995-8.htm b/L2J_Mobius_CT_2.6_HighFive/dist/game/data/html/default/30995-8.htm index d198a9d1ac..f0176b871f 100644 --- a/L2J_Mobius_CT_2.6_HighFive/dist/game/data/html/default/30995-8.htm +++ b/L2J_Mobius_CT_2.6_HighFive/dist/game/data/html/default/30995-8.htm @@ -4,11 +4,11 @@
-
Race #19090
-Lane 1
-Bet: 100 adena
-1st place: Lane [4]
-Odds:[0.01] +
Race #%raceId%
+Lane: %lane%
+Bet: %bet% Adena
+1st place: Lane [%firstLane%]
+Odds: [%odd%]
@@ -18,8 +18,8 @@ Odds:[0.01] - + - +
\ No newline at end of file diff --git a/L2J_Mobius_CT_2.6_HighFive/dist/game/data/html/default/30995-9.htm b/L2J_Mobius_CT_2.6_HighFive/dist/game/data/html/default/30995-9.htm index 6d8775492d..5cf365047b 100644 --- a/L2J_Mobius_CT_2.6_HighFive/dist/game/data/html/default/30995-9.htm +++ b/L2J_Mobius_CT_2.6_HighFive/dist/game/data/html/default/30995-9.htm @@ -7,41 +7,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +%infos%
7 th8 Lane 1 Lane0.00 Times
6 th4 Lane 8 Lane0.00 Times
5 th7 Lane 2 Lane0.00 Times
4 th6 Lane 8 Lane0.00 Times
3 th4 Lane 1 Lane0.00 Times
2 th1 Lane 8 Lane0.00 Times
1 th7 Lane 6 Lane0.00 Times
diff --git a/L2J_Mobius_CT_2.6_HighFive/dist/game/data/html/default/30995.htm b/L2J_Mobius_CT_2.6_HighFive/dist/game/data/html/default/30995.htm index 6ed69be1b8..8109a968ae 100644 --- a/L2J_Mobius_CT_2.6_HighFive/dist/game/data/html/default/30995.htm +++ b/L2J_Mobius_CT_2.6_HighFive/dist/game/data/html/default/30995.htm @@ -7,10 +7,11 @@ View Odds View Monster Information Purchase Ticket -Calculate Winnings -View Past Results +Calculate Winnings +View Past Results Exit the monster race track. + diff --git a/L2J_Mobius_CT_2.6_HighFive/dist/game/data/scripts/handlers/MasterHandler.java b/L2J_Mobius_CT_2.6_HighFive/dist/game/data/scripts/handlers/MasterHandler.java index def51cbcbb..0fd49e5a15 100644 --- a/L2J_Mobius_CT_2.6_HighFive/dist/game/data/scripts/handlers/MasterHandler.java +++ b/L2J_Mobius_CT_2.6_HighFive/dist/game/data/scripts/handlers/MasterHandler.java @@ -101,7 +101,6 @@ import handlers.admincommandhandlers.AdminManor; import handlers.admincommandhandlers.AdminMenu; import handlers.admincommandhandlers.AdminMessages; import handlers.admincommandhandlers.AdminMobGroup; -import handlers.admincommandhandlers.AdminMonsterRace; import handlers.admincommandhandlers.AdminOnline; import handlers.admincommandhandlers.AdminPForge; import handlers.admincommandhandlers.AdminPathNode; @@ -401,7 +400,6 @@ public class MasterHandler AdminMenu.class, AdminMessages.class, AdminMobGroup.class, - AdminMonsterRace.class, AdminOnline.class, AdminPathNode.class, AdminPetition.class, diff --git a/L2J_Mobius_CT_2.6_HighFive/dist/game/data/scripts/handlers/admincommandhandlers/AdminMonsterRace.java b/L2J_Mobius_CT_2.6_HighFive/dist/game/data/scripts/handlers/admincommandhandlers/AdminMonsterRace.java deleted file mode 100644 index bf504a5032..0000000000 --- a/L2J_Mobius_CT_2.6_HighFive/dist/game/data/scripts/handlers/admincommandhandlers/AdminMonsterRace.java +++ /dev/null @@ -1,168 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * This program 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. - * - * This program 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 . - */ -package handlers.admincommandhandlers; - -import org.l2jmobius.commons.concurrent.ThreadPool; -import org.l2jmobius.gameserver.MonsterRace; -import org.l2jmobius.gameserver.handler.IAdminCommandHandler; -import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance; -import org.l2jmobius.gameserver.network.SystemMessageId; -import org.l2jmobius.gameserver.network.serverpackets.DeleteObject; -import org.l2jmobius.gameserver.network.serverpackets.MonRaceInfo; -import org.l2jmobius.gameserver.network.serverpackets.PlaySound; -import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; - -/** - * This class handles following admin commands: - invul = turns invulnerability on/off - * @version $Revision: 1.1.6.4 $ $Date: 2007/07/31 10:06:00 $ - */ -public class AdminMonsterRace implements IAdminCommandHandler -{ - // private static final Logger LOGGER = Logger.getLogger(AdminMonsterRace.class.getName()); - - private static final String[] ADMIN_COMMANDS = - { - "admin_mons" - }; - - protected static int state = -1; - - @Override - public boolean useAdminCommand(String command, PlayerInstance activeChar) - { - if (command.equalsIgnoreCase("admin_mons")) - { - handleSendPacket(activeChar); - } - return true; - } - - @Override - public String[] getAdminCommandList() - { - return ADMIN_COMMANDS; - } - - private void handleSendPacket(PlayerInstance activeChar) - { - /* - * -1 0 to initialize the race 0 15322 to start race 13765 -1 in middle of race -1 0 to end the race 8003 to 8027 - */ - - final int[][] codes = - { - { - -1, - 0 - }, - { - 0, - 15322 - }, - { - 13765, - -1 - }, - { - -1, - 0 - } - }; - final MonsterRace race = MonsterRace.getInstance(); - - if (state == -1) - { - state++; - race.newRace(); - race.newSpeeds(); - final MonRaceInfo spk = new MonRaceInfo(codes[state][0], codes[state][1], race.getMonsters(), race.getSpeeds()); - activeChar.sendPacket(spk); - activeChar.broadcastPacket(spk); - } - else if (state == 0) - { - state++; - final SystemMessage sm = new SystemMessage(SystemMessageId.THEY_RE_OFF); - sm.addInt(0); - activeChar.sendPacket(sm); - final PlaySound sRace = new PlaySound(1, "S_Race", 0, 0, 0, 0, 0); - activeChar.sendPacket(sRace); - activeChar.broadcastPacket(sRace); - final PlaySound sRace2 = new PlaySound(0, "ItemSound2.race_start", 1, 121209259, 12125, 182487, -3559); - activeChar.sendPacket(sRace2); - activeChar.broadcastPacket(sRace2); - final MonRaceInfo spk = new MonRaceInfo(codes[state][0], codes[state][1], race.getMonsters(), race.getSpeeds()); - activeChar.sendPacket(spk); - activeChar.broadcastPacket(spk); - - ThreadPool.schedule(new RunRace(codes, activeChar), 5000); - } - - } - - class RunRace implements Runnable - { - - private final int[][] codes; - private final PlayerInstance activeChar; - - public RunRace(int[][] pCodes, PlayerInstance pActiveChar) - { - codes = pCodes; - activeChar = pActiveChar; - } - - @Override - public void run() - { - // int[][] speeds1 = MonsterRace.getInstance().getSpeeds(); - // MonsterRace.getInstance().newSpeeds(); - // int[][] speeds2 = MonsterRace.getInstance().getSpeeds(); - /* - * int[] speed = new int[8]; for (int i=0; i<8; i++) { for (int j=0; j<20; j++) { //_log.info("Adding "+speeds1[i][j] +" and "+ speeds2[i][j]); speed[i] += (speeds1[i][j]*1); // + (speeds2[i][j]*1); } LOGGER.info("Total speed for "+(i+1)+" = "+speed[i]); } - */ - - final MonRaceInfo spk = new MonRaceInfo(codes[2][0], codes[2][1], MonsterRace.getInstance().getMonsters(), MonsterRace.getInstance().getSpeeds()); - activeChar.sendPacket(spk); - activeChar.broadcastPacket(spk); - ThreadPool.schedule(new RunEnd(activeChar), 30000); - } - } - - private static class RunEnd implements Runnable - { - private final PlayerInstance activeChar; - - public RunEnd(PlayerInstance pActiveChar) - { - activeChar = pActiveChar; - } - - @Override - public void run() - { - DeleteObject obj = null; - for (int i = 0; i < 8; i++) - { - obj = new DeleteObject(MonsterRace.getInstance().getMonsters()[i]); - activeChar.sendPacket(obj); - activeChar.broadcastPacket(obj); - } - state = -1; - } - } -} diff --git a/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/GameServer.java b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/GameServer.java index 36dff336df..32e232699f 100644 --- a/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/GameServer.java +++ b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/GameServer.java @@ -134,6 +134,7 @@ import org.l2jmobius.gameserver.instancemanager.TerritoryWarManager; import org.l2jmobius.gameserver.instancemanager.WalkingManager; import org.l2jmobius.gameserver.instancemanager.ZoneManager; import org.l2jmobius.gameserver.instancemanager.games.Lottery; +import org.l2jmobius.gameserver.instancemanager.games.MonsterRace; import org.l2jmobius.gameserver.model.AutoSpawnHandler; import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.entity.Hero; diff --git a/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/MonsterRace.java b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/MonsterRace.java deleted file mode 100644 index da1a3f8676..0000000000 --- a/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/MonsterRace.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * This file is part of the L2J Mobius project. - * - * This program 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. - * - * This program 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 . - */ -package org.l2jmobius.gameserver; - -import java.util.logging.Level; -import java.util.logging.Logger; - -import org.l2jmobius.commons.util.Rnd; -import org.l2jmobius.gameserver.data.xml.impl.NpcData; -import org.l2jmobius.gameserver.model.actor.Npc; -import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate; - -public class MonsterRace -{ - protected static final Logger LOGGER = Logger.getLogger(MonsterRace.class.getName()); - - private final Npc[] _monsters; - private int[][] _speeds; - private final int[] _first; - - private final int[] _second; - - protected MonsterRace() - { - _monsters = new Npc[8]; - _speeds = new int[8][20]; - _first = new int[2]; - _second = new int[2]; - } - - public void newRace() - { - int random = 0; - - for (int i = 0; i < 8; i++) - { - final int id = 31003; - random = Rnd.get(24); - while (true) - { - for (int j = i - 1; j >= 0; j--) - { - if (_monsters[j].getTemplate().getId() == (id + random)) - { - random = Rnd.get(24); - } - } - break; - } - try - { - final NpcTemplate template = NpcData.getInstance().getTemplate(id + random); - _monsters[i] = (Npc) Class.forName("org.l2jmobius.gameserver.model.actor.instance." + template.getType() + "Instance").getConstructors()[0].newInstance(template); - } - catch (Exception e) - { - LOGGER.log(Level.WARNING, "Unable to create monster!", e); - } - } - newSpeeds(); - } - - public void newSpeeds() - { - _speeds = new int[8][20]; - int total = 0; - _first[1] = 0; - _second[1] = 0; - for (int i = 0; i < 8; i++) - { - total = 0; - for (int j = 0; j < 20; j++) - { - _speeds[i][j] = j == 19 ? 100 : Rnd.get(60) + 65; - total += _speeds[i][j]; - } - if (total >= _first[1]) - { - _second[0] = _first[0]; - _second[1] = _first[1]; - _first[0] = 8 - i; - _first[1] = total; - } - else if (total >= _second[1]) - { - _second[0] = 8 - i; - _second[1] = total; - } - } - } - - /** - * @return Returns the monsters. - */ - public Npc[] getMonsters() - { - return _monsters; - } - - /** - * @return Returns the speeds. - */ - public int[][] getSpeeds() - { - return _speeds; - } - - public int getFirstPlace() - { - return _first[0]; - } - - public int getSecondPlace() - { - return _second[0]; - } - - public static MonsterRace getInstance() - { - return SingletonHolder.INSTANCE; - } - - private static class SingletonHolder - { - protected static final MonsterRace INSTANCE = new MonsterRace(); - } -} diff --git a/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/instancemanager/games/MonsterRace.java b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/instancemanager/games/MonsterRace.java new file mode 100644 index 0000000000..2fa4c519f6 --- /dev/null +++ b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/instancemanager/games/MonsterRace.java @@ -0,0 +1,623 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program 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. + * + * This program 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 . + */ +package org.l2jmobius.gameserver.instancemanager.games; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.Config; +import org.l2jmobius.commons.concurrent.ThreadPool; +import org.l2jmobius.commons.database.DatabaseFactory; +import org.l2jmobius.commons.util.Rnd; +import org.l2jmobius.gameserver.data.xml.impl.NpcData; +import org.l2jmobius.gameserver.model.actor.Npc; +import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.model.zone.type.DerbyTrackZone; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.serverpackets.DeleteObject; +import org.l2jmobius.gameserver.network.serverpackets.MonRaceInfo; +import org.l2jmobius.gameserver.network.serverpackets.PlaySound; +import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; +import org.l2jmobius.gameserver.util.Broadcast; + +public class MonsterRace +{ + protected static final Logger LOGGER = Logger.getLogger(MonsterRace.class.getName()); + + public static enum RaceState + { + ACCEPTING_BETS, + WAITING, + STARTING_RACE, + RACE_END + } + + protected static final PlaySound SOUND_1 = new PlaySound(1, "S_Race", 0, 0, 0, 0, 0); + protected static final PlaySound SOUND_2 = new PlaySound("ItemSound2.race_start"); + + protected static final int[][] CODES = + { + { + -1, + 0 + }, + { + 0, + 15322 + }, + { + 13765, + -1 + } + }; + + protected final List _npcTemplates = new ArrayList<>(); // List holding npc templates, shuffled on a new race. + protected final List _history = new ArrayList<>(); // List holding old race records. + protected final Map _betsPerLane = new ConcurrentHashMap<>(); // Map holding all bets for each lane ; values setted to 0 after every race. + protected final List _odds = new ArrayList<>(); // List holding sorted odds per lane ; cleared at new odds calculation. + + protected int _raceNumber = 1; + protected int _finalCountdown = 0; + protected RaceState _state = RaceState.RACE_END; + + protected MonRaceInfo _packet; + + private final Npc[] _monsters = new Npc[8]; + private int[][] _speeds = new int[8][20]; + private final int[] _first = new int[2]; + private final int[] _second = new int[2]; + + protected MonsterRace() + { + if (!Config.ALLOW_RACE) + { + return; + } + + // Feed _history with previous race results. + loadHistory(); + + // Feed _betsPerLane with stored informations on bets. + loadBets(); + + // Feed _npcTemplates, we will only have to shuffle it when needed. + for (int i = 31003; i < 31027; i++) + { + _npcTemplates.add(i); + } + + ThreadPool.scheduleAtFixedRate(new Announcement(), 0, 1000); + } + + public static class HistoryInfo + { + private final int _raceId; + private int _first; + private int _second; + private double _oddRate; + + public HistoryInfo(int raceId, int first, int second, double oddRate) + { + _raceId = raceId; + _first = first; + _second = second; + _oddRate = oddRate; + } + + public int getRaceId() + { + return _raceId; + } + + public int getFirst() + { + return _first; + } + + public int getSecond() + { + return _second; + } + + public double getOddRate() + { + return _oddRate; + } + + public void setFirst(int first) + { + _first = first; + } + + public void setSecond(int second) + { + _second = second; + } + + public void setOddRate(double oddRate) + { + _oddRate = oddRate; + } + } + + private class Announcement implements Runnable + { + public Announcement() + { + } + + @Override + public void run() + { + if (_finalCountdown > 1200) + { + _finalCountdown = 0; + } + + switch (_finalCountdown) + { + case 0: + { + newRace(); + newSpeeds(); + + _state = RaceState.ACCEPTING_BETS; + _packet = new MonRaceInfo(CODES[0][0], CODES[0][1], getMonsters(), getSpeeds()); + + final SystemMessage msg = new SystemMessage(SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, _packet, msg); + 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 + { + final SystemMessage msg = new SystemMessage(SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + break; + } + case 300: // 5 min + { + final SystemMessage msg = new SystemMessage(SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S); + msg2.addInt(10); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + break; + } + case 600: // 10 min + { + final SystemMessage msg = new SystemMessage(SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S); + msg2.addInt(5); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + break; + } + case 840: // 14 min + { + final SystemMessage msg = new SystemMessage(SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S); + msg2.addInt(1); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + break; + } + case 900: // 15 min + { + _state = RaceState.WAITING; + + calculateOdds(); + + final SystemMessage msg = new SystemMessage(SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.TICKETS_SALES_ARE_CLOSED_FOR_MONSTER_RACE_S1_ODDS_ARE_POSTED); + msg2.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + break; + } + case 960: // 16 min + case 1020: // 17 min + { + final int minutes = (_finalCountdown == 960) ? 2 : 1; + final SystemMessage msg = new SystemMessage(SystemMessageId.MONSTER_RACE_S2_WILL_BEGIN_IN_S1_MINUTE_S); + msg.addInt(minutes); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + break; + } + case 1050: // 17 min 30 sec + { + final SystemMessage msg = new SystemMessage(SystemMessageId.MONSTER_RACE_S1_WILL_BEGIN_IN_30_SECONDS); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + break; + } + case 1070: // 17 min 50 sec + { + final SystemMessage msg = new SystemMessage(SystemMessageId.MONSTER_RACE_S1_IS_ABOUT_TO_BEGIN_COUNTDOWN_IN_FIVE_SECONDS); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + 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; + final SystemMessage msg = new SystemMessage(SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SECOND_S); + msg.addInt(seconds); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + break; + } + case 1080: // 18 min + { + _state = RaceState.STARTING_RACE; + _packet = new MonRaceInfo(CODES[1][0], CODES[1][1], getMonsters(), getSpeeds()); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, new SystemMessage(SystemMessageId.THEY_RE_OFF), SOUND_1, SOUND_2, _packet); + break; + } + case 1085: // 18 min 5 sec + { + _packet = new MonRaceInfo(CODES[2][0], CODES[2][1], getMonsters(), getSpeeds()); + + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, _packet); + break; + } + case 1115: // 18 min 35 sec + { + _state = RaceState.RACE_END; + + // Populate history info with data, stores it in database. + final HistoryInfo info = _history.get(_history.size() - 1); + info.setFirst(getFirstPlace()); + info.setSecond(getSecondPlace()); + info.setOddRate(_odds.get(getFirstPlace() - 1)); + + saveHistory(info); + clearBets(); + + final SystemMessage msg = new SystemMessage(SystemMessageId.FIRST_PRIZE_GOES_TO_THE_PLAYER_IN_LANE_S1_SECOND_PRIZE_GOES_TO_THE_PLAYER_IN_LANE_S2); + msg.addInt(getFirstPlace()); + msg.addInt(getSecondPlace()); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.MONSTER_RACE_S1_IS_FINISHED); + msg2.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + _raceNumber++; + break; + } + case 1140: // 19 min + { + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, new DeleteObject(getMonsters()[0]), new DeleteObject(getMonsters()[1]), new DeleteObject(getMonsters()[2]), new DeleteObject(getMonsters()[3]), new DeleteObject(getMonsters()[4]), new DeleteObject(getMonsters()[5]), new DeleteObject(getMonsters()[6]), new DeleteObject(getMonsters()[7])); + break; + } + } + _finalCountdown += 1; + } + } + + public void newRace() + { + // Edit _history. + _history.add(new HistoryInfo(_raceNumber, 0, 0, 0)); + + // Randomize _npcTemplates. + Collections.shuffle(_npcTemplates); + + // Setup 8 new creatures ; pickup the first 8 from _npcTemplates. + for (int i = 0; i < 8; i++) + { + try + { + final NpcTemplate template = NpcData.getInstance().getTemplate(_npcTemplates.get(i)); + _monsters[i] = (Npc) Class.forName("org.l2jmobius.gameserver.model.actor.instance." + template.getType() + "Instance").getConstructors()[0].newInstance(template); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "", e); + } + } + } + + public void newSpeeds() + { + _speeds = new int[8][20]; + int total = 0; + _first[1] = 0; + _second[1] = 0; + + for (int i = 0; i < 8; i++) + { + total = 0; + for (int j = 0; j < 20; j++) + { + if (j == 19) + { + _speeds[i][j] = 100; + } + else + { + _speeds[i][j] = Rnd.get(60) + 65; + } + total += _speeds[i][j]; + } + + if (total >= _first[1]) + { + _second[0] = _first[0]; + _second[1] = _first[1]; + _first[0] = 8 - i; + _first[1] = total; + } + else if (total >= _second[1]) + { + _second[0] = 8 - i; + _second[1] = total; + } + } + } + + /** + * Load past races informations, feeding _history arrayList.
+ * Also sets _raceNumber, based on latest HistoryInfo loaded. + */ + protected void loadHistory() + { + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("SELECT * FROM mdt_history"); + ResultSet rset = statement.executeQuery(); + + while (rset.next()) + { + _history.add(new HistoryInfo(rset.getInt("race_id"), rset.getInt("first"), rset.getInt("second"), rset.getDouble("odd_rate"))); + _raceNumber++; + } + rset.close(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't load history: " + e.getMessage(), e); + } + LOGGER.info("MonsterRace: loaded " + _history.size() + " records, currently on race #" + _raceNumber); + } + + /** + * Save an history record into database. + * @param history The infos to store. + */ + protected void saveHistory(HistoryInfo history) + { + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("INSERT INTO mdt_history (race_id, first, second, odd_rate) VALUES (?,?,?,?)"); + statement.setInt(1, history.getRaceId()); + statement.setInt(2, history.getFirst()); + statement.setInt(3, history.getSecond()); + statement.setDouble(4, history.getOddRate()); + statement.execute(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't save history: " + e.getMessage(), e); + } + } + + /** + * Load current bets per lane ; initialize the map keys. + */ + protected void loadBets() + { + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("SELECT * FROM mdt_bets"); + ResultSet rset = statement.executeQuery(); + + while (rset.next()) + { + setBetOnLane(rset.getInt("lane_id"), rset.getLong("bet"), false); + } + + rset.close(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't load bets: " + e.getMessage(), e); + } + } + + /** + * Save the current lane bet into database. + * @param lane : The lane to affect. + * @param sum : The sum to set. + */ + protected void saveBet(int lane, long sum) + { + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("REPLACE INTO mdt_bets (lane_id, bet) VALUES (?,?)"); + statement.setInt(1, lane); + statement.setLong(2, sum); + statement.execute(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't save bet: " + e.getMessage(), e); + } + } + + /** + * Clear all lanes bets, either on database or Map. + */ + protected void clearBets() + { + for (int key : _betsPerLane.keySet()) + { + _betsPerLane.put(key, 0L); + } + + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("UPDATE mdt_bets SET bet = 0"); + statement.execute(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't clear bets: " + e.getMessage(), e); + } + } + + /** + * Setup lane bet, based on previous value (if any). + * @param lane : The lane to edit. + * @param amount : The amount to add. + * @param saveOnDb : Should it be saved on db or not. + */ + public void setBetOnLane(int lane, long amount, boolean saveOnDb) + { + final long sum = (_betsPerLane.containsKey(lane)) ? _betsPerLane.get(lane) + amount : amount; + + _betsPerLane.put(lane, sum); + + if (saveOnDb) + { + saveBet(lane, sum); + } + } + + /** + * Calculate odds for every lane, based on others lanes. + */ + protected void calculateOdds() + { + // Clear previous List holding old odds. + _odds.clear(); + + // Sort bets lanes per lane. + final Map 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; + } + + // 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)); + } + } + + public Npc[] getMonsters() + { + return _monsters; + } + + public int[][] getSpeeds() + { + return _speeds; + } + + public int getFirstPlace() + { + return _first[0]; + } + + public int getSecondPlace() + { + return _second[0]; + } + + public MonRaceInfo getRacePacket() + { + return _packet; + } + + public RaceState getCurrentRaceState() + { + return _state; + } + + public int getRaceNumber() + { + return _raceNumber; + } + + public List getHistory() + { + return _history; + } + + public List getOdds() + { + return _odds; + } + + public static MonsterRace getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final MonsterRace INSTANCE = new MonsterRace(); + } +} \ No newline at end of file diff --git a/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/model/actor/instance/RaceManagerInstance.java b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/model/actor/instance/RaceManagerInstance.java index 62fa858adf..e9aa717369 100644 --- a/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/model/actor/instance/RaceManagerInstance.java +++ b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/model/actor/instance/RaceManagerInstance.java @@ -16,67 +16,26 @@ */ package org.l2jmobius.gameserver.model.actor.instance; -import java.util.ArrayList; import java.util.List; +import java.util.Locale; -import org.l2jmobius.commons.concurrent.ThreadPool; -import org.l2jmobius.gameserver.MonsterRace; -import org.l2jmobius.gameserver.enums.InstanceType; +import org.l2jmobius.Config; +import org.l2jmobius.commons.util.StringUtil; import org.l2jmobius.gameserver.idfactory.IdFactory; +import org.l2jmobius.gameserver.instancemanager.games.MonsterRace; +import org.l2jmobius.gameserver.instancemanager.games.MonsterRace.HistoryInfo; +import org.l2jmobius.gameserver.instancemanager.games.MonsterRace.RaceState; import org.l2jmobius.gameserver.model.actor.Npc; import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate; -import org.l2jmobius.gameserver.model.itemcontainer.Inventory; import org.l2jmobius.gameserver.model.items.instance.ItemInstance; import org.l2jmobius.gameserver.network.SystemMessageId; import org.l2jmobius.gameserver.network.serverpackets.ActionFailed; -import org.l2jmobius.gameserver.network.serverpackets.DeleteObject; -import org.l2jmobius.gameserver.network.serverpackets.IClientOutgoingPacket; -import org.l2jmobius.gameserver.network.serverpackets.InventoryUpdate; -import org.l2jmobius.gameserver.network.serverpackets.MonRaceInfo; import org.l2jmobius.gameserver.network.serverpackets.NpcHtmlMessage; -import org.l2jmobius.gameserver.network.serverpackets.PlaySound; import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; -import org.l2jmobius.gameserver.util.Broadcast; public class RaceManagerInstance extends Npc { - public static final int LANES = 8; - public static final int WINDOW_START = 0; - - private static List _managers; - protected static int _raceNumber = 4; - - // Time Constants - private static final long SECOND = 1000; - private static final long MINUTE = 60 * SECOND; - - private static int _minutes = 5; - - // States - private static final int ACCEPTING_BETS = 0; - private static final int WAITING = 1; - private static final int STARTING_RACE = 2; - private static final int RACE_END = 3; - private static int _state = RACE_END; - - protected static final int[][] _codes = - { - { - -1, - 0 - }, - { - 0, - 15322 - }, - { - 13765, - -1 - } - }; - private static boolean _notInitialized = true; - protected static MonRaceInfo _packet; - protected static final int[] _cost = + protected static final int TICKET_PRICES[] = { 100, 500, @@ -88,453 +47,331 @@ public class RaceManagerInstance extends Npc 100000 }; - /** - * Creates a race manager. - * @param template the race manager NPC template - */ public RaceManagerInstance(NpcTemplate template) { super(template); - setInstanceType(InstanceType.RaceManagerInstance); - if (_notInitialized) - { - _notInitialized = false; - - _managers = new ArrayList<>(); - - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1), 0, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1), 30 * SECOND, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1), MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1), MINUTE + (30 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S), 2 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S), 3 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S), 4 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S), 5 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S), 6 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.TICKETS_SALES_ARE_CLOSED_FOR_MONSTER_RACE_S1_ODDS_ARE_POSTED), 7 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.MONSTER_RACE_S2_WILL_BEGIN_IN_S1_MINUTE_S), 7 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.MONSTER_RACE_S2_WILL_BEGIN_IN_S1_MINUTE_S), 8 * MINUTE, 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.MONSTER_RACE_S1_WILL_BEGIN_IN_30_SECONDS), (8 * MINUTE) + (30 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.MONSTER_RACE_S1_IS_ABOUT_TO_BEGIN_COUNTDOWN_IN_FIVE_SECONDS), (8 * MINUTE) + (50 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SECOND_S), (8 * MINUTE) + (55 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SECOND_S), (8 * MINUTE) + (56 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SECOND_S), (8 * MINUTE) + (57 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SECOND_S), (8 * MINUTE) + (58 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SECOND_S), (8 * MINUTE) + (59 * SECOND), 10 * MINUTE); - ThreadPool.scheduleAtFixedRate(new Announcement(SystemMessageId.THEY_RE_OFF), 9 * MINUTE, 10 * MINUTE); - } - _managers.add(this); - } - - class Announcement implements Runnable - { - private final SystemMessageId _type; - - public Announcement(SystemMessageId pType) - { - _type = pType; - } - - @Override - public void run() - { - makeAnnouncement(_type); - } - } - - public void makeAnnouncement(SystemMessageId type) - { - final SystemMessage sm = new SystemMessage(type); - switch (type.getId()) - { - case 816: // SystemMessageId.MONSRACE_TICKETS_AVAILABLE_FOR_S1_RACE - case 817: // SystemMessageId.MONSRACE_TICKETS_NOW_AVAILABLE_FOR_S1_RACE - { - if (_state != ACCEPTING_BETS) - {// LOGGER.info("Race Initializing"); - _state = ACCEPTING_BETS; - startRace(); - } // else{_log.info("Race open");} - sm.addInt(_raceNumber); - break; - } - case 818: // SystemMessageId.MONSRACE_TICKETS_STOP_IN_S1_MINUTES - case 820: // SystemMessageId.MONSRACE_S2_BEGINS_IN_S1_MINUTES - case 823: // SystemMessageId.MONSRACE_BEGINS_IN_S1_SECONDS - { - sm.addInt(_minutes); - if (type.getId() == 820) - { - sm.addInt(_raceNumber); - } - _minutes--; - break; - } - case 819: // SystemMessageId.MONSRACE_S1_TICKET_SALES_CLOSED - { - // LOGGER.info("Sales closed"); - sm.addInt(_raceNumber); - _state = WAITING; - _minutes = 2; - break; - } - case 821: // SystemMessageId.MONSRACE_S1_BEGINS_IN_30_SECONDS - case 822: // SystemMessageId.MONSRACE_S1_COUNTDOWN_IN_FIVE_SECONDS - case 825: // SystemMessageId.MONSRACE_S1_RACE_END - { - sm.addInt(_raceNumber); - _minutes = 5; - break; - } - case 826: // SystemMessageId.MONSRACE_FIRST_PLACE_S1_SECOND_S2 - { - // LOGGER.info("Placing"); - _state = RACE_END; - sm.addInt(MonsterRace.getInstance().getFirstPlace()); - sm.addInt(MonsterRace.getInstance().getSecondPlace()); - break; - } - } - // _logn.info("Counter: "+minutes); - // LOGGER.info("State: "+state); - broadcast(sm); - // LOGGER.info("Player's known: "+getKnownPlayers().size()); - - if (type == SystemMessageId.THEY_RE_OFF) - { - // LOGGER.info("Starting race"); - _state = STARTING_RACE; - startRace(); - _minutes = 5; - } - } - - protected void broadcast(IClientOutgoingPacket pkt) - { - for (RaceManagerInstance manager : _managers) - { - if (!manager.isDead()) - { - Broadcast.toKnownPlayers(manager, pkt); - } - } - } - - public void sendMonsterInfo() - { - broadcast(_packet); - } - - private void startRace() - { - final MonsterRace race = MonsterRace.getInstance(); - if (_state == STARTING_RACE) - { - // state++; - broadcast(new PlaySound(1, "S_Race", 0, 0, 0, 0, 0)); - broadcast(new PlaySound(0, "ItemSound2.race_start", 1, 121209259, 12125, 182487, -3559)); - _packet = new MonRaceInfo(_codes[1][0], _codes[1][1], race.getMonsters(), race.getSpeeds()); - sendMonsterInfo(); - - ThreadPool.schedule(new RunRace(), 5000); - } - else - { - // state++; - race.newRace(); - race.newSpeeds(); - _packet = new MonRaceInfo(_codes[0][0], _codes[0][1], race.getMonsters(), race.getSpeeds()); - sendMonsterInfo(); - } } @Override public void onBypassFeedback(PlayerInstance player, String command) { - if (command.startsWith("BuyTicket") && (_state != ACCEPTING_BETS)) - { - player.sendPacket(SystemMessageId.MONSTER_RACE_TICKETS_ARE_NO_LONGER_AVAILABLE); - command = "Chat 0"; - } - if (command.startsWith("ShowOdds") && (_state == ACCEPTING_BETS)) - { - player.sendPacket(SystemMessageId.MONSTER_RACE_PAYOUT_INFORMATION_IS_NOT_AVAILABLE_WHILE_TICKETS_ARE_BEING_SOLD); - command = "Chat 0"; - } - if (command.startsWith("BuyTicket")) { + if (!Config.ALLOW_RACE || (MonsterRace.getInstance().getCurrentRaceState() != RaceState.ACCEPTING_BETS)) + { + player.sendPacket(SystemMessageId.MONSTER_RACE_TICKETS_ARE_NO_LONGER_AVAILABLE); + super.onBypassFeedback(player, "Chat 0"); + return; + } + int 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))) { val = 0; } - showBuyTicket(player, val); + + String search, replace; + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + + if (val < 10) + { + html.setFile(player, getHtmlPath(getId(), 2)); + for (int i = 0; i < 8; i++) + { + int n = i + 1; + search = "Mob" + n; + html.replace(search, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); + } + search = "No1"; + if (val == 0) + { + html.replace(search, ""); + } + else + { + html.replace(search, val); + player.setRace(0, val); + } + } + else if (val < 20) + { + if (player.getRace(0) == 0) + { + return; + } + + html.setFile(player, getHtmlPath(getId(), 3)); + html.replace("0place", player.getRace(0)); + search = "Mob1"; + replace = MonsterRace.getInstance().getMonsters()[player.getRace(0) - 1].getTemplate().getName(); + html.replace(search, replace); + search = "0adena"; + + if (val == 10) + { + html.replace(search, ""); + } + else + { + html.replace(search, TICKET_PRICES[val - 11]); + player.setRace(1, val - 10); + } + } + else if (val == 20) + { + if ((player.getRace(0) == 0) || (player.getRace(1) == 0)) + { + return; + } + + html.setFile(player, getHtmlPath(getId(), 4)); + html.replace("0place", player.getRace(0)); + search = "Mob1"; + replace = MonsterRace.getInstance().getMonsters()[player.getRace(0) - 1].getTemplate().getName(); + 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); + } + else + { + if ((player.getRace(0) == 0) || (player.getRace(1) == 0)) + { + return; + } + + int ticket = player.getRace(0); + int priceId = player.getRace(1); + + if (!player.reduceAdena("Race", TICKET_PRICES[priceId - 1], this, true)) + { + return; + } + + player.setRace(0, 0); + player.setRace(1, 0); + + ItemInstance item = new ItemInstance(IdFactory.getInstance().getNextId(), 4443); + item.setCount(1); + item.setEnchantLevel(MonsterRace.getInstance().getRaceNumber()); + item.setCustomType1(ticket); + item.setCustomType2(TICKET_PRICES[priceId - 1] / 100); + + player.addItem("Race", item, player, false); + final SystemMessage msg = new SystemMessage(SystemMessageId.ACQUIRED_S1_S2); + msg.addInt(MonsterRace.getInstance().getRaceNumber()); + msg.addItemName(4443); + player.sendPacket(msg); + + // Refresh lane bet. + MonsterRace.getInstance().setBetOnLane(ticket, TICKET_PRICES[priceId - 1], true); + super.onBypassFeedback(player, "Chat 0"); + return; + } + html.replace("1race", MonsterRace.getInstance().getRaceNumber()); + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); } else if (command.equals("ShowOdds")) { - showOdds(player); - } - else if (command.equals("ShowInfo")) - { - showMonsterInfo(player); - } - else if (command.equals("calculateWin")) - { - // displayCalculateWinnings(player); - } - else if (command.equals("viewHistory")) - { - // displayHistory(player); - } - else - { - // getKnownList().removeKnownObject(player); - super.onBypassFeedback(player, command); - } - } - - public void showOdds(PlayerInstance player) - { - if (_state == ACCEPTING_BETS) - { - return; - } - final int npcId = getTemplate().getId(); - String filename; - String search; - final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); - filename = getHtmlPath(npcId, 5); - html.setFile(player, filename); - for (int i = 0; i < 8; i++) - { - final int n = i + 1; - search = "Mob" + n; - html.replace(search, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); - } - html.replace("1race", String.valueOf(_raceNumber)); - html.replace("%objectId%", String.valueOf(getObjectId())); - player.sendPacket(html); - player.sendPacket(ActionFailed.STATIC_PACKET); - } - - public void showMonsterInfo(PlayerInstance player) - { - final int npcId = getTemplate().getId(); - String filename; - String search; - final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); - filename = getHtmlPath(npcId, 6); - html.setFile(player, filename); - for (int i = 0; i < 8; i++) - { - final int n = i + 1; - search = "Mob" + n; - html.replace(search, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); - } - html.replace("%objectId%", String.valueOf(getObjectId())); - player.sendPacket(html); - player.sendPacket(ActionFailed.STATIC_PACKET); - } - - public void showBuyTicket(PlayerInstance player, int value) - { - if (_state != ACCEPTING_BETS) - { - return; - } - final int npcId = getTemplate().getId(); - SystemMessage sm; - String filename; - String search; - String replace; - final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); - if (value < 10) - { - filename = getHtmlPath(npcId, 2); - html.setFile(player, filename); + if (!Config.ALLOW_RACE || (MonsterRace.getInstance().getCurrentRaceState() == RaceState.ACCEPTING_BETS)) + { + player.sendPacket(SystemMessageId.MONSTER_RACE_PAYOUT_INFORMATION_IS_NOT_AVAILABLE_WHILE_TICKETS_ARE_BEING_SOLD); + super.onBypassFeedback(player, "Chat 0"); + return; + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 5)); for (int i = 0; i < 8; i++) { final int n = i + 1; - search = "Mob" + n; + + html.replace("Mob" + n, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); + + // Odd + final double odd = MonsterRace.getInstance().getOdds().get(i); + html.replace("Odd" + n, (odd > 0D) ? String.format(Locale.ENGLISH, "%.1f", odd) : "&$804;"); + } + html.replace("1race", MonsterRace.getInstance().getRaceNumber()); + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); + } + else if (command.equals("ShowInfo")) + { + if (!Config.ALLOW_RACE) + { + return; + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 6)); + + for (int i = 0; i < 8; i++) + { + int n = i + 1; + String search = "Mob" + n; html.replace(search, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); } - search = "No1"; - if (value == 0) - { - html.replace(search, ""); - } - else - { - html.replace(search, Integer.toString(value)); - player.setRace(0, value); - } + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); } - else if (value < 20) + else if (command.equals("ShowTickets")) { - if (player.getRace(0) == 0) + if (!Config.ALLOW_RACE) { + super.onBypassFeedback(player, "Chat 0"); return; } - filename = getHtmlPath(npcId, 3); - html.setFile(player, filename); - html.replace("0place", Integer.toString(player.getRace(0))); - search = "Mob1"; - replace = MonsterRace.getInstance().getMonsters()[player.getRace(0) - 1].getTemplate().getName(); - html.replace(search, replace); - search = "0adena"; - if (value == 10) + + // Generate data. + final StringBuilder sb = new StringBuilder(); + + // Retrieve player's tickets. + for (ItemInstance ticket : player.getInventory().getAllItemsByItemId(4443)) { - html.replace(search, ""); - } - else - { - html.replace(search, Integer.toString(_cost[value - 11])); - player.setRace(1, value - 10); + // Don't list current race tickets. + if (ticket.getEnchantLevel() == MonsterRace.getInstance().getRaceNumber()) + { + continue; + } + + StringUtil.append(sb, "", "" + ticket.getEnchantLevel(), " Race Number", "" + ticket.getCustomType1(), " Number", "" + (ticket.getCustomType2() * 100), " Adena"); } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 7)); + html.replace("%tickets%", sb.toString()); + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); } - else if (value == 20) + else if (command.startsWith("ShowTicket")) { - if ((player.getRace(0) == 0) || (player.getRace(1) == 0)) + // Retrieve ticket objectId. + final int val = Integer.parseInt(command.substring(11)); + if (!Config.ALLOW_RACE || (val == 0)) { + super.onBypassFeedback(player, "Chat 0"); return; } - filename = getHtmlPath(npcId, 4); - html.setFile(player, filename); - html.replace("0place", Integer.toString(player.getRace(0))); - search = "Mob1"; - replace = MonsterRace.getInstance().getMonsters()[player.getRace(0) - 1].getTemplate().getName(); - html.replace(search, replace); - search = "0adena"; - final int price = _cost[player.getRace(1) - 1]; - html.replace(search, Integer.toString(price)); - search = "0tax"; - final int tax = 0; - html.replace(search, Integer.toString(tax)); - search = "0total"; - final int total = price + tax; - html.replace(search, Integer.toString(total)); + + // Retrieve ticket on player's inventory. + final ItemInstance 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; + + // Retrieve HistoryInfo for that race. + final HistoryInfo info = MonsterRace.getInstance().getHistory().get(raceId - 1); + if (info == null) + { + super.onBypassFeedback(player, "Chat 0"); + return; + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 8)); + html.replace("%raceId%", raceId); + html.replace("%lane%", lane); + html.replace("%bet%", bet); + html.replace("%firstLane%", info.getFirst()); + html.replace("%odd%", (lane == info.getFirst()) ? String.format(Locale.ENGLISH, "%.2f", info.getOddRate()) : "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 (!Config.ALLOW_RACE || (val == 0)) + { + super.onBypassFeedback(player, "Chat 0"); + return; + } + + // Delete ticket on player's inventory. + final ItemInstance 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; + + // Retrieve HistoryInfo for that race. + final HistoryInfo info = MonsterRace.getInstance().getHistory().get(raceId - 1); + if (info == 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()) ? info.getOddRate() : 0.01)), this, true); + } + + super.onBypassFeedback(player, "Chat 0"); + return; + } + else if (command.equals("ViewHistory")) + { + if (!Config.ALLOW_RACE) + { + super.onBypassFeedback(player, "Chat 0"); + return; + } + + // Generate data. + final StringBuilder sb = new StringBuilder(); + + // Use whole history, pickup from 'last element' and stop at 'latest element - 7'. + final List history = MonsterRace.getInstance().getHistory(); + for (int i = history.size() - 1; i >= Math.max(0, history.size() - 7); i--) + { + final HistoryInfo info = history.get(i); + StringUtil.append(sb, "", "" + info.getRaceId(), " th", "" + info.getFirst(), " Lane ", "" + info.getSecond(), " Lane", String.format(Locale.ENGLISH, "%.2f", info.getOddRate()), " Times"); + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 9)); + html.replace("%infos%", sb.toString()); + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); } else { - if ((player.getRace(0) == 0) || (player.getRace(1) == 0)) - { - return; - } - final int ticket = player.getRace(0); - final int priceId = player.getRace(1); - if (!player.reduceAdena("Race", _cost[priceId - 1], this, true)) - { - return; - } - player.setRace(0, 0); - player.setRace(1, 0); - sm = new SystemMessage(SystemMessageId.ACQUIRED_S1_S2); - sm.addInt(_raceNumber); - sm.addItemName(4443); - player.sendPacket(sm); - final ItemInstance item = new ItemInstance(IdFactory.getInstance().getNextId(), 4443); - item.setCount(1); - item.setEnchantLevel(_raceNumber); - item.setCustomType1(ticket); - item.setCustomType2(_cost[priceId - 1] / 100); - player.getInventory().addItem("Race", item, player, this); - final InventoryUpdate iu = new InventoryUpdate(); - iu.addItem(item); - final ItemInstance adenaupdate = player.getInventory().getItemByItemId(Inventory.ADENA_ID); - iu.addModifiedItem(adenaupdate); - player.sendPacket(iu); - return; - } - html.replace("1race", String.valueOf(_raceNumber)); - html.replace("%objectId%", String.valueOf(getObjectId())); - player.sendPacket(html); - player.sendPacket(ActionFailed.STATIC_PACKET); - } - - public static class Race - { - private final Info[] _info; - - public Race(Info[] pInfo) - { - _info = pInfo; - } - - public Info getLaneInfo(int lane) - { - return _info[lane]; - } - - public class Info - { - private final int _id; - private final int _place; - private final int _odds; - private final int _payout; - - public Info(int pId, int pPlace, int pOdds, int pPayout) - { - _id = pId; - _place = pPlace; - _odds = pOdds; - _payout = pPayout; - } - - public int getId() - { - return _id; - } - - public int getOdds() - { - return _odds; - } - - public int getPayout() - { - return _payout; - } - - public int getPlace() - { - return _place; - } - } - - } - - class RunRace implements Runnable - { - @Override - public void run() - { - _packet = new MonRaceInfo(_codes[2][0], _codes[2][1], MonsterRace.getInstance().getMonsters(), MonsterRace.getInstance().getSpeeds()); - sendMonsterInfo(); - ThreadPool.schedule(new RunEnd(), 30000); - } - } - - class RunEnd implements Runnable - { - @Override - public void run() - { - makeAnnouncement(SystemMessageId.FIRST_PRIZE_GOES_TO_THE_PLAYER_IN_LANE_S1_SECOND_PRIZE_GOES_TO_THE_PLAYER_IN_LANE_S2); - makeAnnouncement(SystemMessageId.MONSTER_RACE_S1_IS_FINISHED); - _raceNumber++; - - DeleteObject obj = null; - for (int i = 0; i < 8; i++) - { - obj = new DeleteObject(MonsterRace.getInstance().getMonsters()[i]); - broadcast(obj); - } + super.onBypassFeedback(player, command); } } } diff --git a/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/util/Broadcast.java b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/util/Broadcast.java index b6216d939a..eaf0c4afb8 100644 --- a/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/util/Broadcast.java +++ b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/util/Broadcast.java @@ -21,9 +21,11 @@ import java.util.logging.Logger; import org.l2jmobius.gameserver.cache.RelationCache; import org.l2jmobius.gameserver.enums.ChatType; +import org.l2jmobius.gameserver.instancemanager.ZoneManager; import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.actor.Creature; import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance; +import org.l2jmobius.gameserver.model.zone.ZoneType; import org.l2jmobius.gameserver.network.serverpackets.CharInfo; import org.l2jmobius.gameserver.network.serverpackets.CreatureSay; import org.l2jmobius.gameserver.network.serverpackets.ExShowScreenMessage; @@ -193,4 +195,29 @@ public class Broadcast { toAllOnlinePlayers(new ExShowScreenMessage(text, 10000)); } + + /** + * Send a packet to all players in a specific zone type. + * @param ZoneType. + * @param zoneType : The zone type to send packets. + * @param packets : The packets to send. + */ + public static void toAllPlayersInZoneType(Class zoneType, IClientOutgoingPacket... packets) + { + for (ZoneType zone : ZoneManager.getInstance().getAllZones(zoneType)) + { + for (Creature creature : zone.getCharactersInside()) + { + if (creature == null) + { + continue; + } + + for (IClientOutgoingPacket packet : packets) + { + creature.sendPacket(packet); + } + } + } + } } diff --git a/L2J_Mobius_Classic_2.0_Saviors/dist/db_installer/sql/game/mdt_bets.sql b/L2J_Mobius_Classic_2.0_Saviors/dist/db_installer/sql/game/mdt_bets.sql new file mode 100644 index 0000000000..8d7d4cb738 --- /dev/null +++ b/L2J_Mobius_Classic_2.0_Saviors/dist/db_installer/sql/game/mdt_bets.sql @@ -0,0 +1,15 @@ +CREATE TABLE IF NOT EXISTS `mdt_bets` ( + `lane_id` INT(1) DEFAULT 0, + `bet` INT DEFAULT 0, + PRIMARY KEY (`lane_id`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci; + +INSERT INTO `mdt_bets` VALUES +('1','0'), +('2','0'), +('3','0'), +('4','0'), +('5','0'), +('6','0'), +('7','0'), +('8','0'); \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.0_Saviors/dist/db_installer/sql/game/mdt_history.sql b/L2J_Mobius_Classic_2.0_Saviors/dist/db_installer/sql/game/mdt_history.sql new file mode 100644 index 0000000000..06978f3377 --- /dev/null +++ b/L2J_Mobius_Classic_2.0_Saviors/dist/db_installer/sql/game/mdt_history.sql @@ -0,0 +1,7 @@ +CREATE TABLE IF NOT EXISTS `mdt_history` ( + `race_id` MEDIUMINT DEFAULT 0, + `first` INT(1) DEFAULT 0, + `second` INT(1) DEFAULT 0, + `odd_rate` DOUBLE(10,2) DEFAULT 0, + PRIMARY KEY (`race_id`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci; \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.0_Saviors/dist/game/config/General.ini b/L2J_Mobius_Classic_2.0_Saviors/dist/game/config/General.ini index bc4b0889cb..761f34bea4 100644 --- a/L2J_Mobius_Classic_2.0_Saviors/dist/game/config/General.ini +++ b/L2J_Mobius_Classic_2.0_Saviors/dist/game/config/General.ini @@ -370,6 +370,9 @@ DefaultFinishTime = 5 # Misc Settings # --------------------------------------------------------------------------- +# Default: True +AllowRace = True + # Default: True AllowWater = True diff --git a/L2J_Mobius_Classic_2.0_Saviors/dist/game/data/html/default/30995-1.htm b/L2J_Mobius_Classic_2.0_Saviors/dist/game/data/html/default/30995-1.htm new file mode 100644 index 0000000000..d38d518149 --- /dev/null +++ b/L2J_Mobius_Classic_2.0_Saviors/dist/game/data/html/default/30995-1.htm @@ -0,0 +1,22 @@ + +
+ +
+In a monster race eight monsters race at the same time. If you guess which monster is expected to finish first and buy that monster's race ticket, based upon the odds to win you will receive an appropriate amount of adena.
+Racing Schedule
+Monster races every 20 minutes in real time.
+Method of Race Participation
+You can purchase a monster race ticket through a race manager NPC. Only between 18 minutes before the start of a race until 3 minutes before the race begins you can buy a ticket for the applicable race. When you purchase a ticket through a conversation with a race manager, you can find out the names of participating monsters in the race and their current statistics. Five minutes before a race starts, the race manager will stop ticket sales and announce monsters' individual odds rate for the current race.
+Types of Race
+There are two types of race betting. The Win type betting means that you back a monster that will win the first prize. Place type of betting means that you will have to guess the 1st and 2nd winning monsters, regardless of who finishes first or second.
+Win +* Among the entire betting amount for Win betting, after subtracting the race track's own share of profit, the amount left will be your total winnings. Three minutes before the race each monster's expected odds are announced. Each Win ticket's dividend amount is calculated by multiplying the odds by the price of a ticket. The odds rate is never less than 1. +* The prize money does not get transferred to the next race.
+Place +* Place betting is a way of betting when you correctly guess the two monsters who will finish first and second regardless of their order. The method of calculating dividend amount is the same as Win betting.
+* Currently place betting is not offered.
+End of Race
+When a race is over, the race manager will shout out the results of the race. Through a conversation with a race manager NPC, you can trade your ticket for adena. By looking up on the Winning Information Data, you can find out the types of tickets and quantity of the winnings in the race you betted on and your win status and prize amount you won. If you won, you can receive your prize money thorough a race manager NPC. If you did not win, a race manager will buy back your ticket by paying you a very small amount of money.
+If a server goes down while tickets are being sold, or while a race is in progress, the race will be cancelled. You can receive a full refund for your ticket for a cancelled race through a race manager.
+ + \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.0_Saviors/dist/game/data/html/default/30995-2.htm b/L2J_Mobius_Classic_2.0_Saviors/dist/game/data/html/default/30995-2.htm new file mode 100644 index 0000000000..0362da5162 --- /dev/null +++ b/L2J_Mobius_Classic_2.0_Saviors/dist/game/data/html/default/30995-2.htm @@ -0,0 +1,94 @@ + +
Monster Race Betting To Win (Race # 1race) + + +
+ + + + + + + + +
Lane SelectionName of RacerRatingOdds
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
1Mob1&$745;13%
2Mob2&$745;6%
3Mob3&$747;4%
4Mob4&$745;12%
5Mob5&$745;14%
6Mob6&$745;13%
7Mob7&$745;13%
8Mob8&$746;14%
+ + + + + + + + + + +
Select Lane : No1
+ + \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.0_Saviors/dist/game/data/html/default/30995-3.htm b/L2J_Mobius_Classic_2.0_Saviors/dist/game/data/html/default/30995-3.htm new file mode 100644 index 0000000000..7ae65a37a4 --- /dev/null +++ b/L2J_Mobius_Classic_2.0_Saviors/dist/game/data/html/default/30995-3.htm @@ -0,0 +1,48 @@ + +
Monster Race Betting To Win (Race # 1race) + + +
+ + + + + + + + +
Lane SelectionRacer's Name RatingOdds
+ + + + + + + + + +
0placeMob1&$745;13%
+ + + + +
Select Bet Amount +100 Adena , 500 Adena +1,000 Adena , 5,000 Adena +10,000 Adena , 20,000 Adena +50,000 Adena , 100,000 Adena +
+ + + + + + + + + + + +
Purchase Price : 0adena
+ + \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.0_Saviors/dist/game/data/html/default/30995-4.htm b/L2J_Mobius_Classic_2.0_Saviors/dist/game/data/html/default/30995-4.htm new file mode 100644 index 0000000000..30c508b9cb --- /dev/null +++ b/L2J_Mobius_Classic_2.0_Saviors/dist/game/data/html/default/30995-4.htm @@ -0,0 +1,56 @@ + +
Monster Race Single Ticket Purchase (Race # 1race) + + +
+ + + + + + + +
Lane SelectionRacer's NameRatingOdds
+ + + + + + + + + +
0placeMob1&$745;13%
+ + + + + + + + + + + + + + + + + + +
Bet Amount
Bet Amount 0adena Adena
Tax 0tax Adena
Total 0total Adena
+
+
If the information is correct, please click Confirm.

+ + + + + + + + + + +
+ \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.0_Saviors/dist/game/data/html/default/30995-5.htm b/L2J_Mobius_Classic_2.0_Saviors/dist/game/data/html/default/30995-5.htm new file mode 100644 index 0000000000..59a05e14a0 --- /dev/null +++ b/L2J_Mobius_Classic_2.0_Saviors/dist/game/data/html/default/30995-5.htm @@ -0,0 +1,74 @@ + +
Monster Race Betting To Win View Odds (Race # 1race ) + + +
+ + + + + + +
LaneRacer's NameOdds
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
No.1Mob1Odd1
No.2Mob2Odd2
No.3Mob3Odd3
No.4Mob4Odd4
No.5Mob5Odd5
No.6Mob6Odd6
No.7Mob7Odd7
No.8Mob8Odd8
+ + + + + + + + + + +
+ \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.0_Saviors/dist/game/data/html/default/30995-6.htm b/L2J_Mobius_Classic_2.0_Saviors/dist/game/data/html/default/30995-6.htm new file mode 100644 index 0000000000..0d845cb665 --- /dev/null +++ b/L2J_Mobius_Classic_2.0_Saviors/dist/game/data/html/default/30995-6.htm @@ -0,0 +1,80 @@ + +
Participating Monsters + + +
+ + + + + + + +
LaneRacer's NameRatingOdds
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
1Mob1&$747;12%
2Mob2&$745;6%
3Mob3&$746;3%
4Mob4&$747;7%
5Mob5&$747;4%
6Mob6&$747;14%
7Mob7&$747;26%
8Mob8&$745;5%
+ + + + + + + + +
\ No newline at end of file diff --git a/L2J_Mobius_Classic_2.0_Saviors/dist/game/data/html/default/30995-7.htm b/L2J_Mobius_Classic_2.0_Saviors/dist/game/data/html/default/30995-7.htm new file mode 100644 index 0000000000..53eb213a52 --- /dev/null +++ b/L2J_Mobius_Classic_2.0_Saviors/dist/game/data/html/default/30995-7.htm @@ -0,0 +1,19 @@ + +
Confirm Monster Race Bet / Calculate Winnings
+ + + + + +
Winnings for a Race.LaneBet Amount
+ +%tickets% +
+ + + + + + + +
\ No newline at end of file diff --git a/L2J_Mobius_Classic_2.0_Saviors/dist/game/data/html/default/30995-8.htm b/L2J_Mobius_Classic_2.0_Saviors/dist/game/data/html/default/30995-8.htm new file mode 100644 index 0000000000..5f856a222f --- /dev/null +++ b/L2J_Mobius_Classic_2.0_Saviors/dist/game/data/html/default/30995-8.htm @@ -0,0 +1,25 @@ + +
Confirm Monster Race Bet / Calculate Winnings
+ + + + + + +
Race #%raceId%
+Lane: %lane%
+Bet: %bet% Adena
+1st place: Lane [%firstLane%]
+Odds: [%odd%] +
+ + + + + + + + + +
+ \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.0_Saviors/dist/game/data/html/default/30995-9.htm b/L2J_Mobius_Classic_2.0_Saviors/dist/game/data/html/default/30995-9.htm new file mode 100644 index 0000000000..c17ab4849a --- /dev/null +++ b/L2J_Mobius_Classic_2.0_Saviors/dist/game/data/html/default/30995-9.htm @@ -0,0 +1,22 @@ + +
View Monster Race Results
+ + + + +
Race Number1st Finish2nd FinishOdds Rate for Win Bet
+ + +%infos% +
+ + + + + + + + + +
+ \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.0_Saviors/dist/game/data/html/default/30995.htm b/L2J_Mobius_Classic_2.0_Saviors/dist/game/data/html/default/30995.htm index ca913c7dd6..271c4ee284 100644 --- a/L2J_Mobius_Classic_2.0_Saviors/dist/game/data/html/default/30995.htm +++ b/L2J_Mobius_Classic_2.0_Saviors/dist/game/data/html/default/30995.htm @@ -1,3 +1,15 @@ -Monster Race Manager:
- +
Monster Race Manager
+ + +
+ + + + + + + + + + \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.0_Saviors/dist/game/data/stats/npcs/30900-30999.xml b/L2J_Mobius_Classic_2.0_Saviors/dist/game/data/stats/npcs/30900-30999.xml index 16670573bc..8993995b8e 100644 --- a/L2J_Mobius_Classic_2.0_Saviors/dist/game/data/stats/npcs/30900-30999.xml +++ b/L2J_Mobius_Classic_2.0_Saviors/dist/game/data/stats/npcs/30900-30999.xml @@ -3591,7 +3591,7 @@ - + DWARF FEMALE diff --git a/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/Config.java b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/Config.java index 8038a17439..48d668a90c 100644 --- a/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/Config.java +++ b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/Config.java @@ -498,6 +498,7 @@ public class Config public static int INSTANCE_FINISH_TIME; public static boolean RESTORE_PLAYER_INSTANCE; public static int EJECT_DEAD_PLAYER_TIME; + public static boolean ALLOW_RACE; public static boolean ALLOW_WATER; public static boolean ALLOW_FISHING; public static boolean ALLOW_BOAT; @@ -1948,6 +1949,7 @@ public class Config INSTANCE_FINISH_TIME = General.getInt("DefaultFinishTime", 5); RESTORE_PLAYER_INSTANCE = General.getBoolean("RestorePlayerInstance", false); EJECT_DEAD_PLAYER_TIME = General.getInt("EjectDeadPlayerTime", 1); + ALLOW_RACE = General.getBoolean("AllowRace", true); ALLOW_WATER = General.getBoolean("AllowWater", true); ALLOW_FISHING = General.getBoolean("AllowFishing", true); ALLOW_MANOR = General.getBoolean("AllowManor", true); diff --git a/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/GameServer.java b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/GameServer.java index aa8c369958..91c661e1a1 100644 --- a/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/GameServer.java +++ b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/GameServer.java @@ -141,6 +141,7 @@ import org.l2jmobius.gameserver.instancemanager.SiegeGuardManager; import org.l2jmobius.gameserver.instancemanager.SiegeManager; import org.l2jmobius.gameserver.instancemanager.WalkingManager; import org.l2jmobius.gameserver.instancemanager.ZoneManager; +import org.l2jmobius.gameserver.instancemanager.games.MonsterRace; import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.entity.Hero; import org.l2jmobius.gameserver.model.events.EventDispatcher; @@ -401,6 +402,8 @@ public class GameServer ItemsAutoDestroy.getInstance(); } + MonsterRace.getInstance(); + TaskManager.getInstance(); AntiFeedManager.getInstance().registerEvent(AntiFeedManager.GAME_ID); diff --git a/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/instancemanager/games/MonsterRace.java b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/instancemanager/games/MonsterRace.java new file mode 100644 index 0000000000..2fa4c519f6 --- /dev/null +++ b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/instancemanager/games/MonsterRace.java @@ -0,0 +1,623 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program 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. + * + * This program 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 . + */ +package org.l2jmobius.gameserver.instancemanager.games; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.Config; +import org.l2jmobius.commons.concurrent.ThreadPool; +import org.l2jmobius.commons.database.DatabaseFactory; +import org.l2jmobius.commons.util.Rnd; +import org.l2jmobius.gameserver.data.xml.impl.NpcData; +import org.l2jmobius.gameserver.model.actor.Npc; +import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.model.zone.type.DerbyTrackZone; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.serverpackets.DeleteObject; +import org.l2jmobius.gameserver.network.serverpackets.MonRaceInfo; +import org.l2jmobius.gameserver.network.serverpackets.PlaySound; +import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; +import org.l2jmobius.gameserver.util.Broadcast; + +public class MonsterRace +{ + protected static final Logger LOGGER = Logger.getLogger(MonsterRace.class.getName()); + + public static enum RaceState + { + ACCEPTING_BETS, + WAITING, + STARTING_RACE, + RACE_END + } + + protected static final PlaySound SOUND_1 = new PlaySound(1, "S_Race", 0, 0, 0, 0, 0); + protected static final PlaySound SOUND_2 = new PlaySound("ItemSound2.race_start"); + + protected static final int[][] CODES = + { + { + -1, + 0 + }, + { + 0, + 15322 + }, + { + 13765, + -1 + } + }; + + protected final List _npcTemplates = new ArrayList<>(); // List holding npc templates, shuffled on a new race. + protected final List _history = new ArrayList<>(); // List holding old race records. + protected final Map _betsPerLane = new ConcurrentHashMap<>(); // Map holding all bets for each lane ; values setted to 0 after every race. + protected final List _odds = new ArrayList<>(); // List holding sorted odds per lane ; cleared at new odds calculation. + + protected int _raceNumber = 1; + protected int _finalCountdown = 0; + protected RaceState _state = RaceState.RACE_END; + + protected MonRaceInfo _packet; + + private final Npc[] _monsters = new Npc[8]; + private int[][] _speeds = new int[8][20]; + private final int[] _first = new int[2]; + private final int[] _second = new int[2]; + + protected MonsterRace() + { + if (!Config.ALLOW_RACE) + { + return; + } + + // Feed _history with previous race results. + loadHistory(); + + // Feed _betsPerLane with stored informations on bets. + loadBets(); + + // Feed _npcTemplates, we will only have to shuffle it when needed. + for (int i = 31003; i < 31027; i++) + { + _npcTemplates.add(i); + } + + ThreadPool.scheduleAtFixedRate(new Announcement(), 0, 1000); + } + + public static class HistoryInfo + { + private final int _raceId; + private int _first; + private int _second; + private double _oddRate; + + public HistoryInfo(int raceId, int first, int second, double oddRate) + { + _raceId = raceId; + _first = first; + _second = second; + _oddRate = oddRate; + } + + public int getRaceId() + { + return _raceId; + } + + public int getFirst() + { + return _first; + } + + public int getSecond() + { + return _second; + } + + public double getOddRate() + { + return _oddRate; + } + + public void setFirst(int first) + { + _first = first; + } + + public void setSecond(int second) + { + _second = second; + } + + public void setOddRate(double oddRate) + { + _oddRate = oddRate; + } + } + + private class Announcement implements Runnable + { + public Announcement() + { + } + + @Override + public void run() + { + if (_finalCountdown > 1200) + { + _finalCountdown = 0; + } + + switch (_finalCountdown) + { + case 0: + { + newRace(); + newSpeeds(); + + _state = RaceState.ACCEPTING_BETS; + _packet = new MonRaceInfo(CODES[0][0], CODES[0][1], getMonsters(), getSpeeds()); + + final SystemMessage msg = new SystemMessage(SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, _packet, msg); + 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 + { + final SystemMessage msg = new SystemMessage(SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + break; + } + case 300: // 5 min + { + final SystemMessage msg = new SystemMessage(SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S); + msg2.addInt(10); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + break; + } + case 600: // 10 min + { + final SystemMessage msg = new SystemMessage(SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S); + msg2.addInt(5); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + break; + } + case 840: // 14 min + { + final SystemMessage msg = new SystemMessage(SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S); + msg2.addInt(1); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + break; + } + case 900: // 15 min + { + _state = RaceState.WAITING; + + calculateOdds(); + + final SystemMessage msg = new SystemMessage(SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.TICKETS_SALES_ARE_CLOSED_FOR_MONSTER_RACE_S1_ODDS_ARE_POSTED); + msg2.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + break; + } + case 960: // 16 min + case 1020: // 17 min + { + final int minutes = (_finalCountdown == 960) ? 2 : 1; + final SystemMessage msg = new SystemMessage(SystemMessageId.MONSTER_RACE_S2_WILL_BEGIN_IN_S1_MINUTE_S); + msg.addInt(minutes); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + break; + } + case 1050: // 17 min 30 sec + { + final SystemMessage msg = new SystemMessage(SystemMessageId.MONSTER_RACE_S1_WILL_BEGIN_IN_30_SECONDS); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + break; + } + case 1070: // 17 min 50 sec + { + final SystemMessage msg = new SystemMessage(SystemMessageId.MONSTER_RACE_S1_IS_ABOUT_TO_BEGIN_COUNTDOWN_IN_FIVE_SECONDS); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + 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; + final SystemMessage msg = new SystemMessage(SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SECOND_S); + msg.addInt(seconds); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + break; + } + case 1080: // 18 min + { + _state = RaceState.STARTING_RACE; + _packet = new MonRaceInfo(CODES[1][0], CODES[1][1], getMonsters(), getSpeeds()); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, new SystemMessage(SystemMessageId.THEY_RE_OFF), SOUND_1, SOUND_2, _packet); + break; + } + case 1085: // 18 min 5 sec + { + _packet = new MonRaceInfo(CODES[2][0], CODES[2][1], getMonsters(), getSpeeds()); + + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, _packet); + break; + } + case 1115: // 18 min 35 sec + { + _state = RaceState.RACE_END; + + // Populate history info with data, stores it in database. + final HistoryInfo info = _history.get(_history.size() - 1); + info.setFirst(getFirstPlace()); + info.setSecond(getSecondPlace()); + info.setOddRate(_odds.get(getFirstPlace() - 1)); + + saveHistory(info); + clearBets(); + + final SystemMessage msg = new SystemMessage(SystemMessageId.FIRST_PRIZE_GOES_TO_THE_PLAYER_IN_LANE_S1_SECOND_PRIZE_GOES_TO_THE_PLAYER_IN_LANE_S2); + msg.addInt(getFirstPlace()); + msg.addInt(getSecondPlace()); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.MONSTER_RACE_S1_IS_FINISHED); + msg2.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + _raceNumber++; + break; + } + case 1140: // 19 min + { + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, new DeleteObject(getMonsters()[0]), new DeleteObject(getMonsters()[1]), new DeleteObject(getMonsters()[2]), new DeleteObject(getMonsters()[3]), new DeleteObject(getMonsters()[4]), new DeleteObject(getMonsters()[5]), new DeleteObject(getMonsters()[6]), new DeleteObject(getMonsters()[7])); + break; + } + } + _finalCountdown += 1; + } + } + + public void newRace() + { + // Edit _history. + _history.add(new HistoryInfo(_raceNumber, 0, 0, 0)); + + // Randomize _npcTemplates. + Collections.shuffle(_npcTemplates); + + // Setup 8 new creatures ; pickup the first 8 from _npcTemplates. + for (int i = 0; i < 8; i++) + { + try + { + final NpcTemplate template = NpcData.getInstance().getTemplate(_npcTemplates.get(i)); + _monsters[i] = (Npc) Class.forName("org.l2jmobius.gameserver.model.actor.instance." + template.getType() + "Instance").getConstructors()[0].newInstance(template); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "", e); + } + } + } + + public void newSpeeds() + { + _speeds = new int[8][20]; + int total = 0; + _first[1] = 0; + _second[1] = 0; + + for (int i = 0; i < 8; i++) + { + total = 0; + for (int j = 0; j < 20; j++) + { + if (j == 19) + { + _speeds[i][j] = 100; + } + else + { + _speeds[i][j] = Rnd.get(60) + 65; + } + total += _speeds[i][j]; + } + + if (total >= _first[1]) + { + _second[0] = _first[0]; + _second[1] = _first[1]; + _first[0] = 8 - i; + _first[1] = total; + } + else if (total >= _second[1]) + { + _second[0] = 8 - i; + _second[1] = total; + } + } + } + + /** + * Load past races informations, feeding _history arrayList.
+ * Also sets _raceNumber, based on latest HistoryInfo loaded. + */ + protected void loadHistory() + { + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("SELECT * FROM mdt_history"); + ResultSet rset = statement.executeQuery(); + + while (rset.next()) + { + _history.add(new HistoryInfo(rset.getInt("race_id"), rset.getInt("first"), rset.getInt("second"), rset.getDouble("odd_rate"))); + _raceNumber++; + } + rset.close(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't load history: " + e.getMessage(), e); + } + LOGGER.info("MonsterRace: loaded " + _history.size() + " records, currently on race #" + _raceNumber); + } + + /** + * Save an history record into database. + * @param history The infos to store. + */ + protected void saveHistory(HistoryInfo history) + { + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("INSERT INTO mdt_history (race_id, first, second, odd_rate) VALUES (?,?,?,?)"); + statement.setInt(1, history.getRaceId()); + statement.setInt(2, history.getFirst()); + statement.setInt(3, history.getSecond()); + statement.setDouble(4, history.getOddRate()); + statement.execute(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't save history: " + e.getMessage(), e); + } + } + + /** + * Load current bets per lane ; initialize the map keys. + */ + protected void loadBets() + { + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("SELECT * FROM mdt_bets"); + ResultSet rset = statement.executeQuery(); + + while (rset.next()) + { + setBetOnLane(rset.getInt("lane_id"), rset.getLong("bet"), false); + } + + rset.close(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't load bets: " + e.getMessage(), e); + } + } + + /** + * Save the current lane bet into database. + * @param lane : The lane to affect. + * @param sum : The sum to set. + */ + protected void saveBet(int lane, long sum) + { + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("REPLACE INTO mdt_bets (lane_id, bet) VALUES (?,?)"); + statement.setInt(1, lane); + statement.setLong(2, sum); + statement.execute(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't save bet: " + e.getMessage(), e); + } + } + + /** + * Clear all lanes bets, either on database or Map. + */ + protected void clearBets() + { + for (int key : _betsPerLane.keySet()) + { + _betsPerLane.put(key, 0L); + } + + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("UPDATE mdt_bets SET bet = 0"); + statement.execute(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't clear bets: " + e.getMessage(), e); + } + } + + /** + * Setup lane bet, based on previous value (if any). + * @param lane : The lane to edit. + * @param amount : The amount to add. + * @param saveOnDb : Should it be saved on db or not. + */ + public void setBetOnLane(int lane, long amount, boolean saveOnDb) + { + final long sum = (_betsPerLane.containsKey(lane)) ? _betsPerLane.get(lane) + amount : amount; + + _betsPerLane.put(lane, sum); + + if (saveOnDb) + { + saveBet(lane, sum); + } + } + + /** + * Calculate odds for every lane, based on others lanes. + */ + protected void calculateOdds() + { + // Clear previous List holding old odds. + _odds.clear(); + + // Sort bets lanes per lane. + final Map 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; + } + + // 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)); + } + } + + public Npc[] getMonsters() + { + return _monsters; + } + + public int[][] getSpeeds() + { + return _speeds; + } + + public int getFirstPlace() + { + return _first[0]; + } + + public int getSecondPlace() + { + return _second[0]; + } + + public MonRaceInfo getRacePacket() + { + return _packet; + } + + public RaceState getCurrentRaceState() + { + return _state; + } + + public int getRaceNumber() + { + return _raceNumber; + } + + public List getHistory() + { + return _history; + } + + public List getOdds() + { + return _odds; + } + + public static MonsterRace getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final MonsterRace INSTANCE = new MonsterRace(); + } +} \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/model/actor/instance/RaceManagerInstance.java b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/model/actor/instance/RaceManagerInstance.java new file mode 100644 index 0000000000..2ae6979233 --- /dev/null +++ b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/model/actor/instance/RaceManagerInstance.java @@ -0,0 +1,377 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program 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. + * + * This program 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 . + */ +package org.l2jmobius.gameserver.model.actor.instance; + +import java.util.List; +import java.util.Locale; + +import org.l2jmobius.Config; +import org.l2jmobius.commons.util.StringUtil; +import org.l2jmobius.gameserver.idfactory.IdFactory; +import org.l2jmobius.gameserver.instancemanager.games.MonsterRace; +import org.l2jmobius.gameserver.instancemanager.games.MonsterRace.HistoryInfo; +import org.l2jmobius.gameserver.instancemanager.games.MonsterRace.RaceState; +import org.l2jmobius.gameserver.model.actor.Npc; +import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.model.items.instance.ItemInstance; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.serverpackets.ActionFailed; +import org.l2jmobius.gameserver.network.serverpackets.NpcHtmlMessage; +import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; + +public class RaceManagerInstance extends Npc +{ + protected static final int TICKET_PRICES[] = + { + 100, + 500, + 1000, + 5000, + 10000, + 20000, + 50000, + 100000 + }; + + public RaceManagerInstance(NpcTemplate template) + { + super(template); + } + + @Override + public void onBypassFeedback(PlayerInstance player, String command) + { + if (command.startsWith("BuyTicket")) + { + if (!Config.ALLOW_RACE || (MonsterRace.getInstance().getCurrentRaceState() != RaceState.ACCEPTING_BETS)) + { + player.sendPacket(SystemMessageId.MONSTER_RACE_TICKETS_ARE_NO_LONGER_AVAILABLE); + super.onBypassFeedback(player, "Chat 0"); + return; + } + + int 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))) + { + val = 0; + } + + String search, replace; + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + + if (val < 10) + { + html.setFile(player, getHtmlPath(getId(), 2, player)); + for (int i = 0; i < 8; i++) + { + int n = i + 1; + search = "Mob" + n; + html.replace(search, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); + } + search = "No1"; + if (val == 0) + { + html.replace(search, ""); + } + else + { + html.replace(search, val); + player.setRace(0, val); + } + } + else if (val < 20) + { + if (player.getRace(0) == 0) + { + return; + } + + html.setFile(player, getHtmlPath(getId(), 3, player)); + html.replace("0place", player.getRace(0)); + search = "Mob1"; + replace = MonsterRace.getInstance().getMonsters()[player.getRace(0) - 1].getTemplate().getName(); + html.replace(search, replace); + search = "0adena"; + + if (val == 10) + { + html.replace(search, ""); + } + else + { + html.replace(search, TICKET_PRICES[val - 11]); + player.setRace(1, val - 10); + } + } + else if (val == 20) + { + if ((player.getRace(0) == 0) || (player.getRace(1) == 0)) + { + return; + } + + html.setFile(player, getHtmlPath(getId(), 4, player)); + html.replace("0place", player.getRace(0)); + search = "Mob1"; + replace = MonsterRace.getInstance().getMonsters()[player.getRace(0) - 1].getTemplate().getName(); + 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); + } + else + { + if ((player.getRace(0) == 0) || (player.getRace(1) == 0)) + { + return; + } + + int ticket = player.getRace(0); + int priceId = player.getRace(1); + + if (!player.reduceAdena("Race", TICKET_PRICES[priceId - 1], this, true)) + { + return; + } + + player.setRace(0, 0); + player.setRace(1, 0); + + ItemInstance item = new ItemInstance(IdFactory.getInstance().getNextId(), 4443); + item.setCount(1); + item.setEnchantLevel(MonsterRace.getInstance().getRaceNumber()); + item.setCustomType1(ticket); + item.setCustomType2(TICKET_PRICES[priceId - 1] / 100); + + player.addItem("Race", item, player, false); + final SystemMessage msg = new SystemMessage(SystemMessageId.ACQUIRED_S1_S2); + msg.addInt(MonsterRace.getInstance().getRaceNumber()); + msg.addItemName(4443); + player.sendPacket(msg); + + // Refresh lane bet. + MonsterRace.getInstance().setBetOnLane(ticket, TICKET_PRICES[priceId - 1], true); + super.onBypassFeedback(player, "Chat 0"); + return; + } + html.replace("1race", MonsterRace.getInstance().getRaceNumber()); + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); + } + else if (command.equals("ShowOdds")) + { + if (!Config.ALLOW_RACE || (MonsterRace.getInstance().getCurrentRaceState() == RaceState.ACCEPTING_BETS)) + { + player.sendPacket(SystemMessageId.MONSTER_RACE_PAYOUT_INFORMATION_IS_NOT_AVAILABLE_WHILE_TICKETS_ARE_BEING_SOLD); + super.onBypassFeedback(player, "Chat 0"); + return; + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 5, player)); + for (int i = 0; i < 8; i++) + { + final int n = i + 1; + + html.replace("Mob" + n, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); + + // Odd + final double odd = MonsterRace.getInstance().getOdds().get(i); + html.replace("Odd" + n, (odd > 0D) ? String.format(Locale.ENGLISH, "%.1f", odd) : "&$804;"); + } + html.replace("1race", MonsterRace.getInstance().getRaceNumber()); + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); + } + else if (command.equals("ShowInfo")) + { + if (!Config.ALLOW_RACE) + { + return; + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 6, player)); + + for (int i = 0; i < 8; i++) + { + int n = i + 1; + String search = "Mob" + n; + html.replace(search, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); + } + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); + } + else if (command.equals("ShowTickets")) + { + if (!Config.ALLOW_RACE) + { + super.onBypassFeedback(player, "Chat 0"); + return; + } + + // Generate data. + final StringBuilder sb = new StringBuilder(); + + // Retrieve player's tickets. + for (ItemInstance ticket : player.getInventory().getAllItemsByItemId(4443)) + { + // Don't list current race tickets. + if (ticket.getEnchantLevel() == MonsterRace.getInstance().getRaceNumber()) + { + continue; + } + + StringUtil.append(sb, "", "" + ticket.getEnchantLevel(), " Race Number", "" + ticket.getCustomType1(), " Number", "" + (ticket.getCustomType2() * 100), " Adena"); + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 7, player)); + 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)); + if (!Config.ALLOW_RACE || (val == 0)) + { + super.onBypassFeedback(player, "Chat 0"); + return; + } + + // Retrieve ticket on player's inventory. + final ItemInstance 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; + + // Retrieve HistoryInfo for that race. + final HistoryInfo info = MonsterRace.getInstance().getHistory().get(raceId - 1); + if (info == null) + { + super.onBypassFeedback(player, "Chat 0"); + return; + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 8, player)); + html.replace("%raceId%", raceId); + html.replace("%lane%", lane); + html.replace("%bet%", bet); + html.replace("%firstLane%", info.getFirst()); + html.replace("%odd%", (lane == info.getFirst()) ? String.format(Locale.ENGLISH, "%.2f", info.getOddRate()) : "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 (!Config.ALLOW_RACE || (val == 0)) + { + super.onBypassFeedback(player, "Chat 0"); + return; + } + + // Delete ticket on player's inventory. + final ItemInstance 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; + + // Retrieve HistoryInfo for that race. + final HistoryInfo info = MonsterRace.getInstance().getHistory().get(raceId - 1); + if (info == 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()) ? info.getOddRate() : 0.01)), this, true); + } + + super.onBypassFeedback(player, "Chat 0"); + return; + } + else if (command.equals("ViewHistory")) + { + if (!Config.ALLOW_RACE) + { + super.onBypassFeedback(player, "Chat 0"); + return; + } + + // Generate data. + final StringBuilder sb = new StringBuilder(); + + // Use whole history, pickup from 'last element' and stop at 'latest element - 7'. + final List history = MonsterRace.getInstance().getHistory(); + for (int i = history.size() - 1; i >= Math.max(0, history.size() - 7); i--) + { + final HistoryInfo info = history.get(i); + StringUtil.append(sb, "", "" + info.getRaceId(), " th", "" + info.getFirst(), " Lane ", "" + info.getSecond(), " Lane", String.format(Locale.ENGLISH, "%.2f", info.getOddRate()), " Times"); + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 9, player)); + html.replace("%infos%", sb.toString()); + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); + } + else + { + super.onBypassFeedback(player, command); + } + } +} diff --git a/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/util/Broadcast.java b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/util/Broadcast.java index 60eb0ff361..8d751afc53 100644 --- a/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/util/Broadcast.java +++ b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/util/Broadcast.java @@ -20,10 +20,12 @@ import java.util.logging.Level; import java.util.logging.Logger; import org.l2jmobius.gameserver.enums.ChatType; +import org.l2jmobius.gameserver.instancemanager.ZoneManager; import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.actor.Creature; import org.l2jmobius.gameserver.model.actor.Summon; import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance; +import org.l2jmobius.gameserver.model.zone.ZoneType; import org.l2jmobius.gameserver.network.serverpackets.CharInfo; import org.l2jmobius.gameserver.network.serverpackets.CreatureSay; import org.l2jmobius.gameserver.network.serverpackets.ExShowScreenMessage; @@ -191,4 +193,29 @@ public class Broadcast { toAllOnlinePlayers(new ExShowScreenMessage(text, 10000)); } + + /** + * Send a packet to all players in a specific zone type. + * @param ZoneType. + * @param zoneType : The zone type to send packets. + * @param packets : The packets to send. + */ + public static void toAllPlayersInZoneType(Class zoneType, IClientOutgoingPacket... packets) + { + for (ZoneType zone : ZoneManager.getInstance().getAllZones(zoneType)) + { + for (Creature creature : zone.getCharactersInside()) + { + if (creature == null) + { + continue; + } + + for (IClientOutgoingPacket packet : packets) + { + creature.sendPacket(packet); + } + } + } + } } diff --git a/L2J_Mobius_Classic_2.1_Zaken/dist/db_installer/sql/game/mdt_bets.sql b/L2J_Mobius_Classic_2.1_Zaken/dist/db_installer/sql/game/mdt_bets.sql new file mode 100644 index 0000000000..8d7d4cb738 --- /dev/null +++ b/L2J_Mobius_Classic_2.1_Zaken/dist/db_installer/sql/game/mdt_bets.sql @@ -0,0 +1,15 @@ +CREATE TABLE IF NOT EXISTS `mdt_bets` ( + `lane_id` INT(1) DEFAULT 0, + `bet` INT DEFAULT 0, + PRIMARY KEY (`lane_id`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci; + +INSERT INTO `mdt_bets` VALUES +('1','0'), +('2','0'), +('3','0'), +('4','0'), +('5','0'), +('6','0'), +('7','0'), +('8','0'); \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.1_Zaken/dist/db_installer/sql/game/mdt_history.sql b/L2J_Mobius_Classic_2.1_Zaken/dist/db_installer/sql/game/mdt_history.sql new file mode 100644 index 0000000000..06978f3377 --- /dev/null +++ b/L2J_Mobius_Classic_2.1_Zaken/dist/db_installer/sql/game/mdt_history.sql @@ -0,0 +1,7 @@ +CREATE TABLE IF NOT EXISTS `mdt_history` ( + `race_id` MEDIUMINT DEFAULT 0, + `first` INT(1) DEFAULT 0, + `second` INT(1) DEFAULT 0, + `odd_rate` DOUBLE(10,2) DEFAULT 0, + PRIMARY KEY (`race_id`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci; \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.1_Zaken/dist/game/config/General.ini b/L2J_Mobius_Classic_2.1_Zaken/dist/game/config/General.ini index bc4b0889cb..761f34bea4 100644 --- a/L2J_Mobius_Classic_2.1_Zaken/dist/game/config/General.ini +++ b/L2J_Mobius_Classic_2.1_Zaken/dist/game/config/General.ini @@ -370,6 +370,9 @@ DefaultFinishTime = 5 # Misc Settings # --------------------------------------------------------------------------- +# Default: True +AllowRace = True + # Default: True AllowWater = True diff --git a/L2J_Mobius_Classic_2.1_Zaken/dist/game/data/html/default/30995-1.htm b/L2J_Mobius_Classic_2.1_Zaken/dist/game/data/html/default/30995-1.htm new file mode 100644 index 0000000000..d38d518149 --- /dev/null +++ b/L2J_Mobius_Classic_2.1_Zaken/dist/game/data/html/default/30995-1.htm @@ -0,0 +1,22 @@ + +
+ +
+In a monster race eight monsters race at the same time. If you guess which monster is expected to finish first and buy that monster's race ticket, based upon the odds to win you will receive an appropriate amount of adena.
+Racing Schedule
+Monster races every 20 minutes in real time.
+Method of Race Participation
+You can purchase a monster race ticket through a race manager NPC. Only between 18 minutes before the start of a race until 3 minutes before the race begins you can buy a ticket for the applicable race. When you purchase a ticket through a conversation with a race manager, you can find out the names of participating monsters in the race and their current statistics. Five minutes before a race starts, the race manager will stop ticket sales and announce monsters' individual odds rate for the current race.
+Types of Race
+There are two types of race betting. The Win type betting means that you back a monster that will win the first prize. Place type of betting means that you will have to guess the 1st and 2nd winning monsters, regardless of who finishes first or second.
+Win +* Among the entire betting amount for Win betting, after subtracting the race track's own share of profit, the amount left will be your total winnings. Three minutes before the race each monster's expected odds are announced. Each Win ticket's dividend amount is calculated by multiplying the odds by the price of a ticket. The odds rate is never less than 1. +* The prize money does not get transferred to the next race.
+Place +* Place betting is a way of betting when you correctly guess the two monsters who will finish first and second regardless of their order. The method of calculating dividend amount is the same as Win betting.
+* Currently place betting is not offered.
+End of Race
+When a race is over, the race manager will shout out the results of the race. Through a conversation with a race manager NPC, you can trade your ticket for adena. By looking up on the Winning Information Data, you can find out the types of tickets and quantity of the winnings in the race you betted on and your win status and prize amount you won. If you won, you can receive your prize money thorough a race manager NPC. If you did not win, a race manager will buy back your ticket by paying you a very small amount of money.
+If a server goes down while tickets are being sold, or while a race is in progress, the race will be cancelled. You can receive a full refund for your ticket for a cancelled race through a race manager.
+ + \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.1_Zaken/dist/game/data/html/default/30995-2.htm b/L2J_Mobius_Classic_2.1_Zaken/dist/game/data/html/default/30995-2.htm new file mode 100644 index 0000000000..0362da5162 --- /dev/null +++ b/L2J_Mobius_Classic_2.1_Zaken/dist/game/data/html/default/30995-2.htm @@ -0,0 +1,94 @@ + +
Monster Race Betting To Win (Race # 1race) + + +
+ + + + + + + + +
Lane SelectionName of RacerRatingOdds
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
1Mob1&$745;13%
2Mob2&$745;6%
3Mob3&$747;4%
4Mob4&$745;12%
5Mob5&$745;14%
6Mob6&$745;13%
7Mob7&$745;13%
8Mob8&$746;14%
+ + + + + + + + + + +
Select Lane : No1
+ + \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.1_Zaken/dist/game/data/html/default/30995-3.htm b/L2J_Mobius_Classic_2.1_Zaken/dist/game/data/html/default/30995-3.htm new file mode 100644 index 0000000000..7ae65a37a4 --- /dev/null +++ b/L2J_Mobius_Classic_2.1_Zaken/dist/game/data/html/default/30995-3.htm @@ -0,0 +1,48 @@ + +
Monster Race Betting To Win (Race # 1race) + + +
+ + + + + + + + +
Lane SelectionRacer's Name RatingOdds
+ + + + + + + + + +
0placeMob1&$745;13%
+ + + + +
Select Bet Amount +100 Adena , 500 Adena +1,000 Adena , 5,000 Adena +10,000 Adena , 20,000 Adena +50,000 Adena , 100,000 Adena +
+ + + + + + + + + + + +
Purchase Price : 0adena
+ + \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.1_Zaken/dist/game/data/html/default/30995-4.htm b/L2J_Mobius_Classic_2.1_Zaken/dist/game/data/html/default/30995-4.htm new file mode 100644 index 0000000000..30c508b9cb --- /dev/null +++ b/L2J_Mobius_Classic_2.1_Zaken/dist/game/data/html/default/30995-4.htm @@ -0,0 +1,56 @@ + +
Monster Race Single Ticket Purchase (Race # 1race) + + +
+ + + + + + + +
Lane SelectionRacer's NameRatingOdds
+ + + + + + + + + +
0placeMob1&$745;13%
+ + + + + + + + + + + + + + + + + + +
Bet Amount
Bet Amount 0adena Adena
Tax 0tax Adena
Total 0total Adena
+
+
If the information is correct, please click Confirm.

+ + + + + + + + + + +
+ \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.1_Zaken/dist/game/data/html/default/30995-5.htm b/L2J_Mobius_Classic_2.1_Zaken/dist/game/data/html/default/30995-5.htm new file mode 100644 index 0000000000..59a05e14a0 --- /dev/null +++ b/L2J_Mobius_Classic_2.1_Zaken/dist/game/data/html/default/30995-5.htm @@ -0,0 +1,74 @@ + +
Monster Race Betting To Win View Odds (Race # 1race ) + + +
+ + + + + + +
LaneRacer's NameOdds
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
No.1Mob1Odd1
No.2Mob2Odd2
No.3Mob3Odd3
No.4Mob4Odd4
No.5Mob5Odd5
No.6Mob6Odd6
No.7Mob7Odd7
No.8Mob8Odd8
+ + + + + + + + + + +
+ \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.1_Zaken/dist/game/data/html/default/30995-6.htm b/L2J_Mobius_Classic_2.1_Zaken/dist/game/data/html/default/30995-6.htm new file mode 100644 index 0000000000..0d845cb665 --- /dev/null +++ b/L2J_Mobius_Classic_2.1_Zaken/dist/game/data/html/default/30995-6.htm @@ -0,0 +1,80 @@ + +
Participating Monsters + + +
+ + + + + + + +
LaneRacer's NameRatingOdds
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
1Mob1&$747;12%
2Mob2&$745;6%
3Mob3&$746;3%
4Mob4&$747;7%
5Mob5&$747;4%
6Mob6&$747;14%
7Mob7&$747;26%
8Mob8&$745;5%
+ + + + + + + + +
\ No newline at end of file diff --git a/L2J_Mobius_Classic_2.1_Zaken/dist/game/data/html/default/30995-7.htm b/L2J_Mobius_Classic_2.1_Zaken/dist/game/data/html/default/30995-7.htm new file mode 100644 index 0000000000..53eb213a52 --- /dev/null +++ b/L2J_Mobius_Classic_2.1_Zaken/dist/game/data/html/default/30995-7.htm @@ -0,0 +1,19 @@ + +
Confirm Monster Race Bet / Calculate Winnings
+ + + + + +
Winnings for a Race.LaneBet Amount
+ +%tickets% +
+ + + + + + + +
\ No newline at end of file diff --git a/L2J_Mobius_Classic_2.1_Zaken/dist/game/data/html/default/30995-8.htm b/L2J_Mobius_Classic_2.1_Zaken/dist/game/data/html/default/30995-8.htm new file mode 100644 index 0000000000..5f856a222f --- /dev/null +++ b/L2J_Mobius_Classic_2.1_Zaken/dist/game/data/html/default/30995-8.htm @@ -0,0 +1,25 @@ + +
Confirm Monster Race Bet / Calculate Winnings
+ + + + + + +
Race #%raceId%
+Lane: %lane%
+Bet: %bet% Adena
+1st place: Lane [%firstLane%]
+Odds: [%odd%] +
+ + + + + + + + + +
+ \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.1_Zaken/dist/game/data/html/default/30995-9.htm b/L2J_Mobius_Classic_2.1_Zaken/dist/game/data/html/default/30995-9.htm new file mode 100644 index 0000000000..c17ab4849a --- /dev/null +++ b/L2J_Mobius_Classic_2.1_Zaken/dist/game/data/html/default/30995-9.htm @@ -0,0 +1,22 @@ + +
View Monster Race Results
+ + + + +
Race Number1st Finish2nd FinishOdds Rate for Win Bet
+ + +%infos% +
+ + + + + + + + + +
+ \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.1_Zaken/dist/game/data/html/default/30995.htm b/L2J_Mobius_Classic_2.1_Zaken/dist/game/data/html/default/30995.htm index ca913c7dd6..271c4ee284 100644 --- a/L2J_Mobius_Classic_2.1_Zaken/dist/game/data/html/default/30995.htm +++ b/L2J_Mobius_Classic_2.1_Zaken/dist/game/data/html/default/30995.htm @@ -1,3 +1,15 @@ -Monster Race Manager:
- +
Monster Race Manager
+ + +
+ + + + + + + + + + \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.1_Zaken/dist/game/data/stats/npcs/30900-30999.xml b/L2J_Mobius_Classic_2.1_Zaken/dist/game/data/stats/npcs/30900-30999.xml index 16670573bc..8993995b8e 100644 --- a/L2J_Mobius_Classic_2.1_Zaken/dist/game/data/stats/npcs/30900-30999.xml +++ b/L2J_Mobius_Classic_2.1_Zaken/dist/game/data/stats/npcs/30900-30999.xml @@ -3591,7 +3591,7 @@
- + DWARF FEMALE diff --git a/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/Config.java b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/Config.java index 5846ec3b55..c69a4a47ad 100644 --- a/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/Config.java +++ b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/Config.java @@ -498,6 +498,7 @@ public class Config public static int INSTANCE_FINISH_TIME; public static boolean RESTORE_PLAYER_INSTANCE; public static int EJECT_DEAD_PLAYER_TIME; + public static boolean ALLOW_RACE; public static boolean ALLOW_WATER; public static boolean ALLOW_FISHING; public static boolean ALLOW_BOAT; @@ -1952,6 +1953,7 @@ public class Config INSTANCE_FINISH_TIME = General.getInt("DefaultFinishTime", 5); RESTORE_PLAYER_INSTANCE = General.getBoolean("RestorePlayerInstance", false); EJECT_DEAD_PLAYER_TIME = General.getInt("EjectDeadPlayerTime", 1); + ALLOW_RACE = General.getBoolean("AllowRace", true); ALLOW_WATER = General.getBoolean("AllowWater", true); ALLOW_FISHING = General.getBoolean("AllowFishing", true); ALLOW_MANOR = General.getBoolean("AllowManor", true); diff --git a/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/GameServer.java b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/GameServer.java index aa8c369958..91c661e1a1 100644 --- a/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/GameServer.java +++ b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/GameServer.java @@ -141,6 +141,7 @@ import org.l2jmobius.gameserver.instancemanager.SiegeGuardManager; import org.l2jmobius.gameserver.instancemanager.SiegeManager; import org.l2jmobius.gameserver.instancemanager.WalkingManager; import org.l2jmobius.gameserver.instancemanager.ZoneManager; +import org.l2jmobius.gameserver.instancemanager.games.MonsterRace; import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.entity.Hero; import org.l2jmobius.gameserver.model.events.EventDispatcher; @@ -401,6 +402,8 @@ public class GameServer ItemsAutoDestroy.getInstance(); } + MonsterRace.getInstance(); + TaskManager.getInstance(); AntiFeedManager.getInstance().registerEvent(AntiFeedManager.GAME_ID); diff --git a/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/instancemanager/games/MonsterRace.java b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/instancemanager/games/MonsterRace.java new file mode 100644 index 0000000000..2fa4c519f6 --- /dev/null +++ b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/instancemanager/games/MonsterRace.java @@ -0,0 +1,623 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program 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. + * + * This program 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 . + */ +package org.l2jmobius.gameserver.instancemanager.games; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.Config; +import org.l2jmobius.commons.concurrent.ThreadPool; +import org.l2jmobius.commons.database.DatabaseFactory; +import org.l2jmobius.commons.util.Rnd; +import org.l2jmobius.gameserver.data.xml.impl.NpcData; +import org.l2jmobius.gameserver.model.actor.Npc; +import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.model.zone.type.DerbyTrackZone; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.serverpackets.DeleteObject; +import org.l2jmobius.gameserver.network.serverpackets.MonRaceInfo; +import org.l2jmobius.gameserver.network.serverpackets.PlaySound; +import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; +import org.l2jmobius.gameserver.util.Broadcast; + +public class MonsterRace +{ + protected static final Logger LOGGER = Logger.getLogger(MonsterRace.class.getName()); + + public static enum RaceState + { + ACCEPTING_BETS, + WAITING, + STARTING_RACE, + RACE_END + } + + protected static final PlaySound SOUND_1 = new PlaySound(1, "S_Race", 0, 0, 0, 0, 0); + protected static final PlaySound SOUND_2 = new PlaySound("ItemSound2.race_start"); + + protected static final int[][] CODES = + { + { + -1, + 0 + }, + { + 0, + 15322 + }, + { + 13765, + -1 + } + }; + + protected final List _npcTemplates = new ArrayList<>(); // List holding npc templates, shuffled on a new race. + protected final List _history = new ArrayList<>(); // List holding old race records. + protected final Map _betsPerLane = new ConcurrentHashMap<>(); // Map holding all bets for each lane ; values setted to 0 after every race. + protected final List _odds = new ArrayList<>(); // List holding sorted odds per lane ; cleared at new odds calculation. + + protected int _raceNumber = 1; + protected int _finalCountdown = 0; + protected RaceState _state = RaceState.RACE_END; + + protected MonRaceInfo _packet; + + private final Npc[] _monsters = new Npc[8]; + private int[][] _speeds = new int[8][20]; + private final int[] _first = new int[2]; + private final int[] _second = new int[2]; + + protected MonsterRace() + { + if (!Config.ALLOW_RACE) + { + return; + } + + // Feed _history with previous race results. + loadHistory(); + + // Feed _betsPerLane with stored informations on bets. + loadBets(); + + // Feed _npcTemplates, we will only have to shuffle it when needed. + for (int i = 31003; i < 31027; i++) + { + _npcTemplates.add(i); + } + + ThreadPool.scheduleAtFixedRate(new Announcement(), 0, 1000); + } + + public static class HistoryInfo + { + private final int _raceId; + private int _first; + private int _second; + private double _oddRate; + + public HistoryInfo(int raceId, int first, int second, double oddRate) + { + _raceId = raceId; + _first = first; + _second = second; + _oddRate = oddRate; + } + + public int getRaceId() + { + return _raceId; + } + + public int getFirst() + { + return _first; + } + + public int getSecond() + { + return _second; + } + + public double getOddRate() + { + return _oddRate; + } + + public void setFirst(int first) + { + _first = first; + } + + public void setSecond(int second) + { + _second = second; + } + + public void setOddRate(double oddRate) + { + _oddRate = oddRate; + } + } + + private class Announcement implements Runnable + { + public Announcement() + { + } + + @Override + public void run() + { + if (_finalCountdown > 1200) + { + _finalCountdown = 0; + } + + switch (_finalCountdown) + { + case 0: + { + newRace(); + newSpeeds(); + + _state = RaceState.ACCEPTING_BETS; + _packet = new MonRaceInfo(CODES[0][0], CODES[0][1], getMonsters(), getSpeeds()); + + final SystemMessage msg = new SystemMessage(SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, _packet, msg); + 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 + { + final SystemMessage msg = new SystemMessage(SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + break; + } + case 300: // 5 min + { + final SystemMessage msg = new SystemMessage(SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S); + msg2.addInt(10); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + break; + } + case 600: // 10 min + { + final SystemMessage msg = new SystemMessage(SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S); + msg2.addInt(5); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + break; + } + case 840: // 14 min + { + final SystemMessage msg = new SystemMessage(SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S); + msg2.addInt(1); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + break; + } + case 900: // 15 min + { + _state = RaceState.WAITING; + + calculateOdds(); + + final SystemMessage msg = new SystemMessage(SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.TICKETS_SALES_ARE_CLOSED_FOR_MONSTER_RACE_S1_ODDS_ARE_POSTED); + msg2.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + break; + } + case 960: // 16 min + case 1020: // 17 min + { + final int minutes = (_finalCountdown == 960) ? 2 : 1; + final SystemMessage msg = new SystemMessage(SystemMessageId.MONSTER_RACE_S2_WILL_BEGIN_IN_S1_MINUTE_S); + msg.addInt(minutes); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + break; + } + case 1050: // 17 min 30 sec + { + final SystemMessage msg = new SystemMessage(SystemMessageId.MONSTER_RACE_S1_WILL_BEGIN_IN_30_SECONDS); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + break; + } + case 1070: // 17 min 50 sec + { + final SystemMessage msg = new SystemMessage(SystemMessageId.MONSTER_RACE_S1_IS_ABOUT_TO_BEGIN_COUNTDOWN_IN_FIVE_SECONDS); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + 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; + final SystemMessage msg = new SystemMessage(SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SECOND_S); + msg.addInt(seconds); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + break; + } + case 1080: // 18 min + { + _state = RaceState.STARTING_RACE; + _packet = new MonRaceInfo(CODES[1][0], CODES[1][1], getMonsters(), getSpeeds()); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, new SystemMessage(SystemMessageId.THEY_RE_OFF), SOUND_1, SOUND_2, _packet); + break; + } + case 1085: // 18 min 5 sec + { + _packet = new MonRaceInfo(CODES[2][0], CODES[2][1], getMonsters(), getSpeeds()); + + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, _packet); + break; + } + case 1115: // 18 min 35 sec + { + _state = RaceState.RACE_END; + + // Populate history info with data, stores it in database. + final HistoryInfo info = _history.get(_history.size() - 1); + info.setFirst(getFirstPlace()); + info.setSecond(getSecondPlace()); + info.setOddRate(_odds.get(getFirstPlace() - 1)); + + saveHistory(info); + clearBets(); + + final SystemMessage msg = new SystemMessage(SystemMessageId.FIRST_PRIZE_GOES_TO_THE_PLAYER_IN_LANE_S1_SECOND_PRIZE_GOES_TO_THE_PLAYER_IN_LANE_S2); + msg.addInt(getFirstPlace()); + msg.addInt(getSecondPlace()); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.MONSTER_RACE_S1_IS_FINISHED); + msg2.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + _raceNumber++; + break; + } + case 1140: // 19 min + { + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, new DeleteObject(getMonsters()[0]), new DeleteObject(getMonsters()[1]), new DeleteObject(getMonsters()[2]), new DeleteObject(getMonsters()[3]), new DeleteObject(getMonsters()[4]), new DeleteObject(getMonsters()[5]), new DeleteObject(getMonsters()[6]), new DeleteObject(getMonsters()[7])); + break; + } + } + _finalCountdown += 1; + } + } + + public void newRace() + { + // Edit _history. + _history.add(new HistoryInfo(_raceNumber, 0, 0, 0)); + + // Randomize _npcTemplates. + Collections.shuffle(_npcTemplates); + + // Setup 8 new creatures ; pickup the first 8 from _npcTemplates. + for (int i = 0; i < 8; i++) + { + try + { + final NpcTemplate template = NpcData.getInstance().getTemplate(_npcTemplates.get(i)); + _monsters[i] = (Npc) Class.forName("org.l2jmobius.gameserver.model.actor.instance." + template.getType() + "Instance").getConstructors()[0].newInstance(template); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "", e); + } + } + } + + public void newSpeeds() + { + _speeds = new int[8][20]; + int total = 0; + _first[1] = 0; + _second[1] = 0; + + for (int i = 0; i < 8; i++) + { + total = 0; + for (int j = 0; j < 20; j++) + { + if (j == 19) + { + _speeds[i][j] = 100; + } + else + { + _speeds[i][j] = Rnd.get(60) + 65; + } + total += _speeds[i][j]; + } + + if (total >= _first[1]) + { + _second[0] = _first[0]; + _second[1] = _first[1]; + _first[0] = 8 - i; + _first[1] = total; + } + else if (total >= _second[1]) + { + _second[0] = 8 - i; + _second[1] = total; + } + } + } + + /** + * Load past races informations, feeding _history arrayList.
+ * Also sets _raceNumber, based on latest HistoryInfo loaded. + */ + protected void loadHistory() + { + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("SELECT * FROM mdt_history"); + ResultSet rset = statement.executeQuery(); + + while (rset.next()) + { + _history.add(new HistoryInfo(rset.getInt("race_id"), rset.getInt("first"), rset.getInt("second"), rset.getDouble("odd_rate"))); + _raceNumber++; + } + rset.close(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't load history: " + e.getMessage(), e); + } + LOGGER.info("MonsterRace: loaded " + _history.size() + " records, currently on race #" + _raceNumber); + } + + /** + * Save an history record into database. + * @param history The infos to store. + */ + protected void saveHistory(HistoryInfo history) + { + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("INSERT INTO mdt_history (race_id, first, second, odd_rate) VALUES (?,?,?,?)"); + statement.setInt(1, history.getRaceId()); + statement.setInt(2, history.getFirst()); + statement.setInt(3, history.getSecond()); + statement.setDouble(4, history.getOddRate()); + statement.execute(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't save history: " + e.getMessage(), e); + } + } + + /** + * Load current bets per lane ; initialize the map keys. + */ + protected void loadBets() + { + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("SELECT * FROM mdt_bets"); + ResultSet rset = statement.executeQuery(); + + while (rset.next()) + { + setBetOnLane(rset.getInt("lane_id"), rset.getLong("bet"), false); + } + + rset.close(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't load bets: " + e.getMessage(), e); + } + } + + /** + * Save the current lane bet into database. + * @param lane : The lane to affect. + * @param sum : The sum to set. + */ + protected void saveBet(int lane, long sum) + { + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("REPLACE INTO mdt_bets (lane_id, bet) VALUES (?,?)"); + statement.setInt(1, lane); + statement.setLong(2, sum); + statement.execute(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't save bet: " + e.getMessage(), e); + } + } + + /** + * Clear all lanes bets, either on database or Map. + */ + protected void clearBets() + { + for (int key : _betsPerLane.keySet()) + { + _betsPerLane.put(key, 0L); + } + + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("UPDATE mdt_bets SET bet = 0"); + statement.execute(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't clear bets: " + e.getMessage(), e); + } + } + + /** + * Setup lane bet, based on previous value (if any). + * @param lane : The lane to edit. + * @param amount : The amount to add. + * @param saveOnDb : Should it be saved on db or not. + */ + public void setBetOnLane(int lane, long amount, boolean saveOnDb) + { + final long sum = (_betsPerLane.containsKey(lane)) ? _betsPerLane.get(lane) + amount : amount; + + _betsPerLane.put(lane, sum); + + if (saveOnDb) + { + saveBet(lane, sum); + } + } + + /** + * Calculate odds for every lane, based on others lanes. + */ + protected void calculateOdds() + { + // Clear previous List holding old odds. + _odds.clear(); + + // Sort bets lanes per lane. + final Map 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; + } + + // 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)); + } + } + + public Npc[] getMonsters() + { + return _monsters; + } + + public int[][] getSpeeds() + { + return _speeds; + } + + public int getFirstPlace() + { + return _first[0]; + } + + public int getSecondPlace() + { + return _second[0]; + } + + public MonRaceInfo getRacePacket() + { + return _packet; + } + + public RaceState getCurrentRaceState() + { + return _state; + } + + public int getRaceNumber() + { + return _raceNumber; + } + + public List getHistory() + { + return _history; + } + + public List getOdds() + { + return _odds; + } + + public static MonsterRace getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final MonsterRace INSTANCE = new MonsterRace(); + } +} \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/model/actor/instance/RaceManagerInstance.java b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/model/actor/instance/RaceManagerInstance.java new file mode 100644 index 0000000000..2ae6979233 --- /dev/null +++ b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/model/actor/instance/RaceManagerInstance.java @@ -0,0 +1,377 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program 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. + * + * This program 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 . + */ +package org.l2jmobius.gameserver.model.actor.instance; + +import java.util.List; +import java.util.Locale; + +import org.l2jmobius.Config; +import org.l2jmobius.commons.util.StringUtil; +import org.l2jmobius.gameserver.idfactory.IdFactory; +import org.l2jmobius.gameserver.instancemanager.games.MonsterRace; +import org.l2jmobius.gameserver.instancemanager.games.MonsterRace.HistoryInfo; +import org.l2jmobius.gameserver.instancemanager.games.MonsterRace.RaceState; +import org.l2jmobius.gameserver.model.actor.Npc; +import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.model.items.instance.ItemInstance; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.serverpackets.ActionFailed; +import org.l2jmobius.gameserver.network.serverpackets.NpcHtmlMessage; +import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; + +public class RaceManagerInstance extends Npc +{ + protected static final int TICKET_PRICES[] = + { + 100, + 500, + 1000, + 5000, + 10000, + 20000, + 50000, + 100000 + }; + + public RaceManagerInstance(NpcTemplate template) + { + super(template); + } + + @Override + public void onBypassFeedback(PlayerInstance player, String command) + { + if (command.startsWith("BuyTicket")) + { + if (!Config.ALLOW_RACE || (MonsterRace.getInstance().getCurrentRaceState() != RaceState.ACCEPTING_BETS)) + { + player.sendPacket(SystemMessageId.MONSTER_RACE_TICKETS_ARE_NO_LONGER_AVAILABLE); + super.onBypassFeedback(player, "Chat 0"); + return; + } + + int 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))) + { + val = 0; + } + + String search, replace; + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + + if (val < 10) + { + html.setFile(player, getHtmlPath(getId(), 2, player)); + for (int i = 0; i < 8; i++) + { + int n = i + 1; + search = "Mob" + n; + html.replace(search, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); + } + search = "No1"; + if (val == 0) + { + html.replace(search, ""); + } + else + { + html.replace(search, val); + player.setRace(0, val); + } + } + else if (val < 20) + { + if (player.getRace(0) == 0) + { + return; + } + + html.setFile(player, getHtmlPath(getId(), 3, player)); + html.replace("0place", player.getRace(0)); + search = "Mob1"; + replace = MonsterRace.getInstance().getMonsters()[player.getRace(0) - 1].getTemplate().getName(); + html.replace(search, replace); + search = "0adena"; + + if (val == 10) + { + html.replace(search, ""); + } + else + { + html.replace(search, TICKET_PRICES[val - 11]); + player.setRace(1, val - 10); + } + } + else if (val == 20) + { + if ((player.getRace(0) == 0) || (player.getRace(1) == 0)) + { + return; + } + + html.setFile(player, getHtmlPath(getId(), 4, player)); + html.replace("0place", player.getRace(0)); + search = "Mob1"; + replace = MonsterRace.getInstance().getMonsters()[player.getRace(0) - 1].getTemplate().getName(); + 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); + } + else + { + if ((player.getRace(0) == 0) || (player.getRace(1) == 0)) + { + return; + } + + int ticket = player.getRace(0); + int priceId = player.getRace(1); + + if (!player.reduceAdena("Race", TICKET_PRICES[priceId - 1], this, true)) + { + return; + } + + player.setRace(0, 0); + player.setRace(1, 0); + + ItemInstance item = new ItemInstance(IdFactory.getInstance().getNextId(), 4443); + item.setCount(1); + item.setEnchantLevel(MonsterRace.getInstance().getRaceNumber()); + item.setCustomType1(ticket); + item.setCustomType2(TICKET_PRICES[priceId - 1] / 100); + + player.addItem("Race", item, player, false); + final SystemMessage msg = new SystemMessage(SystemMessageId.ACQUIRED_S1_S2); + msg.addInt(MonsterRace.getInstance().getRaceNumber()); + msg.addItemName(4443); + player.sendPacket(msg); + + // Refresh lane bet. + MonsterRace.getInstance().setBetOnLane(ticket, TICKET_PRICES[priceId - 1], true); + super.onBypassFeedback(player, "Chat 0"); + return; + } + html.replace("1race", MonsterRace.getInstance().getRaceNumber()); + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); + } + else if (command.equals("ShowOdds")) + { + if (!Config.ALLOW_RACE || (MonsterRace.getInstance().getCurrentRaceState() == RaceState.ACCEPTING_BETS)) + { + player.sendPacket(SystemMessageId.MONSTER_RACE_PAYOUT_INFORMATION_IS_NOT_AVAILABLE_WHILE_TICKETS_ARE_BEING_SOLD); + super.onBypassFeedback(player, "Chat 0"); + return; + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 5, player)); + for (int i = 0; i < 8; i++) + { + final int n = i + 1; + + html.replace("Mob" + n, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); + + // Odd + final double odd = MonsterRace.getInstance().getOdds().get(i); + html.replace("Odd" + n, (odd > 0D) ? String.format(Locale.ENGLISH, "%.1f", odd) : "&$804;"); + } + html.replace("1race", MonsterRace.getInstance().getRaceNumber()); + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); + } + else if (command.equals("ShowInfo")) + { + if (!Config.ALLOW_RACE) + { + return; + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 6, player)); + + for (int i = 0; i < 8; i++) + { + int n = i + 1; + String search = "Mob" + n; + html.replace(search, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); + } + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); + } + else if (command.equals("ShowTickets")) + { + if (!Config.ALLOW_RACE) + { + super.onBypassFeedback(player, "Chat 0"); + return; + } + + // Generate data. + final StringBuilder sb = new StringBuilder(); + + // Retrieve player's tickets. + for (ItemInstance ticket : player.getInventory().getAllItemsByItemId(4443)) + { + // Don't list current race tickets. + if (ticket.getEnchantLevel() == MonsterRace.getInstance().getRaceNumber()) + { + continue; + } + + StringUtil.append(sb, "", "" + ticket.getEnchantLevel(), " Race Number", "" + ticket.getCustomType1(), " Number", "" + (ticket.getCustomType2() * 100), " Adena"); + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 7, player)); + 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)); + if (!Config.ALLOW_RACE || (val == 0)) + { + super.onBypassFeedback(player, "Chat 0"); + return; + } + + // Retrieve ticket on player's inventory. + final ItemInstance 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; + + // Retrieve HistoryInfo for that race. + final HistoryInfo info = MonsterRace.getInstance().getHistory().get(raceId - 1); + if (info == null) + { + super.onBypassFeedback(player, "Chat 0"); + return; + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 8, player)); + html.replace("%raceId%", raceId); + html.replace("%lane%", lane); + html.replace("%bet%", bet); + html.replace("%firstLane%", info.getFirst()); + html.replace("%odd%", (lane == info.getFirst()) ? String.format(Locale.ENGLISH, "%.2f", info.getOddRate()) : "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 (!Config.ALLOW_RACE || (val == 0)) + { + super.onBypassFeedback(player, "Chat 0"); + return; + } + + // Delete ticket on player's inventory. + final ItemInstance 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; + + // Retrieve HistoryInfo for that race. + final HistoryInfo info = MonsterRace.getInstance().getHistory().get(raceId - 1); + if (info == 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()) ? info.getOddRate() : 0.01)), this, true); + } + + super.onBypassFeedback(player, "Chat 0"); + return; + } + else if (command.equals("ViewHistory")) + { + if (!Config.ALLOW_RACE) + { + super.onBypassFeedback(player, "Chat 0"); + return; + } + + // Generate data. + final StringBuilder sb = new StringBuilder(); + + // Use whole history, pickup from 'last element' and stop at 'latest element - 7'. + final List history = MonsterRace.getInstance().getHistory(); + for (int i = history.size() - 1; i >= Math.max(0, history.size() - 7); i--) + { + final HistoryInfo info = history.get(i); + StringUtil.append(sb, "", "" + info.getRaceId(), " th", "" + info.getFirst(), " Lane ", "" + info.getSecond(), " Lane", String.format(Locale.ENGLISH, "%.2f", info.getOddRate()), " Times"); + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 9, player)); + html.replace("%infos%", sb.toString()); + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); + } + else + { + super.onBypassFeedback(player, command); + } + } +} diff --git a/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/util/Broadcast.java b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/util/Broadcast.java index 60eb0ff361..8d751afc53 100644 --- a/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/util/Broadcast.java +++ b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/gameserver/util/Broadcast.java @@ -20,10 +20,12 @@ import java.util.logging.Level; import java.util.logging.Logger; import org.l2jmobius.gameserver.enums.ChatType; +import org.l2jmobius.gameserver.instancemanager.ZoneManager; import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.actor.Creature; import org.l2jmobius.gameserver.model.actor.Summon; import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance; +import org.l2jmobius.gameserver.model.zone.ZoneType; import org.l2jmobius.gameserver.network.serverpackets.CharInfo; import org.l2jmobius.gameserver.network.serverpackets.CreatureSay; import org.l2jmobius.gameserver.network.serverpackets.ExShowScreenMessage; @@ -191,4 +193,29 @@ public class Broadcast { toAllOnlinePlayers(new ExShowScreenMessage(text, 10000)); } + + /** + * Send a packet to all players in a specific zone type. + * @param ZoneType. + * @param zoneType : The zone type to send packets. + * @param packets : The packets to send. + */ + public static void toAllPlayersInZoneType(Class zoneType, IClientOutgoingPacket... packets) + { + for (ZoneType zone : ZoneManager.getInstance().getAllZones(zoneType)) + { + for (Creature creature : zone.getCharactersInside()) + { + if (creature == null) + { + continue; + } + + for (IClientOutgoingPacket packet : packets) + { + creature.sendPacket(packet); + } + } + } + } } diff --git a/L2J_Mobius_Classic_2.2_Antharas/dist/db_installer/sql/game/mdt_bets.sql b/L2J_Mobius_Classic_2.2_Antharas/dist/db_installer/sql/game/mdt_bets.sql new file mode 100644 index 0000000000..8d7d4cb738 --- /dev/null +++ b/L2J_Mobius_Classic_2.2_Antharas/dist/db_installer/sql/game/mdt_bets.sql @@ -0,0 +1,15 @@ +CREATE TABLE IF NOT EXISTS `mdt_bets` ( + `lane_id` INT(1) DEFAULT 0, + `bet` INT DEFAULT 0, + PRIMARY KEY (`lane_id`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci; + +INSERT INTO `mdt_bets` VALUES +('1','0'), +('2','0'), +('3','0'), +('4','0'), +('5','0'), +('6','0'), +('7','0'), +('8','0'); \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.2_Antharas/dist/db_installer/sql/game/mdt_history.sql b/L2J_Mobius_Classic_2.2_Antharas/dist/db_installer/sql/game/mdt_history.sql new file mode 100644 index 0000000000..06978f3377 --- /dev/null +++ b/L2J_Mobius_Classic_2.2_Antharas/dist/db_installer/sql/game/mdt_history.sql @@ -0,0 +1,7 @@ +CREATE TABLE IF NOT EXISTS `mdt_history` ( + `race_id` MEDIUMINT DEFAULT 0, + `first` INT(1) DEFAULT 0, + `second` INT(1) DEFAULT 0, + `odd_rate` DOUBLE(10,2) DEFAULT 0, + PRIMARY KEY (`race_id`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci; \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.2_Antharas/dist/game/config/General.ini b/L2J_Mobius_Classic_2.2_Antharas/dist/game/config/General.ini index bc4b0889cb..761f34bea4 100644 --- a/L2J_Mobius_Classic_2.2_Antharas/dist/game/config/General.ini +++ b/L2J_Mobius_Classic_2.2_Antharas/dist/game/config/General.ini @@ -370,6 +370,9 @@ DefaultFinishTime = 5 # Misc Settings # --------------------------------------------------------------------------- +# Default: True +AllowRace = True + # Default: True AllowWater = True diff --git a/L2J_Mobius_Classic_2.2_Antharas/dist/game/data/html/default/30995-1.htm b/L2J_Mobius_Classic_2.2_Antharas/dist/game/data/html/default/30995-1.htm new file mode 100644 index 0000000000..d38d518149 --- /dev/null +++ b/L2J_Mobius_Classic_2.2_Antharas/dist/game/data/html/default/30995-1.htm @@ -0,0 +1,22 @@ + +
+ +
+In a monster race eight monsters race at the same time. If you guess which monster is expected to finish first and buy that monster's race ticket, based upon the odds to win you will receive an appropriate amount of adena.
+Racing Schedule
+Monster races every 20 minutes in real time.
+Method of Race Participation
+You can purchase a monster race ticket through a race manager NPC. Only between 18 minutes before the start of a race until 3 minutes before the race begins you can buy a ticket for the applicable race. When you purchase a ticket through a conversation with a race manager, you can find out the names of participating monsters in the race and their current statistics. Five minutes before a race starts, the race manager will stop ticket sales and announce monsters' individual odds rate for the current race.
+Types of Race
+There are two types of race betting. The Win type betting means that you back a monster that will win the first prize. Place type of betting means that you will have to guess the 1st and 2nd winning monsters, regardless of who finishes first or second.
+Win +* Among the entire betting amount for Win betting, after subtracting the race track's own share of profit, the amount left will be your total winnings. Three minutes before the race each monster's expected odds are announced. Each Win ticket's dividend amount is calculated by multiplying the odds by the price of a ticket. The odds rate is never less than 1. +* The prize money does not get transferred to the next race.
+Place +* Place betting is a way of betting when you correctly guess the two monsters who will finish first and second regardless of their order. The method of calculating dividend amount is the same as Win betting.
+* Currently place betting is not offered.
+End of Race
+When a race is over, the race manager will shout out the results of the race. Through a conversation with a race manager NPC, you can trade your ticket for adena. By looking up on the Winning Information Data, you can find out the types of tickets and quantity of the winnings in the race you betted on and your win status and prize amount you won. If you won, you can receive your prize money thorough a race manager NPC. If you did not win, a race manager will buy back your ticket by paying you a very small amount of money.
+If a server goes down while tickets are being sold, or while a race is in progress, the race will be cancelled. You can receive a full refund for your ticket for a cancelled race through a race manager.
+ + \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.2_Antharas/dist/game/data/html/default/30995-2.htm b/L2J_Mobius_Classic_2.2_Antharas/dist/game/data/html/default/30995-2.htm new file mode 100644 index 0000000000..0362da5162 --- /dev/null +++ b/L2J_Mobius_Classic_2.2_Antharas/dist/game/data/html/default/30995-2.htm @@ -0,0 +1,94 @@ + +
Monster Race Betting To Win (Race # 1race) + + +
+ + + + + + + + +
Lane SelectionName of RacerRatingOdds
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
1Mob1&$745;13%
2Mob2&$745;6%
3Mob3&$747;4%
4Mob4&$745;12%
5Mob5&$745;14%
6Mob6&$745;13%
7Mob7&$745;13%
8Mob8&$746;14%
+ + + + + + + + + + +
Select Lane : No1
+ + \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.2_Antharas/dist/game/data/html/default/30995-3.htm b/L2J_Mobius_Classic_2.2_Antharas/dist/game/data/html/default/30995-3.htm new file mode 100644 index 0000000000..7ae65a37a4 --- /dev/null +++ b/L2J_Mobius_Classic_2.2_Antharas/dist/game/data/html/default/30995-3.htm @@ -0,0 +1,48 @@ + +
Monster Race Betting To Win (Race # 1race) + + +
+ + + + + + + + +
Lane SelectionRacer's Name RatingOdds
+ + + + + + + + + +
0placeMob1&$745;13%
+ + + + +
Select Bet Amount +100 Adena , 500 Adena +1,000 Adena , 5,000 Adena +10,000 Adena , 20,000 Adena +50,000 Adena , 100,000 Adena +
+ + + + + + + + + + + +
Purchase Price : 0adena
+ + \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.2_Antharas/dist/game/data/html/default/30995-4.htm b/L2J_Mobius_Classic_2.2_Antharas/dist/game/data/html/default/30995-4.htm new file mode 100644 index 0000000000..30c508b9cb --- /dev/null +++ b/L2J_Mobius_Classic_2.2_Antharas/dist/game/data/html/default/30995-4.htm @@ -0,0 +1,56 @@ + +
Monster Race Single Ticket Purchase (Race # 1race) + + +
+ + + + + + + +
Lane SelectionRacer's NameRatingOdds
+ + + + + + + + + +
0placeMob1&$745;13%
+ + + + + + + + + + + + + + + + + + +
Bet Amount
Bet Amount 0adena Adena
Tax 0tax Adena
Total 0total Adena
+
+
If the information is correct, please click Confirm.

+ + + + + + + + + + +
+ \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.2_Antharas/dist/game/data/html/default/30995-5.htm b/L2J_Mobius_Classic_2.2_Antharas/dist/game/data/html/default/30995-5.htm new file mode 100644 index 0000000000..59a05e14a0 --- /dev/null +++ b/L2J_Mobius_Classic_2.2_Antharas/dist/game/data/html/default/30995-5.htm @@ -0,0 +1,74 @@ + +
Monster Race Betting To Win View Odds (Race # 1race ) + + +
+ + + + + + +
LaneRacer's NameOdds
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
No.1Mob1Odd1
No.2Mob2Odd2
No.3Mob3Odd3
No.4Mob4Odd4
No.5Mob5Odd5
No.6Mob6Odd6
No.7Mob7Odd7
No.8Mob8Odd8
+ + + + + + + + + + +
+ \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.2_Antharas/dist/game/data/html/default/30995-6.htm b/L2J_Mobius_Classic_2.2_Antharas/dist/game/data/html/default/30995-6.htm new file mode 100644 index 0000000000..0d845cb665 --- /dev/null +++ b/L2J_Mobius_Classic_2.2_Antharas/dist/game/data/html/default/30995-6.htm @@ -0,0 +1,80 @@ + +
Participating Monsters + + +
+ + + + + + + +
LaneRacer's NameRatingOdds
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
1Mob1&$747;12%
2Mob2&$745;6%
3Mob3&$746;3%
4Mob4&$747;7%
5Mob5&$747;4%
6Mob6&$747;14%
7Mob7&$747;26%
8Mob8&$745;5%
+ + + + + + + + +
\ No newline at end of file diff --git a/L2J_Mobius_Classic_2.2_Antharas/dist/game/data/html/default/30995-7.htm b/L2J_Mobius_Classic_2.2_Antharas/dist/game/data/html/default/30995-7.htm new file mode 100644 index 0000000000..53eb213a52 --- /dev/null +++ b/L2J_Mobius_Classic_2.2_Antharas/dist/game/data/html/default/30995-7.htm @@ -0,0 +1,19 @@ + +
Confirm Monster Race Bet / Calculate Winnings
+ + + + + +
Winnings for a Race.LaneBet Amount
+ +%tickets% +
+ + + + + + + +
\ No newline at end of file diff --git a/L2J_Mobius_Classic_2.2_Antharas/dist/game/data/html/default/30995-8.htm b/L2J_Mobius_Classic_2.2_Antharas/dist/game/data/html/default/30995-8.htm new file mode 100644 index 0000000000..5f856a222f --- /dev/null +++ b/L2J_Mobius_Classic_2.2_Antharas/dist/game/data/html/default/30995-8.htm @@ -0,0 +1,25 @@ + +
Confirm Monster Race Bet / Calculate Winnings
+ + + + + + +
Race #%raceId%
+Lane: %lane%
+Bet: %bet% Adena
+1st place: Lane [%firstLane%]
+Odds: [%odd%] +
+ + + + + + + + + +
+ \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.2_Antharas/dist/game/data/html/default/30995-9.htm b/L2J_Mobius_Classic_2.2_Antharas/dist/game/data/html/default/30995-9.htm new file mode 100644 index 0000000000..c17ab4849a --- /dev/null +++ b/L2J_Mobius_Classic_2.2_Antharas/dist/game/data/html/default/30995-9.htm @@ -0,0 +1,22 @@ + +
View Monster Race Results
+ + + + +
Race Number1st Finish2nd FinishOdds Rate for Win Bet
+ + +%infos% +
+ + + + + + + + + +
+ \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.2_Antharas/dist/game/data/html/default/30995.htm b/L2J_Mobius_Classic_2.2_Antharas/dist/game/data/html/default/30995.htm index ca913c7dd6..271c4ee284 100644 --- a/L2J_Mobius_Classic_2.2_Antharas/dist/game/data/html/default/30995.htm +++ b/L2J_Mobius_Classic_2.2_Antharas/dist/game/data/html/default/30995.htm @@ -1,3 +1,15 @@ -Monster Race Manager:
- +
Monster Race Manager
+ + +
+ + + + + + + + + + \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.2_Antharas/dist/game/data/stats/npcs/30900-30999.xml b/L2J_Mobius_Classic_2.2_Antharas/dist/game/data/stats/npcs/30900-30999.xml index a8092cbf48..6cf285b13a 100644 --- a/L2J_Mobius_Classic_2.2_Antharas/dist/game/data/stats/npcs/30900-30999.xml +++ b/L2J_Mobius_Classic_2.2_Antharas/dist/game/data/stats/npcs/30900-30999.xml @@ -3591,7 +3591,7 @@
- + DWARF FEMALE diff --git a/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/Config.java b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/Config.java index 5846ec3b55..c69a4a47ad 100644 --- a/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/Config.java +++ b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/Config.java @@ -498,6 +498,7 @@ public class Config public static int INSTANCE_FINISH_TIME; public static boolean RESTORE_PLAYER_INSTANCE; public static int EJECT_DEAD_PLAYER_TIME; + public static boolean ALLOW_RACE; public static boolean ALLOW_WATER; public static boolean ALLOW_FISHING; public static boolean ALLOW_BOAT; @@ -1952,6 +1953,7 @@ public class Config INSTANCE_FINISH_TIME = General.getInt("DefaultFinishTime", 5); RESTORE_PLAYER_INSTANCE = General.getBoolean("RestorePlayerInstance", false); EJECT_DEAD_PLAYER_TIME = General.getInt("EjectDeadPlayerTime", 1); + ALLOW_RACE = General.getBoolean("AllowRace", true); ALLOW_WATER = General.getBoolean("AllowWater", true); ALLOW_FISHING = General.getBoolean("AllowFishing", true); ALLOW_MANOR = General.getBoolean("AllowManor", true); diff --git a/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/GameServer.java b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/GameServer.java index aa8c369958..91c661e1a1 100644 --- a/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/GameServer.java +++ b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/GameServer.java @@ -141,6 +141,7 @@ import org.l2jmobius.gameserver.instancemanager.SiegeGuardManager; import org.l2jmobius.gameserver.instancemanager.SiegeManager; import org.l2jmobius.gameserver.instancemanager.WalkingManager; import org.l2jmobius.gameserver.instancemanager.ZoneManager; +import org.l2jmobius.gameserver.instancemanager.games.MonsterRace; import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.entity.Hero; import org.l2jmobius.gameserver.model.events.EventDispatcher; @@ -401,6 +402,8 @@ public class GameServer ItemsAutoDestroy.getInstance(); } + MonsterRace.getInstance(); + TaskManager.getInstance(); AntiFeedManager.getInstance().registerEvent(AntiFeedManager.GAME_ID); diff --git a/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/instancemanager/games/MonsterRace.java b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/instancemanager/games/MonsterRace.java new file mode 100644 index 0000000000..2fa4c519f6 --- /dev/null +++ b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/instancemanager/games/MonsterRace.java @@ -0,0 +1,623 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program 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. + * + * This program 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 . + */ +package org.l2jmobius.gameserver.instancemanager.games; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.Config; +import org.l2jmobius.commons.concurrent.ThreadPool; +import org.l2jmobius.commons.database.DatabaseFactory; +import org.l2jmobius.commons.util.Rnd; +import org.l2jmobius.gameserver.data.xml.impl.NpcData; +import org.l2jmobius.gameserver.model.actor.Npc; +import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.model.zone.type.DerbyTrackZone; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.serverpackets.DeleteObject; +import org.l2jmobius.gameserver.network.serverpackets.MonRaceInfo; +import org.l2jmobius.gameserver.network.serverpackets.PlaySound; +import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; +import org.l2jmobius.gameserver.util.Broadcast; + +public class MonsterRace +{ + protected static final Logger LOGGER = Logger.getLogger(MonsterRace.class.getName()); + + public static enum RaceState + { + ACCEPTING_BETS, + WAITING, + STARTING_RACE, + RACE_END + } + + protected static final PlaySound SOUND_1 = new PlaySound(1, "S_Race", 0, 0, 0, 0, 0); + protected static final PlaySound SOUND_2 = new PlaySound("ItemSound2.race_start"); + + protected static final int[][] CODES = + { + { + -1, + 0 + }, + { + 0, + 15322 + }, + { + 13765, + -1 + } + }; + + protected final List _npcTemplates = new ArrayList<>(); // List holding npc templates, shuffled on a new race. + protected final List _history = new ArrayList<>(); // List holding old race records. + protected final Map _betsPerLane = new ConcurrentHashMap<>(); // Map holding all bets for each lane ; values setted to 0 after every race. + protected final List _odds = new ArrayList<>(); // List holding sorted odds per lane ; cleared at new odds calculation. + + protected int _raceNumber = 1; + protected int _finalCountdown = 0; + protected RaceState _state = RaceState.RACE_END; + + protected MonRaceInfo _packet; + + private final Npc[] _monsters = new Npc[8]; + private int[][] _speeds = new int[8][20]; + private final int[] _first = new int[2]; + private final int[] _second = new int[2]; + + protected MonsterRace() + { + if (!Config.ALLOW_RACE) + { + return; + } + + // Feed _history with previous race results. + loadHistory(); + + // Feed _betsPerLane with stored informations on bets. + loadBets(); + + // Feed _npcTemplates, we will only have to shuffle it when needed. + for (int i = 31003; i < 31027; i++) + { + _npcTemplates.add(i); + } + + ThreadPool.scheduleAtFixedRate(new Announcement(), 0, 1000); + } + + public static class HistoryInfo + { + private final int _raceId; + private int _first; + private int _second; + private double _oddRate; + + public HistoryInfo(int raceId, int first, int second, double oddRate) + { + _raceId = raceId; + _first = first; + _second = second; + _oddRate = oddRate; + } + + public int getRaceId() + { + return _raceId; + } + + public int getFirst() + { + return _first; + } + + public int getSecond() + { + return _second; + } + + public double getOddRate() + { + return _oddRate; + } + + public void setFirst(int first) + { + _first = first; + } + + public void setSecond(int second) + { + _second = second; + } + + public void setOddRate(double oddRate) + { + _oddRate = oddRate; + } + } + + private class Announcement implements Runnable + { + public Announcement() + { + } + + @Override + public void run() + { + if (_finalCountdown > 1200) + { + _finalCountdown = 0; + } + + switch (_finalCountdown) + { + case 0: + { + newRace(); + newSpeeds(); + + _state = RaceState.ACCEPTING_BETS; + _packet = new MonRaceInfo(CODES[0][0], CODES[0][1], getMonsters(), getSpeeds()); + + final SystemMessage msg = new SystemMessage(SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, _packet, msg); + 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 + { + final SystemMessage msg = new SystemMessage(SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + break; + } + case 300: // 5 min + { + final SystemMessage msg = new SystemMessage(SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S); + msg2.addInt(10); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + break; + } + case 600: // 10 min + { + final SystemMessage msg = new SystemMessage(SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S); + msg2.addInt(5); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + break; + } + case 840: // 14 min + { + final SystemMessage msg = new SystemMessage(SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S); + msg2.addInt(1); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + break; + } + case 900: // 15 min + { + _state = RaceState.WAITING; + + calculateOdds(); + + final SystemMessage msg = new SystemMessage(SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.TICKETS_SALES_ARE_CLOSED_FOR_MONSTER_RACE_S1_ODDS_ARE_POSTED); + msg2.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + break; + } + case 960: // 16 min + case 1020: // 17 min + { + final int minutes = (_finalCountdown == 960) ? 2 : 1; + final SystemMessage msg = new SystemMessage(SystemMessageId.MONSTER_RACE_S2_WILL_BEGIN_IN_S1_MINUTE_S); + msg.addInt(minutes); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + break; + } + case 1050: // 17 min 30 sec + { + final SystemMessage msg = new SystemMessage(SystemMessageId.MONSTER_RACE_S1_WILL_BEGIN_IN_30_SECONDS); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + break; + } + case 1070: // 17 min 50 sec + { + final SystemMessage msg = new SystemMessage(SystemMessageId.MONSTER_RACE_S1_IS_ABOUT_TO_BEGIN_COUNTDOWN_IN_FIVE_SECONDS); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + 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; + final SystemMessage msg = new SystemMessage(SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SECOND_S); + msg.addInt(seconds); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + break; + } + case 1080: // 18 min + { + _state = RaceState.STARTING_RACE; + _packet = new MonRaceInfo(CODES[1][0], CODES[1][1], getMonsters(), getSpeeds()); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, new SystemMessage(SystemMessageId.THEY_RE_OFF), SOUND_1, SOUND_2, _packet); + break; + } + case 1085: // 18 min 5 sec + { + _packet = new MonRaceInfo(CODES[2][0], CODES[2][1], getMonsters(), getSpeeds()); + + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, _packet); + break; + } + case 1115: // 18 min 35 sec + { + _state = RaceState.RACE_END; + + // Populate history info with data, stores it in database. + final HistoryInfo info = _history.get(_history.size() - 1); + info.setFirst(getFirstPlace()); + info.setSecond(getSecondPlace()); + info.setOddRate(_odds.get(getFirstPlace() - 1)); + + saveHistory(info); + clearBets(); + + final SystemMessage msg = new SystemMessage(SystemMessageId.FIRST_PRIZE_GOES_TO_THE_PLAYER_IN_LANE_S1_SECOND_PRIZE_GOES_TO_THE_PLAYER_IN_LANE_S2); + msg.addInt(getFirstPlace()); + msg.addInt(getSecondPlace()); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.MONSTER_RACE_S1_IS_FINISHED); + msg2.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + _raceNumber++; + break; + } + case 1140: // 19 min + { + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, new DeleteObject(getMonsters()[0]), new DeleteObject(getMonsters()[1]), new DeleteObject(getMonsters()[2]), new DeleteObject(getMonsters()[3]), new DeleteObject(getMonsters()[4]), new DeleteObject(getMonsters()[5]), new DeleteObject(getMonsters()[6]), new DeleteObject(getMonsters()[7])); + break; + } + } + _finalCountdown += 1; + } + } + + public void newRace() + { + // Edit _history. + _history.add(new HistoryInfo(_raceNumber, 0, 0, 0)); + + // Randomize _npcTemplates. + Collections.shuffle(_npcTemplates); + + // Setup 8 new creatures ; pickup the first 8 from _npcTemplates. + for (int i = 0; i < 8; i++) + { + try + { + final NpcTemplate template = NpcData.getInstance().getTemplate(_npcTemplates.get(i)); + _monsters[i] = (Npc) Class.forName("org.l2jmobius.gameserver.model.actor.instance." + template.getType() + "Instance").getConstructors()[0].newInstance(template); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "", e); + } + } + } + + public void newSpeeds() + { + _speeds = new int[8][20]; + int total = 0; + _first[1] = 0; + _second[1] = 0; + + for (int i = 0; i < 8; i++) + { + total = 0; + for (int j = 0; j < 20; j++) + { + if (j == 19) + { + _speeds[i][j] = 100; + } + else + { + _speeds[i][j] = Rnd.get(60) + 65; + } + total += _speeds[i][j]; + } + + if (total >= _first[1]) + { + _second[0] = _first[0]; + _second[1] = _first[1]; + _first[0] = 8 - i; + _first[1] = total; + } + else if (total >= _second[1]) + { + _second[0] = 8 - i; + _second[1] = total; + } + } + } + + /** + * Load past races informations, feeding _history arrayList.
+ * Also sets _raceNumber, based on latest HistoryInfo loaded. + */ + protected void loadHistory() + { + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("SELECT * FROM mdt_history"); + ResultSet rset = statement.executeQuery(); + + while (rset.next()) + { + _history.add(new HistoryInfo(rset.getInt("race_id"), rset.getInt("first"), rset.getInt("second"), rset.getDouble("odd_rate"))); + _raceNumber++; + } + rset.close(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't load history: " + e.getMessage(), e); + } + LOGGER.info("MonsterRace: loaded " + _history.size() + " records, currently on race #" + _raceNumber); + } + + /** + * Save an history record into database. + * @param history The infos to store. + */ + protected void saveHistory(HistoryInfo history) + { + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("INSERT INTO mdt_history (race_id, first, second, odd_rate) VALUES (?,?,?,?)"); + statement.setInt(1, history.getRaceId()); + statement.setInt(2, history.getFirst()); + statement.setInt(3, history.getSecond()); + statement.setDouble(4, history.getOddRate()); + statement.execute(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't save history: " + e.getMessage(), e); + } + } + + /** + * Load current bets per lane ; initialize the map keys. + */ + protected void loadBets() + { + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("SELECT * FROM mdt_bets"); + ResultSet rset = statement.executeQuery(); + + while (rset.next()) + { + setBetOnLane(rset.getInt("lane_id"), rset.getLong("bet"), false); + } + + rset.close(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't load bets: " + e.getMessage(), e); + } + } + + /** + * Save the current lane bet into database. + * @param lane : The lane to affect. + * @param sum : The sum to set. + */ + protected void saveBet(int lane, long sum) + { + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("REPLACE INTO mdt_bets (lane_id, bet) VALUES (?,?)"); + statement.setInt(1, lane); + statement.setLong(2, sum); + statement.execute(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't save bet: " + e.getMessage(), e); + } + } + + /** + * Clear all lanes bets, either on database or Map. + */ + protected void clearBets() + { + for (int key : _betsPerLane.keySet()) + { + _betsPerLane.put(key, 0L); + } + + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("UPDATE mdt_bets SET bet = 0"); + statement.execute(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't clear bets: " + e.getMessage(), e); + } + } + + /** + * Setup lane bet, based on previous value (if any). + * @param lane : The lane to edit. + * @param amount : The amount to add. + * @param saveOnDb : Should it be saved on db or not. + */ + public void setBetOnLane(int lane, long amount, boolean saveOnDb) + { + final long sum = (_betsPerLane.containsKey(lane)) ? _betsPerLane.get(lane) + amount : amount; + + _betsPerLane.put(lane, sum); + + if (saveOnDb) + { + saveBet(lane, sum); + } + } + + /** + * Calculate odds for every lane, based on others lanes. + */ + protected void calculateOdds() + { + // Clear previous List holding old odds. + _odds.clear(); + + // Sort bets lanes per lane. + final Map 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; + } + + // 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)); + } + } + + public Npc[] getMonsters() + { + return _monsters; + } + + public int[][] getSpeeds() + { + return _speeds; + } + + public int getFirstPlace() + { + return _first[0]; + } + + public int getSecondPlace() + { + return _second[0]; + } + + public MonRaceInfo getRacePacket() + { + return _packet; + } + + public RaceState getCurrentRaceState() + { + return _state; + } + + public int getRaceNumber() + { + return _raceNumber; + } + + public List getHistory() + { + return _history; + } + + public List getOdds() + { + return _odds; + } + + public static MonsterRace getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final MonsterRace INSTANCE = new MonsterRace(); + } +} \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/model/actor/instance/RaceManagerInstance.java b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/model/actor/instance/RaceManagerInstance.java new file mode 100644 index 0000000000..2ae6979233 --- /dev/null +++ b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/model/actor/instance/RaceManagerInstance.java @@ -0,0 +1,377 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program 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. + * + * This program 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 . + */ +package org.l2jmobius.gameserver.model.actor.instance; + +import java.util.List; +import java.util.Locale; + +import org.l2jmobius.Config; +import org.l2jmobius.commons.util.StringUtil; +import org.l2jmobius.gameserver.idfactory.IdFactory; +import org.l2jmobius.gameserver.instancemanager.games.MonsterRace; +import org.l2jmobius.gameserver.instancemanager.games.MonsterRace.HistoryInfo; +import org.l2jmobius.gameserver.instancemanager.games.MonsterRace.RaceState; +import org.l2jmobius.gameserver.model.actor.Npc; +import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.model.items.instance.ItemInstance; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.serverpackets.ActionFailed; +import org.l2jmobius.gameserver.network.serverpackets.NpcHtmlMessage; +import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; + +public class RaceManagerInstance extends Npc +{ + protected static final int TICKET_PRICES[] = + { + 100, + 500, + 1000, + 5000, + 10000, + 20000, + 50000, + 100000 + }; + + public RaceManagerInstance(NpcTemplate template) + { + super(template); + } + + @Override + public void onBypassFeedback(PlayerInstance player, String command) + { + if (command.startsWith("BuyTicket")) + { + if (!Config.ALLOW_RACE || (MonsterRace.getInstance().getCurrentRaceState() != RaceState.ACCEPTING_BETS)) + { + player.sendPacket(SystemMessageId.MONSTER_RACE_TICKETS_ARE_NO_LONGER_AVAILABLE); + super.onBypassFeedback(player, "Chat 0"); + return; + } + + int 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))) + { + val = 0; + } + + String search, replace; + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + + if (val < 10) + { + html.setFile(player, getHtmlPath(getId(), 2, player)); + for (int i = 0; i < 8; i++) + { + int n = i + 1; + search = "Mob" + n; + html.replace(search, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); + } + search = "No1"; + if (val == 0) + { + html.replace(search, ""); + } + else + { + html.replace(search, val); + player.setRace(0, val); + } + } + else if (val < 20) + { + if (player.getRace(0) == 0) + { + return; + } + + html.setFile(player, getHtmlPath(getId(), 3, player)); + html.replace("0place", player.getRace(0)); + search = "Mob1"; + replace = MonsterRace.getInstance().getMonsters()[player.getRace(0) - 1].getTemplate().getName(); + html.replace(search, replace); + search = "0adena"; + + if (val == 10) + { + html.replace(search, ""); + } + else + { + html.replace(search, TICKET_PRICES[val - 11]); + player.setRace(1, val - 10); + } + } + else if (val == 20) + { + if ((player.getRace(0) == 0) || (player.getRace(1) == 0)) + { + return; + } + + html.setFile(player, getHtmlPath(getId(), 4, player)); + html.replace("0place", player.getRace(0)); + search = "Mob1"; + replace = MonsterRace.getInstance().getMonsters()[player.getRace(0) - 1].getTemplate().getName(); + 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); + } + else + { + if ((player.getRace(0) == 0) || (player.getRace(1) == 0)) + { + return; + } + + int ticket = player.getRace(0); + int priceId = player.getRace(1); + + if (!player.reduceAdena("Race", TICKET_PRICES[priceId - 1], this, true)) + { + return; + } + + player.setRace(0, 0); + player.setRace(1, 0); + + ItemInstance item = new ItemInstance(IdFactory.getInstance().getNextId(), 4443); + item.setCount(1); + item.setEnchantLevel(MonsterRace.getInstance().getRaceNumber()); + item.setCustomType1(ticket); + item.setCustomType2(TICKET_PRICES[priceId - 1] / 100); + + player.addItem("Race", item, player, false); + final SystemMessage msg = new SystemMessage(SystemMessageId.ACQUIRED_S1_S2); + msg.addInt(MonsterRace.getInstance().getRaceNumber()); + msg.addItemName(4443); + player.sendPacket(msg); + + // Refresh lane bet. + MonsterRace.getInstance().setBetOnLane(ticket, TICKET_PRICES[priceId - 1], true); + super.onBypassFeedback(player, "Chat 0"); + return; + } + html.replace("1race", MonsterRace.getInstance().getRaceNumber()); + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); + } + else if (command.equals("ShowOdds")) + { + if (!Config.ALLOW_RACE || (MonsterRace.getInstance().getCurrentRaceState() == RaceState.ACCEPTING_BETS)) + { + player.sendPacket(SystemMessageId.MONSTER_RACE_PAYOUT_INFORMATION_IS_NOT_AVAILABLE_WHILE_TICKETS_ARE_BEING_SOLD); + super.onBypassFeedback(player, "Chat 0"); + return; + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 5, player)); + for (int i = 0; i < 8; i++) + { + final int n = i + 1; + + html.replace("Mob" + n, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); + + // Odd + final double odd = MonsterRace.getInstance().getOdds().get(i); + html.replace("Odd" + n, (odd > 0D) ? String.format(Locale.ENGLISH, "%.1f", odd) : "&$804;"); + } + html.replace("1race", MonsterRace.getInstance().getRaceNumber()); + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); + } + else if (command.equals("ShowInfo")) + { + if (!Config.ALLOW_RACE) + { + return; + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 6, player)); + + for (int i = 0; i < 8; i++) + { + int n = i + 1; + String search = "Mob" + n; + html.replace(search, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); + } + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); + } + else if (command.equals("ShowTickets")) + { + if (!Config.ALLOW_RACE) + { + super.onBypassFeedback(player, "Chat 0"); + return; + } + + // Generate data. + final StringBuilder sb = new StringBuilder(); + + // Retrieve player's tickets. + for (ItemInstance ticket : player.getInventory().getAllItemsByItemId(4443)) + { + // Don't list current race tickets. + if (ticket.getEnchantLevel() == MonsterRace.getInstance().getRaceNumber()) + { + continue; + } + + StringUtil.append(sb, "", "" + ticket.getEnchantLevel(), " Race Number", "" + ticket.getCustomType1(), " Number", "" + (ticket.getCustomType2() * 100), " Adena"); + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 7, player)); + 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)); + if (!Config.ALLOW_RACE || (val == 0)) + { + super.onBypassFeedback(player, "Chat 0"); + return; + } + + // Retrieve ticket on player's inventory. + final ItemInstance 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; + + // Retrieve HistoryInfo for that race. + final HistoryInfo info = MonsterRace.getInstance().getHistory().get(raceId - 1); + if (info == null) + { + super.onBypassFeedback(player, "Chat 0"); + return; + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 8, player)); + html.replace("%raceId%", raceId); + html.replace("%lane%", lane); + html.replace("%bet%", bet); + html.replace("%firstLane%", info.getFirst()); + html.replace("%odd%", (lane == info.getFirst()) ? String.format(Locale.ENGLISH, "%.2f", info.getOddRate()) : "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 (!Config.ALLOW_RACE || (val == 0)) + { + super.onBypassFeedback(player, "Chat 0"); + return; + } + + // Delete ticket on player's inventory. + final ItemInstance 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; + + // Retrieve HistoryInfo for that race. + final HistoryInfo info = MonsterRace.getInstance().getHistory().get(raceId - 1); + if (info == 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()) ? info.getOddRate() : 0.01)), this, true); + } + + super.onBypassFeedback(player, "Chat 0"); + return; + } + else if (command.equals("ViewHistory")) + { + if (!Config.ALLOW_RACE) + { + super.onBypassFeedback(player, "Chat 0"); + return; + } + + // Generate data. + final StringBuilder sb = new StringBuilder(); + + // Use whole history, pickup from 'last element' and stop at 'latest element - 7'. + final List history = MonsterRace.getInstance().getHistory(); + for (int i = history.size() - 1; i >= Math.max(0, history.size() - 7); i--) + { + final HistoryInfo info = history.get(i); + StringUtil.append(sb, "", "" + info.getRaceId(), " th", "" + info.getFirst(), " Lane ", "" + info.getSecond(), " Lane", String.format(Locale.ENGLISH, "%.2f", info.getOddRate()), " Times"); + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 9, player)); + html.replace("%infos%", sb.toString()); + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); + } + else + { + super.onBypassFeedback(player, command); + } + } +} diff --git a/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/util/Broadcast.java b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/util/Broadcast.java index 60eb0ff361..8d751afc53 100644 --- a/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/util/Broadcast.java +++ b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/gameserver/util/Broadcast.java @@ -20,10 +20,12 @@ import java.util.logging.Level; import java.util.logging.Logger; import org.l2jmobius.gameserver.enums.ChatType; +import org.l2jmobius.gameserver.instancemanager.ZoneManager; import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.actor.Creature; import org.l2jmobius.gameserver.model.actor.Summon; import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance; +import org.l2jmobius.gameserver.model.zone.ZoneType; import org.l2jmobius.gameserver.network.serverpackets.CharInfo; import org.l2jmobius.gameserver.network.serverpackets.CreatureSay; import org.l2jmobius.gameserver.network.serverpackets.ExShowScreenMessage; @@ -191,4 +193,29 @@ public class Broadcast { toAllOnlinePlayers(new ExShowScreenMessage(text, 10000)); } + + /** + * Send a packet to all players in a specific zone type. + * @param ZoneType. + * @param zoneType : The zone type to send packets. + * @param packets : The packets to send. + */ + public static void toAllPlayersInZoneType(Class zoneType, IClientOutgoingPacket... packets) + { + for (ZoneType zone : ZoneManager.getInstance().getAllZones(zoneType)) + { + for (Creature creature : zone.getCharactersInside()) + { + if (creature == null) + { + continue; + } + + for (IClientOutgoingPacket packet : packets) + { + creature.sendPacket(packet); + } + } + } + } } diff --git a/L2J_Mobius_Classic_2.3_SevenSigns/dist/db_installer/sql/game/mdt_bets.sql b/L2J_Mobius_Classic_2.3_SevenSigns/dist/db_installer/sql/game/mdt_bets.sql new file mode 100644 index 0000000000..8d7d4cb738 --- /dev/null +++ b/L2J_Mobius_Classic_2.3_SevenSigns/dist/db_installer/sql/game/mdt_bets.sql @@ -0,0 +1,15 @@ +CREATE TABLE IF NOT EXISTS `mdt_bets` ( + `lane_id` INT(1) DEFAULT 0, + `bet` INT DEFAULT 0, + PRIMARY KEY (`lane_id`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci; + +INSERT INTO `mdt_bets` VALUES +('1','0'), +('2','0'), +('3','0'), +('4','0'), +('5','0'), +('6','0'), +('7','0'), +('8','0'); \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.3_SevenSigns/dist/db_installer/sql/game/mdt_history.sql b/L2J_Mobius_Classic_2.3_SevenSigns/dist/db_installer/sql/game/mdt_history.sql new file mode 100644 index 0000000000..06978f3377 --- /dev/null +++ b/L2J_Mobius_Classic_2.3_SevenSigns/dist/db_installer/sql/game/mdt_history.sql @@ -0,0 +1,7 @@ +CREATE TABLE IF NOT EXISTS `mdt_history` ( + `race_id` MEDIUMINT DEFAULT 0, + `first` INT(1) DEFAULT 0, + `second` INT(1) DEFAULT 0, + `odd_rate` DOUBLE(10,2) DEFAULT 0, + PRIMARY KEY (`race_id`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci; \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.3_SevenSigns/dist/game/config/General.ini b/L2J_Mobius_Classic_2.3_SevenSigns/dist/game/config/General.ini index bc4b0889cb..761f34bea4 100644 --- a/L2J_Mobius_Classic_2.3_SevenSigns/dist/game/config/General.ini +++ b/L2J_Mobius_Classic_2.3_SevenSigns/dist/game/config/General.ini @@ -370,6 +370,9 @@ DefaultFinishTime = 5 # Misc Settings # --------------------------------------------------------------------------- +# Default: True +AllowRace = True + # Default: True AllowWater = True diff --git a/L2J_Mobius_Classic_2.3_SevenSigns/dist/game/data/html/default/30995-1.htm b/L2J_Mobius_Classic_2.3_SevenSigns/dist/game/data/html/default/30995-1.htm new file mode 100644 index 0000000000..d38d518149 --- /dev/null +++ b/L2J_Mobius_Classic_2.3_SevenSigns/dist/game/data/html/default/30995-1.htm @@ -0,0 +1,22 @@ + +
+ +
+In a monster race eight monsters race at the same time. If you guess which monster is expected to finish first and buy that monster's race ticket, based upon the odds to win you will receive an appropriate amount of adena.
+Racing Schedule
+Monster races every 20 minutes in real time.
+Method of Race Participation
+You can purchase a monster race ticket through a race manager NPC. Only between 18 minutes before the start of a race until 3 minutes before the race begins you can buy a ticket for the applicable race. When you purchase a ticket through a conversation with a race manager, you can find out the names of participating monsters in the race and their current statistics. Five minutes before a race starts, the race manager will stop ticket sales and announce monsters' individual odds rate for the current race.
+Types of Race
+There are two types of race betting. The Win type betting means that you back a monster that will win the first prize. Place type of betting means that you will have to guess the 1st and 2nd winning monsters, regardless of who finishes first or second.
+Win +* Among the entire betting amount for Win betting, after subtracting the race track's own share of profit, the amount left will be your total winnings. Three minutes before the race each monster's expected odds are announced. Each Win ticket's dividend amount is calculated by multiplying the odds by the price of a ticket. The odds rate is never less than 1. +* The prize money does not get transferred to the next race.
+Place +* Place betting is a way of betting when you correctly guess the two monsters who will finish first and second regardless of their order. The method of calculating dividend amount is the same as Win betting.
+* Currently place betting is not offered.
+End of Race
+When a race is over, the race manager will shout out the results of the race. Through a conversation with a race manager NPC, you can trade your ticket for adena. By looking up on the Winning Information Data, you can find out the types of tickets and quantity of the winnings in the race you betted on and your win status and prize amount you won. If you won, you can receive your prize money thorough a race manager NPC. If you did not win, a race manager will buy back your ticket by paying you a very small amount of money.
+If a server goes down while tickets are being sold, or while a race is in progress, the race will be cancelled. You can receive a full refund for your ticket for a cancelled race through a race manager.
+ + \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.3_SevenSigns/dist/game/data/html/default/30995-2.htm b/L2J_Mobius_Classic_2.3_SevenSigns/dist/game/data/html/default/30995-2.htm new file mode 100644 index 0000000000..0362da5162 --- /dev/null +++ b/L2J_Mobius_Classic_2.3_SevenSigns/dist/game/data/html/default/30995-2.htm @@ -0,0 +1,94 @@ + +
Monster Race Betting To Win (Race # 1race) + + +
+ + + + + + + + +
Lane SelectionName of RacerRatingOdds
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
1Mob1&$745;13%
2Mob2&$745;6%
3Mob3&$747;4%
4Mob4&$745;12%
5Mob5&$745;14%
6Mob6&$745;13%
7Mob7&$745;13%
8Mob8&$746;14%
+ + + + + + + + + + +
Select Lane : No1
+ + \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.3_SevenSigns/dist/game/data/html/default/30995-3.htm b/L2J_Mobius_Classic_2.3_SevenSigns/dist/game/data/html/default/30995-3.htm new file mode 100644 index 0000000000..7ae65a37a4 --- /dev/null +++ b/L2J_Mobius_Classic_2.3_SevenSigns/dist/game/data/html/default/30995-3.htm @@ -0,0 +1,48 @@ + +
Monster Race Betting To Win (Race # 1race) + + +
+ + + + + + + + +
Lane SelectionRacer's Name RatingOdds
+ + + + + + + + + +
0placeMob1&$745;13%
+ + + + +
Select Bet Amount +100 Adena , 500 Adena +1,000 Adena , 5,000 Adena +10,000 Adena , 20,000 Adena +50,000 Adena , 100,000 Adena +
+ + + + + + + + + + + +
Purchase Price : 0adena
+ + \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.3_SevenSigns/dist/game/data/html/default/30995-4.htm b/L2J_Mobius_Classic_2.3_SevenSigns/dist/game/data/html/default/30995-4.htm new file mode 100644 index 0000000000..30c508b9cb --- /dev/null +++ b/L2J_Mobius_Classic_2.3_SevenSigns/dist/game/data/html/default/30995-4.htm @@ -0,0 +1,56 @@ + +
Monster Race Single Ticket Purchase (Race # 1race) + + +
+ + + + + + + +
Lane SelectionRacer's NameRatingOdds
+ + + + + + + + + +
0placeMob1&$745;13%
+ + + + + + + + + + + + + + + + + + +
Bet Amount
Bet Amount 0adena Adena
Tax 0tax Adena
Total 0total Adena
+
+
If the information is correct, please click Confirm.

+ + + + + + + + + + +
+ \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.3_SevenSigns/dist/game/data/html/default/30995-5.htm b/L2J_Mobius_Classic_2.3_SevenSigns/dist/game/data/html/default/30995-5.htm new file mode 100644 index 0000000000..59a05e14a0 --- /dev/null +++ b/L2J_Mobius_Classic_2.3_SevenSigns/dist/game/data/html/default/30995-5.htm @@ -0,0 +1,74 @@ + +
Monster Race Betting To Win View Odds (Race # 1race ) + + +
+ + + + + + +
LaneRacer's NameOdds
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
No.1Mob1Odd1
No.2Mob2Odd2
No.3Mob3Odd3
No.4Mob4Odd4
No.5Mob5Odd5
No.6Mob6Odd6
No.7Mob7Odd7
No.8Mob8Odd8
+ + + + + + + + + + +
+ \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.3_SevenSigns/dist/game/data/html/default/30995-6.htm b/L2J_Mobius_Classic_2.3_SevenSigns/dist/game/data/html/default/30995-6.htm new file mode 100644 index 0000000000..0d845cb665 --- /dev/null +++ b/L2J_Mobius_Classic_2.3_SevenSigns/dist/game/data/html/default/30995-6.htm @@ -0,0 +1,80 @@ + +
Participating Monsters + + +
+ + + + + + + +
LaneRacer's NameRatingOdds
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
1Mob1&$747;12%
2Mob2&$745;6%
3Mob3&$746;3%
4Mob4&$747;7%
5Mob5&$747;4%
6Mob6&$747;14%
7Mob7&$747;26%
8Mob8&$745;5%
+ + + + + + + + +
\ No newline at end of file diff --git a/L2J_Mobius_Classic_2.3_SevenSigns/dist/game/data/html/default/30995-7.htm b/L2J_Mobius_Classic_2.3_SevenSigns/dist/game/data/html/default/30995-7.htm new file mode 100644 index 0000000000..53eb213a52 --- /dev/null +++ b/L2J_Mobius_Classic_2.3_SevenSigns/dist/game/data/html/default/30995-7.htm @@ -0,0 +1,19 @@ + +
Confirm Monster Race Bet / Calculate Winnings
+ + + + + +
Winnings for a Race.LaneBet Amount
+ +%tickets% +
+ + + + + + + +
\ No newline at end of file diff --git a/L2J_Mobius_Classic_2.3_SevenSigns/dist/game/data/html/default/30995-8.htm b/L2J_Mobius_Classic_2.3_SevenSigns/dist/game/data/html/default/30995-8.htm new file mode 100644 index 0000000000..5f856a222f --- /dev/null +++ b/L2J_Mobius_Classic_2.3_SevenSigns/dist/game/data/html/default/30995-8.htm @@ -0,0 +1,25 @@ + +
Confirm Monster Race Bet / Calculate Winnings
+ + + + + + +
Race #%raceId%
+Lane: %lane%
+Bet: %bet% Adena
+1st place: Lane [%firstLane%]
+Odds: [%odd%] +
+ + + + + + + + + +
+ \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.3_SevenSigns/dist/game/data/html/default/30995-9.htm b/L2J_Mobius_Classic_2.3_SevenSigns/dist/game/data/html/default/30995-9.htm new file mode 100644 index 0000000000..c17ab4849a --- /dev/null +++ b/L2J_Mobius_Classic_2.3_SevenSigns/dist/game/data/html/default/30995-9.htm @@ -0,0 +1,22 @@ + +
View Monster Race Results
+ + + + +
Race Number1st Finish2nd FinishOdds Rate for Win Bet
+ + +%infos% +
+ + + + + + + + + +
+ \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.3_SevenSigns/dist/game/data/html/default/30995.htm b/L2J_Mobius_Classic_2.3_SevenSigns/dist/game/data/html/default/30995.htm index ca913c7dd6..271c4ee284 100644 --- a/L2J_Mobius_Classic_2.3_SevenSigns/dist/game/data/html/default/30995.htm +++ b/L2J_Mobius_Classic_2.3_SevenSigns/dist/game/data/html/default/30995.htm @@ -1,3 +1,15 @@ -Monster Race Manager:
- +
Monster Race Manager
+ + +
+ + + + + + + + + + \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.3_SevenSigns/dist/game/data/stats/npcs/30900-30999.xml b/L2J_Mobius_Classic_2.3_SevenSigns/dist/game/data/stats/npcs/30900-30999.xml index ef532eaecb..72d8e1ea4c 100644 --- a/L2J_Mobius_Classic_2.3_SevenSigns/dist/game/data/stats/npcs/30900-30999.xml +++ b/L2J_Mobius_Classic_2.3_SevenSigns/dist/game/data/stats/npcs/30900-30999.xml @@ -3593,7 +3593,7 @@
- + DWARF FEMALE diff --git a/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/Config.java b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/Config.java index 5846ec3b55..c69a4a47ad 100644 --- a/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/Config.java +++ b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/Config.java @@ -498,6 +498,7 @@ public class Config public static int INSTANCE_FINISH_TIME; public static boolean RESTORE_PLAYER_INSTANCE; public static int EJECT_DEAD_PLAYER_TIME; + public static boolean ALLOW_RACE; public static boolean ALLOW_WATER; public static boolean ALLOW_FISHING; public static boolean ALLOW_BOAT; @@ -1952,6 +1953,7 @@ public class Config INSTANCE_FINISH_TIME = General.getInt("DefaultFinishTime", 5); RESTORE_PLAYER_INSTANCE = General.getBoolean("RestorePlayerInstance", false); EJECT_DEAD_PLAYER_TIME = General.getInt("EjectDeadPlayerTime", 1); + ALLOW_RACE = General.getBoolean("AllowRace", true); ALLOW_WATER = General.getBoolean("AllowWater", true); ALLOW_FISHING = General.getBoolean("AllowFishing", true); ALLOW_MANOR = General.getBoolean("AllowManor", true); diff --git a/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/GameServer.java b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/GameServer.java index 119fd13f59..9342008991 100644 --- a/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/GameServer.java +++ b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/GameServer.java @@ -142,6 +142,7 @@ import org.l2jmobius.gameserver.instancemanager.SiegeGuardManager; import org.l2jmobius.gameserver.instancemanager.SiegeManager; import org.l2jmobius.gameserver.instancemanager.WalkingManager; import org.l2jmobius.gameserver.instancemanager.ZoneManager; +import org.l2jmobius.gameserver.instancemanager.games.MonsterRace; import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.entity.Hero; import org.l2jmobius.gameserver.model.events.EventDispatcher; @@ -403,6 +404,8 @@ public class GameServer ItemsAutoDestroy.getInstance(); } + MonsterRace.getInstance(); + TaskManager.getInstance(); AntiFeedManager.getInstance().registerEvent(AntiFeedManager.GAME_ID); diff --git a/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/instancemanager/games/MonsterRace.java b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/instancemanager/games/MonsterRace.java new file mode 100644 index 0000000000..2fa4c519f6 --- /dev/null +++ b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/instancemanager/games/MonsterRace.java @@ -0,0 +1,623 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program 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. + * + * This program 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 . + */ +package org.l2jmobius.gameserver.instancemanager.games; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.Config; +import org.l2jmobius.commons.concurrent.ThreadPool; +import org.l2jmobius.commons.database.DatabaseFactory; +import org.l2jmobius.commons.util.Rnd; +import org.l2jmobius.gameserver.data.xml.impl.NpcData; +import org.l2jmobius.gameserver.model.actor.Npc; +import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.model.zone.type.DerbyTrackZone; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.serverpackets.DeleteObject; +import org.l2jmobius.gameserver.network.serverpackets.MonRaceInfo; +import org.l2jmobius.gameserver.network.serverpackets.PlaySound; +import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; +import org.l2jmobius.gameserver.util.Broadcast; + +public class MonsterRace +{ + protected static final Logger LOGGER = Logger.getLogger(MonsterRace.class.getName()); + + public static enum RaceState + { + ACCEPTING_BETS, + WAITING, + STARTING_RACE, + RACE_END + } + + protected static final PlaySound SOUND_1 = new PlaySound(1, "S_Race", 0, 0, 0, 0, 0); + protected static final PlaySound SOUND_2 = new PlaySound("ItemSound2.race_start"); + + protected static final int[][] CODES = + { + { + -1, + 0 + }, + { + 0, + 15322 + }, + { + 13765, + -1 + } + }; + + protected final List _npcTemplates = new ArrayList<>(); // List holding npc templates, shuffled on a new race. + protected final List _history = new ArrayList<>(); // List holding old race records. + protected final Map _betsPerLane = new ConcurrentHashMap<>(); // Map holding all bets for each lane ; values setted to 0 after every race. + protected final List _odds = new ArrayList<>(); // List holding sorted odds per lane ; cleared at new odds calculation. + + protected int _raceNumber = 1; + protected int _finalCountdown = 0; + protected RaceState _state = RaceState.RACE_END; + + protected MonRaceInfo _packet; + + private final Npc[] _monsters = new Npc[8]; + private int[][] _speeds = new int[8][20]; + private final int[] _first = new int[2]; + private final int[] _second = new int[2]; + + protected MonsterRace() + { + if (!Config.ALLOW_RACE) + { + return; + } + + // Feed _history with previous race results. + loadHistory(); + + // Feed _betsPerLane with stored informations on bets. + loadBets(); + + // Feed _npcTemplates, we will only have to shuffle it when needed. + for (int i = 31003; i < 31027; i++) + { + _npcTemplates.add(i); + } + + ThreadPool.scheduleAtFixedRate(new Announcement(), 0, 1000); + } + + public static class HistoryInfo + { + private final int _raceId; + private int _first; + private int _second; + private double _oddRate; + + public HistoryInfo(int raceId, int first, int second, double oddRate) + { + _raceId = raceId; + _first = first; + _second = second; + _oddRate = oddRate; + } + + public int getRaceId() + { + return _raceId; + } + + public int getFirst() + { + return _first; + } + + public int getSecond() + { + return _second; + } + + public double getOddRate() + { + return _oddRate; + } + + public void setFirst(int first) + { + _first = first; + } + + public void setSecond(int second) + { + _second = second; + } + + public void setOddRate(double oddRate) + { + _oddRate = oddRate; + } + } + + private class Announcement implements Runnable + { + public Announcement() + { + } + + @Override + public void run() + { + if (_finalCountdown > 1200) + { + _finalCountdown = 0; + } + + switch (_finalCountdown) + { + case 0: + { + newRace(); + newSpeeds(); + + _state = RaceState.ACCEPTING_BETS; + _packet = new MonRaceInfo(CODES[0][0], CODES[0][1], getMonsters(), getSpeeds()); + + final SystemMessage msg = new SystemMessage(SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, _packet, msg); + 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 + { + final SystemMessage msg = new SystemMessage(SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + break; + } + case 300: // 5 min + { + final SystemMessage msg = new SystemMessage(SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S); + msg2.addInt(10); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + break; + } + case 600: // 10 min + { + final SystemMessage msg = new SystemMessage(SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S); + msg2.addInt(5); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + break; + } + case 840: // 14 min + { + final SystemMessage msg = new SystemMessage(SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S); + msg2.addInt(1); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + break; + } + case 900: // 15 min + { + _state = RaceState.WAITING; + + calculateOdds(); + + final SystemMessage msg = new SystemMessage(SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.TICKETS_SALES_ARE_CLOSED_FOR_MONSTER_RACE_S1_ODDS_ARE_POSTED); + msg2.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + break; + } + case 960: // 16 min + case 1020: // 17 min + { + final int minutes = (_finalCountdown == 960) ? 2 : 1; + final SystemMessage msg = new SystemMessage(SystemMessageId.MONSTER_RACE_S2_WILL_BEGIN_IN_S1_MINUTE_S); + msg.addInt(minutes); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + break; + } + case 1050: // 17 min 30 sec + { + final SystemMessage msg = new SystemMessage(SystemMessageId.MONSTER_RACE_S1_WILL_BEGIN_IN_30_SECONDS); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + break; + } + case 1070: // 17 min 50 sec + { + final SystemMessage msg = new SystemMessage(SystemMessageId.MONSTER_RACE_S1_IS_ABOUT_TO_BEGIN_COUNTDOWN_IN_FIVE_SECONDS); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + 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; + final SystemMessage msg = new SystemMessage(SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SECOND_S); + msg.addInt(seconds); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + break; + } + case 1080: // 18 min + { + _state = RaceState.STARTING_RACE; + _packet = new MonRaceInfo(CODES[1][0], CODES[1][1], getMonsters(), getSpeeds()); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, new SystemMessage(SystemMessageId.THEY_RE_OFF), SOUND_1, SOUND_2, _packet); + break; + } + case 1085: // 18 min 5 sec + { + _packet = new MonRaceInfo(CODES[2][0], CODES[2][1], getMonsters(), getSpeeds()); + + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, _packet); + break; + } + case 1115: // 18 min 35 sec + { + _state = RaceState.RACE_END; + + // Populate history info with data, stores it in database. + final HistoryInfo info = _history.get(_history.size() - 1); + info.setFirst(getFirstPlace()); + info.setSecond(getSecondPlace()); + info.setOddRate(_odds.get(getFirstPlace() - 1)); + + saveHistory(info); + clearBets(); + + final SystemMessage msg = new SystemMessage(SystemMessageId.FIRST_PRIZE_GOES_TO_THE_PLAYER_IN_LANE_S1_SECOND_PRIZE_GOES_TO_THE_PLAYER_IN_LANE_S2); + msg.addInt(getFirstPlace()); + msg.addInt(getSecondPlace()); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.MONSTER_RACE_S1_IS_FINISHED); + msg2.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + _raceNumber++; + break; + } + case 1140: // 19 min + { + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, new DeleteObject(getMonsters()[0]), new DeleteObject(getMonsters()[1]), new DeleteObject(getMonsters()[2]), new DeleteObject(getMonsters()[3]), new DeleteObject(getMonsters()[4]), new DeleteObject(getMonsters()[5]), new DeleteObject(getMonsters()[6]), new DeleteObject(getMonsters()[7])); + break; + } + } + _finalCountdown += 1; + } + } + + public void newRace() + { + // Edit _history. + _history.add(new HistoryInfo(_raceNumber, 0, 0, 0)); + + // Randomize _npcTemplates. + Collections.shuffle(_npcTemplates); + + // Setup 8 new creatures ; pickup the first 8 from _npcTemplates. + for (int i = 0; i < 8; i++) + { + try + { + final NpcTemplate template = NpcData.getInstance().getTemplate(_npcTemplates.get(i)); + _monsters[i] = (Npc) Class.forName("org.l2jmobius.gameserver.model.actor.instance." + template.getType() + "Instance").getConstructors()[0].newInstance(template); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "", e); + } + } + } + + public void newSpeeds() + { + _speeds = new int[8][20]; + int total = 0; + _first[1] = 0; + _second[1] = 0; + + for (int i = 0; i < 8; i++) + { + total = 0; + for (int j = 0; j < 20; j++) + { + if (j == 19) + { + _speeds[i][j] = 100; + } + else + { + _speeds[i][j] = Rnd.get(60) + 65; + } + total += _speeds[i][j]; + } + + if (total >= _first[1]) + { + _second[0] = _first[0]; + _second[1] = _first[1]; + _first[0] = 8 - i; + _first[1] = total; + } + else if (total >= _second[1]) + { + _second[0] = 8 - i; + _second[1] = total; + } + } + } + + /** + * Load past races informations, feeding _history arrayList.
+ * Also sets _raceNumber, based on latest HistoryInfo loaded. + */ + protected void loadHistory() + { + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("SELECT * FROM mdt_history"); + ResultSet rset = statement.executeQuery(); + + while (rset.next()) + { + _history.add(new HistoryInfo(rset.getInt("race_id"), rset.getInt("first"), rset.getInt("second"), rset.getDouble("odd_rate"))); + _raceNumber++; + } + rset.close(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't load history: " + e.getMessage(), e); + } + LOGGER.info("MonsterRace: loaded " + _history.size() + " records, currently on race #" + _raceNumber); + } + + /** + * Save an history record into database. + * @param history The infos to store. + */ + protected void saveHistory(HistoryInfo history) + { + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("INSERT INTO mdt_history (race_id, first, second, odd_rate) VALUES (?,?,?,?)"); + statement.setInt(1, history.getRaceId()); + statement.setInt(2, history.getFirst()); + statement.setInt(3, history.getSecond()); + statement.setDouble(4, history.getOddRate()); + statement.execute(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't save history: " + e.getMessage(), e); + } + } + + /** + * Load current bets per lane ; initialize the map keys. + */ + protected void loadBets() + { + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("SELECT * FROM mdt_bets"); + ResultSet rset = statement.executeQuery(); + + while (rset.next()) + { + setBetOnLane(rset.getInt("lane_id"), rset.getLong("bet"), false); + } + + rset.close(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't load bets: " + e.getMessage(), e); + } + } + + /** + * Save the current lane bet into database. + * @param lane : The lane to affect. + * @param sum : The sum to set. + */ + protected void saveBet(int lane, long sum) + { + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("REPLACE INTO mdt_bets (lane_id, bet) VALUES (?,?)"); + statement.setInt(1, lane); + statement.setLong(2, sum); + statement.execute(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't save bet: " + e.getMessage(), e); + } + } + + /** + * Clear all lanes bets, either on database or Map. + */ + protected void clearBets() + { + for (int key : _betsPerLane.keySet()) + { + _betsPerLane.put(key, 0L); + } + + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("UPDATE mdt_bets SET bet = 0"); + statement.execute(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't clear bets: " + e.getMessage(), e); + } + } + + /** + * Setup lane bet, based on previous value (if any). + * @param lane : The lane to edit. + * @param amount : The amount to add. + * @param saveOnDb : Should it be saved on db or not. + */ + public void setBetOnLane(int lane, long amount, boolean saveOnDb) + { + final long sum = (_betsPerLane.containsKey(lane)) ? _betsPerLane.get(lane) + amount : amount; + + _betsPerLane.put(lane, sum); + + if (saveOnDb) + { + saveBet(lane, sum); + } + } + + /** + * Calculate odds for every lane, based on others lanes. + */ + protected void calculateOdds() + { + // Clear previous List holding old odds. + _odds.clear(); + + // Sort bets lanes per lane. + final Map 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; + } + + // 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)); + } + } + + public Npc[] getMonsters() + { + return _monsters; + } + + public int[][] getSpeeds() + { + return _speeds; + } + + public int getFirstPlace() + { + return _first[0]; + } + + public int getSecondPlace() + { + return _second[0]; + } + + public MonRaceInfo getRacePacket() + { + return _packet; + } + + public RaceState getCurrentRaceState() + { + return _state; + } + + public int getRaceNumber() + { + return _raceNumber; + } + + public List getHistory() + { + return _history; + } + + public List getOdds() + { + return _odds; + } + + public static MonsterRace getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final MonsterRace INSTANCE = new MonsterRace(); + } +} \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/model/actor/instance/RaceManagerInstance.java b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/model/actor/instance/RaceManagerInstance.java new file mode 100644 index 0000000000..2ae6979233 --- /dev/null +++ b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/model/actor/instance/RaceManagerInstance.java @@ -0,0 +1,377 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program 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. + * + * This program 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 . + */ +package org.l2jmobius.gameserver.model.actor.instance; + +import java.util.List; +import java.util.Locale; + +import org.l2jmobius.Config; +import org.l2jmobius.commons.util.StringUtil; +import org.l2jmobius.gameserver.idfactory.IdFactory; +import org.l2jmobius.gameserver.instancemanager.games.MonsterRace; +import org.l2jmobius.gameserver.instancemanager.games.MonsterRace.HistoryInfo; +import org.l2jmobius.gameserver.instancemanager.games.MonsterRace.RaceState; +import org.l2jmobius.gameserver.model.actor.Npc; +import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.model.items.instance.ItemInstance; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.serverpackets.ActionFailed; +import org.l2jmobius.gameserver.network.serverpackets.NpcHtmlMessage; +import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; + +public class RaceManagerInstance extends Npc +{ + protected static final int TICKET_PRICES[] = + { + 100, + 500, + 1000, + 5000, + 10000, + 20000, + 50000, + 100000 + }; + + public RaceManagerInstance(NpcTemplate template) + { + super(template); + } + + @Override + public void onBypassFeedback(PlayerInstance player, String command) + { + if (command.startsWith("BuyTicket")) + { + if (!Config.ALLOW_RACE || (MonsterRace.getInstance().getCurrentRaceState() != RaceState.ACCEPTING_BETS)) + { + player.sendPacket(SystemMessageId.MONSTER_RACE_TICKETS_ARE_NO_LONGER_AVAILABLE); + super.onBypassFeedback(player, "Chat 0"); + return; + } + + int 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))) + { + val = 0; + } + + String search, replace; + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + + if (val < 10) + { + html.setFile(player, getHtmlPath(getId(), 2, player)); + for (int i = 0; i < 8; i++) + { + int n = i + 1; + search = "Mob" + n; + html.replace(search, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); + } + search = "No1"; + if (val == 0) + { + html.replace(search, ""); + } + else + { + html.replace(search, val); + player.setRace(0, val); + } + } + else if (val < 20) + { + if (player.getRace(0) == 0) + { + return; + } + + html.setFile(player, getHtmlPath(getId(), 3, player)); + html.replace("0place", player.getRace(0)); + search = "Mob1"; + replace = MonsterRace.getInstance().getMonsters()[player.getRace(0) - 1].getTemplate().getName(); + html.replace(search, replace); + search = "0adena"; + + if (val == 10) + { + html.replace(search, ""); + } + else + { + html.replace(search, TICKET_PRICES[val - 11]); + player.setRace(1, val - 10); + } + } + else if (val == 20) + { + if ((player.getRace(0) == 0) || (player.getRace(1) == 0)) + { + return; + } + + html.setFile(player, getHtmlPath(getId(), 4, player)); + html.replace("0place", player.getRace(0)); + search = "Mob1"; + replace = MonsterRace.getInstance().getMonsters()[player.getRace(0) - 1].getTemplate().getName(); + 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); + } + else + { + if ((player.getRace(0) == 0) || (player.getRace(1) == 0)) + { + return; + } + + int ticket = player.getRace(0); + int priceId = player.getRace(1); + + if (!player.reduceAdena("Race", TICKET_PRICES[priceId - 1], this, true)) + { + return; + } + + player.setRace(0, 0); + player.setRace(1, 0); + + ItemInstance item = new ItemInstance(IdFactory.getInstance().getNextId(), 4443); + item.setCount(1); + item.setEnchantLevel(MonsterRace.getInstance().getRaceNumber()); + item.setCustomType1(ticket); + item.setCustomType2(TICKET_PRICES[priceId - 1] / 100); + + player.addItem("Race", item, player, false); + final SystemMessage msg = new SystemMessage(SystemMessageId.ACQUIRED_S1_S2); + msg.addInt(MonsterRace.getInstance().getRaceNumber()); + msg.addItemName(4443); + player.sendPacket(msg); + + // Refresh lane bet. + MonsterRace.getInstance().setBetOnLane(ticket, TICKET_PRICES[priceId - 1], true); + super.onBypassFeedback(player, "Chat 0"); + return; + } + html.replace("1race", MonsterRace.getInstance().getRaceNumber()); + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); + } + else if (command.equals("ShowOdds")) + { + if (!Config.ALLOW_RACE || (MonsterRace.getInstance().getCurrentRaceState() == RaceState.ACCEPTING_BETS)) + { + player.sendPacket(SystemMessageId.MONSTER_RACE_PAYOUT_INFORMATION_IS_NOT_AVAILABLE_WHILE_TICKETS_ARE_BEING_SOLD); + super.onBypassFeedback(player, "Chat 0"); + return; + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 5, player)); + for (int i = 0; i < 8; i++) + { + final int n = i + 1; + + html.replace("Mob" + n, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); + + // Odd + final double odd = MonsterRace.getInstance().getOdds().get(i); + html.replace("Odd" + n, (odd > 0D) ? String.format(Locale.ENGLISH, "%.1f", odd) : "&$804;"); + } + html.replace("1race", MonsterRace.getInstance().getRaceNumber()); + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); + } + else if (command.equals("ShowInfo")) + { + if (!Config.ALLOW_RACE) + { + return; + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 6, player)); + + for (int i = 0; i < 8; i++) + { + int n = i + 1; + String search = "Mob" + n; + html.replace(search, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); + } + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); + } + else if (command.equals("ShowTickets")) + { + if (!Config.ALLOW_RACE) + { + super.onBypassFeedback(player, "Chat 0"); + return; + } + + // Generate data. + final StringBuilder sb = new StringBuilder(); + + // Retrieve player's tickets. + for (ItemInstance ticket : player.getInventory().getAllItemsByItemId(4443)) + { + // Don't list current race tickets. + if (ticket.getEnchantLevel() == MonsterRace.getInstance().getRaceNumber()) + { + continue; + } + + StringUtil.append(sb, "", "" + ticket.getEnchantLevel(), " Race Number", "" + ticket.getCustomType1(), " Number", "" + (ticket.getCustomType2() * 100), " Adena"); + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 7, player)); + 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)); + if (!Config.ALLOW_RACE || (val == 0)) + { + super.onBypassFeedback(player, "Chat 0"); + return; + } + + // Retrieve ticket on player's inventory. + final ItemInstance 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; + + // Retrieve HistoryInfo for that race. + final HistoryInfo info = MonsterRace.getInstance().getHistory().get(raceId - 1); + if (info == null) + { + super.onBypassFeedback(player, "Chat 0"); + return; + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 8, player)); + html.replace("%raceId%", raceId); + html.replace("%lane%", lane); + html.replace("%bet%", bet); + html.replace("%firstLane%", info.getFirst()); + html.replace("%odd%", (lane == info.getFirst()) ? String.format(Locale.ENGLISH, "%.2f", info.getOddRate()) : "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 (!Config.ALLOW_RACE || (val == 0)) + { + super.onBypassFeedback(player, "Chat 0"); + return; + } + + // Delete ticket on player's inventory. + final ItemInstance 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; + + // Retrieve HistoryInfo for that race. + final HistoryInfo info = MonsterRace.getInstance().getHistory().get(raceId - 1); + if (info == 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()) ? info.getOddRate() : 0.01)), this, true); + } + + super.onBypassFeedback(player, "Chat 0"); + return; + } + else if (command.equals("ViewHistory")) + { + if (!Config.ALLOW_RACE) + { + super.onBypassFeedback(player, "Chat 0"); + return; + } + + // Generate data. + final StringBuilder sb = new StringBuilder(); + + // Use whole history, pickup from 'last element' and stop at 'latest element - 7'. + final List history = MonsterRace.getInstance().getHistory(); + for (int i = history.size() - 1; i >= Math.max(0, history.size() - 7); i--) + { + final HistoryInfo info = history.get(i); + StringUtil.append(sb, "", "" + info.getRaceId(), " th", "" + info.getFirst(), " Lane ", "" + info.getSecond(), " Lane", String.format(Locale.ENGLISH, "%.2f", info.getOddRate()), " Times"); + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 9, player)); + html.replace("%infos%", sb.toString()); + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); + } + else + { + super.onBypassFeedback(player, command); + } + } +} diff --git a/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/util/Broadcast.java b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/util/Broadcast.java index 60eb0ff361..8d751afc53 100644 --- a/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/util/Broadcast.java +++ b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/gameserver/util/Broadcast.java @@ -20,10 +20,12 @@ import java.util.logging.Level; import java.util.logging.Logger; import org.l2jmobius.gameserver.enums.ChatType; +import org.l2jmobius.gameserver.instancemanager.ZoneManager; import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.actor.Creature; import org.l2jmobius.gameserver.model.actor.Summon; import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance; +import org.l2jmobius.gameserver.model.zone.ZoneType; import org.l2jmobius.gameserver.network.serverpackets.CharInfo; import org.l2jmobius.gameserver.network.serverpackets.CreatureSay; import org.l2jmobius.gameserver.network.serverpackets.ExShowScreenMessage; @@ -191,4 +193,29 @@ public class Broadcast { toAllOnlinePlayers(new ExShowScreenMessage(text, 10000)); } + + /** + * Send a packet to all players in a specific zone type. + * @param ZoneType. + * @param zoneType : The zone type to send packets. + * @param packets : The packets to send. + */ + public static void toAllPlayersInZoneType(Class zoneType, IClientOutgoingPacket... packets) + { + for (ZoneType zone : ZoneManager.getInstance().getAllZones(zoneType)) + { + for (Creature creature : zone.getCharactersInside()) + { + if (creature == null) + { + continue; + } + + for (IClientOutgoingPacket packet : packets) + { + creature.sendPacket(packet); + } + } + } + } } diff --git a/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/db_installer/sql/game/mdt_bets.sql b/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/db_installer/sql/game/mdt_bets.sql new file mode 100644 index 0000000000..8d7d4cb738 --- /dev/null +++ b/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/db_installer/sql/game/mdt_bets.sql @@ -0,0 +1,15 @@ +CREATE TABLE IF NOT EXISTS `mdt_bets` ( + `lane_id` INT(1) DEFAULT 0, + `bet` INT DEFAULT 0, + PRIMARY KEY (`lane_id`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci; + +INSERT INTO `mdt_bets` VALUES +('1','0'), +('2','0'), +('3','0'), +('4','0'), +('5','0'), +('6','0'), +('7','0'), +('8','0'); \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/db_installer/sql/game/mdt_history.sql b/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/db_installer/sql/game/mdt_history.sql new file mode 100644 index 0000000000..06978f3377 --- /dev/null +++ b/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/db_installer/sql/game/mdt_history.sql @@ -0,0 +1,7 @@ +CREATE TABLE IF NOT EXISTS `mdt_history` ( + `race_id` MEDIUMINT DEFAULT 0, + `first` INT(1) DEFAULT 0, + `second` INT(1) DEFAULT 0, + `odd_rate` DOUBLE(10,2) DEFAULT 0, + PRIMARY KEY (`race_id`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci; \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/game/config/General.ini b/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/game/config/General.ini index bc4b0889cb..761f34bea4 100644 --- a/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/game/config/General.ini +++ b/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/game/config/General.ini @@ -370,6 +370,9 @@ DefaultFinishTime = 5 # Misc Settings # --------------------------------------------------------------------------- +# Default: True +AllowRace = True + # Default: True AllowWater = True diff --git a/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/game/data/html/default/30995-1.htm b/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/game/data/html/default/30995-1.htm new file mode 100644 index 0000000000..d38d518149 --- /dev/null +++ b/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/game/data/html/default/30995-1.htm @@ -0,0 +1,22 @@ + +
+ +
+In a monster race eight monsters race at the same time. If you guess which monster is expected to finish first and buy that monster's race ticket, based upon the odds to win you will receive an appropriate amount of adena.
+Racing Schedule
+Monster races every 20 minutes in real time.
+Method of Race Participation
+You can purchase a monster race ticket through a race manager NPC. Only between 18 minutes before the start of a race until 3 minutes before the race begins you can buy a ticket for the applicable race. When you purchase a ticket through a conversation with a race manager, you can find out the names of participating monsters in the race and their current statistics. Five minutes before a race starts, the race manager will stop ticket sales and announce monsters' individual odds rate for the current race.
+Types of Race
+There are two types of race betting. The Win type betting means that you back a monster that will win the first prize. Place type of betting means that you will have to guess the 1st and 2nd winning monsters, regardless of who finishes first or second.
+Win +* Among the entire betting amount for Win betting, after subtracting the race track's own share of profit, the amount left will be your total winnings. Three minutes before the race each monster's expected odds are announced. Each Win ticket's dividend amount is calculated by multiplying the odds by the price of a ticket. The odds rate is never less than 1. +* The prize money does not get transferred to the next race.
+Place +* Place betting is a way of betting when you correctly guess the two monsters who will finish first and second regardless of their order. The method of calculating dividend amount is the same as Win betting.
+* Currently place betting is not offered.
+End of Race
+When a race is over, the race manager will shout out the results of the race. Through a conversation with a race manager NPC, you can trade your ticket for adena. By looking up on the Winning Information Data, you can find out the types of tickets and quantity of the winnings in the race you betted on and your win status and prize amount you won. If you won, you can receive your prize money thorough a race manager NPC. If you did not win, a race manager will buy back your ticket by paying you a very small amount of money.
+If a server goes down while tickets are being sold, or while a race is in progress, the race will be cancelled. You can receive a full refund for your ticket for a cancelled race through a race manager.
+ + \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/game/data/html/default/30995-2.htm b/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/game/data/html/default/30995-2.htm new file mode 100644 index 0000000000..0362da5162 --- /dev/null +++ b/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/game/data/html/default/30995-2.htm @@ -0,0 +1,94 @@ + +
Monster Race Betting To Win (Race # 1race) + + +
+ + + + + + + + +
Lane SelectionName of RacerRatingOdds
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
1Mob1&$745;13%
2Mob2&$745;6%
3Mob3&$747;4%
4Mob4&$745;12%
5Mob5&$745;14%
6Mob6&$745;13%
7Mob7&$745;13%
8Mob8&$746;14%
+ + + + + + + + + + +
Select Lane : No1
+ + \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/game/data/html/default/30995-3.htm b/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/game/data/html/default/30995-3.htm new file mode 100644 index 0000000000..7ae65a37a4 --- /dev/null +++ b/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/game/data/html/default/30995-3.htm @@ -0,0 +1,48 @@ + +
Monster Race Betting To Win (Race # 1race) + + +
+ + + + + + + + +
Lane SelectionRacer's Name RatingOdds
+ + + + + + + + + +
0placeMob1&$745;13%
+ + + + +
Select Bet Amount +100 Adena , 500 Adena +1,000 Adena , 5,000 Adena +10,000 Adena , 20,000 Adena +50,000 Adena , 100,000 Adena +
+ + + + + + + + + + + +
Purchase Price : 0adena
+ + \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/game/data/html/default/30995-4.htm b/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/game/data/html/default/30995-4.htm new file mode 100644 index 0000000000..30c508b9cb --- /dev/null +++ b/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/game/data/html/default/30995-4.htm @@ -0,0 +1,56 @@ + +
Monster Race Single Ticket Purchase (Race # 1race) + + +
+ + + + + + + +
Lane SelectionRacer's NameRatingOdds
+ + + + + + + + + +
0placeMob1&$745;13%
+ + + + + + + + + + + + + + + + + + +
Bet Amount
Bet Amount 0adena Adena
Tax 0tax Adena
Total 0total Adena
+
+
If the information is correct, please click Confirm.

+ + + + + + + + + + +
+ \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/game/data/html/default/30995-5.htm b/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/game/data/html/default/30995-5.htm new file mode 100644 index 0000000000..59a05e14a0 --- /dev/null +++ b/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/game/data/html/default/30995-5.htm @@ -0,0 +1,74 @@ + +
Monster Race Betting To Win View Odds (Race # 1race ) + + +
+ + + + + + +
LaneRacer's NameOdds
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
No.1Mob1Odd1
No.2Mob2Odd2
No.3Mob3Odd3
No.4Mob4Odd4
No.5Mob5Odd5
No.6Mob6Odd6
No.7Mob7Odd7
No.8Mob8Odd8
+ + + + + + + + + + +
+ \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/game/data/html/default/30995-6.htm b/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/game/data/html/default/30995-6.htm new file mode 100644 index 0000000000..0d845cb665 --- /dev/null +++ b/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/game/data/html/default/30995-6.htm @@ -0,0 +1,80 @@ + +
Participating Monsters + + +
+ + + + + + + +
LaneRacer's NameRatingOdds
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
1Mob1&$747;12%
2Mob2&$745;6%
3Mob3&$746;3%
4Mob4&$747;7%
5Mob5&$747;4%
6Mob6&$747;14%
7Mob7&$747;26%
8Mob8&$745;5%
+ + + + + + + + +
\ No newline at end of file diff --git a/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/game/data/html/default/30995-7.htm b/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/game/data/html/default/30995-7.htm new file mode 100644 index 0000000000..53eb213a52 --- /dev/null +++ b/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/game/data/html/default/30995-7.htm @@ -0,0 +1,19 @@ + +
Confirm Monster Race Bet / Calculate Winnings
+ + + + + +
Winnings for a Race.LaneBet Amount
+ +%tickets% +
+ + + + + + + +
\ No newline at end of file diff --git a/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/game/data/html/default/30995-8.htm b/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/game/data/html/default/30995-8.htm new file mode 100644 index 0000000000..5f856a222f --- /dev/null +++ b/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/game/data/html/default/30995-8.htm @@ -0,0 +1,25 @@ + +
Confirm Monster Race Bet / Calculate Winnings
+ + + + + + +
Race #%raceId%
+Lane: %lane%
+Bet: %bet% Adena
+1st place: Lane [%firstLane%]
+Odds: [%odd%] +
+ + + + + + + + + +
+ \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/game/data/html/default/30995-9.htm b/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/game/data/html/default/30995-9.htm new file mode 100644 index 0000000000..c17ab4849a --- /dev/null +++ b/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/game/data/html/default/30995-9.htm @@ -0,0 +1,22 @@ + +
View Monster Race Results
+ + + + +
Race Number1st Finish2nd FinishOdds Rate for Win Bet
+ + +%infos% +
+ + + + + + + + + +
+ \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/game/data/html/default/30995.htm b/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/game/data/html/default/30995.htm index ca913c7dd6..271c4ee284 100644 --- a/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/game/data/html/default/30995.htm +++ b/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/game/data/html/default/30995.htm @@ -1,3 +1,15 @@ -Monster Race Manager:
- +
Monster Race Manager
+ + +
+ + + + + + + + + + \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/game/data/stats/npcs/30900-30999.xml b/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/game/data/stats/npcs/30900-30999.xml index ef532eaecb..72d8e1ea4c 100644 --- a/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/game/data/stats/npcs/30900-30999.xml +++ b/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/game/data/stats/npcs/30900-30999.xml @@ -3593,7 +3593,7 @@
- + DWARF FEMALE diff --git a/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/Config.java b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/Config.java index 57b475da6e..1bcd627c87 100644 --- a/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/Config.java +++ b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/Config.java @@ -498,6 +498,7 @@ public class Config public static int INSTANCE_FINISH_TIME; public static boolean RESTORE_PLAYER_INSTANCE; public static int EJECT_DEAD_PLAYER_TIME; + public static boolean ALLOW_RACE; public static boolean ALLOW_WATER; public static boolean ALLOW_FISHING; public static boolean ALLOW_BOAT; @@ -1960,6 +1961,7 @@ public class Config INSTANCE_FINISH_TIME = General.getInt("DefaultFinishTime", 5); RESTORE_PLAYER_INSTANCE = General.getBoolean("RestorePlayerInstance", false); EJECT_DEAD_PLAYER_TIME = General.getInt("EjectDeadPlayerTime", 1); + ALLOW_RACE = General.getBoolean("AllowRace", true); ALLOW_WATER = General.getBoolean("AllowWater", true); ALLOW_FISHING = General.getBoolean("AllowFishing", true); ALLOW_MANOR = General.getBoolean("AllowManor", true); diff --git a/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/GameServer.java b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/GameServer.java index b5b027b620..a6322a53a7 100644 --- a/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/GameServer.java +++ b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/GameServer.java @@ -143,6 +143,7 @@ import org.l2jmobius.gameserver.instancemanager.SiegeGuardManager; import org.l2jmobius.gameserver.instancemanager.SiegeManager; import org.l2jmobius.gameserver.instancemanager.WalkingManager; import org.l2jmobius.gameserver.instancemanager.ZoneManager; +import org.l2jmobius.gameserver.instancemanager.games.MonsterRace; import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.entity.Hero; import org.l2jmobius.gameserver.model.events.EventDispatcher; @@ -405,6 +406,8 @@ public class GameServer ItemsAutoDestroy.getInstance(); } + MonsterRace.getInstance(); + TaskManager.getInstance(); AntiFeedManager.getInstance().registerEvent(AntiFeedManager.GAME_ID); diff --git a/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/instancemanager/games/MonsterRace.java b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/instancemanager/games/MonsterRace.java new file mode 100644 index 0000000000..2fa4c519f6 --- /dev/null +++ b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/instancemanager/games/MonsterRace.java @@ -0,0 +1,623 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program 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. + * + * This program 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 . + */ +package org.l2jmobius.gameserver.instancemanager.games; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.Config; +import org.l2jmobius.commons.concurrent.ThreadPool; +import org.l2jmobius.commons.database.DatabaseFactory; +import org.l2jmobius.commons.util.Rnd; +import org.l2jmobius.gameserver.data.xml.impl.NpcData; +import org.l2jmobius.gameserver.model.actor.Npc; +import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.model.zone.type.DerbyTrackZone; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.serverpackets.DeleteObject; +import org.l2jmobius.gameserver.network.serverpackets.MonRaceInfo; +import org.l2jmobius.gameserver.network.serverpackets.PlaySound; +import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; +import org.l2jmobius.gameserver.util.Broadcast; + +public class MonsterRace +{ + protected static final Logger LOGGER = Logger.getLogger(MonsterRace.class.getName()); + + public static enum RaceState + { + ACCEPTING_BETS, + WAITING, + STARTING_RACE, + RACE_END + } + + protected static final PlaySound SOUND_1 = new PlaySound(1, "S_Race", 0, 0, 0, 0, 0); + protected static final PlaySound SOUND_2 = new PlaySound("ItemSound2.race_start"); + + protected static final int[][] CODES = + { + { + -1, + 0 + }, + { + 0, + 15322 + }, + { + 13765, + -1 + } + }; + + protected final List _npcTemplates = new ArrayList<>(); // List holding npc templates, shuffled on a new race. + protected final List _history = new ArrayList<>(); // List holding old race records. + protected final Map _betsPerLane = new ConcurrentHashMap<>(); // Map holding all bets for each lane ; values setted to 0 after every race. + protected final List _odds = new ArrayList<>(); // List holding sorted odds per lane ; cleared at new odds calculation. + + protected int _raceNumber = 1; + protected int _finalCountdown = 0; + protected RaceState _state = RaceState.RACE_END; + + protected MonRaceInfo _packet; + + private final Npc[] _monsters = new Npc[8]; + private int[][] _speeds = new int[8][20]; + private final int[] _first = new int[2]; + private final int[] _second = new int[2]; + + protected MonsterRace() + { + if (!Config.ALLOW_RACE) + { + return; + } + + // Feed _history with previous race results. + loadHistory(); + + // Feed _betsPerLane with stored informations on bets. + loadBets(); + + // Feed _npcTemplates, we will only have to shuffle it when needed. + for (int i = 31003; i < 31027; i++) + { + _npcTemplates.add(i); + } + + ThreadPool.scheduleAtFixedRate(new Announcement(), 0, 1000); + } + + public static class HistoryInfo + { + private final int _raceId; + private int _first; + private int _second; + private double _oddRate; + + public HistoryInfo(int raceId, int first, int second, double oddRate) + { + _raceId = raceId; + _first = first; + _second = second; + _oddRate = oddRate; + } + + public int getRaceId() + { + return _raceId; + } + + public int getFirst() + { + return _first; + } + + public int getSecond() + { + return _second; + } + + public double getOddRate() + { + return _oddRate; + } + + public void setFirst(int first) + { + _first = first; + } + + public void setSecond(int second) + { + _second = second; + } + + public void setOddRate(double oddRate) + { + _oddRate = oddRate; + } + } + + private class Announcement implements Runnable + { + public Announcement() + { + } + + @Override + public void run() + { + if (_finalCountdown > 1200) + { + _finalCountdown = 0; + } + + switch (_finalCountdown) + { + case 0: + { + newRace(); + newSpeeds(); + + _state = RaceState.ACCEPTING_BETS; + _packet = new MonRaceInfo(CODES[0][0], CODES[0][1], getMonsters(), getSpeeds()); + + final SystemMessage msg = new SystemMessage(SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, _packet, msg); + 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 + { + final SystemMessage msg = new SystemMessage(SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + break; + } + case 300: // 5 min + { + final SystemMessage msg = new SystemMessage(SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S); + msg2.addInt(10); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + break; + } + case 600: // 10 min + { + final SystemMessage msg = new SystemMessage(SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S); + msg2.addInt(5); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + break; + } + case 840: // 14 min + { + final SystemMessage msg = new SystemMessage(SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S); + msg2.addInt(1); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + break; + } + case 900: // 15 min + { + _state = RaceState.WAITING; + + calculateOdds(); + + final SystemMessage msg = new SystemMessage(SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.TICKETS_SALES_ARE_CLOSED_FOR_MONSTER_RACE_S1_ODDS_ARE_POSTED); + msg2.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + break; + } + case 960: // 16 min + case 1020: // 17 min + { + final int minutes = (_finalCountdown == 960) ? 2 : 1; + final SystemMessage msg = new SystemMessage(SystemMessageId.MONSTER_RACE_S2_WILL_BEGIN_IN_S1_MINUTE_S); + msg.addInt(minutes); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + break; + } + case 1050: // 17 min 30 sec + { + final SystemMessage msg = new SystemMessage(SystemMessageId.MONSTER_RACE_S1_WILL_BEGIN_IN_30_SECONDS); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + break; + } + case 1070: // 17 min 50 sec + { + final SystemMessage msg = new SystemMessage(SystemMessageId.MONSTER_RACE_S1_IS_ABOUT_TO_BEGIN_COUNTDOWN_IN_FIVE_SECONDS); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + 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; + final SystemMessage msg = new SystemMessage(SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SECOND_S); + msg.addInt(seconds); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + break; + } + case 1080: // 18 min + { + _state = RaceState.STARTING_RACE; + _packet = new MonRaceInfo(CODES[1][0], CODES[1][1], getMonsters(), getSpeeds()); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, new SystemMessage(SystemMessageId.THEY_RE_OFF), SOUND_1, SOUND_2, _packet); + break; + } + case 1085: // 18 min 5 sec + { + _packet = new MonRaceInfo(CODES[2][0], CODES[2][1], getMonsters(), getSpeeds()); + + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, _packet); + break; + } + case 1115: // 18 min 35 sec + { + _state = RaceState.RACE_END; + + // Populate history info with data, stores it in database. + final HistoryInfo info = _history.get(_history.size() - 1); + info.setFirst(getFirstPlace()); + info.setSecond(getSecondPlace()); + info.setOddRate(_odds.get(getFirstPlace() - 1)); + + saveHistory(info); + clearBets(); + + final SystemMessage msg = new SystemMessage(SystemMessageId.FIRST_PRIZE_GOES_TO_THE_PLAYER_IN_LANE_S1_SECOND_PRIZE_GOES_TO_THE_PLAYER_IN_LANE_S2); + msg.addInt(getFirstPlace()); + msg.addInt(getSecondPlace()); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.MONSTER_RACE_S1_IS_FINISHED); + msg2.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + _raceNumber++; + break; + } + case 1140: // 19 min + { + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, new DeleteObject(getMonsters()[0]), new DeleteObject(getMonsters()[1]), new DeleteObject(getMonsters()[2]), new DeleteObject(getMonsters()[3]), new DeleteObject(getMonsters()[4]), new DeleteObject(getMonsters()[5]), new DeleteObject(getMonsters()[6]), new DeleteObject(getMonsters()[7])); + break; + } + } + _finalCountdown += 1; + } + } + + public void newRace() + { + // Edit _history. + _history.add(new HistoryInfo(_raceNumber, 0, 0, 0)); + + // Randomize _npcTemplates. + Collections.shuffle(_npcTemplates); + + // Setup 8 new creatures ; pickup the first 8 from _npcTemplates. + for (int i = 0; i < 8; i++) + { + try + { + final NpcTemplate template = NpcData.getInstance().getTemplate(_npcTemplates.get(i)); + _monsters[i] = (Npc) Class.forName("org.l2jmobius.gameserver.model.actor.instance." + template.getType() + "Instance").getConstructors()[0].newInstance(template); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "", e); + } + } + } + + public void newSpeeds() + { + _speeds = new int[8][20]; + int total = 0; + _first[1] = 0; + _second[1] = 0; + + for (int i = 0; i < 8; i++) + { + total = 0; + for (int j = 0; j < 20; j++) + { + if (j == 19) + { + _speeds[i][j] = 100; + } + else + { + _speeds[i][j] = Rnd.get(60) + 65; + } + total += _speeds[i][j]; + } + + if (total >= _first[1]) + { + _second[0] = _first[0]; + _second[1] = _first[1]; + _first[0] = 8 - i; + _first[1] = total; + } + else if (total >= _second[1]) + { + _second[0] = 8 - i; + _second[1] = total; + } + } + } + + /** + * Load past races informations, feeding _history arrayList.
+ * Also sets _raceNumber, based on latest HistoryInfo loaded. + */ + protected void loadHistory() + { + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("SELECT * FROM mdt_history"); + ResultSet rset = statement.executeQuery(); + + while (rset.next()) + { + _history.add(new HistoryInfo(rset.getInt("race_id"), rset.getInt("first"), rset.getInt("second"), rset.getDouble("odd_rate"))); + _raceNumber++; + } + rset.close(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't load history: " + e.getMessage(), e); + } + LOGGER.info("MonsterRace: loaded " + _history.size() + " records, currently on race #" + _raceNumber); + } + + /** + * Save an history record into database. + * @param history The infos to store. + */ + protected void saveHistory(HistoryInfo history) + { + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("INSERT INTO mdt_history (race_id, first, second, odd_rate) VALUES (?,?,?,?)"); + statement.setInt(1, history.getRaceId()); + statement.setInt(2, history.getFirst()); + statement.setInt(3, history.getSecond()); + statement.setDouble(4, history.getOddRate()); + statement.execute(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't save history: " + e.getMessage(), e); + } + } + + /** + * Load current bets per lane ; initialize the map keys. + */ + protected void loadBets() + { + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("SELECT * FROM mdt_bets"); + ResultSet rset = statement.executeQuery(); + + while (rset.next()) + { + setBetOnLane(rset.getInt("lane_id"), rset.getLong("bet"), false); + } + + rset.close(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't load bets: " + e.getMessage(), e); + } + } + + /** + * Save the current lane bet into database. + * @param lane : The lane to affect. + * @param sum : The sum to set. + */ + protected void saveBet(int lane, long sum) + { + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("REPLACE INTO mdt_bets (lane_id, bet) VALUES (?,?)"); + statement.setInt(1, lane); + statement.setLong(2, sum); + statement.execute(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't save bet: " + e.getMessage(), e); + } + } + + /** + * Clear all lanes bets, either on database or Map. + */ + protected void clearBets() + { + for (int key : _betsPerLane.keySet()) + { + _betsPerLane.put(key, 0L); + } + + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("UPDATE mdt_bets SET bet = 0"); + statement.execute(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't clear bets: " + e.getMessage(), e); + } + } + + /** + * Setup lane bet, based on previous value (if any). + * @param lane : The lane to edit. + * @param amount : The amount to add. + * @param saveOnDb : Should it be saved on db or not. + */ + public void setBetOnLane(int lane, long amount, boolean saveOnDb) + { + final long sum = (_betsPerLane.containsKey(lane)) ? _betsPerLane.get(lane) + amount : amount; + + _betsPerLane.put(lane, sum); + + if (saveOnDb) + { + saveBet(lane, sum); + } + } + + /** + * Calculate odds for every lane, based on others lanes. + */ + protected void calculateOdds() + { + // Clear previous List holding old odds. + _odds.clear(); + + // Sort bets lanes per lane. + final Map 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; + } + + // 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)); + } + } + + public Npc[] getMonsters() + { + return _monsters; + } + + public int[][] getSpeeds() + { + return _speeds; + } + + public int getFirstPlace() + { + return _first[0]; + } + + public int getSecondPlace() + { + return _second[0]; + } + + public MonRaceInfo getRacePacket() + { + return _packet; + } + + public RaceState getCurrentRaceState() + { + return _state; + } + + public int getRaceNumber() + { + return _raceNumber; + } + + public List getHistory() + { + return _history; + } + + public List getOdds() + { + return _odds; + } + + public static MonsterRace getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final MonsterRace INSTANCE = new MonsterRace(); + } +} \ No newline at end of file diff --git a/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/model/actor/instance/RaceManagerInstance.java b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/model/actor/instance/RaceManagerInstance.java new file mode 100644 index 0000000000..2ae6979233 --- /dev/null +++ b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/model/actor/instance/RaceManagerInstance.java @@ -0,0 +1,377 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program 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. + * + * This program 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 . + */ +package org.l2jmobius.gameserver.model.actor.instance; + +import java.util.List; +import java.util.Locale; + +import org.l2jmobius.Config; +import org.l2jmobius.commons.util.StringUtil; +import org.l2jmobius.gameserver.idfactory.IdFactory; +import org.l2jmobius.gameserver.instancemanager.games.MonsterRace; +import org.l2jmobius.gameserver.instancemanager.games.MonsterRace.HistoryInfo; +import org.l2jmobius.gameserver.instancemanager.games.MonsterRace.RaceState; +import org.l2jmobius.gameserver.model.actor.Npc; +import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.model.items.instance.ItemInstance; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.serverpackets.ActionFailed; +import org.l2jmobius.gameserver.network.serverpackets.NpcHtmlMessage; +import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; + +public class RaceManagerInstance extends Npc +{ + protected static final int TICKET_PRICES[] = + { + 100, + 500, + 1000, + 5000, + 10000, + 20000, + 50000, + 100000 + }; + + public RaceManagerInstance(NpcTemplate template) + { + super(template); + } + + @Override + public void onBypassFeedback(PlayerInstance player, String command) + { + if (command.startsWith("BuyTicket")) + { + if (!Config.ALLOW_RACE || (MonsterRace.getInstance().getCurrentRaceState() != RaceState.ACCEPTING_BETS)) + { + player.sendPacket(SystemMessageId.MONSTER_RACE_TICKETS_ARE_NO_LONGER_AVAILABLE); + super.onBypassFeedback(player, "Chat 0"); + return; + } + + int 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))) + { + val = 0; + } + + String search, replace; + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + + if (val < 10) + { + html.setFile(player, getHtmlPath(getId(), 2, player)); + for (int i = 0; i < 8; i++) + { + int n = i + 1; + search = "Mob" + n; + html.replace(search, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); + } + search = "No1"; + if (val == 0) + { + html.replace(search, ""); + } + else + { + html.replace(search, val); + player.setRace(0, val); + } + } + else if (val < 20) + { + if (player.getRace(0) == 0) + { + return; + } + + html.setFile(player, getHtmlPath(getId(), 3, player)); + html.replace("0place", player.getRace(0)); + search = "Mob1"; + replace = MonsterRace.getInstance().getMonsters()[player.getRace(0) - 1].getTemplate().getName(); + html.replace(search, replace); + search = "0adena"; + + if (val == 10) + { + html.replace(search, ""); + } + else + { + html.replace(search, TICKET_PRICES[val - 11]); + player.setRace(1, val - 10); + } + } + else if (val == 20) + { + if ((player.getRace(0) == 0) || (player.getRace(1) == 0)) + { + return; + } + + html.setFile(player, getHtmlPath(getId(), 4, player)); + html.replace("0place", player.getRace(0)); + search = "Mob1"; + replace = MonsterRace.getInstance().getMonsters()[player.getRace(0) - 1].getTemplate().getName(); + 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); + } + else + { + if ((player.getRace(0) == 0) || (player.getRace(1) == 0)) + { + return; + } + + int ticket = player.getRace(0); + int priceId = player.getRace(1); + + if (!player.reduceAdena("Race", TICKET_PRICES[priceId - 1], this, true)) + { + return; + } + + player.setRace(0, 0); + player.setRace(1, 0); + + ItemInstance item = new ItemInstance(IdFactory.getInstance().getNextId(), 4443); + item.setCount(1); + item.setEnchantLevel(MonsterRace.getInstance().getRaceNumber()); + item.setCustomType1(ticket); + item.setCustomType2(TICKET_PRICES[priceId - 1] / 100); + + player.addItem("Race", item, player, false); + final SystemMessage msg = new SystemMessage(SystemMessageId.ACQUIRED_S1_S2); + msg.addInt(MonsterRace.getInstance().getRaceNumber()); + msg.addItemName(4443); + player.sendPacket(msg); + + // Refresh lane bet. + MonsterRace.getInstance().setBetOnLane(ticket, TICKET_PRICES[priceId - 1], true); + super.onBypassFeedback(player, "Chat 0"); + return; + } + html.replace("1race", MonsterRace.getInstance().getRaceNumber()); + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); + } + else if (command.equals("ShowOdds")) + { + if (!Config.ALLOW_RACE || (MonsterRace.getInstance().getCurrentRaceState() == RaceState.ACCEPTING_BETS)) + { + player.sendPacket(SystemMessageId.MONSTER_RACE_PAYOUT_INFORMATION_IS_NOT_AVAILABLE_WHILE_TICKETS_ARE_BEING_SOLD); + super.onBypassFeedback(player, "Chat 0"); + return; + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 5, player)); + for (int i = 0; i < 8; i++) + { + final int n = i + 1; + + html.replace("Mob" + n, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); + + // Odd + final double odd = MonsterRace.getInstance().getOdds().get(i); + html.replace("Odd" + n, (odd > 0D) ? String.format(Locale.ENGLISH, "%.1f", odd) : "&$804;"); + } + html.replace("1race", MonsterRace.getInstance().getRaceNumber()); + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); + } + else if (command.equals("ShowInfo")) + { + if (!Config.ALLOW_RACE) + { + return; + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 6, player)); + + for (int i = 0; i < 8; i++) + { + int n = i + 1; + String search = "Mob" + n; + html.replace(search, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); + } + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); + } + else if (command.equals("ShowTickets")) + { + if (!Config.ALLOW_RACE) + { + super.onBypassFeedback(player, "Chat 0"); + return; + } + + // Generate data. + final StringBuilder sb = new StringBuilder(); + + // Retrieve player's tickets. + for (ItemInstance ticket : player.getInventory().getAllItemsByItemId(4443)) + { + // Don't list current race tickets. + if (ticket.getEnchantLevel() == MonsterRace.getInstance().getRaceNumber()) + { + continue; + } + + StringUtil.append(sb, "", "" + ticket.getEnchantLevel(), " Race Number", "" + ticket.getCustomType1(), " Number", "" + (ticket.getCustomType2() * 100), " Adena"); + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 7, player)); + 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)); + if (!Config.ALLOW_RACE || (val == 0)) + { + super.onBypassFeedback(player, "Chat 0"); + return; + } + + // Retrieve ticket on player's inventory. + final ItemInstance 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; + + // Retrieve HistoryInfo for that race. + final HistoryInfo info = MonsterRace.getInstance().getHistory().get(raceId - 1); + if (info == null) + { + super.onBypassFeedback(player, "Chat 0"); + return; + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 8, player)); + html.replace("%raceId%", raceId); + html.replace("%lane%", lane); + html.replace("%bet%", bet); + html.replace("%firstLane%", info.getFirst()); + html.replace("%odd%", (lane == info.getFirst()) ? String.format(Locale.ENGLISH, "%.2f", info.getOddRate()) : "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 (!Config.ALLOW_RACE || (val == 0)) + { + super.onBypassFeedback(player, "Chat 0"); + return; + } + + // Delete ticket on player's inventory. + final ItemInstance 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; + + // Retrieve HistoryInfo for that race. + final HistoryInfo info = MonsterRace.getInstance().getHistory().get(raceId - 1); + if (info == 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()) ? info.getOddRate() : 0.01)), this, true); + } + + super.onBypassFeedback(player, "Chat 0"); + return; + } + else if (command.equals("ViewHistory")) + { + if (!Config.ALLOW_RACE) + { + super.onBypassFeedback(player, "Chat 0"); + return; + } + + // Generate data. + final StringBuilder sb = new StringBuilder(); + + // Use whole history, pickup from 'last element' and stop at 'latest element - 7'. + final List history = MonsterRace.getInstance().getHistory(); + for (int i = history.size() - 1; i >= Math.max(0, history.size() - 7); i--) + { + final HistoryInfo info = history.get(i); + StringUtil.append(sb, "", "" + info.getRaceId(), " th", "" + info.getFirst(), " Lane ", "" + info.getSecond(), " Lane", String.format(Locale.ENGLISH, "%.2f", info.getOddRate()), " Times"); + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 9, player)); + html.replace("%infos%", sb.toString()); + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); + } + else + { + super.onBypassFeedback(player, command); + } + } +} diff --git a/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/util/Broadcast.java b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/util/Broadcast.java index 60eb0ff361..8d751afc53 100644 --- a/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/util/Broadcast.java +++ b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/gameserver/util/Broadcast.java @@ -20,10 +20,12 @@ import java.util.logging.Level; import java.util.logging.Logger; import org.l2jmobius.gameserver.enums.ChatType; +import org.l2jmobius.gameserver.instancemanager.ZoneManager; import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.actor.Creature; import org.l2jmobius.gameserver.model.actor.Summon; import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance; +import org.l2jmobius.gameserver.model.zone.ZoneType; import org.l2jmobius.gameserver.network.serverpackets.CharInfo; import org.l2jmobius.gameserver.network.serverpackets.CreatureSay; import org.l2jmobius.gameserver.network.serverpackets.ExShowScreenMessage; @@ -191,4 +193,29 @@ public class Broadcast { toAllOnlinePlayers(new ExShowScreenMessage(text, 10000)); } + + /** + * Send a packet to all players in a specific zone type. + * @param ZoneType. + * @param zoneType : The zone type to send packets. + * @param packets : The packets to send. + */ + public static void toAllPlayersInZoneType(Class zoneType, IClientOutgoingPacket... packets) + { + for (ZoneType zone : ZoneManager.getInstance().getAllZones(zoneType)) + { + for (Creature creature : zone.getCharactersInside()) + { + if (creature == null) + { + continue; + } + + for (IClientOutgoingPacket packet : packets) + { + creature.sendPacket(packet); + } + } + } + } } diff --git a/L2J_Mobius_Classic_3.0_TheKamael/dist/db_installer/sql/game/mdt_bets.sql b/L2J_Mobius_Classic_3.0_TheKamael/dist/db_installer/sql/game/mdt_bets.sql new file mode 100644 index 0000000000..8d7d4cb738 --- /dev/null +++ b/L2J_Mobius_Classic_3.0_TheKamael/dist/db_installer/sql/game/mdt_bets.sql @@ -0,0 +1,15 @@ +CREATE TABLE IF NOT EXISTS `mdt_bets` ( + `lane_id` INT(1) DEFAULT 0, + `bet` INT DEFAULT 0, + PRIMARY KEY (`lane_id`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci; + +INSERT INTO `mdt_bets` VALUES +('1','0'), +('2','0'), +('3','0'), +('4','0'), +('5','0'), +('6','0'), +('7','0'), +('8','0'); \ No newline at end of file diff --git a/L2J_Mobius_Classic_3.0_TheKamael/dist/db_installer/sql/game/mdt_history.sql b/L2J_Mobius_Classic_3.0_TheKamael/dist/db_installer/sql/game/mdt_history.sql new file mode 100644 index 0000000000..06978f3377 --- /dev/null +++ b/L2J_Mobius_Classic_3.0_TheKamael/dist/db_installer/sql/game/mdt_history.sql @@ -0,0 +1,7 @@ +CREATE TABLE IF NOT EXISTS `mdt_history` ( + `race_id` MEDIUMINT DEFAULT 0, + `first` INT(1) DEFAULT 0, + `second` INT(1) DEFAULT 0, + `odd_rate` DOUBLE(10,2) DEFAULT 0, + PRIMARY KEY (`race_id`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci; \ No newline at end of file diff --git a/L2J_Mobius_Classic_3.0_TheKamael/dist/game/config/General.ini b/L2J_Mobius_Classic_3.0_TheKamael/dist/game/config/General.ini index bc4b0889cb..761f34bea4 100644 --- a/L2J_Mobius_Classic_3.0_TheKamael/dist/game/config/General.ini +++ b/L2J_Mobius_Classic_3.0_TheKamael/dist/game/config/General.ini @@ -370,6 +370,9 @@ DefaultFinishTime = 5 # Misc Settings # --------------------------------------------------------------------------- +# Default: True +AllowRace = True + # Default: True AllowWater = True diff --git a/L2J_Mobius_Classic_3.0_TheKamael/dist/game/data/html/default/30995-1.htm b/L2J_Mobius_Classic_3.0_TheKamael/dist/game/data/html/default/30995-1.htm new file mode 100644 index 0000000000..d38d518149 --- /dev/null +++ b/L2J_Mobius_Classic_3.0_TheKamael/dist/game/data/html/default/30995-1.htm @@ -0,0 +1,22 @@ + +
+ +
+In a monster race eight monsters race at the same time. If you guess which monster is expected to finish first and buy that monster's race ticket, based upon the odds to win you will receive an appropriate amount of adena.
+Racing Schedule
+Monster races every 20 minutes in real time.
+Method of Race Participation
+You can purchase a monster race ticket through a race manager NPC. Only between 18 minutes before the start of a race until 3 minutes before the race begins you can buy a ticket for the applicable race. When you purchase a ticket through a conversation with a race manager, you can find out the names of participating monsters in the race and their current statistics. Five minutes before a race starts, the race manager will stop ticket sales and announce monsters' individual odds rate for the current race.
+Types of Race
+There are two types of race betting. The Win type betting means that you back a monster that will win the first prize. Place type of betting means that you will have to guess the 1st and 2nd winning monsters, regardless of who finishes first or second.
+Win +* Among the entire betting amount for Win betting, after subtracting the race track's own share of profit, the amount left will be your total winnings. Three minutes before the race each monster's expected odds are announced. Each Win ticket's dividend amount is calculated by multiplying the odds by the price of a ticket. The odds rate is never less than 1. +* The prize money does not get transferred to the next race.
+Place +* Place betting is a way of betting when you correctly guess the two monsters who will finish first and second regardless of their order. The method of calculating dividend amount is the same as Win betting.
+* Currently place betting is not offered.
+End of Race
+When a race is over, the race manager will shout out the results of the race. Through a conversation with a race manager NPC, you can trade your ticket for adena. By looking up on the Winning Information Data, you can find out the types of tickets and quantity of the winnings in the race you betted on and your win status and prize amount you won. If you won, you can receive your prize money thorough a race manager NPC. If you did not win, a race manager will buy back your ticket by paying you a very small amount of money.
+If a server goes down while tickets are being sold, or while a race is in progress, the race will be cancelled. You can receive a full refund for your ticket for a cancelled race through a race manager.
+ + \ No newline at end of file diff --git a/L2J_Mobius_Classic_3.0_TheKamael/dist/game/data/html/default/30995-2.htm b/L2J_Mobius_Classic_3.0_TheKamael/dist/game/data/html/default/30995-2.htm new file mode 100644 index 0000000000..0362da5162 --- /dev/null +++ b/L2J_Mobius_Classic_3.0_TheKamael/dist/game/data/html/default/30995-2.htm @@ -0,0 +1,94 @@ + +
Monster Race Betting To Win (Race # 1race) + + +
+ + + + + + + + +
Lane SelectionName of RacerRatingOdds
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
1Mob1&$745;13%
2Mob2&$745;6%
3Mob3&$747;4%
4Mob4&$745;12%
5Mob5&$745;14%
6Mob6&$745;13%
7Mob7&$745;13%
8Mob8&$746;14%
+ + + + + + + + + + +
Select Lane : No1
+ + \ No newline at end of file diff --git a/L2J_Mobius_Classic_3.0_TheKamael/dist/game/data/html/default/30995-3.htm b/L2J_Mobius_Classic_3.0_TheKamael/dist/game/data/html/default/30995-3.htm new file mode 100644 index 0000000000..7ae65a37a4 --- /dev/null +++ b/L2J_Mobius_Classic_3.0_TheKamael/dist/game/data/html/default/30995-3.htm @@ -0,0 +1,48 @@ + +
Monster Race Betting To Win (Race # 1race) + + +
+ + + + + + + + +
Lane SelectionRacer's Name RatingOdds
+ + + + + + + + + +
0placeMob1&$745;13%
+ + + + +
Select Bet Amount +100 Adena , 500 Adena +1,000 Adena , 5,000 Adena +10,000 Adena , 20,000 Adena +50,000 Adena , 100,000 Adena +
+ + + + + + + + + + + +
Purchase Price : 0adena
+ + \ No newline at end of file diff --git a/L2J_Mobius_Classic_3.0_TheKamael/dist/game/data/html/default/30995-4.htm b/L2J_Mobius_Classic_3.0_TheKamael/dist/game/data/html/default/30995-4.htm new file mode 100644 index 0000000000..30c508b9cb --- /dev/null +++ b/L2J_Mobius_Classic_3.0_TheKamael/dist/game/data/html/default/30995-4.htm @@ -0,0 +1,56 @@ + +
Monster Race Single Ticket Purchase (Race # 1race) + + +
+ + + + + + + +
Lane SelectionRacer's NameRatingOdds
+ + + + + + + + + +
0placeMob1&$745;13%
+ + + + + + + + + + + + + + + + + + +
Bet Amount
Bet Amount 0adena Adena
Tax 0tax Adena
Total 0total Adena
+
+
If the information is correct, please click Confirm.

+ + + + + + + + + + +
+ \ No newline at end of file diff --git a/L2J_Mobius_Classic_3.0_TheKamael/dist/game/data/html/default/30995-5.htm b/L2J_Mobius_Classic_3.0_TheKamael/dist/game/data/html/default/30995-5.htm new file mode 100644 index 0000000000..59a05e14a0 --- /dev/null +++ b/L2J_Mobius_Classic_3.0_TheKamael/dist/game/data/html/default/30995-5.htm @@ -0,0 +1,74 @@ + +
Monster Race Betting To Win View Odds (Race # 1race ) + + +
+ + + + + + +
LaneRacer's NameOdds
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
No.1Mob1Odd1
No.2Mob2Odd2
No.3Mob3Odd3
No.4Mob4Odd4
No.5Mob5Odd5
No.6Mob6Odd6
No.7Mob7Odd7
No.8Mob8Odd8
+ + + + + + + + + + +
+ \ No newline at end of file diff --git a/L2J_Mobius_Classic_3.0_TheKamael/dist/game/data/html/default/30995-6.htm b/L2J_Mobius_Classic_3.0_TheKamael/dist/game/data/html/default/30995-6.htm new file mode 100644 index 0000000000..0d845cb665 --- /dev/null +++ b/L2J_Mobius_Classic_3.0_TheKamael/dist/game/data/html/default/30995-6.htm @@ -0,0 +1,80 @@ + +
Participating Monsters + + +
+ + + + + + + +
LaneRacer's NameRatingOdds
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
1Mob1&$747;12%
2Mob2&$745;6%
3Mob3&$746;3%
4Mob4&$747;7%
5Mob5&$747;4%
6Mob6&$747;14%
7Mob7&$747;26%
8Mob8&$745;5%
+ + + + + + + + +
\ No newline at end of file diff --git a/L2J_Mobius_Classic_3.0_TheKamael/dist/game/data/html/default/30995-7.htm b/L2J_Mobius_Classic_3.0_TheKamael/dist/game/data/html/default/30995-7.htm new file mode 100644 index 0000000000..53eb213a52 --- /dev/null +++ b/L2J_Mobius_Classic_3.0_TheKamael/dist/game/data/html/default/30995-7.htm @@ -0,0 +1,19 @@ + +
Confirm Monster Race Bet / Calculate Winnings
+ + + + + +
Winnings for a Race.LaneBet Amount
+ +%tickets% +
+ + + + + + + +
\ No newline at end of file diff --git a/L2J_Mobius_Classic_3.0_TheKamael/dist/game/data/html/default/30995-8.htm b/L2J_Mobius_Classic_3.0_TheKamael/dist/game/data/html/default/30995-8.htm new file mode 100644 index 0000000000..5f856a222f --- /dev/null +++ b/L2J_Mobius_Classic_3.0_TheKamael/dist/game/data/html/default/30995-8.htm @@ -0,0 +1,25 @@ + +
Confirm Monster Race Bet / Calculate Winnings
+ + + + + + +
Race #%raceId%
+Lane: %lane%
+Bet: %bet% Adena
+1st place: Lane [%firstLane%]
+Odds: [%odd%] +
+ + + + + + + + + +
+ \ No newline at end of file diff --git a/L2J_Mobius_Classic_3.0_TheKamael/dist/game/data/html/default/30995-9.htm b/L2J_Mobius_Classic_3.0_TheKamael/dist/game/data/html/default/30995-9.htm new file mode 100644 index 0000000000..c17ab4849a --- /dev/null +++ b/L2J_Mobius_Classic_3.0_TheKamael/dist/game/data/html/default/30995-9.htm @@ -0,0 +1,22 @@ + +
View Monster Race Results
+ + + + +
Race Number1st Finish2nd FinishOdds Rate for Win Bet
+ + +%infos% +
+ + + + + + + + + +
+ \ No newline at end of file diff --git a/L2J_Mobius_Classic_3.0_TheKamael/dist/game/data/html/default/30995.htm b/L2J_Mobius_Classic_3.0_TheKamael/dist/game/data/html/default/30995.htm index ca913c7dd6..271c4ee284 100644 --- a/L2J_Mobius_Classic_3.0_TheKamael/dist/game/data/html/default/30995.htm +++ b/L2J_Mobius_Classic_3.0_TheKamael/dist/game/data/html/default/30995.htm @@ -1,3 +1,15 @@ -Monster Race Manager:
- +
Monster Race Manager
+ + +
+ + + + + + + + + + \ No newline at end of file diff --git a/L2J_Mobius_Classic_3.0_TheKamael/dist/game/data/stats/npcs/30900-30999.xml b/L2J_Mobius_Classic_3.0_TheKamael/dist/game/data/stats/npcs/30900-30999.xml index ef532eaecb..72d8e1ea4c 100644 --- a/L2J_Mobius_Classic_3.0_TheKamael/dist/game/data/stats/npcs/30900-30999.xml +++ b/L2J_Mobius_Classic_3.0_TheKamael/dist/game/data/stats/npcs/30900-30999.xml @@ -3593,7 +3593,7 @@
- + DWARF FEMALE diff --git a/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/Config.java b/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/Config.java index d7c9fac22f..f232e273a8 100644 --- a/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/Config.java +++ b/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/Config.java @@ -498,6 +498,7 @@ public class Config public static int INSTANCE_FINISH_TIME; public static boolean RESTORE_PLAYER_INSTANCE; public static int EJECT_DEAD_PLAYER_TIME; + public static boolean ALLOW_RACE; public static boolean ALLOW_WATER; public static boolean ALLOW_FISHING; public static boolean ALLOW_BOAT; @@ -1971,6 +1972,7 @@ public class Config INSTANCE_FINISH_TIME = General.getInt("DefaultFinishTime", 5); RESTORE_PLAYER_INSTANCE = General.getBoolean("RestorePlayerInstance", false); EJECT_DEAD_PLAYER_TIME = General.getInt("EjectDeadPlayerTime", 1); + ALLOW_RACE = General.getBoolean("AllowRace", true); ALLOW_WATER = General.getBoolean("AllowWater", true); ALLOW_FISHING = General.getBoolean("AllowFishing", true); ALLOW_MANOR = General.getBoolean("AllowManor", true); diff --git a/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/GameServer.java b/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/GameServer.java index 84c6798628..93f21a9232 100644 --- a/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/GameServer.java +++ b/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/GameServer.java @@ -145,6 +145,7 @@ import org.l2jmobius.gameserver.instancemanager.SiegeGuardManager; import org.l2jmobius.gameserver.instancemanager.SiegeManager; import org.l2jmobius.gameserver.instancemanager.WalkingManager; import org.l2jmobius.gameserver.instancemanager.ZoneManager; +import org.l2jmobius.gameserver.instancemanager.games.MonsterRace; import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.entity.Hero; import org.l2jmobius.gameserver.model.events.EventDispatcher; @@ -409,6 +410,8 @@ public class GameServer ItemsAutoDestroy.getInstance(); } + MonsterRace.getInstance(); + TaskManager.getInstance(); AntiFeedManager.getInstance().registerEvent(AntiFeedManager.GAME_ID); diff --git a/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/instancemanager/games/MonsterRace.java b/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/instancemanager/games/MonsterRace.java new file mode 100644 index 0000000000..2fa4c519f6 --- /dev/null +++ b/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/instancemanager/games/MonsterRace.java @@ -0,0 +1,623 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program 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. + * + * This program 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 . + */ +package org.l2jmobius.gameserver.instancemanager.games; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.Config; +import org.l2jmobius.commons.concurrent.ThreadPool; +import org.l2jmobius.commons.database.DatabaseFactory; +import org.l2jmobius.commons.util.Rnd; +import org.l2jmobius.gameserver.data.xml.impl.NpcData; +import org.l2jmobius.gameserver.model.actor.Npc; +import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.model.zone.type.DerbyTrackZone; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.serverpackets.DeleteObject; +import org.l2jmobius.gameserver.network.serverpackets.MonRaceInfo; +import org.l2jmobius.gameserver.network.serverpackets.PlaySound; +import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; +import org.l2jmobius.gameserver.util.Broadcast; + +public class MonsterRace +{ + protected static final Logger LOGGER = Logger.getLogger(MonsterRace.class.getName()); + + public static enum RaceState + { + ACCEPTING_BETS, + WAITING, + STARTING_RACE, + RACE_END + } + + protected static final PlaySound SOUND_1 = new PlaySound(1, "S_Race", 0, 0, 0, 0, 0); + protected static final PlaySound SOUND_2 = new PlaySound("ItemSound2.race_start"); + + protected static final int[][] CODES = + { + { + -1, + 0 + }, + { + 0, + 15322 + }, + { + 13765, + -1 + } + }; + + protected final List _npcTemplates = new ArrayList<>(); // List holding npc templates, shuffled on a new race. + protected final List _history = new ArrayList<>(); // List holding old race records. + protected final Map _betsPerLane = new ConcurrentHashMap<>(); // Map holding all bets for each lane ; values setted to 0 after every race. + protected final List _odds = new ArrayList<>(); // List holding sorted odds per lane ; cleared at new odds calculation. + + protected int _raceNumber = 1; + protected int _finalCountdown = 0; + protected RaceState _state = RaceState.RACE_END; + + protected MonRaceInfo _packet; + + private final Npc[] _monsters = new Npc[8]; + private int[][] _speeds = new int[8][20]; + private final int[] _first = new int[2]; + private final int[] _second = new int[2]; + + protected MonsterRace() + { + if (!Config.ALLOW_RACE) + { + return; + } + + // Feed _history with previous race results. + loadHistory(); + + // Feed _betsPerLane with stored informations on bets. + loadBets(); + + // Feed _npcTemplates, we will only have to shuffle it when needed. + for (int i = 31003; i < 31027; i++) + { + _npcTemplates.add(i); + } + + ThreadPool.scheduleAtFixedRate(new Announcement(), 0, 1000); + } + + public static class HistoryInfo + { + private final int _raceId; + private int _first; + private int _second; + private double _oddRate; + + public HistoryInfo(int raceId, int first, int second, double oddRate) + { + _raceId = raceId; + _first = first; + _second = second; + _oddRate = oddRate; + } + + public int getRaceId() + { + return _raceId; + } + + public int getFirst() + { + return _first; + } + + public int getSecond() + { + return _second; + } + + public double getOddRate() + { + return _oddRate; + } + + public void setFirst(int first) + { + _first = first; + } + + public void setSecond(int second) + { + _second = second; + } + + public void setOddRate(double oddRate) + { + _oddRate = oddRate; + } + } + + private class Announcement implements Runnable + { + public Announcement() + { + } + + @Override + public void run() + { + if (_finalCountdown > 1200) + { + _finalCountdown = 0; + } + + switch (_finalCountdown) + { + case 0: + { + newRace(); + newSpeeds(); + + _state = RaceState.ACCEPTING_BETS; + _packet = new MonRaceInfo(CODES[0][0], CODES[0][1], getMonsters(), getSpeeds()); + + final SystemMessage msg = new SystemMessage(SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, _packet, msg); + 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 + { + final SystemMessage msg = new SystemMessage(SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + break; + } + case 300: // 5 min + { + final SystemMessage msg = new SystemMessage(SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S); + msg2.addInt(10); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + break; + } + case 600: // 10 min + { + final SystemMessage msg = new SystemMessage(SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S); + msg2.addInt(5); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + break; + } + case 840: // 14 min + { + final SystemMessage msg = new SystemMessage(SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S); + msg2.addInt(1); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + break; + } + case 900: // 15 min + { + _state = RaceState.WAITING; + + calculateOdds(); + + final SystemMessage msg = new SystemMessage(SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.TICKETS_SALES_ARE_CLOSED_FOR_MONSTER_RACE_S1_ODDS_ARE_POSTED); + msg2.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + break; + } + case 960: // 16 min + case 1020: // 17 min + { + final int minutes = (_finalCountdown == 960) ? 2 : 1; + final SystemMessage msg = new SystemMessage(SystemMessageId.MONSTER_RACE_S2_WILL_BEGIN_IN_S1_MINUTE_S); + msg.addInt(minutes); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + break; + } + case 1050: // 17 min 30 sec + { + final SystemMessage msg = new SystemMessage(SystemMessageId.MONSTER_RACE_S1_WILL_BEGIN_IN_30_SECONDS); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + break; + } + case 1070: // 17 min 50 sec + { + final SystemMessage msg = new SystemMessage(SystemMessageId.MONSTER_RACE_S1_IS_ABOUT_TO_BEGIN_COUNTDOWN_IN_FIVE_SECONDS); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + 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; + final SystemMessage msg = new SystemMessage(SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SECOND_S); + msg.addInt(seconds); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + break; + } + case 1080: // 18 min + { + _state = RaceState.STARTING_RACE; + _packet = new MonRaceInfo(CODES[1][0], CODES[1][1], getMonsters(), getSpeeds()); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, new SystemMessage(SystemMessageId.THEY_RE_OFF), SOUND_1, SOUND_2, _packet); + break; + } + case 1085: // 18 min 5 sec + { + _packet = new MonRaceInfo(CODES[2][0], CODES[2][1], getMonsters(), getSpeeds()); + + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, _packet); + break; + } + case 1115: // 18 min 35 sec + { + _state = RaceState.RACE_END; + + // Populate history info with data, stores it in database. + final HistoryInfo info = _history.get(_history.size() - 1); + info.setFirst(getFirstPlace()); + info.setSecond(getSecondPlace()); + info.setOddRate(_odds.get(getFirstPlace() - 1)); + + saveHistory(info); + clearBets(); + + final SystemMessage msg = new SystemMessage(SystemMessageId.FIRST_PRIZE_GOES_TO_THE_PLAYER_IN_LANE_S1_SECOND_PRIZE_GOES_TO_THE_PLAYER_IN_LANE_S2); + msg.addInt(getFirstPlace()); + msg.addInt(getSecondPlace()); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.MONSTER_RACE_S1_IS_FINISHED); + msg2.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + _raceNumber++; + break; + } + case 1140: // 19 min + { + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, new DeleteObject(getMonsters()[0]), new DeleteObject(getMonsters()[1]), new DeleteObject(getMonsters()[2]), new DeleteObject(getMonsters()[3]), new DeleteObject(getMonsters()[4]), new DeleteObject(getMonsters()[5]), new DeleteObject(getMonsters()[6]), new DeleteObject(getMonsters()[7])); + break; + } + } + _finalCountdown += 1; + } + } + + public void newRace() + { + // Edit _history. + _history.add(new HistoryInfo(_raceNumber, 0, 0, 0)); + + // Randomize _npcTemplates. + Collections.shuffle(_npcTemplates); + + // Setup 8 new creatures ; pickup the first 8 from _npcTemplates. + for (int i = 0; i < 8; i++) + { + try + { + final NpcTemplate template = NpcData.getInstance().getTemplate(_npcTemplates.get(i)); + _monsters[i] = (Npc) Class.forName("org.l2jmobius.gameserver.model.actor.instance." + template.getType() + "Instance").getConstructors()[0].newInstance(template); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "", e); + } + } + } + + public void newSpeeds() + { + _speeds = new int[8][20]; + int total = 0; + _first[1] = 0; + _second[1] = 0; + + for (int i = 0; i < 8; i++) + { + total = 0; + for (int j = 0; j < 20; j++) + { + if (j == 19) + { + _speeds[i][j] = 100; + } + else + { + _speeds[i][j] = Rnd.get(60) + 65; + } + total += _speeds[i][j]; + } + + if (total >= _first[1]) + { + _second[0] = _first[0]; + _second[1] = _first[1]; + _first[0] = 8 - i; + _first[1] = total; + } + else if (total >= _second[1]) + { + _second[0] = 8 - i; + _second[1] = total; + } + } + } + + /** + * Load past races informations, feeding _history arrayList.
+ * Also sets _raceNumber, based on latest HistoryInfo loaded. + */ + protected void loadHistory() + { + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("SELECT * FROM mdt_history"); + ResultSet rset = statement.executeQuery(); + + while (rset.next()) + { + _history.add(new HistoryInfo(rset.getInt("race_id"), rset.getInt("first"), rset.getInt("second"), rset.getDouble("odd_rate"))); + _raceNumber++; + } + rset.close(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't load history: " + e.getMessage(), e); + } + LOGGER.info("MonsterRace: loaded " + _history.size() + " records, currently on race #" + _raceNumber); + } + + /** + * Save an history record into database. + * @param history The infos to store. + */ + protected void saveHistory(HistoryInfo history) + { + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("INSERT INTO mdt_history (race_id, first, second, odd_rate) VALUES (?,?,?,?)"); + statement.setInt(1, history.getRaceId()); + statement.setInt(2, history.getFirst()); + statement.setInt(3, history.getSecond()); + statement.setDouble(4, history.getOddRate()); + statement.execute(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't save history: " + e.getMessage(), e); + } + } + + /** + * Load current bets per lane ; initialize the map keys. + */ + protected void loadBets() + { + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("SELECT * FROM mdt_bets"); + ResultSet rset = statement.executeQuery(); + + while (rset.next()) + { + setBetOnLane(rset.getInt("lane_id"), rset.getLong("bet"), false); + } + + rset.close(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't load bets: " + e.getMessage(), e); + } + } + + /** + * Save the current lane bet into database. + * @param lane : The lane to affect. + * @param sum : The sum to set. + */ + protected void saveBet(int lane, long sum) + { + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("REPLACE INTO mdt_bets (lane_id, bet) VALUES (?,?)"); + statement.setInt(1, lane); + statement.setLong(2, sum); + statement.execute(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't save bet: " + e.getMessage(), e); + } + } + + /** + * Clear all lanes bets, either on database or Map. + */ + protected void clearBets() + { + for (int key : _betsPerLane.keySet()) + { + _betsPerLane.put(key, 0L); + } + + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("UPDATE mdt_bets SET bet = 0"); + statement.execute(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't clear bets: " + e.getMessage(), e); + } + } + + /** + * Setup lane bet, based on previous value (if any). + * @param lane : The lane to edit. + * @param amount : The amount to add. + * @param saveOnDb : Should it be saved on db or not. + */ + public void setBetOnLane(int lane, long amount, boolean saveOnDb) + { + final long sum = (_betsPerLane.containsKey(lane)) ? _betsPerLane.get(lane) + amount : amount; + + _betsPerLane.put(lane, sum); + + if (saveOnDb) + { + saveBet(lane, sum); + } + } + + /** + * Calculate odds for every lane, based on others lanes. + */ + protected void calculateOdds() + { + // Clear previous List holding old odds. + _odds.clear(); + + // Sort bets lanes per lane. + final Map 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; + } + + // 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)); + } + } + + public Npc[] getMonsters() + { + return _monsters; + } + + public int[][] getSpeeds() + { + return _speeds; + } + + public int getFirstPlace() + { + return _first[0]; + } + + public int getSecondPlace() + { + return _second[0]; + } + + public MonRaceInfo getRacePacket() + { + return _packet; + } + + public RaceState getCurrentRaceState() + { + return _state; + } + + public int getRaceNumber() + { + return _raceNumber; + } + + public List getHistory() + { + return _history; + } + + public List getOdds() + { + return _odds; + } + + public static MonsterRace getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final MonsterRace INSTANCE = new MonsterRace(); + } +} \ No newline at end of file diff --git a/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/model/actor/instance/RaceManagerInstance.java b/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/model/actor/instance/RaceManagerInstance.java new file mode 100644 index 0000000000..2ae6979233 --- /dev/null +++ b/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/model/actor/instance/RaceManagerInstance.java @@ -0,0 +1,377 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program 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. + * + * This program 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 . + */ +package org.l2jmobius.gameserver.model.actor.instance; + +import java.util.List; +import java.util.Locale; + +import org.l2jmobius.Config; +import org.l2jmobius.commons.util.StringUtil; +import org.l2jmobius.gameserver.idfactory.IdFactory; +import org.l2jmobius.gameserver.instancemanager.games.MonsterRace; +import org.l2jmobius.gameserver.instancemanager.games.MonsterRace.HistoryInfo; +import org.l2jmobius.gameserver.instancemanager.games.MonsterRace.RaceState; +import org.l2jmobius.gameserver.model.actor.Npc; +import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.model.items.instance.ItemInstance; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.serverpackets.ActionFailed; +import org.l2jmobius.gameserver.network.serverpackets.NpcHtmlMessage; +import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; + +public class RaceManagerInstance extends Npc +{ + protected static final int TICKET_PRICES[] = + { + 100, + 500, + 1000, + 5000, + 10000, + 20000, + 50000, + 100000 + }; + + public RaceManagerInstance(NpcTemplate template) + { + super(template); + } + + @Override + public void onBypassFeedback(PlayerInstance player, String command) + { + if (command.startsWith("BuyTicket")) + { + if (!Config.ALLOW_RACE || (MonsterRace.getInstance().getCurrentRaceState() != RaceState.ACCEPTING_BETS)) + { + player.sendPacket(SystemMessageId.MONSTER_RACE_TICKETS_ARE_NO_LONGER_AVAILABLE); + super.onBypassFeedback(player, "Chat 0"); + return; + } + + int 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))) + { + val = 0; + } + + String search, replace; + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + + if (val < 10) + { + html.setFile(player, getHtmlPath(getId(), 2, player)); + for (int i = 0; i < 8; i++) + { + int n = i + 1; + search = "Mob" + n; + html.replace(search, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); + } + search = "No1"; + if (val == 0) + { + html.replace(search, ""); + } + else + { + html.replace(search, val); + player.setRace(0, val); + } + } + else if (val < 20) + { + if (player.getRace(0) == 0) + { + return; + } + + html.setFile(player, getHtmlPath(getId(), 3, player)); + html.replace("0place", player.getRace(0)); + search = "Mob1"; + replace = MonsterRace.getInstance().getMonsters()[player.getRace(0) - 1].getTemplate().getName(); + html.replace(search, replace); + search = "0adena"; + + if (val == 10) + { + html.replace(search, ""); + } + else + { + html.replace(search, TICKET_PRICES[val - 11]); + player.setRace(1, val - 10); + } + } + else if (val == 20) + { + if ((player.getRace(0) == 0) || (player.getRace(1) == 0)) + { + return; + } + + html.setFile(player, getHtmlPath(getId(), 4, player)); + html.replace("0place", player.getRace(0)); + search = "Mob1"; + replace = MonsterRace.getInstance().getMonsters()[player.getRace(0) - 1].getTemplate().getName(); + 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); + } + else + { + if ((player.getRace(0) == 0) || (player.getRace(1) == 0)) + { + return; + } + + int ticket = player.getRace(0); + int priceId = player.getRace(1); + + if (!player.reduceAdena("Race", TICKET_PRICES[priceId - 1], this, true)) + { + return; + } + + player.setRace(0, 0); + player.setRace(1, 0); + + ItemInstance item = new ItemInstance(IdFactory.getInstance().getNextId(), 4443); + item.setCount(1); + item.setEnchantLevel(MonsterRace.getInstance().getRaceNumber()); + item.setCustomType1(ticket); + item.setCustomType2(TICKET_PRICES[priceId - 1] / 100); + + player.addItem("Race", item, player, false); + final SystemMessage msg = new SystemMessage(SystemMessageId.ACQUIRED_S1_S2); + msg.addInt(MonsterRace.getInstance().getRaceNumber()); + msg.addItemName(4443); + player.sendPacket(msg); + + // Refresh lane bet. + MonsterRace.getInstance().setBetOnLane(ticket, TICKET_PRICES[priceId - 1], true); + super.onBypassFeedback(player, "Chat 0"); + return; + } + html.replace("1race", MonsterRace.getInstance().getRaceNumber()); + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); + } + else if (command.equals("ShowOdds")) + { + if (!Config.ALLOW_RACE || (MonsterRace.getInstance().getCurrentRaceState() == RaceState.ACCEPTING_BETS)) + { + player.sendPacket(SystemMessageId.MONSTER_RACE_PAYOUT_INFORMATION_IS_NOT_AVAILABLE_WHILE_TICKETS_ARE_BEING_SOLD); + super.onBypassFeedback(player, "Chat 0"); + return; + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 5, player)); + for (int i = 0; i < 8; i++) + { + final int n = i + 1; + + html.replace("Mob" + n, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); + + // Odd + final double odd = MonsterRace.getInstance().getOdds().get(i); + html.replace("Odd" + n, (odd > 0D) ? String.format(Locale.ENGLISH, "%.1f", odd) : "&$804;"); + } + html.replace("1race", MonsterRace.getInstance().getRaceNumber()); + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); + } + else if (command.equals("ShowInfo")) + { + if (!Config.ALLOW_RACE) + { + return; + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 6, player)); + + for (int i = 0; i < 8; i++) + { + int n = i + 1; + String search = "Mob" + n; + html.replace(search, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); + } + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); + } + else if (command.equals("ShowTickets")) + { + if (!Config.ALLOW_RACE) + { + super.onBypassFeedback(player, "Chat 0"); + return; + } + + // Generate data. + final StringBuilder sb = new StringBuilder(); + + // Retrieve player's tickets. + for (ItemInstance ticket : player.getInventory().getAllItemsByItemId(4443)) + { + // Don't list current race tickets. + if (ticket.getEnchantLevel() == MonsterRace.getInstance().getRaceNumber()) + { + continue; + } + + StringUtil.append(sb, "", "" + ticket.getEnchantLevel(), " Race Number", "" + ticket.getCustomType1(), " Number", "" + (ticket.getCustomType2() * 100), " Adena"); + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 7, player)); + 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)); + if (!Config.ALLOW_RACE || (val == 0)) + { + super.onBypassFeedback(player, "Chat 0"); + return; + } + + // Retrieve ticket on player's inventory. + final ItemInstance 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; + + // Retrieve HistoryInfo for that race. + final HistoryInfo info = MonsterRace.getInstance().getHistory().get(raceId - 1); + if (info == null) + { + super.onBypassFeedback(player, "Chat 0"); + return; + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 8, player)); + html.replace("%raceId%", raceId); + html.replace("%lane%", lane); + html.replace("%bet%", bet); + html.replace("%firstLane%", info.getFirst()); + html.replace("%odd%", (lane == info.getFirst()) ? String.format(Locale.ENGLISH, "%.2f", info.getOddRate()) : "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 (!Config.ALLOW_RACE || (val == 0)) + { + super.onBypassFeedback(player, "Chat 0"); + return; + } + + // Delete ticket on player's inventory. + final ItemInstance 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; + + // Retrieve HistoryInfo for that race. + final HistoryInfo info = MonsterRace.getInstance().getHistory().get(raceId - 1); + if (info == 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()) ? info.getOddRate() : 0.01)), this, true); + } + + super.onBypassFeedback(player, "Chat 0"); + return; + } + else if (command.equals("ViewHistory")) + { + if (!Config.ALLOW_RACE) + { + super.onBypassFeedback(player, "Chat 0"); + return; + } + + // Generate data. + final StringBuilder sb = new StringBuilder(); + + // Use whole history, pickup from 'last element' and stop at 'latest element - 7'. + final List history = MonsterRace.getInstance().getHistory(); + for (int i = history.size() - 1; i >= Math.max(0, history.size() - 7); i--) + { + final HistoryInfo info = history.get(i); + StringUtil.append(sb, "", "" + info.getRaceId(), " th", "" + info.getFirst(), " Lane ", "" + info.getSecond(), " Lane", String.format(Locale.ENGLISH, "%.2f", info.getOddRate()), " Times"); + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 9, player)); + html.replace("%infos%", sb.toString()); + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); + } + else + { + super.onBypassFeedback(player, command); + } + } +} diff --git a/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/util/Broadcast.java b/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/util/Broadcast.java index 60eb0ff361..8d751afc53 100644 --- a/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/util/Broadcast.java +++ b/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/util/Broadcast.java @@ -20,10 +20,12 @@ import java.util.logging.Level; import java.util.logging.Logger; import org.l2jmobius.gameserver.enums.ChatType; +import org.l2jmobius.gameserver.instancemanager.ZoneManager; import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.actor.Creature; import org.l2jmobius.gameserver.model.actor.Summon; import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance; +import org.l2jmobius.gameserver.model.zone.ZoneType; import org.l2jmobius.gameserver.network.serverpackets.CharInfo; import org.l2jmobius.gameserver.network.serverpackets.CreatureSay; import org.l2jmobius.gameserver.network.serverpackets.ExShowScreenMessage; @@ -191,4 +193,29 @@ public class Broadcast { toAllOnlinePlayers(new ExShowScreenMessage(text, 10000)); } + + /** + * Send a packet to all players in a specific zone type. + * @param ZoneType. + * @param zoneType : The zone type to send packets. + * @param packets : The packets to send. + */ + public static void toAllPlayersInZoneType(Class zoneType, IClientOutgoingPacket... packets) + { + for (ZoneType zone : ZoneManager.getInstance().getAllZones(zoneType)) + { + for (Creature creature : zone.getCharactersInside()) + { + if (creature == null) + { + continue; + } + + for (IClientOutgoingPacket packet : packets) + { + creature.sendPacket(packet); + } + } + } + } } diff --git a/L2J_Mobius_Classic_Interlude/dist/db_installer/sql/game/mdt_bets.sql b/L2J_Mobius_Classic_Interlude/dist/db_installer/sql/game/mdt_bets.sql new file mode 100644 index 0000000000..8d7d4cb738 --- /dev/null +++ b/L2J_Mobius_Classic_Interlude/dist/db_installer/sql/game/mdt_bets.sql @@ -0,0 +1,15 @@ +CREATE TABLE IF NOT EXISTS `mdt_bets` ( + `lane_id` INT(1) DEFAULT 0, + `bet` INT DEFAULT 0, + PRIMARY KEY (`lane_id`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci; + +INSERT INTO `mdt_bets` VALUES +('1','0'), +('2','0'), +('3','0'), +('4','0'), +('5','0'), +('6','0'), +('7','0'), +('8','0'); \ No newline at end of file diff --git a/L2J_Mobius_Classic_Interlude/dist/db_installer/sql/game/mdt_history.sql b/L2J_Mobius_Classic_Interlude/dist/db_installer/sql/game/mdt_history.sql new file mode 100644 index 0000000000..06978f3377 --- /dev/null +++ b/L2J_Mobius_Classic_Interlude/dist/db_installer/sql/game/mdt_history.sql @@ -0,0 +1,7 @@ +CREATE TABLE IF NOT EXISTS `mdt_history` ( + `race_id` MEDIUMINT DEFAULT 0, + `first` INT(1) DEFAULT 0, + `second` INT(1) DEFAULT 0, + `odd_rate` DOUBLE(10,2) DEFAULT 0, + PRIMARY KEY (`race_id`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci; \ No newline at end of file diff --git a/L2J_Mobius_Classic_Interlude/dist/game/config/General.ini b/L2J_Mobius_Classic_Interlude/dist/game/config/General.ini index bc4b0889cb..761f34bea4 100644 --- a/L2J_Mobius_Classic_Interlude/dist/game/config/General.ini +++ b/L2J_Mobius_Classic_Interlude/dist/game/config/General.ini @@ -370,6 +370,9 @@ DefaultFinishTime = 5 # Misc Settings # --------------------------------------------------------------------------- +# Default: True +AllowRace = True + # Default: True AllowWater = True diff --git a/L2J_Mobius_Classic_Interlude/dist/game/data/html/default/30995-1.htm b/L2J_Mobius_Classic_Interlude/dist/game/data/html/default/30995-1.htm new file mode 100644 index 0000000000..d38d518149 --- /dev/null +++ b/L2J_Mobius_Classic_Interlude/dist/game/data/html/default/30995-1.htm @@ -0,0 +1,22 @@ + +
+ +
+In a monster race eight monsters race at the same time. If you guess which monster is expected to finish first and buy that monster's race ticket, based upon the odds to win you will receive an appropriate amount of adena.
+Racing Schedule
+Monster races every 20 minutes in real time.
+Method of Race Participation
+You can purchase a monster race ticket through a race manager NPC. Only between 18 minutes before the start of a race until 3 minutes before the race begins you can buy a ticket for the applicable race. When you purchase a ticket through a conversation with a race manager, you can find out the names of participating monsters in the race and their current statistics. Five minutes before a race starts, the race manager will stop ticket sales and announce monsters' individual odds rate for the current race.
+Types of Race
+There are two types of race betting. The Win type betting means that you back a monster that will win the first prize. Place type of betting means that you will have to guess the 1st and 2nd winning monsters, regardless of who finishes first or second.
+Win +* Among the entire betting amount for Win betting, after subtracting the race track's own share of profit, the amount left will be your total winnings. Three minutes before the race each monster's expected odds are announced. Each Win ticket's dividend amount is calculated by multiplying the odds by the price of a ticket. The odds rate is never less than 1. +* The prize money does not get transferred to the next race.
+Place +* Place betting is a way of betting when you correctly guess the two monsters who will finish first and second regardless of their order. The method of calculating dividend amount is the same as Win betting.
+* Currently place betting is not offered.
+End of Race
+When a race is over, the race manager will shout out the results of the race. Through a conversation with a race manager NPC, you can trade your ticket for adena. By looking up on the Winning Information Data, you can find out the types of tickets and quantity of the winnings in the race you betted on and your win status and prize amount you won. If you won, you can receive your prize money thorough a race manager NPC. If you did not win, a race manager will buy back your ticket by paying you a very small amount of money.
+If a server goes down while tickets are being sold, or while a race is in progress, the race will be cancelled. You can receive a full refund for your ticket for a cancelled race through a race manager.
+ + \ No newline at end of file diff --git a/L2J_Mobius_Classic_Interlude/dist/game/data/html/default/30995-2.htm b/L2J_Mobius_Classic_Interlude/dist/game/data/html/default/30995-2.htm new file mode 100644 index 0000000000..0362da5162 --- /dev/null +++ b/L2J_Mobius_Classic_Interlude/dist/game/data/html/default/30995-2.htm @@ -0,0 +1,94 @@ + +
Monster Race Betting To Win (Race # 1race) + + +
+ + + + + + + + +
Lane SelectionName of RacerRatingOdds
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
1Mob1&$745;13%
2Mob2&$745;6%
3Mob3&$747;4%
4Mob4&$745;12%
5Mob5&$745;14%
6Mob6&$745;13%
7Mob7&$745;13%
8Mob8&$746;14%
+ + + + + + + + + + +
Select Lane : No1
+ + \ No newline at end of file diff --git a/L2J_Mobius_Classic_Interlude/dist/game/data/html/default/30995-3.htm b/L2J_Mobius_Classic_Interlude/dist/game/data/html/default/30995-3.htm new file mode 100644 index 0000000000..7ae65a37a4 --- /dev/null +++ b/L2J_Mobius_Classic_Interlude/dist/game/data/html/default/30995-3.htm @@ -0,0 +1,48 @@ + +
Monster Race Betting To Win (Race # 1race) + + +
+ + + + + + + + +
Lane SelectionRacer's Name RatingOdds
+ + + + + + + + + +
0placeMob1&$745;13%
+ + + + +
Select Bet Amount +100 Adena , 500 Adena +1,000 Adena , 5,000 Adena +10,000 Adena , 20,000 Adena +50,000 Adena , 100,000 Adena +
+ + + + + + + + + + + +
Purchase Price : 0adena
+ + \ No newline at end of file diff --git a/L2J_Mobius_Classic_Interlude/dist/game/data/html/default/30995-4.htm b/L2J_Mobius_Classic_Interlude/dist/game/data/html/default/30995-4.htm new file mode 100644 index 0000000000..30c508b9cb --- /dev/null +++ b/L2J_Mobius_Classic_Interlude/dist/game/data/html/default/30995-4.htm @@ -0,0 +1,56 @@ + +
Monster Race Single Ticket Purchase (Race # 1race) + + +
+ + + + + + + +
Lane SelectionRacer's NameRatingOdds
+ + + + + + + + + +
0placeMob1&$745;13%
+ + + + + + + + + + + + + + + + + + +
Bet Amount
Bet Amount 0adena Adena
Tax 0tax Adena
Total 0total Adena
+
+
If the information is correct, please click Confirm.

+ + + + + + + + + + +
+ \ No newline at end of file diff --git a/L2J_Mobius_Classic_Interlude/dist/game/data/html/default/30995-5.htm b/L2J_Mobius_Classic_Interlude/dist/game/data/html/default/30995-5.htm new file mode 100644 index 0000000000..59a05e14a0 --- /dev/null +++ b/L2J_Mobius_Classic_Interlude/dist/game/data/html/default/30995-5.htm @@ -0,0 +1,74 @@ + +
Monster Race Betting To Win View Odds (Race # 1race ) + + +
+ + + + + + +
LaneRacer's NameOdds
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
No.1Mob1Odd1
No.2Mob2Odd2
No.3Mob3Odd3
No.4Mob4Odd4
No.5Mob5Odd5
No.6Mob6Odd6
No.7Mob7Odd7
No.8Mob8Odd8
+ + + + + + + + + + +
+ \ No newline at end of file diff --git a/L2J_Mobius_Classic_Interlude/dist/game/data/html/default/30995-6.htm b/L2J_Mobius_Classic_Interlude/dist/game/data/html/default/30995-6.htm new file mode 100644 index 0000000000..0d845cb665 --- /dev/null +++ b/L2J_Mobius_Classic_Interlude/dist/game/data/html/default/30995-6.htm @@ -0,0 +1,80 @@ + +
Participating Monsters + + +
+ + + + + + + +
LaneRacer's NameRatingOdds
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
1Mob1&$747;12%
2Mob2&$745;6%
3Mob3&$746;3%
4Mob4&$747;7%
5Mob5&$747;4%
6Mob6&$747;14%
7Mob7&$747;26%
8Mob8&$745;5%
+ + + + + + + + +
\ No newline at end of file diff --git a/L2J_Mobius_Classic_Interlude/dist/game/data/html/default/30995-7.htm b/L2J_Mobius_Classic_Interlude/dist/game/data/html/default/30995-7.htm new file mode 100644 index 0000000000..53eb213a52 --- /dev/null +++ b/L2J_Mobius_Classic_Interlude/dist/game/data/html/default/30995-7.htm @@ -0,0 +1,19 @@ + +
Confirm Monster Race Bet / Calculate Winnings
+ + + + + +
Winnings for a Race.LaneBet Amount
+ +%tickets% +
+ + + + + + + +
\ No newline at end of file diff --git a/L2J_Mobius_Classic_Interlude/dist/game/data/html/default/30995-8.htm b/L2J_Mobius_Classic_Interlude/dist/game/data/html/default/30995-8.htm new file mode 100644 index 0000000000..5f856a222f --- /dev/null +++ b/L2J_Mobius_Classic_Interlude/dist/game/data/html/default/30995-8.htm @@ -0,0 +1,25 @@ + +
Confirm Monster Race Bet / Calculate Winnings
+ + + + + + +
Race #%raceId%
+Lane: %lane%
+Bet: %bet% Adena
+1st place: Lane [%firstLane%]
+Odds: [%odd%] +
+ + + + + + + + + +
+ \ No newline at end of file diff --git a/L2J_Mobius_Classic_Interlude/dist/game/data/html/default/30995-9.htm b/L2J_Mobius_Classic_Interlude/dist/game/data/html/default/30995-9.htm new file mode 100644 index 0000000000..c17ab4849a --- /dev/null +++ b/L2J_Mobius_Classic_Interlude/dist/game/data/html/default/30995-9.htm @@ -0,0 +1,22 @@ + +
View Monster Race Results
+ + + + +
Race Number1st Finish2nd FinishOdds Rate for Win Bet
+ + +%infos% +
+ + + + + + + + + +
+ \ No newline at end of file diff --git a/L2J_Mobius_Classic_Interlude/dist/game/data/html/default/30995.htm b/L2J_Mobius_Classic_Interlude/dist/game/data/html/default/30995.htm index ca913c7dd6..271c4ee284 100644 --- a/L2J_Mobius_Classic_Interlude/dist/game/data/html/default/30995.htm +++ b/L2J_Mobius_Classic_Interlude/dist/game/data/html/default/30995.htm @@ -1,3 +1,15 @@ -Monster Race Manager:
- +
Monster Race Manager
+ + +
+ + + + + + + + + + \ No newline at end of file diff --git a/L2J_Mobius_Classic_Interlude/dist/game/data/stats/npcs/30900-30999.xml b/L2J_Mobius_Classic_Interlude/dist/game/data/stats/npcs/30900-30999.xml index 86481bbc18..5d8c628c43 100644 --- a/L2J_Mobius_Classic_Interlude/dist/game/data/stats/npcs/30900-30999.xml +++ b/L2J_Mobius_Classic_Interlude/dist/game/data/stats/npcs/30900-30999.xml @@ -3856,7 +3856,7 @@
- + DWARF FEMALE diff --git a/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/Config.java b/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/Config.java index e22a69a596..140ebc3fe0 100644 --- a/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/Config.java +++ b/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/Config.java @@ -500,6 +500,7 @@ public class Config public static int INSTANCE_FINISH_TIME; public static boolean RESTORE_PLAYER_INSTANCE; public static int EJECT_DEAD_PLAYER_TIME; + public static boolean ALLOW_RACE; public static boolean ALLOW_WATER; public static boolean ALLOW_FISHING; public static boolean ALLOW_BOAT; @@ -1960,6 +1961,7 @@ public class Config INSTANCE_FINISH_TIME = General.getInt("DefaultFinishTime", 5); RESTORE_PLAYER_INSTANCE = General.getBoolean("RestorePlayerInstance", false); EJECT_DEAD_PLAYER_TIME = General.getInt("EjectDeadPlayerTime", 1); + ALLOW_RACE = General.getBoolean("AllowRace", true); ALLOW_WATER = General.getBoolean("AllowWater", true); ALLOW_FISHING = General.getBoolean("AllowFishing", true); ALLOW_MANOR = General.getBoolean("AllowManor", true); diff --git a/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/GameServer.java b/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/GameServer.java index aa8c369958..91c661e1a1 100644 --- a/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/GameServer.java +++ b/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/GameServer.java @@ -141,6 +141,7 @@ import org.l2jmobius.gameserver.instancemanager.SiegeGuardManager; import org.l2jmobius.gameserver.instancemanager.SiegeManager; import org.l2jmobius.gameserver.instancemanager.WalkingManager; import org.l2jmobius.gameserver.instancemanager.ZoneManager; +import org.l2jmobius.gameserver.instancemanager.games.MonsterRace; import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.entity.Hero; import org.l2jmobius.gameserver.model.events.EventDispatcher; @@ -401,6 +402,8 @@ public class GameServer ItemsAutoDestroy.getInstance(); } + MonsterRace.getInstance(); + TaskManager.getInstance(); AntiFeedManager.getInstance().registerEvent(AntiFeedManager.GAME_ID); diff --git a/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/instancemanager/games/MonsterRace.java b/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/instancemanager/games/MonsterRace.java new file mode 100644 index 0000000000..2fa4c519f6 --- /dev/null +++ b/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/instancemanager/games/MonsterRace.java @@ -0,0 +1,623 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program 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. + * + * This program 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 . + */ +package org.l2jmobius.gameserver.instancemanager.games; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.l2jmobius.Config; +import org.l2jmobius.commons.concurrent.ThreadPool; +import org.l2jmobius.commons.database.DatabaseFactory; +import org.l2jmobius.commons.util.Rnd; +import org.l2jmobius.gameserver.data.xml.impl.NpcData; +import org.l2jmobius.gameserver.model.actor.Npc; +import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.model.zone.type.DerbyTrackZone; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.serverpackets.DeleteObject; +import org.l2jmobius.gameserver.network.serverpackets.MonRaceInfo; +import org.l2jmobius.gameserver.network.serverpackets.PlaySound; +import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; +import org.l2jmobius.gameserver.util.Broadcast; + +public class MonsterRace +{ + protected static final Logger LOGGER = Logger.getLogger(MonsterRace.class.getName()); + + public static enum RaceState + { + ACCEPTING_BETS, + WAITING, + STARTING_RACE, + RACE_END + } + + protected static final PlaySound SOUND_1 = new PlaySound(1, "S_Race", 0, 0, 0, 0, 0); + protected static final PlaySound SOUND_2 = new PlaySound("ItemSound2.race_start"); + + protected static final int[][] CODES = + { + { + -1, + 0 + }, + { + 0, + 15322 + }, + { + 13765, + -1 + } + }; + + protected final List _npcTemplates = new ArrayList<>(); // List holding npc templates, shuffled on a new race. + protected final List _history = new ArrayList<>(); // List holding old race records. + protected final Map _betsPerLane = new ConcurrentHashMap<>(); // Map holding all bets for each lane ; values setted to 0 after every race. + protected final List _odds = new ArrayList<>(); // List holding sorted odds per lane ; cleared at new odds calculation. + + protected int _raceNumber = 1; + protected int _finalCountdown = 0; + protected RaceState _state = RaceState.RACE_END; + + protected MonRaceInfo _packet; + + private final Npc[] _monsters = new Npc[8]; + private int[][] _speeds = new int[8][20]; + private final int[] _first = new int[2]; + private final int[] _second = new int[2]; + + protected MonsterRace() + { + if (!Config.ALLOW_RACE) + { + return; + } + + // Feed _history with previous race results. + loadHistory(); + + // Feed _betsPerLane with stored informations on bets. + loadBets(); + + // Feed _npcTemplates, we will only have to shuffle it when needed. + for (int i = 31003; i < 31027; i++) + { + _npcTemplates.add(i); + } + + ThreadPool.scheduleAtFixedRate(new Announcement(), 0, 1000); + } + + public static class HistoryInfo + { + private final int _raceId; + private int _first; + private int _second; + private double _oddRate; + + public HistoryInfo(int raceId, int first, int second, double oddRate) + { + _raceId = raceId; + _first = first; + _second = second; + _oddRate = oddRate; + } + + public int getRaceId() + { + return _raceId; + } + + public int getFirst() + { + return _first; + } + + public int getSecond() + { + return _second; + } + + public double getOddRate() + { + return _oddRate; + } + + public void setFirst(int first) + { + _first = first; + } + + public void setSecond(int second) + { + _second = second; + } + + public void setOddRate(double oddRate) + { + _oddRate = oddRate; + } + } + + private class Announcement implements Runnable + { + public Announcement() + { + } + + @Override + public void run() + { + if (_finalCountdown > 1200) + { + _finalCountdown = 0; + } + + switch (_finalCountdown) + { + case 0: + { + newRace(); + newSpeeds(); + + _state = RaceState.ACCEPTING_BETS; + _packet = new MonRaceInfo(CODES[0][0], CODES[0][1], getMonsters(), getSpeeds()); + + final SystemMessage msg = new SystemMessage(SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, _packet, msg); + 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 + { + final SystemMessage msg = new SystemMessage(SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + break; + } + case 300: // 5 min + { + final SystemMessage msg = new SystemMessage(SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S); + msg2.addInt(10); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + break; + } + case 600: // 10 min + { + final SystemMessage msg = new SystemMessage(SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S); + msg2.addInt(5); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + break; + } + case 840: // 14 min + { + final SystemMessage msg = new SystemMessage(SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S); + msg2.addInt(1); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + break; + } + case 900: // 15 min + { + _state = RaceState.WAITING; + + calculateOdds(); + + final SystemMessage msg = new SystemMessage(SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1); + msg.addInt(_raceNumber); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.TICKETS_SALES_ARE_CLOSED_FOR_MONSTER_RACE_S1_ODDS_ARE_POSTED); + msg2.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + break; + } + case 960: // 16 min + case 1020: // 17 min + { + final int minutes = (_finalCountdown == 960) ? 2 : 1; + final SystemMessage msg = new SystemMessage(SystemMessageId.MONSTER_RACE_S2_WILL_BEGIN_IN_S1_MINUTE_S); + msg.addInt(minutes); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + break; + } + case 1050: // 17 min 30 sec + { + final SystemMessage msg = new SystemMessage(SystemMessageId.MONSTER_RACE_S1_WILL_BEGIN_IN_30_SECONDS); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + break; + } + case 1070: // 17 min 50 sec + { + final SystemMessage msg = new SystemMessage(SystemMessageId.MONSTER_RACE_S1_IS_ABOUT_TO_BEGIN_COUNTDOWN_IN_FIVE_SECONDS); + msg.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + 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; + final SystemMessage msg = new SystemMessage(SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SECOND_S); + msg.addInt(seconds); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg); + break; + } + case 1080: // 18 min + { + _state = RaceState.STARTING_RACE; + _packet = new MonRaceInfo(CODES[1][0], CODES[1][1], getMonsters(), getSpeeds()); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, new SystemMessage(SystemMessageId.THEY_RE_OFF), SOUND_1, SOUND_2, _packet); + break; + } + case 1085: // 18 min 5 sec + { + _packet = new MonRaceInfo(CODES[2][0], CODES[2][1], getMonsters(), getSpeeds()); + + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, _packet); + break; + } + case 1115: // 18 min 35 sec + { + _state = RaceState.RACE_END; + + // Populate history info with data, stores it in database. + final HistoryInfo info = _history.get(_history.size() - 1); + info.setFirst(getFirstPlace()); + info.setSecond(getSecondPlace()); + info.setOddRate(_odds.get(getFirstPlace() - 1)); + + saveHistory(info); + clearBets(); + + final SystemMessage msg = new SystemMessage(SystemMessageId.FIRST_PRIZE_GOES_TO_THE_PLAYER_IN_LANE_S1_SECOND_PRIZE_GOES_TO_THE_PLAYER_IN_LANE_S2); + msg.addInt(getFirstPlace()); + msg.addInt(getSecondPlace()); + final SystemMessage msg2 = new SystemMessage(SystemMessageId.MONSTER_RACE_S1_IS_FINISHED); + msg2.addInt(_raceNumber); + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, msg, msg2); + _raceNumber++; + break; + } + case 1140: // 19 min + { + Broadcast.toAllPlayersInZoneType(DerbyTrackZone.class, new DeleteObject(getMonsters()[0]), new DeleteObject(getMonsters()[1]), new DeleteObject(getMonsters()[2]), new DeleteObject(getMonsters()[3]), new DeleteObject(getMonsters()[4]), new DeleteObject(getMonsters()[5]), new DeleteObject(getMonsters()[6]), new DeleteObject(getMonsters()[7])); + break; + } + } + _finalCountdown += 1; + } + } + + public void newRace() + { + // Edit _history. + _history.add(new HistoryInfo(_raceNumber, 0, 0, 0)); + + // Randomize _npcTemplates. + Collections.shuffle(_npcTemplates); + + // Setup 8 new creatures ; pickup the first 8 from _npcTemplates. + for (int i = 0; i < 8; i++) + { + try + { + final NpcTemplate template = NpcData.getInstance().getTemplate(_npcTemplates.get(i)); + _monsters[i] = (Npc) Class.forName("org.l2jmobius.gameserver.model.actor.instance." + template.getType() + "Instance").getConstructors()[0].newInstance(template); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, "", e); + } + } + } + + public void newSpeeds() + { + _speeds = new int[8][20]; + int total = 0; + _first[1] = 0; + _second[1] = 0; + + for (int i = 0; i < 8; i++) + { + total = 0; + for (int j = 0; j < 20; j++) + { + if (j == 19) + { + _speeds[i][j] = 100; + } + else + { + _speeds[i][j] = Rnd.get(60) + 65; + } + total += _speeds[i][j]; + } + + if (total >= _first[1]) + { + _second[0] = _first[0]; + _second[1] = _first[1]; + _first[0] = 8 - i; + _first[1] = total; + } + else if (total >= _second[1]) + { + _second[0] = 8 - i; + _second[1] = total; + } + } + } + + /** + * Load past races informations, feeding _history arrayList.
+ * Also sets _raceNumber, based on latest HistoryInfo loaded. + */ + protected void loadHistory() + { + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("SELECT * FROM mdt_history"); + ResultSet rset = statement.executeQuery(); + + while (rset.next()) + { + _history.add(new HistoryInfo(rset.getInt("race_id"), rset.getInt("first"), rset.getInt("second"), rset.getDouble("odd_rate"))); + _raceNumber++; + } + rset.close(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't load history: " + e.getMessage(), e); + } + LOGGER.info("MonsterRace: loaded " + _history.size() + " records, currently on race #" + _raceNumber); + } + + /** + * Save an history record into database. + * @param history The infos to store. + */ + protected void saveHistory(HistoryInfo history) + { + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("INSERT INTO mdt_history (race_id, first, second, odd_rate) VALUES (?,?,?,?)"); + statement.setInt(1, history.getRaceId()); + statement.setInt(2, history.getFirst()); + statement.setInt(3, history.getSecond()); + statement.setDouble(4, history.getOddRate()); + statement.execute(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't save history: " + e.getMessage(), e); + } + } + + /** + * Load current bets per lane ; initialize the map keys. + */ + protected void loadBets() + { + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("SELECT * FROM mdt_bets"); + ResultSet rset = statement.executeQuery(); + + while (rset.next()) + { + setBetOnLane(rset.getInt("lane_id"), rset.getLong("bet"), false); + } + + rset.close(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't load bets: " + e.getMessage(), e); + } + } + + /** + * Save the current lane bet into database. + * @param lane : The lane to affect. + * @param sum : The sum to set. + */ + protected void saveBet(int lane, long sum) + { + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("REPLACE INTO mdt_bets (lane_id, bet) VALUES (?,?)"); + statement.setInt(1, lane); + statement.setLong(2, sum); + statement.execute(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't save bet: " + e.getMessage(), e); + } + } + + /** + * Clear all lanes bets, either on database or Map. + */ + protected void clearBets() + { + for (int key : _betsPerLane.keySet()) + { + _betsPerLane.put(key, 0L); + } + + try (Connection con = DatabaseFactory.getConnection()) + { + PreparedStatement statement = con.prepareStatement("UPDATE mdt_bets SET bet = 0"); + statement.execute(); + statement.close(); + } + catch (SQLException e) + { + LOGGER.log(Level.WARNING, "MonsterRace: Can't clear bets: " + e.getMessage(), e); + } + } + + /** + * Setup lane bet, based on previous value (if any). + * @param lane : The lane to edit. + * @param amount : The amount to add. + * @param saveOnDb : Should it be saved on db or not. + */ + public void setBetOnLane(int lane, long amount, boolean saveOnDb) + { + final long sum = (_betsPerLane.containsKey(lane)) ? _betsPerLane.get(lane) + amount : amount; + + _betsPerLane.put(lane, sum); + + if (saveOnDb) + { + saveBet(lane, sum); + } + } + + /** + * Calculate odds for every lane, based on others lanes. + */ + protected void calculateOdds() + { + // Clear previous List holding old odds. + _odds.clear(); + + // Sort bets lanes per lane. + final Map 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; + } + + // 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)); + } + } + + public Npc[] getMonsters() + { + return _monsters; + } + + public int[][] getSpeeds() + { + return _speeds; + } + + public int getFirstPlace() + { + return _first[0]; + } + + public int getSecondPlace() + { + return _second[0]; + } + + public MonRaceInfo getRacePacket() + { + return _packet; + } + + public RaceState getCurrentRaceState() + { + return _state; + } + + public int getRaceNumber() + { + return _raceNumber; + } + + public List getHistory() + { + return _history; + } + + public List getOdds() + { + return _odds; + } + + public static MonsterRace getInstance() + { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder + { + protected static final MonsterRace INSTANCE = new MonsterRace(); + } +} \ No newline at end of file diff --git a/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/model/actor/instance/RaceManagerInstance.java b/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/model/actor/instance/RaceManagerInstance.java new file mode 100644 index 0000000000..2ae6979233 --- /dev/null +++ b/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/model/actor/instance/RaceManagerInstance.java @@ -0,0 +1,377 @@ +/* + * This file is part of the L2J Mobius project. + * + * This program 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. + * + * This program 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 . + */ +package org.l2jmobius.gameserver.model.actor.instance; + +import java.util.List; +import java.util.Locale; + +import org.l2jmobius.Config; +import org.l2jmobius.commons.util.StringUtil; +import org.l2jmobius.gameserver.idfactory.IdFactory; +import org.l2jmobius.gameserver.instancemanager.games.MonsterRace; +import org.l2jmobius.gameserver.instancemanager.games.MonsterRace.HistoryInfo; +import org.l2jmobius.gameserver.instancemanager.games.MonsterRace.RaceState; +import org.l2jmobius.gameserver.model.actor.Npc; +import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate; +import org.l2jmobius.gameserver.model.items.instance.ItemInstance; +import org.l2jmobius.gameserver.network.SystemMessageId; +import org.l2jmobius.gameserver.network.serverpackets.ActionFailed; +import org.l2jmobius.gameserver.network.serverpackets.NpcHtmlMessage; +import org.l2jmobius.gameserver.network.serverpackets.SystemMessage; + +public class RaceManagerInstance extends Npc +{ + protected static final int TICKET_PRICES[] = + { + 100, + 500, + 1000, + 5000, + 10000, + 20000, + 50000, + 100000 + }; + + public RaceManagerInstance(NpcTemplate template) + { + super(template); + } + + @Override + public void onBypassFeedback(PlayerInstance player, String command) + { + if (command.startsWith("BuyTicket")) + { + if (!Config.ALLOW_RACE || (MonsterRace.getInstance().getCurrentRaceState() != RaceState.ACCEPTING_BETS)) + { + player.sendPacket(SystemMessageId.MONSTER_RACE_TICKETS_ARE_NO_LONGER_AVAILABLE); + super.onBypassFeedback(player, "Chat 0"); + return; + } + + int 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))) + { + val = 0; + } + + String search, replace; + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + + if (val < 10) + { + html.setFile(player, getHtmlPath(getId(), 2, player)); + for (int i = 0; i < 8; i++) + { + int n = i + 1; + search = "Mob" + n; + html.replace(search, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); + } + search = "No1"; + if (val == 0) + { + html.replace(search, ""); + } + else + { + html.replace(search, val); + player.setRace(0, val); + } + } + else if (val < 20) + { + if (player.getRace(0) == 0) + { + return; + } + + html.setFile(player, getHtmlPath(getId(), 3, player)); + html.replace("0place", player.getRace(0)); + search = "Mob1"; + replace = MonsterRace.getInstance().getMonsters()[player.getRace(0) - 1].getTemplate().getName(); + html.replace(search, replace); + search = "0adena"; + + if (val == 10) + { + html.replace(search, ""); + } + else + { + html.replace(search, TICKET_PRICES[val - 11]); + player.setRace(1, val - 10); + } + } + else if (val == 20) + { + if ((player.getRace(0) == 0) || (player.getRace(1) == 0)) + { + return; + } + + html.setFile(player, getHtmlPath(getId(), 4, player)); + html.replace("0place", player.getRace(0)); + search = "Mob1"; + replace = MonsterRace.getInstance().getMonsters()[player.getRace(0) - 1].getTemplate().getName(); + 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); + } + else + { + if ((player.getRace(0) == 0) || (player.getRace(1) == 0)) + { + return; + } + + int ticket = player.getRace(0); + int priceId = player.getRace(1); + + if (!player.reduceAdena("Race", TICKET_PRICES[priceId - 1], this, true)) + { + return; + } + + player.setRace(0, 0); + player.setRace(1, 0); + + ItemInstance item = new ItemInstance(IdFactory.getInstance().getNextId(), 4443); + item.setCount(1); + item.setEnchantLevel(MonsterRace.getInstance().getRaceNumber()); + item.setCustomType1(ticket); + item.setCustomType2(TICKET_PRICES[priceId - 1] / 100); + + player.addItem("Race", item, player, false); + final SystemMessage msg = new SystemMessage(SystemMessageId.ACQUIRED_S1_S2); + msg.addInt(MonsterRace.getInstance().getRaceNumber()); + msg.addItemName(4443); + player.sendPacket(msg); + + // Refresh lane bet. + MonsterRace.getInstance().setBetOnLane(ticket, TICKET_PRICES[priceId - 1], true); + super.onBypassFeedback(player, "Chat 0"); + return; + } + html.replace("1race", MonsterRace.getInstance().getRaceNumber()); + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); + } + else if (command.equals("ShowOdds")) + { + if (!Config.ALLOW_RACE || (MonsterRace.getInstance().getCurrentRaceState() == RaceState.ACCEPTING_BETS)) + { + player.sendPacket(SystemMessageId.MONSTER_RACE_PAYOUT_INFORMATION_IS_NOT_AVAILABLE_WHILE_TICKETS_ARE_BEING_SOLD); + super.onBypassFeedback(player, "Chat 0"); + return; + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 5, player)); + for (int i = 0; i < 8; i++) + { + final int n = i + 1; + + html.replace("Mob" + n, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); + + // Odd + final double odd = MonsterRace.getInstance().getOdds().get(i); + html.replace("Odd" + n, (odd > 0D) ? String.format(Locale.ENGLISH, "%.1f", odd) : "&$804;"); + } + html.replace("1race", MonsterRace.getInstance().getRaceNumber()); + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); + } + else if (command.equals("ShowInfo")) + { + if (!Config.ALLOW_RACE) + { + return; + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 6, player)); + + for (int i = 0; i < 8; i++) + { + int n = i + 1; + String search = "Mob" + n; + html.replace(search, MonsterRace.getInstance().getMonsters()[i].getTemplate().getName()); + } + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); + } + else if (command.equals("ShowTickets")) + { + if (!Config.ALLOW_RACE) + { + super.onBypassFeedback(player, "Chat 0"); + return; + } + + // Generate data. + final StringBuilder sb = new StringBuilder(); + + // Retrieve player's tickets. + for (ItemInstance ticket : player.getInventory().getAllItemsByItemId(4443)) + { + // Don't list current race tickets. + if (ticket.getEnchantLevel() == MonsterRace.getInstance().getRaceNumber()) + { + continue; + } + + StringUtil.append(sb, "", "" + ticket.getEnchantLevel(), " Race Number", "" + ticket.getCustomType1(), " Number", "" + (ticket.getCustomType2() * 100), " Adena"); + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 7, player)); + 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)); + if (!Config.ALLOW_RACE || (val == 0)) + { + super.onBypassFeedback(player, "Chat 0"); + return; + } + + // Retrieve ticket on player's inventory. + final ItemInstance 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; + + // Retrieve HistoryInfo for that race. + final HistoryInfo info = MonsterRace.getInstance().getHistory().get(raceId - 1); + if (info == null) + { + super.onBypassFeedback(player, "Chat 0"); + return; + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 8, player)); + html.replace("%raceId%", raceId); + html.replace("%lane%", lane); + html.replace("%bet%", bet); + html.replace("%firstLane%", info.getFirst()); + html.replace("%odd%", (lane == info.getFirst()) ? String.format(Locale.ENGLISH, "%.2f", info.getOddRate()) : "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 (!Config.ALLOW_RACE || (val == 0)) + { + super.onBypassFeedback(player, "Chat 0"); + return; + } + + // Delete ticket on player's inventory. + final ItemInstance 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; + + // Retrieve HistoryInfo for that race. + final HistoryInfo info = MonsterRace.getInstance().getHistory().get(raceId - 1); + if (info == 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()) ? info.getOddRate() : 0.01)), this, true); + } + + super.onBypassFeedback(player, "Chat 0"); + return; + } + else if (command.equals("ViewHistory")) + { + if (!Config.ALLOW_RACE) + { + super.onBypassFeedback(player, "Chat 0"); + return; + } + + // Generate data. + final StringBuilder sb = new StringBuilder(); + + // Use whole history, pickup from 'last element' and stop at 'latest element - 7'. + final List history = MonsterRace.getInstance().getHistory(); + for (int i = history.size() - 1; i >= Math.max(0, history.size() - 7); i--) + { + final HistoryInfo info = history.get(i); + StringUtil.append(sb, "", "" + info.getRaceId(), " th", "" + info.getFirst(), " Lane ", "" + info.getSecond(), " Lane", String.format(Locale.ENGLISH, "%.2f", info.getOddRate()), " Times"); + } + + final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); + html.setFile(player, getHtmlPath(getId(), 9, player)); + html.replace("%infos%", sb.toString()); + html.replace("%objectId%", getObjectId()); + player.sendPacket(html); + player.sendPacket(ActionFailed.STATIC_PACKET); + } + else + { + super.onBypassFeedback(player, command); + } + } +} diff --git a/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/util/Broadcast.java b/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/util/Broadcast.java index 60eb0ff361..8d751afc53 100644 --- a/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/util/Broadcast.java +++ b/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/util/Broadcast.java @@ -20,10 +20,12 @@ import java.util.logging.Level; import java.util.logging.Logger; import org.l2jmobius.gameserver.enums.ChatType; +import org.l2jmobius.gameserver.instancemanager.ZoneManager; import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.actor.Creature; import org.l2jmobius.gameserver.model.actor.Summon; import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance; +import org.l2jmobius.gameserver.model.zone.ZoneType; import org.l2jmobius.gameserver.network.serverpackets.CharInfo; import org.l2jmobius.gameserver.network.serverpackets.CreatureSay; import org.l2jmobius.gameserver.network.serverpackets.ExShowScreenMessage; @@ -191,4 +193,29 @@ public class Broadcast { toAllOnlinePlayers(new ExShowScreenMessage(text, 10000)); } + + /** + * Send a packet to all players in a specific zone type. + * @param ZoneType. + * @param zoneType : The zone type to send packets. + * @param packets : The packets to send. + */ + public static void toAllPlayersInZoneType(Class zoneType, IClientOutgoingPacket... packets) + { + for (ZoneType zone : ZoneManager.getInstance().getAllZones(zoneType)) + { + for (Creature creature : zone.getCharactersInside()) + { + if (creature == null) + { + continue; + } + + for (IClientOutgoingPacket packet : packets) + { + creature.sendPacket(packet); + } + } + } + } }