PageRenderTime 2382ms CodeModel.GetById 44ms RepoModel.GetById 1ms app.codeStats 0ms

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

http://github.com/sk89q/craftbook
Java | 485 lines | 240 code | 60 blank | 185 comment | 48 complexity | 6206846b215c57be4cbbfae652408605 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 java.util.*;
  21. import java.util.logging.Level;
  22. import java.util.logging.Logger;
  23. import org.bukkit.Chunk;
  24. import org.bukkit.Material;
  25. import org.bukkit.block.Block;
  26. import org.bukkit.block.BlockState;
  27. import org.bukkit.block.Sign;
  28. import org.bukkit.event.*;
  29. import org.bukkit.event.Event.Result;
  30. import org.bukkit.event.block.*;
  31. import org.bukkit.event.player.*;
  32. import org.bukkit.inventory.ItemStack;
  33. import com.sk89q.craftbook.bukkit.BaseBukkitPlugin;
  34. import com.sk89q.craftbook.bukkit.ChangedSign;
  35. import com.sk89q.craftbook.util.*;
  36. import com.sk89q.worldedit.*;
  37. import static com.sk89q.worldedit.bukkit.BukkitUtil.*;
  38. /**
  39. * A MechanicManager tracks the BlockVector where loaded Mechanic instances have
  40. * registered triggerability, and dispatches incoming events by checking for
  41. * Mechanic instance that might be triggered by the event and by considering
  42. * instantiation of a new Mechanic instance for unregistered BlockVector.
  43. *
  44. * @author sk89q
  45. * @author hash
  46. */
  47. public class MechanicManager {
  48. public static final boolean DEBUG = false;
  49. /**
  50. * Logger for errors. The Minecraft namespace is required so that messages
  51. * are part of Minecraft's root logger.
  52. */
  53. protected final Logger logger = Logger.getLogger("Minecraft.CraftBook");
  54. /**
  55. * Plugin.
  56. */
  57. protected final BaseBukkitPlugin plugin;
  58. /**
  59. * List of factories that will be used to detect mechanisms at a location.
  60. */
  61. protected final LinkedList<MechanicFactory<? extends Mechanic>> factories;
  62. /**
  63. * Keeps track of trigger blocks. Trigger blocks are the blocks that
  64. * will activate mechanics. No block can be a trigger block for two
  65. * mechanics at once. An example of a trigger block is a sign block
  66. * with [Gate] as the second line, triggering the gate mechanic.
  67. */
  68. private final TriggerBlockManager triggersManager;
  69. /**
  70. * Keeps track of watch blocks. A persistent mechanic have several watch
  71. * blocks that entail the blocks that the mechanic may use. These blocks
  72. * may not be trigger blocks and are probably not. Watch blocks aren't
  73. * utilized yet.
  74. */
  75. private final WatchBlockManager watchBlockManager;
  76. /**
  77. * List of mechanics that think on a routine basis.
  78. */
  79. private Set<SelfTriggeringMechanic> thinkingMechanics = new LinkedHashSet<SelfTriggeringMechanic>();
  80. /**
  81. * Construct the manager.
  82. *
  83. * @param plugin
  84. */
  85. public MechanicManager(BaseBukkitPlugin plugin) {
  86. this.plugin = plugin;
  87. factories = new LinkedList<MechanicFactory<? extends Mechanic>>();
  88. triggersManager = new TriggerBlockManager();
  89. watchBlockManager = new WatchBlockManager();
  90. }
  91. /**
  92. * Register a mechanic factory. Make sure that the same factory isn't
  93. * registered twice -- that condition isn't ever checked.
  94. *
  95. * @param factory
  96. */
  97. public void register(MechanicFactory<? extends Mechanic> factory) {
  98. factories.add(factory);
  99. }
  100. /**
  101. * Handle a block right click event.
  102. *
  103. * @param event
  104. * @return true if there was a mechanic to process the event
  105. */
  106. public boolean dispatchSignChange(SignChangeEvent event) {
  107. // We don't need to handle events that no mechanic we use makes use of
  108. if (!passesFilter(event))
  109. return false;
  110. // Announce the event to anyone who considers it to be on one of their defining blocks
  111. //TODO: separate the processing of events which could destroy blocks vs just interact, because interacts can't really do anything to watch blocks; watch blocks are only really for cancelling illegal block damages and for invalidating the mechanism proactively.
  112. //watchBlockManager.notify(event);
  113. // See if this event could be occurring on any mechanism's triggering blocks
  114. Block block = event.getBlock();
  115. BlockWorldVector pos = toWorldVector(block);
  116. LocalPlayer localPlayer = plugin.wrap(event.getPlayer());
  117. BlockState state = event.getBlock().getState();
  118. if (!(state instanceof Sign)) {
  119. return false;
  120. }
  121. Sign sign = (Sign) state;
  122. try {
  123. load(pos, localPlayer,
  124. new ChangedSign((Sign) sign, event.getLines()));
  125. } catch (InvalidMechanismException e) {
  126. if (e.getMessage() != null) {
  127. localPlayer.printError(e.getMessage());
  128. }
  129. event.setCancelled(true);
  130. block.getWorld().dropItem(block.getLocation(), new ItemStack(Material.SIGN, 1));
  131. block.setTypeId(0);
  132. }
  133. return false;
  134. }
  135. /**
  136. * Handle a block right click event.
  137. *
  138. * @param event
  139. * @return true if there was a mechanic to process the event
  140. */
  141. public boolean dispatchBlockRightClick(PlayerInteractEvent event) {
  142. // We don't need to handle events that no mechanic we use makes use of
  143. if (!passesFilter(event))
  144. return false;
  145. // Announce the event to anyone who considers it to be on one of their defining blocks
  146. //TODO: separate the processing of events which could destroy blocks vs just interact, because interacts can't really do anything to watch blocks; watch blocks are only really for cancelling illegal block damages and for invalidating the mechanism proactively.
  147. //watchBlockManager.notify(event);
  148. // See if this event could be occurring on any mechanism's triggering blocks
  149. BlockWorldVector pos = toWorldVector(event.getClickedBlock());
  150. try {
  151. Mechanic mechanic = load(pos);
  152. if (mechanic != null) {
  153. mechanic.onRightClick(event);
  154. return true;
  155. }
  156. } catch (InvalidMechanismException e) {
  157. if (e.getMessage() != null) {
  158. event.getPlayer().sendMessage(e.getMessage());
  159. }
  160. }
  161. return false;
  162. }
  163. /**
  164. * Handle a block left click event.
  165. *
  166. * @param event
  167. * @return true if there was a mechanic to process the event
  168. */
  169. public boolean dispatchBlockLeftClick(PlayerInteractEvent event) {
  170. // We don't need to handle events that no mechanic we use makes use of
  171. if (!passesFilter(event))
  172. return false;
  173. // Announce the event to anyone who considers it to be on one of their defining blocks
  174. //TODO: separate the processing of events which could destroy blocks vs just interact, because interacts can't really do anything to watch blocks; watch blocks are only really for cancelling illegal block damages and for invalidating the mechanism proactively.
  175. //watchBlockManager.notify(event);
  176. // See if this event could be occurring on any mechanism's triggering blocks
  177. BlockWorldVector pos = toWorldVector(event.getClickedBlock());
  178. try {
  179. Mechanic mechanic = load(pos);
  180. if (mechanic != null) {
  181. mechanic.onLeftClick(event);
  182. return true;
  183. }
  184. } catch (InvalidMechanismException e) {
  185. if (e.getMessage() != null) {
  186. event.getPlayer().sendMessage(e.getMessage());
  187. }
  188. }
  189. return false;
  190. }
  191. /**
  192. * Handle the redstone block change event.
  193. *
  194. * @param event
  195. * @return true if there was a mechanic to process the event
  196. */
  197. public boolean dispatchBlockRedstoneChange(SourcedBlockRedstoneEvent event) {
  198. // We don't need to handle events that no mechanic we use makes use of
  199. if (!passesFilter(event))
  200. return false;
  201. // See if this event could be occurring on any mechanism's triggering blocks
  202. BlockWorldVector pos = toWorldVector(event.getBlock());
  203. try {
  204. Mechanic mechanic = load(pos);
  205. if (mechanic != null) {
  206. mechanic.onBlockRedstoneChange(event);
  207. return true;
  208. }
  209. } catch (InvalidMechanismException e) {
  210. }
  211. return false;
  212. }
  213. /**
  214. * Load a Mechanic at a position. May return an already existing
  215. * PersistentMechanic if one is triggered at that position, or return a new
  216. * Mechanic (persistent or otherwise; if the new mechanic is persistent, it
  217. * will have already been registered with this manager).
  218. *
  219. * @param pos
  220. * @return a {@link Mechanic} if a mechanism could be found at the location;
  221. * null otherwise
  222. * @throws InvalidMechanismException
  223. * if it appears that the position is intended to me a
  224. * mechanism, but the mechanism is misconfigured and inoperable.
  225. */
  226. protected Mechanic load(BlockWorldVector pos)
  227. throws InvalidMechanismException {
  228. Mechanic mechanic = triggersManager.get(pos);
  229. if (mechanic != null) {
  230. if (mechanic.isActive()) {
  231. return mechanic;
  232. } else {
  233. unload(mechanic);
  234. }
  235. }
  236. mechanic = detect(pos);
  237. // No mechanic detected!
  238. if (mechanic == null)
  239. return null;
  240. // Register mechanic if it's a persistent type
  241. if (mechanic instanceof PersistentMechanic) {
  242. PersistentMechanic pm = (PersistentMechanic) mechanic;
  243. triggersManager.register(pm);
  244. watchBlockManager.register(pm);
  245. if (mechanic instanceof SelfTriggeringMechanic) {
  246. synchronized (this) {
  247. thinkingMechanics.add((SelfTriggeringMechanic) mechanic);
  248. }
  249. }
  250. }
  251. return mechanic;
  252. }
  253. /**
  254. * Load a Mechanic at a position.
  255. *
  256. * @param pos
  257. * @param player
  258. * @return a {@link Mechanic} if a mechanism could be found at the location;
  259. * null otherwise
  260. * @throws InvalidMechanismException
  261. * if it appears that the position is intended to me a
  262. * mechanism, but the mechanism is misconfigured and inoperable.
  263. */
  264. protected Mechanic load(BlockWorldVector pos, LocalPlayer player, Sign sign)
  265. throws InvalidMechanismException {
  266. Mechanic mechanic = triggersManager.get(pos);
  267. if (mechanic != null) {
  268. if (mechanic.isActive()) {
  269. return mechanic;
  270. } else {
  271. unload(mechanic);
  272. }
  273. }
  274. mechanic = detect(pos, player, sign);
  275. // No mechanic detected!
  276. if (mechanic == null)
  277. return null;
  278. // Register mechanic if it's a persistent type
  279. if (mechanic instanceof PersistentMechanic) {
  280. PersistentMechanic pm = (PersistentMechanic) mechanic;
  281. triggersManager.register(pm);
  282. watchBlockManager.register(pm);
  283. if (mechanic instanceof SelfTriggeringMechanic) {
  284. synchronized (this) {
  285. thinkingMechanics.add((SelfTriggeringMechanic) mechanic);
  286. }
  287. }
  288. }
  289. return mechanic;
  290. }
  291. /**
  292. * Attempt to detect a mechanic at a location. This is only called in
  293. * response to events for which a trigger block for an existing
  294. * PersistentMechanic cannot be found.
  295. *
  296. * @param pos
  297. * @return a {@link Mechanic} if a mechanism could be found at the location;
  298. * null otherwise
  299. * @throws InvalidMechanismException
  300. * if it appears that the position is intended to me a
  301. * mechanism, but the mechanism is misconfigured and inoperable.
  302. */
  303. protected Mechanic detect(BlockWorldVector pos) throws InvalidMechanismException {
  304. Mechanic mechanic = null;
  305. for (MechanicFactory<? extends Mechanic> factory : factories)
  306. if ((mechanic = factory.detect(pos)) != null)
  307. break;
  308. return mechanic;
  309. }
  310. /**
  311. * Attempt to detect a mechanic at a location, with player information
  312. * available.
  313. *
  314. * @param pos
  315. * @param player
  316. * @return a {@link Mechanic} if a mechanism could be found at the location;
  317. * null otherwise
  318. * @throws InvalidMechanismException
  319. * if it appears that the position is intended to me a
  320. * mechanism, but the mechanism is misconfigured and inoperable.
  321. */
  322. protected Mechanic detect(BlockWorldVector pos, LocalPlayer player, Sign sign)
  323. throws InvalidMechanismException {
  324. Mechanic mechanic = null;
  325. for (MechanicFactory<? extends Mechanic> factory : factories) {
  326. try {
  327. if ((mechanic = factory.detect(pos, player, sign)) != null)
  328. break;
  329. } catch (ProcessedMechanismException e) {
  330. break;
  331. }
  332. }
  333. return mechanic;
  334. }
  335. /**
  336. * Used to filter events for processing. This allows for short circuiting
  337. * code so that code isn't checked unnecessarily.
  338. *
  339. * @param event
  340. * @return true if the event should be processed by this manager; false
  341. * otherwise.
  342. */
  343. protected boolean passesFilter(Event event) {
  344. return true;
  345. }
  346. /**
  347. * Handles chunk load.
  348. *
  349. * @param chunk
  350. */
  351. public void enumerate(Chunk chunk) {
  352. for (BlockState state : chunk.getTileEntities()) {
  353. if (state instanceof Sign) {
  354. try {
  355. try {
  356. load(toWorldVector(state.getBlock()));
  357. } catch (NullPointerException t) {
  358. t.printStackTrace();
  359. }
  360. } catch (InvalidMechanismException e) {
  361. }
  362. }
  363. }
  364. }
  365. /**
  366. * Unload all mechanics inside the given chunk.
  367. *
  368. * @param chunk
  369. */
  370. public void unload(BlockWorldVector2D chunk) {
  371. // Find mechanics that we need to unload
  372. Set<PersistentMechanic> applicable = triggersManager.getByChunk(chunk);
  373. applicable.addAll(watchBlockManager.getByChunk(chunk));
  374. for (Mechanic m : applicable) {
  375. unload(m);
  376. }
  377. }
  378. /**
  379. * Unload a mechanic. This will also remove the trigger points from this
  380. * mechanic manager.
  381. *
  382. * @param mechanic
  383. */
  384. protected void unload(Mechanic mechanic) {
  385. if (mechanic == null) {
  386. logger.log(Level.WARNING, "CraftBook mechanic: Failed to unload(Mechanic) - null.");
  387. return;
  388. }
  389. try {
  390. mechanic.unload();
  391. } catch (Throwable t) { // Mechanic failed to unload for some reason
  392. logger.log(Level.WARNING, "CraftBook mechanic: Failed to unload " + mechanic.getClass().getCanonicalName(), t);
  393. }
  394. synchronized (this) {
  395. thinkingMechanics.remove(mechanic);
  396. }
  397. if (mechanic instanceof PersistentMechanic) {
  398. PersistentMechanic pm = (PersistentMechanic) mechanic;
  399. triggersManager.deregister(pm);
  400. watchBlockManager.deregister(pm);
  401. }
  402. }
  403. /**
  404. * Causes all thinking mechanics to think.
  405. */
  406. public void think() {
  407. SelfTriggeringMechanic[] mechs;
  408. synchronized (this) {
  409. // Copy to array to get rid of concurrency snafus
  410. mechs = new SelfTriggeringMechanic[thinkingMechanics.size()];
  411. thinkingMechanics.toArray(mechs);
  412. }
  413. for (SelfTriggeringMechanic mechanic : mechs) {
  414. if (mechanic.isActive()) {
  415. try {
  416. mechanic.think();
  417. } catch (Throwable t) { // Mechanic failed to unload for some reason
  418. logger.log(Level.WARNING, "CraftBook mechanic: Failed to think for " + mechanic.getClass().getCanonicalName(), t);
  419. }
  420. } else {
  421. unload(mechanic);
  422. }
  423. }
  424. }
  425. }