PageRenderTime 5505ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/common/src/main/java/com/sk89q/craftbook/MechanicManager.java

http://github.com/sk89q/craftbook
Java | 635 lines | 340 code | 94 blank | 201 comment | 87 complexity | 7be3b4628aff5e38f541721e96b462cc MD5 | raw file
Possible License(s): GPL-3.0
  1. // Id
  2. /*
  3. * CraftBook
  4. * Copyright (C) 2010 sk89q <http://www.sk89q.com>
  5. *
  6. * This program is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  18. */
  19. package com.sk89q.craftbook;
  20. import static com.sk89q.worldedit.bukkit.BukkitUtil.toWorldVector;
  21. import java.util.ArrayList;
  22. import java.util.HashMap;
  23. import java.util.LinkedHashSet;
  24. import java.util.LinkedList;
  25. import java.util.List;
  26. import java.util.Set;
  27. import java.util.logging.Level;
  28. import java.util.logging.Logger;
  29. import org.bukkit.Chunk;
  30. import org.bukkit.block.Block;
  31. import org.bukkit.block.BlockState;
  32. import org.bukkit.block.Sign;
  33. import org.bukkit.event.Event;
  34. import org.bukkit.event.block.BlockBreakEvent;
  35. import org.bukkit.event.block.SignChangeEvent;
  36. import org.bukkit.event.player.PlayerInteractEvent;
  37. import org.bukkit.event.world.ChunkUnloadEvent;
  38. import org.bukkit.inventory.ItemStack;
  39. import com.sk89q.craftbook.bukkit.BaseBukkitPlugin;
  40. import com.sk89q.craftbook.bukkit.BukkitUtil;
  41. import com.sk89q.worldedit.BlockWorldVector;
  42. import com.sk89q.worldedit.BlockWorldVector2D;
  43. import com.sk89q.worldedit.blocks.ItemID;
  44. /**
  45. * A MechanicManager tracks the BlockVector where loaded Mechanic instances have
  46. * registered triggerability, and dispatches incoming events by checking for
  47. * Mechanic instance that might be triggered by the event and by considering
  48. * instantiation of a new Mechanic instance for unregistered BlockVector.
  49. *
  50. * @author sk89q
  51. * @author hash
  52. */
  53. public class MechanicManager {
  54. public static final boolean DEBUG = false;
  55. /**
  56. * Logger for errors. The Minecraft namespace is required so that messages
  57. * are part of Minecraft's root logger.
  58. */
  59. protected final Logger logger = Logger.getLogger("Minecraft.CraftBook");
  60. /**
  61. * Plugin.
  62. */
  63. protected final BaseBukkitPlugin plugin;
  64. /**
  65. * List of factories that will be used to detect mechanisms at a location.
  66. */
  67. protected final LinkedList<MechanicFactory<? extends Mechanic>> factories;
  68. /**
  69. * Keeps track of trigger blocks. Trigger blocks are the blocks that
  70. * will activate mechanics. No block can be a trigger block for two
  71. * mechanics at once. An example of a trigger block is a sign block
  72. * with [Gate] as the second line, triggering the gate mechanic.
  73. */
  74. private final TriggerBlockManager triggersManager;
  75. /**
  76. * Keeps track of watch blocks. A persistent mechanic have several watch
  77. * blocks that entail the blocks that the mechanic may use. These blocks
  78. * may not be trigger blocks and are probably not. Watch blocks aren't
  79. * utilized yet.
  80. */
  81. private final WatchBlockManager watchBlockManager;
  82. /**
  83. * List of mechanics that think on a routine basis.
  84. */
  85. private final Set<SelfTriggeringMechanic> thinkingMechanics = new LinkedHashSet<SelfTriggeringMechanic>();
  86. protected HashMap<Class<?>, ArrayList<MechanicFactory<? extends Mechanic>>> eventRegistration = new
  87. HashMap<Class<?>, ArrayList<MechanicFactory<? extends Mechanic>>>();
  88. /**
  89. * Construct the manager.
  90. *
  91. * @param plugin
  92. */
  93. public MechanicManager(BaseBukkitPlugin plugin) {
  94. this.plugin = plugin;
  95. factories = new LinkedList<MechanicFactory<? extends Mechanic>>();
  96. triggersManager = new TriggerBlockManager();
  97. watchBlockManager = new WatchBlockManager();
  98. }
  99. /**
  100. * Register a mechanic factory.
  101. *
  102. * @param factory
  103. */
  104. public void register(MechanicFactory<? extends Mechanic> factory) {
  105. if (!factories.contains(factory)) {
  106. factories.add(factory);
  107. }
  108. }
  109. /**
  110. * Unregister a mechanic factory.
  111. *
  112. * @param factory
  113. */
  114. public boolean unregister(MechanicFactory<? extends Mechanic> factory) {
  115. if (factories.contains(factory)) {
  116. factories.remove(factory);
  117. return true;
  118. }
  119. return false;
  120. }
  121. /**
  122. * Handle a block right click event.
  123. *
  124. * @param event
  125. *
  126. * @return true if there was a mechanic to process the event
  127. */
  128. public boolean dispatchSignChange(SignChangeEvent event) {
  129. // We don't need to handle events that no mechanic we use makes use of
  130. if (!passesFilter(event)) return false;
  131. // See if this event could be occurring on any mechanism's triggering blocks
  132. Block block = event.getBlock();
  133. BlockWorldVector pos = toWorldVector(block);
  134. LocalPlayer localPlayer = plugin.wrap(event.getPlayer());
  135. BlockState state = event.getBlock().getState();
  136. if (!(state instanceof Sign)) return false;
  137. Sign sign = (Sign) state;
  138. try {
  139. load(pos, localPlayer, BukkitUtil.toChangedSign(sign, event.getLines()));
  140. } catch (InvalidMechanismException e) {
  141. if (e.getMessage() != null) {
  142. localPlayer.printError(e.getMessage());
  143. }
  144. event.setCancelled(true);
  145. block.getWorld().dropItem(block.getLocation(), new ItemStack(ItemID.SIGN, 1));
  146. block.setTypeId(0);
  147. }
  148. return false;
  149. }
  150. /**
  151. * Handle a block break event.
  152. *
  153. * @param event
  154. *
  155. * @return the number of mechanics to processed
  156. */
  157. public short dispatchBlockBreak(BlockBreakEvent event) {
  158. // We don't need to handle events that no mechanic we use makes use of
  159. if (!passesFilter(event)) return 0;
  160. short returnValue = 0;
  161. LocalPlayer player = plugin.wrap(event.getPlayer());
  162. if (!plugin.canBuildInArea(event.getBlock().getLocation(), event.getPlayer())) {
  163. player.printError("area.permissions");
  164. return 0;
  165. }
  166. // Announce the event to anyone who considers it to be on one of their defining blocks
  167. watchBlockManager.notify(event);
  168. // See if this event could be occurring on any mechanism's triggering blocks
  169. BlockWorldVector pos = toWorldVector(event.getBlock());
  170. try {
  171. List<Mechanic> mechanics = load(pos);
  172. for (Mechanic aMechanic : mechanics)
  173. if (aMechanic != null) {
  174. aMechanic.onBlockBreak(event);
  175. returnValue++;
  176. }
  177. } catch (InvalidMechanismException e) {
  178. if (e.getMessage() != null) {
  179. player.printError(e.getMessage());
  180. }
  181. }
  182. return returnValue;
  183. }
  184. /**
  185. * Handle a block right click event.
  186. *
  187. * @param event
  188. *
  189. * @return the number of mechanics to processed
  190. */
  191. public short dispatchBlockRightClick(PlayerInteractEvent event) {
  192. // We don't need to handle events that no mechanic we use makes use of
  193. if (!passesFilter(event)) return 0;
  194. short returnValue = 0;
  195. LocalPlayer player = plugin.wrap(event.getPlayer());
  196. if (!plugin.canUseInArea(event.getClickedBlock().getLocation(), event.getPlayer())) {
  197. player.printError("area.permissions");
  198. return 0;
  199. }
  200. // See if this event could be occurring on any mechanism's triggering blocks
  201. BlockWorldVector pos = toWorldVector(event.getClickedBlock());
  202. try {
  203. List<Mechanic> mechanics = load(pos);
  204. for (Mechanic aMechanic : mechanics)
  205. if (aMechanic != null) {
  206. aMechanic.onRightClick(event);
  207. returnValue++;
  208. }
  209. } catch (InvalidMechanismException e) {
  210. if (e.getMessage() != null) {
  211. player.printError(e.getMessage());
  212. }
  213. }
  214. return returnValue;
  215. }
  216. /**
  217. * Handle a block left click event.
  218. *
  219. * @param event
  220. *
  221. * @return the number of mechanics to processed
  222. */
  223. public short dispatchBlockLeftClick(PlayerInteractEvent event) {
  224. // We don't need to handle events that no mechanic we use makes use of
  225. if (!passesFilter(event)) return 0;
  226. short returnValue = 0;
  227. LocalPlayer player = plugin.wrap(event.getPlayer());
  228. if (!plugin.canUseInArea(event.getClickedBlock().getLocation(), event.getPlayer())) {
  229. player.printError("area.permissions");
  230. return 0;
  231. }
  232. // See if this event could be occurring on any mechanism's triggering blocks
  233. BlockWorldVector pos = toWorldVector(event.getClickedBlock());
  234. try {
  235. List<Mechanic> mechanics = load(pos);
  236. for (Mechanic aMechanic : mechanics)
  237. if (aMechanic != null) {
  238. aMechanic.onLeftClick(event);
  239. returnValue++;
  240. }
  241. } catch (InvalidMechanismException e) {
  242. if (e.getMessage() != null) {
  243. player.printError(e.getMessage());
  244. }
  245. }
  246. return returnValue;
  247. }
  248. /**
  249. * Handle the redstone block change event.
  250. *
  251. * @param event
  252. *
  253. * @return the number of mechanics to processed
  254. */
  255. public short dispatchBlockRedstoneChange(SourcedBlockRedstoneEvent event) {
  256. // We don't need to handle events that no mechanic we use makes use of
  257. if (!passesFilter(event)) return 0;
  258. short returnValue = 0;
  259. // See if this event could be occurring on any mechanism's triggering blocks
  260. BlockWorldVector pos = toWorldVector(event.getBlock());
  261. try {
  262. List<Mechanic> mechanics = load(pos);
  263. for (Mechanic aMechanic : mechanics)
  264. if (aMechanic != null) {
  265. aMechanic.onBlockRedstoneChange(event);
  266. returnValue++;
  267. }
  268. } catch (InvalidMechanismException ignored) {
  269. }
  270. return returnValue;
  271. }
  272. /**
  273. * Load a Mechanic at a position. May return an already existing
  274. * PersistentMechanic if one is triggered at that position, or return a new
  275. * Mechanic (persistent or otherwise; if the new mechanic is persistent, it
  276. * will have already been registered with this manager).
  277. *
  278. * @param pos
  279. *
  280. * @return a list of all {@link Mechanic} at the location;
  281. *
  282. * @throws InvalidMechanismException if it appears that the position is intended to me a
  283. * mechanism, but the mechanism is misconfigured and inoperable.
  284. */
  285. protected List<Mechanic> load(BlockWorldVector pos)
  286. throws InvalidMechanismException {
  287. List<Mechanic> detectedMechanics = detect(pos);
  288. Mechanic ptMechanic = triggersManager.get(pos);
  289. if (ptMechanic != null && ptMechanic instanceof PersistentMechanic && !((PersistentMechanic) ptMechanic).isActive()) {
  290. unload(ptMechanic, null);
  291. ptMechanic = null;
  292. }
  293. for (Mechanic aMechanic : detectedMechanics) {
  294. // No mechanic detected!
  295. if (ptMechanic != null) {
  296. break;
  297. }
  298. if (aMechanic == null) {
  299. continue;
  300. }
  301. // Register mechanic if it's a persistent type
  302. if (aMechanic instanceof PersistentMechanic) {
  303. PersistentMechanic pm = (PersistentMechanic) aMechanic;
  304. triggersManager.register(pm);
  305. watchBlockManager.register(pm);
  306. if (aMechanic instanceof SelfTriggeringMechanic) {
  307. synchronized (this) {
  308. thinkingMechanics.add((SelfTriggeringMechanic) aMechanic);
  309. }
  310. }
  311. break;
  312. }
  313. }
  314. // Lets handle what happens when ptMechanic is here
  315. if (ptMechanic != null) {
  316. List<Mechanic> removedMechanics = new ArrayList<Mechanic>();
  317. for (Mechanic aMechanic : detectedMechanics)
  318. if (ptMechanic.getClass().equals(aMechanic.getClass())) {
  319. removedMechanics.add(aMechanic);
  320. }
  321. for (Mechanic aMechanic : removedMechanics)
  322. if (detectedMechanics.contains(aMechanic)) {
  323. detectedMechanics.remove(aMechanic);
  324. }
  325. detectedMechanics.add(ptMechanic);
  326. }
  327. return detectedMechanics;
  328. }
  329. /**
  330. * Load a Mechanic at a position.
  331. *
  332. * @param pos
  333. * @param player
  334. * @param sign
  335. *
  336. * @return a list of all {@link Mechanic} at the location;
  337. *
  338. * @throws InvalidMechanismException if it appears that the position is intended to me a
  339. * mechanism, but the mechanism is misconfigured and inoperable.
  340. */
  341. protected List<Mechanic> load(BlockWorldVector pos, LocalPlayer player, ChangedSign sign)
  342. throws InvalidMechanismException {
  343. List<Mechanic> detectedMechanics = detect(pos, player, sign);
  344. Mechanic ptMechanic = triggersManager.get(pos);
  345. if (ptMechanic != null && ptMechanic instanceof PersistentMechanic && !((PersistentMechanic) ptMechanic).isActive()) {
  346. unload(ptMechanic, null);
  347. ptMechanic = null;
  348. }
  349. for (Mechanic aMechanic : detectedMechanics) {
  350. // No mechanic detected!
  351. if (ptMechanic != null) {
  352. break;
  353. }
  354. if (aMechanic == null) {
  355. continue;
  356. }
  357. // Register mechanic if it's a persistent type
  358. if (aMechanic instanceof PersistentMechanic) {
  359. PersistentMechanic pm = (PersistentMechanic) aMechanic;
  360. triggersManager.register(pm);
  361. watchBlockManager.register(pm);
  362. if (aMechanic instanceof SelfTriggeringMechanic) {
  363. synchronized (this) {
  364. thinkingMechanics.add((SelfTriggeringMechanic) aMechanic);
  365. }
  366. }
  367. break;
  368. }
  369. }
  370. // Lets handle what happens when ptMechanic is here
  371. if (ptMechanic != null) {
  372. List<Mechanic> removedMechanics = new ArrayList<Mechanic>();
  373. for (Mechanic aMechanic : detectedMechanics)
  374. if (ptMechanic.getClass().equals(aMechanic.getClass())) {
  375. removedMechanics.add(aMechanic);
  376. }
  377. for (Mechanic aMechanic : removedMechanics)
  378. if (detectedMechanics.contains(aMechanic)) {
  379. detectedMechanics.remove(aMechanic);
  380. }
  381. detectedMechanics.add(ptMechanic);
  382. }
  383. return detectedMechanics;
  384. }
  385. /**
  386. * Attempt to detect a mechanic at a location. This is only called in
  387. * response to events for which a trigger block for an existing
  388. * PersistentMechanic cannot be found.
  389. *
  390. * @param pos
  391. *
  392. * @return a {@link Mechanic} if a mechanism could be found at the location;
  393. * null otherwise
  394. *
  395. * @throws InvalidMechanismException if it appears that the position is intended to me a
  396. * mechanism, but the mechanism is misconfigured and inoperable.
  397. */
  398. protected List<Mechanic> detect(BlockWorldVector pos) throws InvalidMechanismException {
  399. List<Mechanic> mechanics = new ArrayList<Mechanic>();
  400. for (MechanicFactory<? extends Mechanic> factory : factories) {
  401. Mechanic mechanic;
  402. if ((mechanic = factory.detect(pos)) != null) {
  403. mechanics.add(mechanic);
  404. }
  405. }
  406. return mechanics;
  407. }
  408. /**
  409. * Attempt to detect a mechanic at a location, with player information
  410. * available.
  411. *
  412. * @param pos
  413. * @param player
  414. *
  415. * @return a {@link Mechanic} if a mechanism could be found at the location;
  416. * null otherwise
  417. *
  418. * @throws InvalidMechanismException if it appears that the position is intended to me a
  419. * mechanism, but the mechanism is misconfigured and inoperable.
  420. */
  421. protected List<Mechanic> detect(BlockWorldVector pos, LocalPlayer player, ChangedSign sign)
  422. throws InvalidMechanismException {
  423. List<Mechanic> mechanics = new ArrayList<Mechanic>();
  424. for (MechanicFactory<? extends Mechanic> factory : factories) {
  425. try {
  426. Mechanic mechanic;
  427. if ((mechanic = factory.detect(pos, player, sign)) != null) {
  428. mechanics.add(mechanic);
  429. }
  430. } catch (ProcessedMechanismException ignored) {
  431. // Do nothing here one screwed up mech doesn't mean all them are wrong
  432. }
  433. }
  434. return mechanics;
  435. }
  436. /**
  437. * Used to filter events for processing. This allows for short circuiting
  438. * code so that code isn't checked unnecessarily.
  439. *
  440. * @param event
  441. *
  442. * @return true if the event should be processed by this manager; false
  443. * otherwise.
  444. */
  445. protected boolean passesFilter(Event event) {
  446. return true;
  447. }
  448. /**
  449. * Handles chunk load.
  450. *
  451. * @param chunk
  452. */
  453. public void enumerate(Chunk chunk) {
  454. for (BlockState state : chunk.getTileEntities()) {
  455. if (state == null) continue;
  456. if (state instanceof Sign) {
  457. try {
  458. try {
  459. load(toWorldVector(state.getBlock()));
  460. } catch (NullPointerException t) {
  461. t.printStackTrace();
  462. }
  463. } catch (InvalidMechanismException ignored) {
  464. }
  465. }
  466. }
  467. }
  468. /**
  469. * Unload all mechanics inside the given chunk.
  470. *
  471. * @param chunk
  472. */
  473. public void unload(BlockWorldVector2D chunk, ChunkUnloadEvent event) {
  474. // Find mechanics that we need to unload
  475. Set<PersistentMechanic> applicable = triggersManager.getByChunk(chunk);
  476. applicable.addAll(watchBlockManager.getByChunk(chunk));
  477. for (Mechanic m : applicable) {
  478. unload(m, event);
  479. }
  480. }
  481. /**
  482. * Unload a mechanic. This will also remove the trigger points from this
  483. * mechanic manager.
  484. *
  485. * @param mechanic
  486. */
  487. protected void unload(Mechanic mechanic, ChunkUnloadEvent event) {
  488. if (mechanic == null) {
  489. logger.log(Level.WARNING, "CraftBook mechanic: Failed to unload(Mechanic) - null.");
  490. return;
  491. }
  492. try {
  493. if(event == null)
  494. mechanic.unload();
  495. else
  496. mechanic.unloadWithEvent(event);
  497. } catch (Throwable t) { // Mechanic failed to unload for some reason
  498. logger.log(Level.WARNING, "CraftBook mechanic: Failed to unload " + mechanic.getClass().getCanonicalName
  499. (), t);
  500. t.printStackTrace();
  501. }
  502. synchronized (this) {
  503. thinkingMechanics.remove(mechanic);
  504. }
  505. if (mechanic instanceof PersistentMechanic) {
  506. PersistentMechanic pm = (PersistentMechanic) mechanic;
  507. triggersManager.deregister(pm);
  508. watchBlockManager.deregister(pm);
  509. }
  510. }
  511. /**
  512. * Causes all thinking mechanics to think.
  513. */
  514. public void think() {
  515. SelfTriggeringMechanic[] mechs;
  516. synchronized (this) {
  517. // Copy to array to get rid of concurrency snafus
  518. mechs = thinkingMechanics.toArray(new SelfTriggeringMechanic[thinkingMechanics.size()]);
  519. }
  520. for (SelfTriggeringMechanic mechanic : mechs)
  521. if (mechanic instanceof PersistentMechanic && ((PersistentMechanic) mechanic).isActive()) {
  522. try {
  523. mechanic.think();
  524. } catch (Throwable t) { // Mechanic failed to think for some reason
  525. logger.log(Level.WARNING, "CraftBook mechanic: Failed to think for " + mechanic.getClass()
  526. .getCanonicalName(), t);
  527. t.printStackTrace();
  528. }
  529. } else {
  530. unload(mechanic, null);
  531. }
  532. }
  533. public void registerEvent(Class<?> event, MechanicFactory<? extends Mechanic> mechanic) {
  534. ArrayList<MechanicFactory<? extends Mechanic>> list = eventRegistration.get(event);
  535. if (list == null) {
  536. list = new ArrayList<MechanicFactory<? extends Mechanic>>();
  537. }
  538. list.add(mechanic);
  539. eventRegistration.put(event, list);
  540. }
  541. }