first commit

This commit is contained in:
LunarAkai 2024-03-19 23:25:18 +01:00
commit 63354c563f
19 changed files with 885 additions and 0 deletions

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
# Project exclude paths
/target/
/.vscode

12
Minecleaner.iml Normal file
View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module version="4">
<component name="FacetManager">
<facet type="minecraft" name="Minecraft">
<configuration>
<autoDetectTypes>
<platformType>SPIGOT</platformType>
</autoDetectTypes>
</configuration>
</facet>
</component>
</module>

84
pom.xml Normal file
View file

@ -0,0 +1,84 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>de.lunarakai</groupId>
<artifactId>Minecleaner</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>Minecleaner</name>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>16</source>
<target>16</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
</build>
<repositories>
<repository>
<id>spigotmc-repo</id>
<url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>
</repository>
<repository>
<id>sonatype</id>
<url>https://oss.sonatype.org/content/groups/public/</url>
</repository>
<repository>
<id>brokkonaut</id>
<url>https://www.iani.de/nexus/content/groups/public</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>1.20.4-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>de.iani.cubeside</groupId>
<artifactId>CubesideUtilsBukkit</artifactId>
<version>1.16-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

View file

@ -0,0 +1,94 @@
package de.lunarakai.minecleaner;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.UUID;
import java.util.logging.Level;
import org.bukkit.Location;
import org.bukkit.block.Block;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.InvalidConfigurationException;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player;
public class ArenaList {
private static final String ARENAS_FILENAME = "mcl_arenas.yml";
private final MinecleanerPlugin plugin;
private File arenaFile;
private final HashMap<String, MinecleanerArena> arenas;
private final HashMap<UUID, MinecleanerArena> playersInArena;
private final HashMap<Location, MinecleanerArena> arenaBlocks;
public ArenaList(MinecleanerPlugin plugin) {
this.plugin = plugin;
this.arenas = new HashMap<>();
this.arenaFile = new File(plugin.getDataFolder(), ARENAS_FILENAME);
this.arenaBlocks = new HashMap<>();
this.playersInArena = new HashMap<>();
}
public void load() {
arenas.clear();
if(!this.arenaFile.isFile()) {
return;
}
YamlConfiguration conf = new YamlConfiguration();
try {
conf.load(this.arenaFile);
} catch(IOException | InvalidConfigurationException e) {
plugin.getLogger().log(Level.SEVERE, "Could not load arenas file", e);
}
ConfigurationSection arenasSection = conf.getConfigurationSection("arenas");
if(arenasSection != null) {
for(String arenaName : arenasSection.getKeys(false)) {
ConfigurationSection arenaSection = arenasSection.getConfigurationSection(arenaName);
if(arenaSection != null) {
MinecleanerArena arena = new MinecleanerArena(plugin, arenaSection);
this.arenas.put(arena.getName(), arena);
}
}
}
}
public void save() {
YamlConfiguration conf = new YamlConfiguration();
ConfigurationSection arenaSection = conf.createSection("arenas");
int i = 0;
for(MinecleanerArena arena : this.arenas.values()) {
arena.save(arenaSection.createSection(Integer.toString(i++)));
}
this.arenaFile.getParentFile().mkdirs();
try {
conf.save(this.arenaFile);
} catch(IOException e) {
plugin.getLogger().log(Level.SEVERE, "Could not save arenas file", e);
}
}
/*private void setArenaBlocks(MinecleanerArena arena) {
for(Location location : arena.getBlocks()) {
arenaBlocks
}
}*/
public void setArenaForPlayer(Player player, MinecleanerArena arena) {
if(arena != null) {
playersInArena.put(player.getUniqueId(), arena);
} else {
playersInArena.remove(player.getUniqueId());
}
}
public MinecleanerArena getPlayerArena(Player player) {
return playersInArena.get(player.getUniqueId());
}
public MinecleanerArena getArenaAtBlock(Block block) {
return arenaBlocks.get(block.getLocation());
}
}

View file

@ -0,0 +1,8 @@
package de.lunarakai.minecleaner;
public enum ArenaStatus {
INACTIVE,
CONFIRM_PLAYING,
PLAYING,
COMPLETED
}

View file

