progress??
This commit is contained in:
		
					parent
					
						
							
								ce060a26bf
							
						
					
				
			
			
				commit
				
					
						1aeb0d7504
					
				
			
		
					 7 changed files with 197 additions and 32 deletions
				
			
		|  | @ -121,7 +121,11 @@ public class ArenaList { | |||
|     public MinecleanerArena getArenaAtBlock(Block block) { | ||||
|         return arenaBlocks.get(block.getLocation()); | ||||
|     } | ||||
|      | ||||
| 
 | ||||
|     public MinecleanerArena getArenaForBlockDisplay(UUID id) { | ||||
|         return arenaBlockDisplays.get(id); | ||||
|     } | ||||
| 
 | ||||
|     public void removeArena(MinecleanerArena arena) { | ||||
|         if(arena.hasPlayer()) { | ||||
|             plugin.getManager().leaveArena(arena.getCurrentPlayer(), true); | ||||
|  |  | |||
|  | @ -28,7 +28,7 @@ public class MinecleanerArena { | |||
|     private int widthIndex = 0; | ||||
|     private final BlockFace orientation; | ||||
|     private ArenaStatus arenaStatus = ArenaStatus.INACTIVE; | ||||
|     private UUID[] blockDisplays = new UUID[widthIndex * widthIndex]; | ||||
|     private UUID[] blockDisplays = new UUID[81]; // todo needs to be of size boardSizes[widthIndex] | ||||
|      | ||||
|     private Player currentPlayer; | ||||
|     private long currentGameStartTime; | ||||
|  | @ -87,10 +87,9 @@ public class MinecleanerArena { | |||
| 
 | ||||
|         BlockData block0 = Material.NETHER_BRICKS.createBlockData(); | ||||
|         BlockData block1 = Material.BRICKS.createBlockData(); | ||||
|          | ||||
|         for(int fx = -1; fx < 2; fx++) { | ||||
|             for(int fy = -1; fy < 2; fy++) { | ||||
|                 loc.set(location.getX() + d1x * fx, location.getY() + fy, location.getZ() + d1z + fx); | ||||
|         for (int fx = -1; fx < 2; fx++) { | ||||
|             for (int fy = -1; fy < 2; fy++) { | ||||
|                 loc.set(location.getX() + d1x * fx, location.getY() + fy, location.getZ() + d1z * fx); | ||||
|                 boolean f = (fx + fy) % 2 == 0; | ||||
|                 world.setBlockData(loc, f ? block0 : block1); | ||||
|             } | ||||
|  | @ -133,13 +132,14 @@ public class MinecleanerArena { | |||
|             final int fxf = fx; | ||||
|             for(int fz = 0; fz < 9; fz++) { | ||||
|                 final int fzf = fz; | ||||
|                 // Todo not correctly alligned at different orientations (other than NORTH) | ||||
| 
 | ||||
|                 loc.set(location.getX() + 0.5 - (d1x * fz) / 3.0 + d0x * 0.501 + d1x * 1.847, location.getY() + fxf / 3.0, location.getZ() + 0.5 - (d1z * fz) / 3.0 + d0z * 0.501 + d1z * 1.847); | ||||
|                 loc.set(location.getX() + 0.11 - (d1x * fz) / 3.0 + d0x * 0.501 + d1x * 1.847, location.getY() - 0.9725 + fxf / 3.0, location.getZ() + 0.45 - (d1z * fz) / 3.0 + d0z * 0.501 + d1z * 1.847); | ||||
| 
 | ||||
|                 Display blockDisplay = world.spawn(loc, BlockDisplay.class, blockdisplay -> { | ||||
|                     Transformation transformation = blockdisplay.getTransformation(); | ||||
|                     Transformation newTransform; | ||||
|                     Vector3f newTranslationScale = new Vector3f(0.25f, 0.25f, 0.25f); | ||||
|                     Vector3f newTranslationScale = new Vector3f(0.30f, 0.25f, 0.25f); | ||||
|                     newTransform = new Transformation( | ||||
|                         transformation.getTranslation(), | ||||
|                         transformation.getLeftRotation(),  | ||||
|  | @ -167,6 +167,7 @@ public class MinecleanerArena { | |||
|     } | ||||
| 
 | ||||
|     public void startNewGame() { | ||||
|         currentMinecleanerGame = new Game(); | ||||
|         currentMinecleanerGame.start(); | ||||
|         arenaStatus = ArenaStatus.PLAYING; | ||||
|     } | ||||
|  | @ -176,11 +177,8 @@ public class MinecleanerArena { | |||
|         Preconditions.checkState(arenaStatus == ArenaStatus.INACTIVE); | ||||
|         this.arenaStatus = ArenaStatus.CONFIRM_PLAYING; | ||||
|         this.currentPlayer = player; | ||||
| 
 | ||||
|     }  | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|     public void removePlayer() { | ||||
|         this.arenaStatus = ArenaStatus.INACTIVE; | ||||
|         this.currentPlayer = null; | ||||
|  | @ -188,9 +186,9 @@ public class MinecleanerArena { | |||
| 
 | ||||
|     public void removeBlockDisplays() { | ||||
|         World world = location.getWorld(); | ||||
|         for(int fx = 0; fx < matchWidthIndexToActualWidth(widthIndex); fx++) { | ||||
|             for(int fy = 0; fy < matchWidthIndexToActualWidth(widthIndex); fy++) { | ||||
|                 UUID blockDisplayUuid = blockDisplays[fx + fy * matchWidthIndexToActualWidth(widthIndex)]; | ||||
|         for(int fx = 0; fx < 9; fx++) { | ||||
|             for(int fy = 0; fy < 9; fy++) { | ||||
|                 UUID blockDisplayUuid = blockDisplays[fx + fy * 9]; | ||||
|                 Entity blockDisplayEntity = blockDisplayUuid != null ? world.getEntity(blockDisplayUuid) : null; | ||||
|                 if(blockDisplayEntity instanceof Display blockdisplay) { | ||||
|                     blockDisplayEntity.remove(); | ||||
|  | @ -201,7 +199,7 @@ public class MinecleanerArena { | |||
| 
 | ||||
|     public void flagCell(int x, int y) { | ||||
|         if(currentMinecleanerGame != null) { | ||||
|             int id = x + y * matchWidthIndexToActualWidth(widthIndex); | ||||
|             int id = x + y * 9; | ||||
|             boolean unflaggedCell = currentMinecleanerGame.flag(x, y); | ||||
|             if(!unflaggedCell) { | ||||
|                 // todo set flag head on block display | ||||
|  | @ -213,7 +211,7 @@ public class MinecleanerArena { | |||
| 
 | ||||
|     public void revealCell(int x, int y) { | ||||
|         if(currentMinecleanerGame != null) { | ||||
|             int id = x + y * matchWidthIndexToActualWidth(widthIndex); | ||||
|             int id = x + y * 9; | ||||
|             // todo check if cell is flagged already | ||||
|             currentMinecleanerGame.reveal(x, y); | ||||
|             // todo update block of blockdisplay | ||||
|  |  | |||
|  | @ -1,11 +1,21 @@ | |||
| package de.lunarakai.minecleaner; | ||||
| 
 | ||||
| import java.util.Iterator; | ||||
| import org.bukkit.Location; | ||||
| import org.bukkit.block.Block; | ||||
| import org.bukkit.entity.Player; | ||||
| import org.bukkit.event.EventHandler; | ||||
| import org.bukkit.event.EventPriority; | ||||
| import org.bukkit.event.Listener; | ||||
| import org.bukkit.event.block.Action; | ||||
| import org.bukkit.event.block.BlockBurnEvent; | ||||
| import org.bukkit.event.block.BlockPistonExtendEvent; | ||||
| import org.bukkit.event.block.BlockPistonRetractEvent; | ||||
| import org.bukkit.event.entity.EntityDamageEvent; | ||||
| import org.bukkit.event.inventory.InventoryClickEvent; | ||||
| import org.bukkit.event.inventory.InventoryCloseEvent; | ||||
| import org.bukkit.event.player.PlayerInteractEvent; | ||||
| import org.bukkit.event.player.PlayerMoveEvent; | ||||
| import org.bukkit.event.player.PlayerQuitEvent; | ||||
| import org.bukkit.inventory.EquipmentSlot; | ||||
| import net.md_5.bungee.api.ChatColor; | ||||
|  | @ -23,10 +33,41 @@ public class MinecleanerListener implements Listener { | |||
|             Block block = e.getClickedBlock(); | ||||
|             MinecleanerArena arena = plugin.getArenaList().getPlayerArena(e.getPlayer()); | ||||
|             if(arena != null) { | ||||
|                 e.setCancelled(true); | ||||
|                 MinecleanerArena arenaClicked = plugin.getArenaList().getArenaAtBlock(block); | ||||
|                 if(arenaClicked == arena && arena.getArenaStatus() == ArenaStatus.PLAYING) { | ||||
|                     int d0x = arena.getOrientation().getModX(); | ||||
|                     int d0z = arena.getOrientation().getModZ(); | ||||
|                     int d1x = -d0z; | ||||
|                     int d1z = d0x; | ||||
| 
 | ||||
|                 // TODO | ||||
|                 e.getPlayer().sendMessage(ChatColor.GREEN + "Minecleaner Arena!!"); | ||||
|                     if (e.getBlockFace() == arena.getOrientation()) { | ||||
|                         Location loc = e.getInteractionPoint().clone().subtract(arena.getLocation()).subtract(0.5, 0.5, 0.5); // null on left-click | ||||
|                         double lx = loc.getX(); | ||||
|                         double ly = loc.getY(); | ||||
|                         double lz = loc.getZ(); | ||||
|                         double dy = ly + 1.5; | ||||
|                         double dz = -d1x * lx - d1z * lz + 1.5; | ||||
| 
 | ||||
|                         double blockx = (dy / 3.0) * 9.0; | ||||
|                         double blockz = (dz / 3.0) * 9.0; | ||||
| 
 | ||||
|                         int blockxInt = (int) blockx; | ||||
|                         int blockzInt = (int) blockz; | ||||
|                         blockx -= blockxInt; | ||||
|                         blockz -= blockzInt; | ||||
|                         if (blockx >= 0.1 && blockx <= 0.9 && blockz >= 0.1 && blockz <= 0.9) { | ||||
|                             boolean hasRightClicked = false; | ||||
|                             if(e.getAction() == Action.RIGHT_CLICK_BLOCK) { | ||||
|                                 hasRightClicked = true; | ||||
|                             } | ||||
|                             // TODO Doesnt show messages for Cells: [ROW] [>5] (6, 7, 8 are missing) | ||||
| 
 | ||||
|                             e.getPlayer().sendMessage("Arena click! " + blockxInt + " " + blockzInt + " Right Clicked: " + hasRightClicked); | ||||
|                             //plugin.getManager().handleFieldClick(e.getPlayer(), blockxInt, blockzInt, hasRightClicked); | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } else { | ||||
|                 arena = plugin.getArenaList().getArenaAtBlock(block); | ||||
|                 if(arena != null) { | ||||
|  | @ -43,6 +84,57 @@ public class MinecleanerListener implements Listener { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @EventHandler | ||||
|     public void onEntityDamage(EntityDamageEvent e) { | ||||
|         if(plugin.getArenaList().getArenaForBlockDisplay(e.getEntity().getUniqueId()) != null) { | ||||
|             e.setCancelled(true); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @EventHandler | ||||
|     public void onPlayerInventoryClick(InventoryClickEvent e) { | ||||
|         if(e.getWhoClicked() instanceof Player player) { | ||||
|             MinecleanerArena arena = plugin.getArenaList().getPlayerArena(player); | ||||
|             if(arena != null) { | ||||
|                 if(e.getInventory().equals(plugin.getManager().getConfirmPlayingInventory())) { | ||||
|                     e.setCancelled(true); | ||||
|                     if(arena.getArenaStatus() == ArenaStatus.CONFIRM_PLAYING) { | ||||
|                         int slot = e.getRawSlot(); | ||||
|                         boolean hasConfirmed = slot == 1 ? true : false; | ||||
|                         if(hasConfirmed) {   | ||||
|                             plugin.getManager().startGame(player); | ||||
|                             player.closeInventory(); | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     @EventHandler | ||||
|     public void onPlayerInventoryClose(InventoryCloseEvent e) { | ||||
|         if(e.getPlayer() instanceof Player player) { | ||||
|             MinecleanerArena arena = plugin.getArenaList().getPlayerArena(player); | ||||
|             if(arena != null) { | ||||
|                 if(arena.getArenaStatus() == ArenaStatus.CONFIRM_PLAYING && e.getInventory().equals(plugin.getManager().getConfirmPlayingInventory())) { | ||||
|                     plugin.getManager().leaveArena(player, false); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @EventHandler | ||||
|     public void onPlayerMove(PlayerMoveEvent e) { | ||||
|         final Player player = e.getPlayer(); | ||||
|         MinecleanerArena arena = plugin.getArenaList().getPlayerArena(player); | ||||
|         if(arena != null) { | ||||
|             if(arena.isTooFarAway(player)) { | ||||
|                 player.sendMessage(ChatColor.YELLOW + "Du hast dich zu weit von der Arena entfernt. Das Spiel wurde abgebrochen."); | ||||
|                 plugin.getManager().leaveArena(player, false); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @EventHandler | ||||
|     public void onPlayerQuit(PlayerQuitEvent e) { | ||||
|         MinecleanerArena arena = plugin.getArenaList().getPlayerArena(e.getPlayer()); | ||||
|  | @ -50,5 +142,37 @@ public class MinecleanerListener implements Listener { | |||
|             plugin.getManager().leaveArena(e.getPlayer(), false); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @EventHandler | ||||
|     public void onBlockBurn(BlockBurnEvent e) { | ||||
|         if(plugin.getArenaList().getArenaAtBlock(e.getBlock()) != null) { | ||||
|             e.setCancelled(true); | ||||
|             return; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @EventHandler | ||||
|     public void onBlockPistonExtend(BlockPistonExtendEvent e) { | ||||
|         Iterator<Block> it = e.getBlocks().iterator(); | ||||
|         while(it.hasNext()) { | ||||
|             Block block = it.next(); | ||||
|             if(plugin.getArenaList().getArenaAtBlock(block) != null) { | ||||
|                 e.setCancelled(true); | ||||
|                 return; | ||||
|             } | ||||
|         }  | ||||
|     } | ||||
| 
 | ||||
|     @EventHandler | ||||
|     public void onBlockPistonRetract(BlockPistonRetractEvent e) { | ||||
|         Iterator<Block> it = e.getBlocks().iterator(); | ||||
|         while(it.hasNext()) { | ||||
|             Block block = it.next(); | ||||
|             if(plugin.getArenaList().getArenaAtBlock(block) != null) { | ||||
|                 e.setCancelled(true); | ||||
|                 return; | ||||
|             } | ||||
|         }  | ||||
|     } | ||||
|      | ||||
| } | ||||
|  |  | |||
|  | @ -4,8 +4,12 @@ import java.util.ArrayList; | |||
| import java.util.List; | ||||
| import java.util.UUID; | ||||
| import java.util.function.Consumer; | ||||
| import org.bukkit.Material; | ||||
| import org.bukkit.OfflinePlayer; | ||||
| import org.bukkit.entity.Player; | ||||
| import org.bukkit.event.inventory.InventoryType; | ||||
| import org.bukkit.inventory.Inventory; | ||||
| import org.bukkit.inventory.ItemStack; | ||||
| import com.google.common.base.Preconditions; | ||||
| import de.iani.cubesidestats.api.PlayerStatistics; | ||||
| import de.iani.cubesidestats.api.PlayerStatisticsQueryKey; | ||||
|  | @ -13,17 +17,28 @@ import de.iani.cubesidestats.api.PlayerStatisticsQueryKey.QueryType; | |||
| import de.iani.cubesidestats.api.StatisticKey; | ||||
| import de.iani.cubesidestats.api.StatisticsQueryKey; | ||||
| import de.iani.cubesidestats.api.TimeFrame; | ||||
| import de.iani.cubesideutils.bukkit.items.ItemStacks; | ||||
| import de.iani.playerUUIDCache.CachedPlayer; | ||||
| import net.md_5.bungee.api.ChatColor; | ||||
| 
 | ||||
| public class MinecleanerManager { | ||||
|     private final MinecleanerPlugin plugin; | ||||
|     private final Inventory confirmPlayingInventory; | ||||
| 
 | ||||
|     private final StatisticKey statisticsGamesTotal; | ||||
| 
 | ||||
|     @SuppressWarnings("deprecation") | ||||
|     public MinecleanerManager(MinecleanerPlugin plugin) { | ||||
|         this.plugin = plugin; | ||||
| 
 | ||||
|         // Deprecated | ||||
|         this.confirmPlayingInventory = plugin.getServer().createInventory(null, InventoryType.HOPPER, "Möchtest du Minecleaner spielen?"); | ||||
|         this.confirmPlayingInventory.setItem(1,  | ||||
|             ItemStacks.lore(ItemStacks.rename(new ItemStack(Material.GREEN_CONCRETE), ChatColor.GREEN + "Bestätigen"))); | ||||
|         this.confirmPlayingInventory.setItem(3, | ||||
|             ItemStacks.lore(ItemStacks.rename(new ItemStack(Material.RED_CONCRETE), ChatColor.RED + "Abbrechen"))); | ||||
| 
 | ||||
| 
 | ||||
|         statisticsGamesTotal = plugin.getCubesideStatistics().getStatisticKey("minecleaner.gamesTotal"); | ||||
|         statisticsGamesTotal.setIsMonthlyStats(true); | ||||
|         statisticsGamesTotal.setDisplayName("Runden gespielt"); | ||||
|  | @ -38,6 +53,7 @@ public class MinecleanerManager { | |||
|         Preconditions.checkArgument(arena.getArenaStatus() == ArenaStatus.INACTIVE, "arena is in use"); | ||||
|         arena.addJoiningPlayer(player); | ||||
|         plugin.getArenaList().setArenaForPlayer(player, arena); | ||||
|         player.openInventory(confirmPlayingInventory); | ||||
|     } | ||||
| 
 | ||||
|     public void leaveArena(Player player, boolean message) { | ||||
|  | @ -50,6 +66,26 @@ public class MinecleanerManager { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public void startGame(Player player) { | ||||
|         MinecleanerArena arena = plugin.getArenaList().getPlayerArena(player); | ||||
|         Preconditions.checkArgument(arena != null, "player is in no arena"); | ||||
|         Preconditions.checkState(arena.getArenaStatus() == ArenaStatus.CONFIRM_PLAYING, "not confirming playing status"); | ||||
|         arena.startNewGame(); | ||||
|         player.sendMessage(ChatColor.YELLOW + "Du hast eine neue Runde Minecleaner gestartet."); | ||||
|     } | ||||
| 
 | ||||
|     public void clearAllArenas() { | ||||
|         for(MinecleanerArena arena : plugin.getArenaList().getArenas()) { | ||||
|             if(arena.hasPlayer()) { | ||||
|                 leaveArena(arena.getCurrentPlayer(), true); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public Inventory getConfirmPlayingInventory() { | ||||
|         return confirmPlayingInventory; | ||||
|     } | ||||
| 
 | ||||
|     public void getStatisticsForPlayer(OfflinePlayer player, Consumer<PlayerStatisticsData> callback) { | ||||
|         List<StatisticsQueryKey> keys = new ArrayList<>(); | ||||
|         PlayerStatistics pStatistics = plugin.getCubesideStatistics().getStatistics(player.getUniqueId()); | ||||
|  |  | |||
|  | @ -10,14 +10,6 @@ import de.lunarakai.minecleaner.commands.ListCommand; | |||
| import de.lunarakai.minecleaner.commands.StatsCommand; | ||||
| 
 | ||||
| public final class MinecleanerPlugin extends JavaPlugin { | ||||
| 
 | ||||
| 
 | ||||
|     // ------------------------------ | ||||
|     // TODO: start a new game (via ui) | ||||
|     // TODO: For testing purposes -> write coords of cell into chat | ||||
|     //  Format: Cell(X,Y) - CellType: Type | ||||
|     // ------------------------------ | ||||
| 
 | ||||
|     public static final String PERMISSION_PLAY = "minecleaner.play"; | ||||
|     public static final String PERMISSION_ADMIN = "minecleaner.admin"; | ||||
| 
 | ||||
|  | @ -29,9 +21,12 @@ public final class MinecleanerPlugin extends JavaPlugin { | |||
|     @Override | ||||
|     public void onEnable() { | ||||
|         getServer().getScheduler().runTask(this, this::onLateEnable); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     // TODO: | ||||
|     //  doesnt save / load arenas correctly? | ||||
|     //  -> can't delete arena after server restart  | ||||
| 
 | ||||
|     public void onLateEnable() { | ||||
|         playerUUIDCache = (PlayerUUIDCache) getServer().getPluginManager().getPlugin("PlayerUUIDCache"); | ||||
|         cubesideStatistics = getServer().getServicesManager().load(CubesideStatisticsAPI.class); | ||||
|  | @ -51,7 +46,9 @@ public final class MinecleanerPlugin extends JavaPlugin { | |||
| 
 | ||||
|     @Override | ||||
|     public void onDisable() { | ||||
|         // Plugin shutdown logic | ||||
|         if(minecleanerManager != null) { | ||||
|             minecleanerManager.clearAllArenas(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public ArenaList getArenaList() { | ||||
|  |  | |||
|  | @ -4,7 +4,7 @@ public class Board { | |||
| 
 | ||||
|     public Tilemap tilemap; | ||||
| 
 | ||||
|     public void draw(Cell[][] state) { | ||||
|     public void draw(Cell[][] state, Tilemap tilemap) { | ||||
|         tilemap.clearAllTiles(); | ||||
| 
 | ||||
|         int width = state[0].length; | ||||
|  |  | |||
|  | @ -12,12 +12,15 @@ public class Game { | |||
|     private boolean gameover; | ||||
|     private Board board; | ||||
|     private BoardSize boardSize; | ||||
|     private Tilemap tilemap; | ||||
| 
 | ||||
|     private void onValidate() { | ||||
|         mineCount = MathUtils.clamp(mineCount, 0, width*height); | ||||
|     } | ||||
| 
 | ||||
|     public void start() { | ||||
|         board = new Board(); | ||||
| 
 | ||||
|         int[] _boardSizes = boardSize.boardSizes; | ||||
|         int[] _mineCounter = boardSize.mineCounter; | ||||
| 
 | ||||
|  | @ -31,6 +34,9 @@ public class Game { | |||
| 
 | ||||
|     private void newGame() { | ||||
|         state = new Cell[width][height]; | ||||
|         Tile[][] tile = new Tile[width][height]; | ||||
| 
 | ||||
|         tilemap = new Tilemap(tile); | ||||
| 
 | ||||
|         gameover = false; | ||||
| 
 | ||||
|  | @ -38,7 +44,7 @@ public class Game { | |||
|         generateMines(); | ||||
|         generateNumbers(); | ||||
| 
 | ||||
|         board.draw(state); | ||||
|         board.draw(state, tilemap); | ||||
|     } | ||||
| 
 | ||||
|     private void generateCells() { | ||||
|  | @ -129,7 +135,7 @@ public class Game { | |||
| 
 | ||||
|         cell.flagged = !cell.flagged; | ||||
|         state[x][y] = cell; | ||||
|         board.draw(state); | ||||
|         board.draw(state, tilemap); | ||||
|         return isFlaggedAlready; | ||||
|     } | ||||
| 
 | ||||
|  | @ -159,7 +165,7 @@ public class Game { | |||
|                 checkWinCondition(); | ||||
|                 break; | ||||
|         } | ||||
|         board.draw(state); | ||||
|         board.draw(state, tilemap); | ||||
|         return hitMine; | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue