Monster race changes.

This commit is contained in:
MobiusDev
2017-08-02 20:48:31 +00:00
parent 88c545a5f4
commit 94d2283c8a
33 changed files with 117 additions and 1393 deletions

View File

@@ -488,7 +488,6 @@ public final 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_RENTPET;
public static boolean ALLOW_FISHING;
@@ -1787,7 +1786,6 @@ public final 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_RENTPET = General.getBoolean("AllowRentPet", false);
ALLOW_FISHING = General.getBoolean("AllowFishing", true);

View File

@@ -377,7 +377,6 @@ public class GameServer
ItemsAutoDestroy.getInstance();
}
MonsterRace.getInstance();
TaskManager.getInstance();
AntiFeedManager.getInstance().registerEvent(AntiFeedManager.GAME_ID);

View File

@@ -1,150 +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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver;
import java.lang.reflect.Constructor;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.l2jmobius.commons.util.Rnd;
import com.l2jmobius.gameserver.data.xml.impl.NpcData;
import com.l2jmobius.gameserver.model.actor.L2Npc;
import com.l2jmobius.gameserver.model.actor.templates.L2NpcTemplate;
public class MonsterRace
{
protected static final Logger LOGGER = Logger.getLogger(MonsterRace.class.getName());
private final L2Npc[] _monsters;
private int[][] _speeds;
private final int[] _first, _second;
protected MonsterRace()
{
_monsters = new L2Npc[8];
_speeds = new int[8][20];
_first = new int[2];
_second = new int[2];
}
public static MonsterRace getInstance()
{
return SingletonHolder._instance;
}
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);
continue;
}
}
break;
}
try
{
final L2NpcTemplate template = NpcData.getInstance().getTemplate(id + random);
final Constructor<?> constructor = Class.forName("com.l2jmobius.gameserver.model.actor.instance." + template.getType() + "Instance").getConstructors()[0];
_monsters[i] = (L2Npc) constructor.newInstance(template);
}
catch (Exception e)
{
LOGGER.log(Level.WARNING, "", e);
}
// LOGGER.info("Monster "+i+" is id: "+(id+random));
}
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 L2Npc[] getMonsters()
{
return _monsters;
}
/**
* @return Returns the speeds.
*/
public int[][] getSpeeds()
{
return _speeds;
}
public int getFirstPlace()
{
return _first[0];
}
public int getSecondPlace()
{
return _second[0];
}
private static class SingletonHolder
{
protected static final MonsterRace _instance = new MonsterRace();
}
}

View File

@@ -77,7 +77,6 @@ public enum InstanceType
L2ObservationInstance(L2Npc),
L2OlympiadManagerInstance(L2Npc),
L2PetManagerInstance(L2MerchantInstance),
L2RaceManagerInstance(L2Npc),
L2TeleporterInstance(L2Npc),
L2VillageMasterInstance(L2NpcInstance),
// Doormens

View File

@@ -1,540 +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 <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.model.actor.instance;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import com.l2jmobius.gameserver.MonsterRace;
import com.l2jmobius.gameserver.ThreadPoolManager;
import com.l2jmobius.gameserver.enums.InstanceType;
import com.l2jmobius.gameserver.idfactory.IdFactory;
import com.l2jmobius.gameserver.model.actor.L2Character;
import com.l2jmobius.gameserver.model.actor.L2Npc;
import com.l2jmobius.gameserver.model.actor.templates.L2NpcTemplate;
import com.l2jmobius.gameserver.model.itemcontainer.Inventory;
import com.l2jmobius.gameserver.model.items.instance.L2ItemInstance;
import com.l2jmobius.gameserver.network.SystemMessageId;
import com.l2jmobius.gameserver.network.serverpackets.ActionFailed;
import com.l2jmobius.gameserver.network.serverpackets.DeleteObject;
import com.l2jmobius.gameserver.network.serverpackets.IClientOutgoingPacket;
import com.l2jmobius.gameserver.network.serverpackets.InventoryUpdate;
import com.l2jmobius.gameserver.network.serverpackets.MonRaceInfo;
import com.l2jmobius.gameserver.network.serverpackets.NpcHtmlMessage;
import com.l2jmobius.gameserver.network.serverpackets.PlaySound;
import com.l2jmobius.gameserver.network.serverpackets.SystemMessage;
import com.l2jmobius.gameserver.util.Broadcast;
public class L2RaceManagerInstance extends L2Npc
{
public static final int LANES = 8;
public static final int WINDOW_START = 0;
private static List<L2RaceManagerInstance> _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 L2RaceManagerInstance(L2NpcTemplate template)
{
super(template);
setInstanceType(InstanceType.L2RaceManagerInstance);
if (_notInitialized)
{
_notInitialized = false;
_managers = new CopyOnWriteArrayList<>();
final ThreadPoolManager s = ThreadPoolManager.getInstance();
s.scheduleGeneralAtFixedRate(new Announcement(SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1), 0, 10 * MINUTE);
s.scheduleGeneralAtFixedRate(new Announcement(SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1), 30 * SECOND, 10 * MINUTE);
s.scheduleGeneralAtFixedRate(new Announcement(SystemMessageId.TICKETS_ARE_NOW_AVAILABLE_FOR_MONSTER_RACE_S1), MINUTE, 10 * MINUTE);
s.scheduleGeneralAtFixedRate(new Announcement(SystemMessageId.NOW_SELLING_TICKETS_FOR_MONSTER_RACE_S1), MINUTE + (30 * SECOND), 10 * MINUTE);
s.scheduleGeneralAtFixedRate(new Announcement(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S), 2 * MINUTE, 10 * MINUTE);
s.scheduleGeneralAtFixedRate(new Announcement(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S), 3 * MINUTE, 10 * MINUTE);
s.scheduleGeneralAtFixedRate(new Announcement(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S), 4 * MINUTE, 10 * MINUTE);
s.scheduleGeneralAtFixedRate(new Announcement(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S), 5 * MINUTE, 10 * MINUTE);
s.scheduleGeneralAtFixedRate(new Announcement(SystemMessageId.TICKET_SALES_FOR_THE_MONSTER_RACE_WILL_END_IN_S1_MINUTE_S), 6 * MINUTE, 10 * MINUTE);
s.scheduleGeneralAtFixedRate(new Announcement(SystemMessageId.TICKETS_SALES_ARE_CLOSED_FOR_MONSTER_RACE_S1_ODDS_ARE_POSTED), 7 * MINUTE, 10 * MINUTE);
s.scheduleGeneralAtFixedRate(new Announcement(SystemMessageId.MONSTER_RACE_S2_WILL_BEGIN_IN_S1_MINUTE_S), 7 * MINUTE, 10 * MINUTE);
s.scheduleGeneralAtFixedRate(new Announcement(SystemMessageId.MONSTER_RACE_S2_WILL_BEGIN_IN_S1_MINUTE_S), 8 * MINUTE, 10 * MINUTE);
s.scheduleGeneralAtFixedRate(new Announcement(SystemMessageId.MONSTER_RACE_S1_WILL_BEGIN_IN_30_SECONDS), (8 * MINUTE) + (30 * SECOND), 10 * MINUTE);
s.scheduleGeneralAtFixedRate(new Announcement(SystemMessageId.MONSTER_RACE_S1_IS_ABOUT_TO_BEGIN_COUNTDOWN_IN_FIVE_SECONDS), (8 * MINUTE) + (50 * SECOND), 10 * MINUTE);
s.scheduleGeneralAtFixedRate(new Announcement(SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SECOND_S), (8 * MINUTE) + (55 * SECOND), 10 * MINUTE);
s.scheduleGeneralAtFixedRate(new Announcement(SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SECOND_S), (8 * MINUTE) + (56 * SECOND), 10 * MINUTE);
s.scheduleGeneralAtFixedRate(new Announcement(SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SECOND_S), (8 * MINUTE) + (57 * SECOND), 10 * MINUTE);
s.scheduleGeneralAtFixedRate(new Announcement(SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SECOND_S), (8 * MINUTE) + (58 * SECOND), 10 * MINUTE);
s.scheduleGeneralAtFixedRate(new Announcement(SystemMessageId.THE_RACE_WILL_BEGIN_IN_S1_SECOND_S), (8 * MINUTE) + (59 * SECOND), 10 * MINUTE);
s.scheduleGeneralAtFixedRate(new Announcement(SystemMessageId.THEY_RE_OFF), 9 * MINUTE, 10 * MINUTE);
// */
}
_managers.add(this);
}
@Override
public boolean isAutoAttackable(L2Character 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 = SystemMessage.getSystemMessage(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 (L2RaceManagerInstance 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++;
final PlaySound SRace = new PlaySound(1, "S_Race", 0, 0, 0, 0, 0);
broadcast(SRace);
final PlaySound SRace2 = new PlaySound(0, "ItemSound2.race_start", 1, 121209259, 12125, 182487, -3559);
broadcast(SRace2);
_packet = new MonRaceInfo(_codes[1][0], _codes[1][1], race.getMonsters(), race.getSpeeds());
sendMonsterInfo();
ThreadPoolManager.getInstance().scheduleGeneral(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(L2PcInstance 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"))
{
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
{
// getKnownList().removeKnownObject(player);
super.onBypassFeedback(player, command);
}
}
public void showOdds(L2PcInstance player)
{
if (_state == ACCEPTING_BETS)
{
return;
}
final int npcId = getTemplate().getId();
String filename, search;
final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId());
filename = getHtmlPath(npcId, 5);
html.setFile(player.getHtmlPrefix(), 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(L2PcInstance player)
{
final int npcId = getTemplate().getId();
String filename, search;
final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId());
filename = getHtmlPath(npcId, 6);
html.setFile(player.getHtmlPrefix(), 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(L2PcInstance player, int val)
{
if (_state != ACCEPTING_BETS)
{
return;
}
final int npcId = getTemplate().getId();
SystemMessage sm;
String filename, search, replace;
final NpcHtmlMessage html = new NpcHtmlMessage(getObjectId());
if (val < 10)
{
filename = getHtmlPath(npcId, 2);
html.setFile(player.getHtmlPrefix(), 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 (val == 0)
{
html.replace(search, "");
}
else
{
html.replace(search, "" + val);
player.setRace(0, val);
}
}
else if (val < 20)
{
if (player.getRace(0) == 0)
{
return;
}
filename = getHtmlPath(npcId, 3);
html.setFile(player.getHtmlPrefix(), 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 (val == 10)
{
html.replace(search, "");
}
else
{
html.replace(search, "" + _cost[val - 11]);
player.setRace(1, val - 10);
}
}
else if (val == 20)
{
if ((player.getRace(0) == 0) || (player.getRace(1) == 0))
{
return;
}
filename = getHtmlPath(npcId, 4);
html.setFile(player.getHtmlPrefix(), 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 = SystemMessage.getSystemMessage(SystemMessageId.ACQUIRED_S1_S2);
sm.addInt(_raceNumber);
sm.addItemName(4443);
player.sendPacket(sm);
final L2ItemInstance item = new L2ItemInstance(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 L2ItemInstance 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();
ThreadPoolManager.getInstance().scheduleGeneral(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);
}
}
}
}