@ -0,0 +1,110 @@
package de.lunarakai.minecleaner;
import java.util.UUID;
import org.bukkit.Location;
import org.bukkit.block.BlockFace;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.entity.Player;
import com.google.common.base.Preconditions;
import de.lunarakai.minecleaner.game.Game;
public class MinecleanerArena {
private final MinecleanerPlugin plugin;
private final String name;
private final Location location;
private final Location centerLocation;
private final BlockFace orientation;
private ArenaStatus arenaStatus = ArenaStatus.INACTIVE;
private UUID[] displayEntities;
private Player currentPlayer;
private long currentGameStartTime;
private Game currentMinecleanerGame;
public MinecleanerArena(MinecleanerPlugin plugin, ConfigurationSection arenaSection) {
this.plugin = plugin;
this.name = Preconditions.checkNotNull(arenaSection.getString("name"));
this.location = Preconditions.checkNotNull(arenaSection.getLocation("location"));
BlockFace orientation = BlockFace.NORTH;
try {
orientation = BlockFace.valueOf(arenaSection.getString("orientation"));
} catch(IllegalArgumentException ignored) {
}
this.orientation = orientation;
this.centerLocation = location.clone().add(0.5, 0, 0.5);
displayEntities = new UUID[Game.width * Game.height];
}
public MinecleanerArena(MinecleanerPlugin plugin, String name, Location location, BlockFace orientation) {
this.plugin = plugin;
this.name = Preconditions.checkNotNull(name, "name");
this.location = Preconditions.checkNotNull(location, "location");
Preconditions.checkArgument(Math.abs(orientation.getModX()) + Math.abs(orientation.getModZ()) == 1, "no cardinal direction");
this.orientation = orientation;
int d0x = orientation.getModX();
int d0z = orientation.getModZ();
int d1x = -d0z;
int d1z = d0x;
this.centerLocation = location.clone().add(0.5, 0, 0.5);
}
public void save(ConfigurationSection arenaSection) {
arenaSection.set("name", this.name);
arenaSection.set("location", this.location);
arenaSection.set("orientation", this.orientation.name());
}
public void startNewGame() {
currentMinecleanerGame.start();
arenaStatus = ArenaStatus.PLAYING;
}
public void removePlayer() {
this.arenaStatus = ArenaStatus.INACTIVE;
this.currentPlayer = null;
}
public String getName() {
return name;
}
public boolean hasPlayer() {
return currentPlayer != null;
}
public Player getCurrentPlayer() {
return currentPlayer;
}
public Location getLocation() {
return location;
}
public ArenaStatus getArenaStatus() {
return arenaStatus;
}
public void flagCell(int x, int y) {
if(currentMinecleanerGame != null) {
int id = x + y * 8;
boolean unflaggedCell = currentMinecleanerGame.flag(x, y);
if(!unflaggedCell) {
// todo set flag head on block display
} else {
// todo set normal head on block display
}
}
}
public void revealCell(int x, int y) {
if(currentMinecleanerGame != null) {
int id = x + y * 8;
// todo check if cell is flagged already
currentMinecleanerGame.reveal(x, y);
// todo update block of blockdisplay
}
}
}

View file

@ -0,0 +1,53 @@
package de.lunarakai.minecleaner;
import org.bukkit.block.Block;
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.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.inventory.EquipmentSlot;
import net.md_5.bungee.api.ChatColor;
public class MinecleanerListener implements Listener {
private final MinecleanerPlugin plugin;
public MinecleanerListener(MinecleanerPlugin plugin) {
this.plugin = plugin;
}
@EventHandler(priority = EventPriority.LOWEST)
public void onPlayerInteract(PlayerInteractEvent e) {
if((e.getAction() == Action.LEFT_CLICK_BLOCK || e.getAction() == Action.RIGHT_CLICK_BLOCK)) {
Block block = e.getClickedBlock();
MinecleanerArena arena = plugin.getArenaList().getPlayerArena(e.getPlayer());
if(arena != null) {
// TODO
} else {
arena = plugin.getArenaList().getArenaAtBlock(block);
if(arena != null) {
e.setCancelled(true);
if(e.getHand() == EquipmentSlot.HAND) {
if(arena.getArenaStatus() == ArenaStatus.INACTIVE) {
plugin.getManager().joinArena(e.getPlayer(), arena);
} else {
e.getPlayer().sendMessage(ChatColor.YELLOW + "Hier spielt schon jemand anderes");
}
}
}
}
}
}
@EventHandler
public void onPlayerQuit(PlayerQuitEvent e) {
MinecleanerArena arena = plugin.getArenaList().getPlayerArena(e.getPlayer());
if(arena != null) {
plugin.getManager().leaveArena(e.getPlayer(), false);
}
}
}

