PageRenderTime 72ms CodeModel.GetById 28ms RepoModel.GetById 1ms app.codeStats 0ms

/src/simpleserver/Player.java

https://github.com/Simoyd/SimpleServer
Java | 855 lines | 673 code | 157 blank | 25 comment | 113 complexity | 468b681e53799813fbc29ae57d9d4910 MD5 | raw file
  1. /*
  2. * Copyright (c) 2010 SimpleServer authors (see CONTRIBUTORS)
  3. *
  4. * Permission is hereby granted, free of charge, to any person obtaining a copy
  5. * of this software and associated documentation files (the "Software"), to deal
  6. * in the Software without restriction, including without limitation the rights
  7. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. * copies of the Software, and to permit persons to whom the Software is
  9. * furnished to do so, subject to the following conditions:
  10. * The above copyright notice and this permission notice shall be included in
  11. * all copies or substantial portions of the Software.
  12. *
  13. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  14. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  15. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  16. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  17. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  18. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  19. * THE SOFTWARE.
  20. */
  21. package simpleserver;
  22. import static simpleserver.lang.Translations.t;
  23. import java.io.IOException;
  24. import java.net.InetAddress;
  25. import java.net.Socket;
  26. import java.util.Queue;
  27. import java.util.Timer;
  28. import java.util.TimerTask;
  29. import java.util.concurrent.ConcurrentLinkedQueue;
  30. import org.xml.sax.SAXException;
  31. import simpleserver.Coordinate.Dimension;
  32. import simpleserver.bot.BotController.ConnectException;
  33. import simpleserver.bot.Giver;
  34. import simpleserver.bot.Teleporter;
  35. import simpleserver.command.ExternalCommand;
  36. import simpleserver.command.PlayerCommand;
  37. import simpleserver.config.KitList.Kit;
  38. import simpleserver.config.data.Stats.StatField;
  39. import simpleserver.config.xml.CommandConfig;
  40. import simpleserver.config.xml.CommandConfig.Forwarding;
  41. import simpleserver.config.xml.Group;
  42. import simpleserver.config.xml.Permission;
  43. import simpleserver.message.AbstractChat;
  44. import simpleserver.message.Chat;
  45. import simpleserver.message.GlobalChat;
  46. import simpleserver.stream.StreamTunnel;
  47. public class Player {
  48. private final long connected;
  49. private final Socket extsocket;
  50. private final Server server;
  51. private Socket intsocket;
  52. private StreamTunnel serverToClient;
  53. private StreamTunnel clientToServer;
  54. private Watchdog watchdog;
  55. private String name = null;
  56. private String renameName = null;
  57. private String connectionHash;
  58. private boolean closed = false;
  59. private boolean isKicked = false;
  60. private Action attemptedAction;
  61. private boolean instantDestroy = false;
  62. private boolean godMode = false;
  63. private String kickMsg = null;
  64. public Position position;
  65. private Position deathPlace;
  66. private short health = 0;
  67. private int group = 0;
  68. private int entityId = 0;
  69. private Group groupObject = null;
  70. private boolean isRobot = false;
  71. // player is not authenticated with minecraft.net:
  72. private boolean guest = false;
  73. private boolean usedAuthenticator = false;
  74. private int blocksPlaced = 0;
  75. private int blocksDestroyed = 0;
  76. private Player reply = null;
  77. private String lastCommand = "";
  78. private AbstractChat chatType;
  79. private Queue<String> messages = new ConcurrentLinkedQueue<String>();
  80. private Queue<String> forwardMessages = new ConcurrentLinkedQueue<String>();
  81. private Queue<PlayerVisitRequest> visitreqs = new ConcurrentLinkedQueue<PlayerVisitRequest>();
  82. private Coordinate chestPlaced;
  83. private Coordinate chestOpened;
  84. private String chestArgument;
  85. // temporary coordinate storage for /myarea command
  86. public Coordinate areastart;
  87. public Coordinate areaend;
  88. private long lastTeleport;
  89. public Player(Socket inc, Server parent) {
  90. connected = System.currentTimeMillis();
  91. position = new Position();
  92. server = parent;
  93. chatType = new GlobalChat(this);
  94. extsocket = inc;
  95. if (server.isRobot(getIPAddress())) {
  96. System.out.println("[SimpleServer] Robot Heartbeat: " + getIPAddress()
  97. + ".");
  98. isRobot = true;
  99. } else {
  100. System.out.println("[SimpleServer] IP Connection from " + getIPAddress()
  101. + "!");
  102. }
  103. if (server.isIPBanned(getIPAddress())) {
  104. System.out.println("[SimpleServer] IP " + getIPAddress() + " is banned!");
  105. cleanup();
  106. return;
  107. }
  108. server.requestTracker.addRequest(getIPAddress());
  109. try {
  110. InetAddress localAddress = InetAddress.getByName(Server.addressFactory.getNextAddress());
  111. intsocket = new Socket(InetAddress.getByName(null),
  112. server.options.getInt("internalPort"),
  113. localAddress, 0);
  114. } catch (Exception e) {
  115. try {
  116. intsocket = new Socket(InetAddress.getByName(null), server.options.getInt("internalPort"));
  117. } catch (Exception E) {
  118. e.printStackTrace();
  119. if (server.config.properties.getBoolean("exitOnFailure")) {
  120. server.stop();
  121. } else {
  122. server.restart();
  123. }
  124. cleanup();
  125. return;
  126. }
  127. }
  128. watchdog = new Watchdog();
  129. try {
  130. serverToClient = new StreamTunnel(intsocket.getInputStream(),
  131. extsocket.getOutputStream(), true, this);
  132. clientToServer = new StreamTunnel(extsocket.getInputStream(),
  133. intsocket.getOutputStream(), false,
  134. this);
  135. } catch (IOException e) {
  136. e.printStackTrace();
  137. cleanup();
  138. return;
  139. }
  140. if (isRobot) {
  141. server.addRobotPort(intsocket.getLocalPort());
  142. }
  143. watchdog.start();
  144. }
  145. public boolean setName(String name) {
  146. renameName = server.data.players.getRenameName(name);
  147. name = name.trim();
  148. if (name.length() == 0 || this.name != null) {
  149. kick(t("Invalid Name!"));
  150. return false;
  151. }
  152. if (name == "Player") {
  153. kick(t("Too many guests in server!"));
  154. return false;
  155. }
  156. if (!guest && server.config.properties.getBoolean("useWhitelist")
  157. && !server.whitelist.isWhitelisted(name)) {
  158. kick(t("You are not whitelisted!"));
  159. return false;
  160. }
  161. if (server.playerList.findPlayerExact(name) != null) {
  162. kick(t("Player already in server!"));
  163. return false;
  164. }
  165. this.name = name;
  166. updateGroup();
  167. watchdog.setName("PlayerWatchdog-" + name);
  168. server.connectionLog("player", extsocket, name);
  169. if (server.numPlayers() == 0) {
  170. server.time.set();
  171. }
  172. server.playerList.addPlayer(this);
  173. return true;
  174. }
  175. public String getName() {
  176. return renameName;
  177. }
  178. public String getName(boolean original) {
  179. return (original) ? name : renameName;
  180. }
  181. public String getRealName() {
  182. return server.data.players.getRealName(name);
  183. }
  184. public void updateRealName(String name) {
  185. server.data.players.setRealName(name);
  186. }
  187. public String getConnectionHash() {
  188. if (connectionHash == null) {
  189. connectionHash = server.nextHash();
  190. }
  191. return connectionHash;
  192. }
  193. public double distanceTo(Player player) {
  194. return Math.sqrt(Math.pow(x() - player.x(), 2) + Math.pow(y() - player.y(), 2) + Math.pow(z() - player.z(), 2));
  195. }
  196. public long getConnectedAt() {
  197. return connected;
  198. }
  199. public boolean isAttemptLock() {
  200. return attemptedAction == Action.Lock;
  201. }
  202. public void setAttemptedAction(Action action) {
  203. attemptedAction = action;
  204. }
  205. public boolean instantDestroyEnabled() {
  206. return instantDestroy;
  207. }
  208. public void toggleInstantDestroy() {
  209. instantDestroy = !instantDestroy;
  210. }
  211. public Server getServer() {
  212. return server;
  213. }
  214. public void setChat(AbstractChat chat) {
  215. chatType = chat;
  216. }
  217. public String getChatRoom() {
  218. return chatType.toString();
  219. }
  220. public void sendMessage(String message) {
  221. sendMessage(chatType, message);
  222. }
  223. public void sendMessage(String message, boolean build) {
  224. sendMessage(chatType, message, build);
  225. }
  226. public void sendMessage(Chat messageType, String message) {
  227. server.getMessager().propagate(messageType, message);
  228. }
  229. public void sendMessage(Chat messageType, String message, boolean build) {
  230. server.getMessager().propagate(messageType, message, build);
  231. }
  232. public void forwardMessage(String message) {
  233. forwardMessages.add(message);
  234. }
  235. public boolean hasForwardMessages() {
  236. return !forwardMessages.isEmpty();
  237. }
  238. public boolean hasMessages() {
  239. return !messages.isEmpty();
  240. }
  241. public void addMessage(Color color, String format, Object... args) {
  242. addMessage(color, String.format(format, args));
  243. }
  244. public void addMessage(Color color, String message) {
  245. addMessage(color + message);
  246. }
  247. public void addMessage(String format, Object... args) {
  248. addMessage(String.format(format, args));
  249. }
  250. public void addCaptionedMessage(String caption, String format, Object... args) {
  251. addMessage("%s%s: %s%s", Color.GRAY, caption, Color.WHITE, String.format(format, args));
  252. }
  253. public void addMessage(String msg) {
  254. messages.add(msg);
  255. }
  256. public void addTMessage(Color color, String format, Object... args) {
  257. addMessage(color + t(format, args));
  258. }
  259. public void addTMessage(Color color, String message) {
  260. addMessage(color + t(message));
  261. }
  262. public void addTMessage(String msg) {
  263. addMessage(t(msg));
  264. }
  265. public void addTCaptionedTMessage(String caption, String format, Object... args) {
  266. addMessage("%s%s: %s%s", Color.GRAY, t(caption), Color.WHITE, t(format, args));
  267. }
  268. public void addTCaptionedMessage(String caption, String format, Object... args) {
  269. addMessage("%s%s: %s%s", Color.GRAY, t(caption),
  270. Color.WHITE, String.format(format, args));
  271. }
  272. public String getForwardMessage() {
  273. return forwardMessages.remove();
  274. }
  275. public String getMessage() {
  276. return messages.remove();
  277. }
  278. public void addVisitRequest(Player source) {
  279. visitreqs.add(new PlayerVisitRequest(source));
  280. }
  281. public void handleVisitRequests() {
  282. while (visitreqs.size() > 0) {
  283. PlayerVisitRequest req = visitreqs.remove();
  284. if (System.currentTimeMillis() < req.timestamp + 10000 && server.findPlayerExact(req.source.getName()) != null) {
  285. req.source.addTMessage(Color.GRAY, "Request accepted!");
  286. req.source.teleportTo(this);
  287. }
  288. }
  289. }
  290. public void kick(String reason) {
  291. kickMsg = reason;
  292. isKicked = true;
  293. serverToClient.stop();
  294. clientToServer.stop();
  295. }
  296. public boolean isKicked() {
  297. return isKicked;
  298. }
  299. public void setKicked(boolean b) {
  300. isKicked = b;
  301. }
  302. public String getKickMsg() {
  303. return kickMsg;
  304. }
  305. public boolean isMuted() {
  306. return server.mutelist.isMuted(name);
  307. }
  308. public boolean isRobot() {
  309. return isRobot;
  310. }
  311. public boolean godModeEnabled() {
  312. return godMode;
  313. }
  314. public void toggleGodMode() {
  315. godMode = !godMode;
  316. }
  317. public int getEntityId() {
  318. return entityId;
  319. }
  320. public void setEntityId(int readInt) {
  321. entityId = readInt;
  322. }
  323. public int getGroupId() {
  324. return group;
  325. }
  326. public Group getGroup() {
  327. return groupObject;
  328. }
  329. public void setGuest(boolean guest) {
  330. this.guest = guest;
  331. }
  332. public boolean isGuest() {
  333. return guest;
  334. }
  335. public void setUsedAuthenticator(boolean usedAuthenticator) {
  336. this.usedAuthenticator = usedAuthenticator;
  337. }
  338. public boolean usedAuthenticator() {
  339. return usedAuthenticator;
  340. }
  341. public String getIPAddress() {
  342. return extsocket.getInetAddress().getHostAddress();
  343. }
  344. public InetAddress getInetAddress() {
  345. return extsocket.getInetAddress();
  346. }
  347. public boolean ignoresChestLocks() {
  348. return groupObject.ignoreChestLocks;
  349. }
  350. private void setDeathPlace(Position deathPosition) {
  351. deathPlace = deathPosition;
  352. }
  353. public Position getDeathPlace() {
  354. return deathPlace;
  355. }
  356. public short getHealth() {
  357. return health;
  358. }
  359. public void updateHealth(short health) {
  360. this.health = health;
  361. if (health <= 0) {
  362. setDeathPlace(new Position(position()));
  363. }
  364. }
  365. public double x() {
  366. return position.x;
  367. }
  368. public double y() {
  369. return position.y;
  370. }
  371. public double z() {
  372. return position.z;
  373. }
  374. public Coordinate position() {
  375. return position.coordinate();
  376. }
  377. public float yaw() {
  378. return position.yaw;
  379. }
  380. public float pitch() {
  381. return position.pitch;
  382. }
  383. public String parseCommand(String message) {
  384. // TODO: Handle aliases of external commands
  385. if (closed) {
  386. return null;
  387. }
  388. // Repeat last command
  389. if (message.equals(server.getCommandParser().commandPrefix() + "!")) {
  390. message = lastCommand;
  391. } else {
  392. lastCommand = message;
  393. }
  394. String commandName = message.split(" ")[0].substring(1).toLowerCase();
  395. String args = commandName.length() + 1 >= message.length() ? "" : message.substring(commandName.length() + 2);
  396. CommandConfig config = server.config.commands.getTopConfig(commandName);
  397. String originalName = config == null ? commandName : config.originalName;
  398. PlayerCommand command;
  399. if (config == null) {
  400. command = server.getCommandParser().getPlayerCommand(commandName);
  401. if (command != null && !command.hidden()) {
  402. command = null;
  403. }
  404. } else {
  405. command = server.getCommandParser().getPlayerCommand(originalName);
  406. }
  407. if (command == null) {
  408. if (groupObject.forwardUnknownCommands || config != null) {
  409. command = new ExternalCommand(commandName);
  410. } else {
  411. command = server.getCommandParser().getPlayerCommand((String) null);
  412. }
  413. }
  414. if (config != null) {
  415. Permission permission = server.config.getCommandPermission(config.name, args, position.coordinate());
  416. if (!permission.contains(this)) {
  417. addTMessage(Color.RED, "Insufficient permission.");
  418. return null;
  419. }
  420. }
  421. if (!(command instanceof ExternalCommand) && (config == null || config.forwarding != Forwarding.ONLY)) {
  422. command.execute(this, message);
  423. }
  424. if (command instanceof ExternalCommand) {
  425. return "/" + originalName + " " + args;
  426. } else if ((config != null && config.forwarding != Forwarding.NONE) || server.config.properties.getBoolean("forwardAllCommands")) {
  427. return message;
  428. } else {
  429. return null;
  430. }
  431. }
  432. public void execute(Class<? extends PlayerCommand> c) {
  433. execute(c, "");
  434. }
  435. public void execute(Class<? extends PlayerCommand> c, String arguments) {
  436. server.getCommandParser().getPlayerCommand(c).execute(this, "a " + arguments);
  437. }
  438. public void teleportTo(Player target) {
  439. server.runCommand("tp", getName() + " " + target.getName());
  440. }
  441. public void sendMOTD() {
  442. String[] lines = server.motd.getMOTD().split("\\r?\\n");
  443. for (String line : lines) {
  444. addMessage(line);
  445. }
  446. }
  447. public void give(int id, int amount) {
  448. String baseCommand = getName() + " " + id + " ";
  449. for (int c = 0; c < amount / 64; ++c) {
  450. server.runCommand("give", baseCommand + 64);
  451. }
  452. if (amount % 64 != 0) {
  453. server.runCommand("give", baseCommand + amount % 64);
  454. }
  455. }
  456. public void give(int id, short damage, int amount) throws ConnectException {
  457. if (damage == 0) {
  458. give(id, amount);
  459. } else {
  460. Giver giver = new Giver(this);
  461. for (int c = 0; c < amount / 64; ++c) {
  462. giver.add(id, 64, damage);
  463. }
  464. if (amount % 64 != 0) {
  465. giver.add(id, amount % 64, damage);
  466. }
  467. server.bots.connect(giver);
  468. }
  469. }
  470. public void give(Kit kit) throws ConnectException {
  471. Giver giver = new Giver(this);
  472. int invSize = 45;
  473. int slot = invSize;
  474. for (Kit.Entry e : kit.items) {
  475. if (e.damage() == 0) {
  476. give(e.item(), e.amount());
  477. } else {
  478. int restAmount = e.amount();
  479. while (restAmount > 0 && --slot >= 0) {
  480. giver.add(e.item(), Math.min(restAmount, 64), e.damage());
  481. restAmount -= 64;
  482. if (slot == 0) {
  483. slot = invSize;
  484. server.bots.connect(giver);
  485. giver = new Giver(this);
  486. }
  487. }
  488. }
  489. }
  490. if (slot != invSize) {
  491. server.bots.connect(giver);
  492. }
  493. }
  494. public void updateGroup() {
  495. try {
  496. groupObject = server.config.getGroup(this);
  497. } catch (SAXException e) {
  498. System.out.println("[SimpleServer] A player could not be assigned to any group. (" + e + ")");
  499. kick("You could not be asigned to any group.");
  500. return;
  501. }
  502. group = groupObject.id;
  503. }
  504. public void placedBlock() {
  505. blocksPlaced += 1;
  506. }
  507. public void destroyedBlock() {
  508. blocksDestroyed += 1;
  509. }
  510. public Integer[] stats() {
  511. Integer[] stats = new Integer[4];
  512. stats[0] = (int) (System.currentTimeMillis() - connected) / 1000 / 60;
  513. stats[1] = server.data.players.stats.get(this, StatField.PLAY_TIME) + stats[0];
  514. stats[2] = server.data.players.stats.add(this, StatField.BLOCKS_PLACED, blocksPlaced);
  515. stats[3] = server.data.players.stats.add(this, StatField.BLOCKS_DESTROYED, blocksDestroyed);
  516. blocksPlaced = 0;
  517. blocksDestroyed = 0;
  518. server.data.save();
  519. return stats;
  520. }
  521. public void setReply(Player answer) {
  522. // set Player to reply with !reply command
  523. reply = answer;
  524. }
  525. public Player getReply() {
  526. return reply;
  527. }
  528. public void close() {
  529. if (serverToClient != null) {
  530. serverToClient.stop();
  531. }
  532. if (clientToServer != null) {
  533. clientToServer.stop();
  534. }
  535. if (name != null) {
  536. server.authenticator.unbanLogin(this);
  537. if (usedAuthenticator) {
  538. if (guest) {
  539. server.authenticator.releaseGuestName(name);
  540. } else {
  541. server.authenticator.rememberAuthentication(name, getIPAddress());
  542. }
  543. } else if (guest) {
  544. if (isKicked) {
  545. server.authenticator.releaseGuestName(name);
  546. } else {
  547. server.authenticator.rememberGuest(name, getIPAddress());
  548. }
  549. }
  550. server.data.players.stats.add(this, StatField.PLAY_TIME, (int) (System.currentTimeMillis() - connected) / 1000 / 60);
  551. server.data.players.stats.add(this, StatField.BLOCKS_DESTROYED, blocksDestroyed);
  552. server.data.players.stats.add(this, StatField.BLOCKS_PLACED, blocksPlaced);
  553. server.data.save();
  554. server.playerList.removePlayer(this);
  555. name = renameName = null;
  556. }
  557. }
  558. private void cleanup() {
  559. if (!closed) {
  560. closed = true;
  561. entityId = 0;
  562. close();
  563. try {
  564. extsocket.close();
  565. } catch (Exception e) {
  566. }
  567. try {
  568. intsocket.close();
  569. } catch (Exception e) {
  570. }
  571. if (!isRobot) {
  572. System.out.println("[SimpleServer] Socket Closed: "
  573. + extsocket.getInetAddress().getHostAddress());
  574. }
  575. }
  576. }
  577. private class PlayerVisitRequest {
  578. public Player source;
  579. public long timestamp;
  580. public PlayerVisitRequest(Player source) {
  581. timestamp = System.currentTimeMillis();
  582. this.source = source;
  583. }
  584. }
  585. private final class Watchdog extends Thread {
  586. @Override
  587. public void run() {
  588. while (serverToClient.isAlive() || clientToServer.isAlive()) {
  589. if (!serverToClient.isActive() || !clientToServer.isActive()) {
  590. System.out.println("[SimpleServer] Disconnecting " + getIPAddress()
  591. + " due to inactivity.");
  592. close();
  593. break;
  594. }
  595. try {
  596. Thread.sleep(2000);
  597. } catch (InterruptedException e) {
  598. }
  599. }
  600. cleanup();
  601. }
  602. }
  603. public void placingChest(Coordinate coord) {
  604. chestPlaced = coord;
  605. }
  606. public boolean placedChest(Coordinate coordinate) {
  607. return chestPlaced != null && chestPlaced.equals(coordinate);
  608. }
  609. public void openingChest(Coordinate coordinate) {
  610. chestOpened = coordinate;
  611. }
  612. public Coordinate openedChest() {
  613. return chestOpened;
  614. }
  615. public void setChestArgument(String name) {
  616. chestArgument = name;
  617. }
  618. public String getChestArgument() {
  619. return chestArgument;
  620. }
  621. public enum Action {
  622. Lock, Unlock, Rename, Key;
  623. }
  624. public boolean isModifyingKeys() {
  625. return attemptedAction == Action.Key;
  626. }
  627. public boolean isAttemptingUnlock() {
  628. return attemptedAction == Action.Unlock;
  629. }
  630. public void setDimension(Dimension dimension) {
  631. position.updateDimension(dimension);
  632. }
  633. public Dimension getDimension() {
  634. return position.dimension();
  635. }
  636. public void teleport(Coordinate coordinate) throws ConnectException, IOException {
  637. teleport(new Position(coordinate));
  638. }
  639. public void teleport(Position position) throws ConnectException, IOException {
  640. if (position.dimension() == getDimension()) {
  641. server.bots.connect(new Teleporter(this, position));
  642. } else {
  643. addTMessage(Color.RED, "You're not in the same dimension as the specified warppoint.");
  644. }
  645. }
  646. public void teleportSelf(Coordinate coordinate) {
  647. teleportSelf(new Position(coordinate));
  648. }
  649. public void teleportSelf(Position position) {
  650. try {
  651. teleport(position);
  652. } catch (Exception e) {
  653. addTMessage(Color.RED, "Teleporting failed.");
  654. return;
  655. }
  656. lastTeleport = System.currentTimeMillis();
  657. }
  658. private int cooldownLeft() {
  659. int cooldown = getGroup().cooldown();
  660. if (lastTeleport > System.currentTimeMillis() - cooldown) {
  661. return (int) (cooldown - System.currentTimeMillis() + lastTeleport);
  662. } else {
  663. return 0;
  664. }
  665. }
  666. public synchronized void teleportWithWarmup(Coordinate coordinate) {
  667. teleportWithWarmup(new Position(coordinate));
  668. }
  669. public synchronized void teleportWithWarmup(Position position) {
  670. int cooldown = cooldownLeft();
  671. if (lastTeleport < 0) {
  672. addTMessage(Color.RED, "You are already waiting for a teleport.");
  673. } else if (cooldown > 0) {
  674. addTMessage(Color.RED, "You have to wait %d seconds before you can teleport again.", cooldown / 1000);
  675. } else {
  676. int warmup = getGroup().warmup();
  677. if (warmup > 0) {
  678. lastTeleport = -1;
  679. Timer timer = new Timer();
  680. timer.schedule(new Warmup(position), warmup);
  681. addTMessage(Color.GRAY, "You will be teleported in %s seconds.", warmup / 1000);
  682. } else {
  683. teleportSelf(position);
  684. }
  685. }
  686. }
  687. private final class Warmup extends TimerTask {
  688. private final Position position;
  689. private Warmup(Position position) {
  690. super();
  691. this.position = position;
  692. }
  693. @Override
  694. public void run() {
  695. teleportSelf(position);
  696. }
  697. }
  698. }