View file

@ -0,0 +1,33 @@
package de.lunarakai.minecleaner;
import org.bukkit.entity.Player;
import com.google.common.base.Preconditions;
import net.md_5.bungee.api.ChatColor;
public class MinecleanerManager {
private final MinecleanerPlugin plugin;
public MinecleanerManager(MinecleanerPlugin plugin) {
this.plugin = plugin;
}
public void joinArena(Player player, MinecleanerArena arena) {
if (!player.hasPermission(MinecleanerPlugin.PERMISSION_PLAY)) {
return;
}
Preconditions.checkArgument(plugin.getArenaList().getPlayerArena(player) == null, "player is in an arena");
Preconditions.checkArgument(arena.getArenaStatus() == ArenaStatus.INACTIVE, "arena is in use");
arena.addJoiningPlayer(player);
plugin.getArenaList().setArenaForPlayer(player, arena);
}
public void leaveArena(Player player, boolean message) {
MinecleanerArena arena = plugin.getArenaList().getPlayerArena(player);
Preconditions.checkArgument(arena != null, "player is in no arena");
arena.removePlayer();
plugin.getArenaList().setArenaForPlayer(player, null);
if(message) {
player.sendMessage(ChatColor.YELLOW + "Das Minecleanerspiel wurde abgebrochen.");
}
}
}

View file

@ -0,0 +1,51 @@
package de.lunarakai.minecleaner;
import org.bukkit.plugin.java.JavaPlugin;
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";
private MinecleanerManager minecleanerManager;
private ArenaList arenaList;
@Override
public void onEnable() {
// Plugin startup logic
arenaList = new ArenaList(this);
arenaList.load();
//CommandRouter minecleanerCommand = new CommandRouter(getCommand("minecleaner"));
//minecleanerCommand.addCommandMapping(new CreateCommand(this), "create");
//minecleanerCommand.addCommandMapping(new DeleteCommand(this), "delete");
//minecleanerCommand.addCommandMapping(new ListCommand(this), "list");
//Test Commands
//minecleanerCommand.addCommandMapping(new TestCommand(this), "testSingle");
//minecleanerCommand.addCommandMapping(new TestArrayCommand(this), "testArray");
}
@Override
public void onDisable() {
// Plugin shutdown logic
}
public ArenaList getArenaList() {
return arenaList;
}
public MinecleanerManager getManager() {
return minecleanerManager;
}
}

View file

@ -0,0 +1,54 @@
package de.lunarakai.minecleaner.game;
public class Board {
public Tilemap tilemap;
public void draw(Cell[][] state) {
tilemap.clearAllTiles();
int width = state[0].length;
int height = state[1].length;
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
Cell cell = state[x][y];
tilemap.setTile(cell.position, getTile(cell));
}
}
}
private Tile.TileType getTile(Cell cell) {
if(cell.revealed) {
return getRevealedTile(cell);
} else if (cell.flagged) {
return Tile.TileType.TileFlag;
} else {
return Tile.TileType.TileUnknown;
}
}
private Tile.TileType getRevealedTile(Cell cell) {
switch (cell.type) {
case Empty: return Tile.TileType.TileEmpty;
case Mine: return cell.exploded ? Tile.TileType.TileExploded : Tile.TileType.TileMine;
case Number: return getNumberTile(cell);
default: return null;
}
}
private Tile.TileType getNumberTile(Cell cell) {
switch (cell.number) {
case 1: return Tile.TileType.TileNum1;
case 2: return Tile.TileType.TileNum2;
case 3: return Tile.TileType.TileNum3;
case 4: return Tile.TileType.TileNum4;
case 5: return Tile.TileType.TileNum5;
case 6: return Tile.TileType.TileNum6;
case 7: return Tile.TileType.TileNum7;
case 8: return Tile.TileType.TileNum8;
default: return null;
}
}
}

View file

@ -0,0 +1,17 @@
package de.lunarakai.minecleaner.game;
public class BoardSize {
public int[] boardSizes = {
8,
16,
32, // nicht größer als 24
64
};
public int[] mineCounter = {
10,
40,
128,
512
};
}

View file

@ -0,0 +1,29 @@
package de.lunarakai.minecleaner.game;
import org.joml.Vector3i;
public class Cell {
public enum CellType {
Invalid,
Empty,
Mine,
Number,
}
public Vector3i position;
public CellType type;
public int number;
public boolean revealed;
public boolean flagged;
public boolean exploded;
public void setType(CellType type) {
this.type = type;
}
public CellType getType() {
return type;
}
}

View file

@ -0,0 +1,235 @@
package de.lunarakai.minecleaner.game;
import org.joml.Vector3i;
import de.lunarakai.minecleaner.utils.MathUtils;
public class Game {
public static int width;
public static int height;
private int mineCount = 10;
private Cell[][] state;
private boolean gameover;
private Board board;
private BoardSize boardSize;
private void onValidate() {
mineCount = MathUtils.clamp(mineCount, 0, width*height);
}
public void start() {
int[] _boardSizes = boardSize.boardSizes;
int[] _mineCounter = boardSize.mineCounter;
int _boardSizeIndex = 0;
width = _boardSizes[_boardSizeIndex];
height = _boardSizes[_boardSizeIndex];
mineCount = _mineCounter[_boardSizeIndex];
newGame();
}
private void newGame() {
state = new Cell[width][height];
gameover = false;
generateCells();
generateMines();
generateNumbers();
board.draw(state);
}
private void generateCells() {
for (int x = 0; x < width; x ++) {
for (int y = 0; y < height; y++) {
Cell cell = new Cell();
cell.position = new Vector3i(x, 0, y);
cell.setType(Cell.CellType.Empty);
state[x][y] = cell;
}
}
}
private void generateMines() {
for (int i = 0; i < mineCount; i++) {
int x = (int) (Math.random() * width);
int y = (int) (Math.random() * height);
while (state[x][y].type == Cell.CellType.Mine) {
x++;
if(x >= width) {
x = 0;
y++;
if(y >= height) {
y=0;
}
}
}
state[x][y].setType(Cell.CellType.Mine);
}
}
private void generateNumbers() {
for(int x = 0; x < width; x++) {
for(int y = 0; y < height; y++) {
Cell cell = state[x][y];
if(cell.getType() == Cell.CellType.Mine) {
continue;
}
cell.number = countMines(x, y);
if(cell.number > 0) {
cell.setType(Cell.CellType.Number);
}
state[x][y] = cell;
}
}
}
private int countMines(int cellX, int cellY) {
int count = 0;
for (int adjacentX = -1; adjacentX <= 1; adjacentX++){
for (int adjacentY = -1; adjacentY <= 1; adjacentY++) {
if(adjacentX == 0 && adjacentY == 0) {
continue;
}
int x = cellX + adjacentX;
int y = cellY + adjacentY;
if (getCell(x,y).getType() == Cell.CellType.Mine) {
count++;
}
}
}
return count;
}
public boolean flag(int x, int y) {
// TODO: Vector3 worldPosition = Camera.main.ScreenToWorldPoint(Input.mousePosition); <- Unity
//Vector3i cellPosition = null; // TODO board.tilemap.WorldToCell(worldPosition); <- Unity
Cell cell = getCell(x, y);
if (cell.getType() == Cell.CellType.Invalid || cell.revealed) {
return false;
}
boolean isFlaggedAlready = false;
if(cell.flagged) {
isFlaggedAlready = true;
}
cell.flagged = !cell.flagged;
state[x][y] = cell;
board.draw(state);
return isFlaggedAlready;
}
public boolean reveal(int x, int y) {
// TODO: Vector3 worldPosition = Camera.main.ScreenToWorldPoint(Input.mousePosition); <- Unity
//Vector3i cellPosition = null; // TODO board.tilemap.WorldToCell(worldPosition); <- Unity
Cell cell = getCell(x, y);
if(cell.getType() == Cell.CellType.Invalid || cell.revealed || cell.flagged) {
return false;
}
boolean hitMine = false;
switch (cell.getType()) {
case Mine:
explode(cell);
hitMine = true;
break;
case Empty:
flood(cell);
checkWinCondition();
break;
default:
cell.revealed = true;
state[x][y] = cell;
checkWinCondition();
break;
}
board.draw(state);
return hitMine;
}
public void flood(Cell cell) {
if(cell.revealed) return;
if(cell.getType() == Cell.CellType.Mine || cell.getType() == Cell.CellType.Invalid) return;
cell.revealed = true;
state[cell.position.x][cell.position.z] = cell;
if(cell.getType() == Cell.CellType.Empty) {
flood(getCell(cell.position.x -1, cell.position.z));
flood(getCell(cell.position.x +1, cell.position.z));
flood(getCell(cell.position.x, cell.position.z -1));
flood(getCell(cell.position.x, cell.position.z +1));
}
// TODO return cellpos of flooded cell to update the block displays
}
private void explode(Cell cell) {
gameover = true;
cell.revealed = true;
cell.exploded = true;
state[cell.position.x][cell.position.z] = cell;
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
cell = state[x][y];
if(cell.getType() == Cell.CellType.Mine) {
cell.revealed = true;
state[x][y] = cell;
}
}
}
}
private void checkWinCondition() {
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
Cell cell = state[x][y];
if(cell.getType() != Cell.CellType.Mine && !cell.revealed) {
return;
}
}
}
gameover = true;
for(int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
Cell cell = state[x][y];
if(cell.getType() == Cell.CellType.Mine) {
cell.flagged = true;
state[x][y] = cell;
}
}
}
}
private Cell getCell(int x, int y) {
if(isValid(x,y)) {
return state[x][y];
} else {
return new Cell();
}
}
private boolean isValid(int x, int y) {
return x >= 0 && x < width && y >= 0 && y < height;
}
}

View file

@ -0,0 +1,34 @@
package de.lunarakai.minecleaner.game;
public class Tile {
public enum TileType {
TileUnknown,
TileNum1,
TileNum2,
TileNum3,
TileNum4,
TileNum5,
TileNum6,
TileNum7,
TileNum8,
TileEmpty,
TileMine,
TileExploded,
TileFlag,
}
public Tile.TileType tileType;
public Tile() {
setTileType(TileType.TileUnknown);
}
public void setTileType(Tile.TileType tileType) {
this.tileType = tileType;
}
public Tile.TileType getTileType() {
return tileType;
}
}

View file

@ -0,0 +1,39 @@
package de.lunarakai.minecleaner.game;
import org.joml.Vector3i;
public class Tilemap {
private Tile[][] tiles;
public Tilemap(Tile[][] tiles) {
this.tiles = tiles;
}
public void clearAllTiles() {
for(int i = 0; i < tiles.length; i++) {
for(int j = 0; j < tiles[0].length; j++) {
tiles[i][j] = new Tile();
}
}
}
public Tile getTile(int x, int y) {
if (x >= 0 && x < tiles.length && y >= 0 && y < tiles[0].length) {
return tiles[x][y];
} else {
throw new IllegalArgumentException("Invalid coordinates");
}
}
// Set method
public void setTile(Vector3i pos, Tile.TileType tileType) {
int x = pos.x();
int y = pos.z();
if (x >= 0 && x < tiles.length && y >= 0 && y < tiles[0].length) {
tiles[x][y].setTileType(tileType);
} else {
throw new IllegalArgumentException("Invalid coordinates");
}
}
}

View file

@ -0,0 +1,14 @@
package de.lunarakai.minecleaner.utils;
import org.bukkit.entity.Display;
import de.lunarakai.minecleaner.game.Tile;
public class BoardUtils {
public static Display matchTileToBlockDisplay(Tile tile) {
switch (tile.getTileType()) {
case TileEmpty:
}
return null;
}
}

View file

@ -0,0 +1,7 @@
package de.lunarakai.minecleaner.utils;
public class MathUtils {
public static int clamp(int value, int min, int max) {
return Math.max(min, Math.min(max, value));
}
}

View file

View file

@ -0,0 +1,8 @@
name: Minecleaner
version: '${project.version}'
main: de.lunarakai.minecleaner.MinecleanerPlugin
api-version: '1.20'
commands:
minecleaner:
description: main command
aliases: "mcl"