This commit is contained in:
ZockerKatze
2025-04-26 17:12:44 +02:00
commit d3c7e8f298
34 changed files with 974 additions and 0 deletions

5
.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,5 @@
# Default ignored files
/shelf/
/workspace.xml
# Environment-dependent path to Maven home directory
/mavenHomeManager.xml

13
.idea/compiler.xml generated Normal file
View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<annotationProcessing>
<profile name="Maven default annotation processors profile" enabled="true">
<sourceOutputDir name="target/generated-sources/annotations" />
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
<outputRelativeToContentRoot value="true" />
<module name="CctvPlugin" />
</profile>
</annotationProcessing>
</component>
</project>

7
.idea/encodings.xml generated Normal file
View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding">
<file url="file://$PROJECT_DIR$/src/main/kotlin" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
</component>
</project>

30
.idea/jarRepositories.xml generated Normal file
View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RemoteRepositoriesConfiguration">
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Central Repository" />
<option name="url" value="https://repo.maven.apache.org/maven2" />
</remote-repository>
<remote-repository>
<option name="id" value="sonatype" />
<option name="name" value="sonatype" />
<option name="url" value="https://oss.sonatype.org/content/groups/public/" />
</remote-repository>
<remote-repository>
<option name="id" value="papermc-repo" />
<option name="name" value="papermc-repo" />
<option name="url" value="https://repo.papermc.io/repository/maven-public/" />
</remote-repository>
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Maven Central repository" />
<option name="url" value="https://repo1.maven.org/maven2" />
</remote-repository>
<remote-repository>
<option name="id" value="jboss.community" />
<option name="name" value="JBoss Community repository" />
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
</remote-repository>
</component>
</project>

6
.idea/kotlinc.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="KotlinJpsPluginSettings">
<option name="version" value="2.2.0-Beta1" />
</component>
</project>

12
.idea/material_theme_project_new.xml generated Normal file
View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="MaterialThemeProjectNewConfig">
<option name="metadata">
<MTProjectMetadataState>
<option name="migrated" value="true" />
<option name="pristineConfig" value="false" />
<option name="userId" value="-2e14201d:196619b083a:-7ffe" />
</MTProjectMetadataState>
</option>
</component>
</project>

14
.idea/misc.xml generated Normal file
View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="MavenProjectsManager">
<option name="originalFiles">
<list>
<option value="$PROJECT_DIR$/pom.xml" />
</list>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_24" default="true" project-jdk-name="24" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

8
.idea/modules.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/CctvPlugin.iml" filepath="$PROJECT_DIR$/CctvPlugin.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

13
CctvPlugin.iml Normal file
View File

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

View File

@@ -0,0 +1,72 @@
<?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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.CctvPlugin</groupId>
<artifactId>CctvPlugin</artifactId>
<name>CctvPlugin</name>
<version>1.0-SNAPSHOT</version>
<build>
<sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory>
<defaultGoal>clean package</defaultGoal>
<resources>
<resource>
<filtering>true</filtering>
<directory>src/main/resources</directory>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>${kotlin.version}</version>
<executions>
<execution>
<id>compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
<configuration>
<jvmTarget>${java.version}</jvmTarget>
</configuration>
</plugin>
<plugin>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.3</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>papermc-repo</id>
<url>https://repo.papermc.io/repository/maven-public/</url>
</repository>
<repository>
<id>sonatype</id>
<url>https://oss.sonatype.org/content/groups/public/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>io.papermc.paper</groupId>
<artifactId>paper-api</artifactId>
<version>1.21.5-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
</dependencies>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<kotlin.version>2.2.0-Beta1</kotlin.version>
<java.version>21</java.version>
</properties>
</project>

87
pom.xml Normal file
View File

@@ -0,0 +1,87 @@
<?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>org.CctvPlugin</groupId>
<artifactId>CctvPlugin</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>CctvPlugin</name>
<properties>
<java.version>21</java.version>
<kotlin.version>2.2.0-Beta1</kotlin.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<build>
<defaultGoal>clean package</defaultGoal>
<sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory>
<plugins>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>${kotlin.version}</version>
<executions>
<execution>
<id>compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
<configuration>
<jvmTarget>${java.version}</jvmTarget>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.3</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
</build>
<repositories>
<repository>
<id>papermc-repo</id>
<url>https://repo.papermc.io/repository/maven-public/</url>
</repository>
<repository>
<id>sonatype</id>
<url>https://oss.sonatype.org/content/groups/public/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>io.papermc.paper</groupId>
<artifactId>paper-api</artifactId>
<version>1.21.5-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib-jdk8</artifactId>
<version>${kotlin.version}</version>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,115 @@
// CctvCommand.kt
package com.example.cctv
import org.bukkit.command.Command
import org.bukkit.command.CommandExecutor
import org.bukkit.command.CommandSender
import org.bukkit.command.TabCompleter
import org.bukkit.entity.Player
import com.example.cctv.menus.MainMenu
import com.example.cctv.menus.ViewCamerasMenu
import com.example.cctv.menus.ManageCamerasMenu
import com.example.cctv.menus.PlaceCameraMenu
class CctvCommand(private val plugin: CctvPlugin) : CommandExecutor, TabCompleter {
override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array<out String>): Boolean {
if (sender !is Player) {
sender.sendMessage("§cThis command can only be used by players.")
return true
}
if (!sender.hasPermission("cctv.use")) {
sender.sendMessage("§cYou don't have permission to use this command.")
return true
}
if (args.isEmpty()) {
MainMenu(plugin, sender).open()
return true
}
when (args[0].lowercase()) {
"place", "create", "add" -> {
if (!sender.hasPermission("cctv.place")) {
sender.sendMessage("§cYou don't have permission to place cameras.")
return true
}
if (args.size < 2) {
sender.sendMessage("§cUsage: /cctv place <name>")
return true
}
val name = args.drop(1).joinToString(" ")
if (plugin.createCamera(sender, name)) {
sender.sendMessage("§aCamera '$name' placed successfully!")
} else {
sender.sendMessage("§cFailed to place camera.")
}
}
"list", "view" -> {
ViewCamerasMenu(plugin, sender).open()
}
"remove", "delete" -> {
if (args.size < 2) {
ManageCamerasMenu(plugin, sender).open()
return true
}
val id = args[1]
if (plugin.removeCamera(id, sender)) {
sender.sendMessage("§aCamera removed successfully!")
} else {
sender.sendMessage("§cFailed to remove camera. Make sure you own it or have admin permissions.")
}
}
"help" -> {
sender.sendMessage("§6=== CCTV Plugin Help ===")
sender.sendMessage("§e/cctv §7- Open main menu")
sender.sendMessage("§e/cctv place <name> §7- Place a camera at your location")
sender.sendMessage("§e/cctv list §7- View available cameras")
sender.sendMessage("§e/cctv remove [id] §7- Remove a camera")
sender.sendMessage("§e/cctv help §7- Show this help message")
}
else -> {
sender.sendMessage("§cUnknown subcommand. Use '/cctv help' for help.")
}
}
return true
}
override fun onTabComplete(sender: CommandSender, command: Command, alias: String, args: Array<out String>): List<String> {
if (sender !is Player) return emptyList()
if (args.isEmpty()) {
return emptyList()
}
if (args.size == 1) {
val subCommands = mutableListOf<String>()
if (sender.hasPermission("cctv.use")) {
subCommands.addAll(listOf("help", "list", "view"))
}
if (sender.hasPermission("cctv.place")) {
subCommands.addAll(listOf("place", "create", "add"))
}
if (sender.hasPermission("cctv.remove") || sender.hasPermission("cctv.admin")) {
subCommands.addAll(listOf("remove", "delete"))
}
return subCommands.filter { it.startsWith(args[0].lowercase()) }
}
if (args.size == 2 && (args[0].equals("remove", ignoreCase = true) || args[0].equals("delete", ignoreCase = true))) {
val cameras = plugin.getCameras()
return cameras.keys.filter { it.startsWith(args[1]) }
}
return emptyList()
}
}

View File

@@ -0,0 +1,235 @@
// CctvPlugin.kt
package com.example.cctv
import org.bukkit.Bukkit
import org.bukkit.Location
import org.bukkit.Material
import org.bukkit.configuration.file.YamlConfiguration
import org.bukkit.entity.Player
import org.bukkit.event.EventHandler
import org.bukkit.event.Listener
import org.bukkit.event.inventory.InventoryClickEvent
import org.bukkit.event.player.PlayerJoinEvent
import org.bukkit.event.player.PlayerQuitEvent
import org.bukkit.inventory.ItemStack
import org.bukkit.plugin.java.JavaPlugin
import org.bukkit.scheduler.BukkitTask
import java.io.File
import java.util.*
import com.example.cctv.menus.CctvMenu
import com.example.cctv.menus.MainMenu
import com.example.cctv.menus.ViewCamerasMenu
import com.example.cctv.menus.ManageCamerasMenu
import com.example.cctv.menus.PlaceCameraMenu
class CctvPlugin : JavaPlugin(), Listener {
private val cameras = mutableMapOf<String, Camera>()
private val playerViewing = mutableMapOf<UUID, Camera>()
private val viewingTasks = mutableMapOf<UUID, BukkitTask>()
private lateinit var cameraFile: File
private lateinit var cameraConfig: YamlConfiguration
override fun onEnable() {
// Create config if it doesn't exist
saveDefaultConfig()
// Register command
getCommand("cctv")?.setExecutor(CctvCommand(this))
// Register events
server.pluginManager.registerEvents(this, this)
// Initialize camera data
cameraFile = File(dataFolder, "cameras.yml")
if (!cameraFile.exists()) {
saveResource("cameras.yml", false)
}
cameraConfig = YamlConfiguration.loadConfiguration(cameraFile)
// Load cameras from configuration
loadCameras()
logger.info("CCTV Plugin enabled!")
}
override fun onDisable() {
// Stop all viewing sessions
playerViewing.keys.forEach { stopViewing(it) }
// Save cameras to configuration
saveCameras()
logger.info("CCTV Plugin disabled!")
}
@EventHandler
fun onPlayerQuit(event: PlayerQuitEvent) {
// Stop viewing when player leaves
stopViewing(event.player.uniqueId)
}
private fun loadCameras() {
if (cameraConfig.contains("cameras")) {
val camerasSection = cameraConfig.getConfigurationSection("cameras")
camerasSection?.getKeys(false)?.forEach { id ->
val section = camerasSection.getConfigurationSection(id)
if (section != null) {
val world = Bukkit.getWorld(section.getString("world") ?: "world")
if (world != null) {
val x = section.getDouble("x")
val y = section.getDouble("y")
val z = section.getDouble("z")
val yaw = section.getDouble("yaw").toFloat()
val pitch = section.getDouble("pitch").toFloat()
val name = section.getString("name") ?: "Camera $id"
val owner = UUID.fromString(section.getString("owner") ?: "00000000-0000-0000-0000-000000000000")
val location = Location(world, x, y, z, yaw, pitch)
cameras[id] = Camera(id, name, location, owner)
}
}
}
}
}
private fun saveCameras() {
// Clear existing cameras
cameraConfig.set("cameras", null)
// Save each camera
cameras.forEach { (id, camera) ->
val path = "cameras.$id"
cameraConfig.set("$path.name", camera.name)
cameraConfig.set("$path.world", camera.location.world?.name)
cameraConfig.set("$path.x", camera.location.x)
cameraConfig.set("$path.y", camera.location.y)
cameraConfig.set("$path.z", camera.location.z)
cameraConfig.set("$path.yaw", camera.location.yaw)
cameraConfig.set("$path.pitch", camera.location.pitch)
cameraConfig.set("$path.owner", camera.owner.toString())
}
// Save to file
cameraConfig.save(cameraFile)
}
fun getCameras(): Map<String, Camera> {
return cameras.toMap()
}
fun createCamera(player: Player, name: String): Boolean {
val id = UUID.randomUUID().toString().substring(0, 8)
val camera = Camera(id, name, player.location.clone(), player.uniqueId)
cameras[id] = camera
saveCameras()
return true
}
fun removeCamera(id: String, player: Player): Boolean {
val camera = cameras[id] ?: return false
// Check if the player is the owner or has permission
if (camera.owner != player.uniqueId && !player.hasPermission("cctv.admin")) {
return false
}
// Stop anyone viewing this camera
playerViewing.forEach { (uuid, cam) ->
if (cam.id == id) {
stopViewing(uuid)
}
}
cameras.remove(id)
saveCameras()
return true
}
fun startViewing(player: Player, cameraId: String): Boolean {
// Stop current viewing if any
stopViewing(player.uniqueId)
val camera = cameras[cameraId] ?: return false
// Store original location
val originalLocation = player.location.clone()
camera.viewerLocations[player.uniqueId] = originalLocation
// Teleport to camera view (spectator mode)
val gameMode = player.gameMode
camera.viewerGameModes[player.uniqueId] = gameMode
player.gameMode = org.bukkit.GameMode.SPECTATOR
player.teleport(camera.location)
// Set up viewing task
playerViewing[player.uniqueId] = camera
// Create a task to check if player tries to exit spectator
val task = Bukkit.getScheduler().runTaskTimer(this, Runnable {
val currentPlayer = Bukkit.getPlayer(player.uniqueId)
if (currentPlayer == null || !currentPlayer.isOnline) {
stopViewing(player.uniqueId)
return@Runnable
}
// Keep the player at the camera location
if (currentPlayer.location.distance(camera.location) > 0.5) {
currentPlayer.teleport(camera.location)
}
}, 5L, 5L)
viewingTasks[player.uniqueId] = task
return true
}
fun stopViewing(uuid: UUID) {
val camera = playerViewing[uuid] ?: return
val player = Bukkit.getPlayer(uuid) ?: return
// Restore original location
val originalLocation = camera.viewerLocations[uuid]
if (originalLocation != null) {
player.teleport(originalLocation)
camera.viewerLocations.remove(uuid)
}
// Restore game mode
val originalGameMode = camera.viewerGameModes[uuid]
if (originalGameMode != null) {
player.gameMode = originalGameMode
camera.viewerGameModes.remove(uuid)
}
// Cancel task
viewingTasks[uuid]?.cancel()
viewingTasks.remove(uuid)
// Remove from viewing map
playerViewing.remove(uuid)
}
@EventHandler
fun onInventoryClick(event: InventoryClickEvent) {
val player = event.whoClicked as? Player ?: return
val inventory = event.inventory
if (inventory.holder is CctvMenu) {
event.isCancelled = true
val item = event.currentItem ?: return
val menu = inventory.holder as CctvMenu
menu.handleClick(player, item, event.slot)
}
}
data class Camera(
val id: String,
val name: String,
val location: Location,
val owner: UUID,
val viewerLocations: MutableMap<UUID, Location> = mutableMapOf(),
val viewerGameModes: MutableMap<UUID, org.bukkit.GameMode> = mutableMapOf()
)
}

View File

@@ -0,0 +1,288 @@
// Menus.kt
package com.example.cctv.menus
import org.bukkit.Bukkit
import org.bukkit.Material
import org.bukkit.entity.Player
import org.bukkit.inventory.Inventory
import org.bukkit.inventory.InventoryHolder
import org.bukkit.inventory.ItemStack
import org.bukkit.inventory.meta.ItemMeta
import java.util.*
import com.example.cctv.CctvPlugin
interface CctvMenu : InventoryHolder {
fun open()
fun handleClick(player: Player, item: ItemStack, slot: Int)
}
class MainMenu(private val plugin: CctvPlugin, private val player: Player) : CctvMenu {
private val inventory: Inventory = Bukkit.createInventory(this, 27, "CCTV System")
override fun getInventory(): Inventory = inventory
override fun open() {
// Create menu items
val placeCameraItem = createItem(Material.ENDER_EYE, "§aPlace New Camera", listOf("§7Click to place a camera", "§7at your current location"))
val viewCamerasItem = createItem(Material.SPYGLASS, "§bView Cameras", listOf("§7Click to view available cameras"))
val manageCamerasItem = createItem(Material.REDSTONE_BLOCK, "§cManage Your Cameras", listOf("§7Click to manage your cameras"))
// Place items in inventory
inventory.setItem(11, placeCameraItem)
inventory.setItem(13, viewCamerasItem)
inventory.setItem(15, manageCamerasItem)
// Open inventory for player
player.openInventory(inventory)
}
override fun handleClick(player: Player, item: ItemStack, slot: Int) {
when (slot) {
11 -> {
if (player.hasPermission("cctv.place")) {
player.closeInventory()
PlaceCameraMenu(plugin, player).open()
} else {
player.sendMessage("§cYou don't have permission to place cameras.")
}
}
13 -> {
player.closeInventory()
ViewCamerasMenu(plugin, player).open()
}
15 -> {
player.closeInventory()
ManageCamerasMenu(plugin, player).open()
}
}
}
private fun createItem(material: Material, name: String, lore: List<String>): ItemStack {
val item = ItemStack(material)
val meta = item.itemMeta
meta?.setDisplayName(name)
meta?.lore = lore
item.itemMeta = meta
return item
}
}
class PlaceCameraMenu(private val plugin: CctvPlugin, private val player: Player) : CctvMenu {
private val inventory: Inventory = Bukkit.createInventory(this, 36, "Place Camera")
override fun getInventory(): Inventory = inventory
override fun open() {
// Create menu items
for (i in 0..7) {
val cameraItem = createItem(Material.ENDER_EYE, "§aCamera ${i + 1}", listOf("§7Click to place", "§7Camera ${i + 1} at your location"))
inventory.setItem(10 + i, cameraItem)
}
val closeItem = createItem(Material.BARRIER, "§cCancel", listOf("§7Click to return to main menu"))
inventory.setItem(31, closeItem)
// Open inventory for player
player.openInventory(inventory)
}
override fun handleClick(player: Player, item: ItemStack, slot: Int) {
if (slot in 10..17) {
val name = item.itemMeta?.displayName?.replace("§a", "") ?: "Camera"
if (plugin.createCamera(player, name)) {
player.sendMessage("§aCamera '$name' placed successfully at your location!")
} else {
player.sendMessage("§cFailed to place camera.")
}
player.closeInventory()
MainMenu(plugin, player).open()
} else if (slot == 31) {
player.closeInventory()
MainMenu(plugin, player).open()
}
}
private fun createItem(material: Material, name: String, lore: List<String>): ItemStack {
val item = ItemStack(material)
val meta = item.itemMeta
meta?.setDisplayName(name)
meta?.lore = lore
item.itemMeta = meta
return item
}
}
class ViewCamerasMenu(private val plugin: CctvPlugin, private val player: Player) : CctvMenu {
private val inventory: Inventory = Bukkit.createInventory(this, 54, "View Cameras")
override fun getInventory(): Inventory = inventory
override fun open() {
val cameras = plugin.getCameras()
if (cameras.isEmpty()) {
val noItem = createItem(Material.BARRIER, "§cNo Cameras Available", listOf("§7No cameras have been placed yet"))
inventory.setItem(22, noItem)
} else {
var slot = 0
cameras.forEach { (id, camera) ->
if (slot < 45) {
val cameraItem = createItem(
Material.ENDER_EYE,
"§a${camera.name}",
listOf(
"§7ID: §f$id",
"§7Location: §f${camera.location.world?.name} (${camera.location.blockX}, ${camera.location.blockY}, ${camera.location.blockZ})",
"§7Owner: §f${Bukkit.getOfflinePlayer(camera.owner).name ?: "Unknown"}",
"",
"§eClick to view this camera"
)
)
// Store camera ID in item NBT
val meta = cameraItem.itemMeta
meta?.persistentDataContainer?.set(
org.bukkit.NamespacedKey(plugin, "camera-id"),
org.bukkit.persistence.PersistentDataType.STRING,
id
)
cameraItem.itemMeta = meta
inventory.setItem(slot, cameraItem)
slot++
}
}
}
val backItem = createItem(Material.ARROW, "§cBack", listOf("§7Return to main menu"))
inventory.setItem(49, backItem)
// Open inventory for player
player.openInventory(inventory)
}
override fun handleClick(player: Player, item: ItemStack, slot: Int) {
if (slot == 49) {
player.closeInventory()
MainMenu(plugin, player).open()
return
}
if (item.type == Material.ENDER_EYE) {
val meta = item.itemMeta ?: return
val cameraId = meta.persistentDataContainer.get(
org.bukkit.NamespacedKey(plugin, "camera-id"),
org.bukkit.persistence.PersistentDataType.STRING
) ?: return
player.closeInventory()
if (plugin.startViewing(player, cameraId)) {
player.sendMessage("§aViewing camera feed. Type /cctv to return.")
} else {
player.sendMessage("§cFailed to view camera.")
MainMenu(plugin, player).open()
}
}
}
private fun createItem(material: Material, name: String, lore: List<String>): ItemStack {
val item = ItemStack(material)
val meta = item.itemMeta
meta?.setDisplayName(name)
meta?.lore = lore
item.itemMeta = meta
return item
}
}
class ManageCamerasMenu(private val plugin: CctvPlugin, private val player: Player) : CctvMenu {
private val inventory: Inventory = Bukkit.createInventory(this, 54, "Manage Cameras")
override fun getInventory(): Inventory = inventory
override fun open() {
val cameras = plugin.getCameras().filter {
it.value.owner == player.uniqueId || player.hasPermission("cctv.admin")
}
if (cameras.isEmpty()) {
val noItem = createItem(Material.BARRIER, "§cNo Cameras Available", listOf("§7You don't have any cameras to manage"))
inventory.setItem(22, noItem)
} else {
var slot = 0
cameras.forEach { (id, camera) ->
if (slot < 45) {
val cameraItem = createItem(
Material.ENDER_EYE,
"§a${camera.name}",
listOf(
"§7ID: §f$id",
"§7Location: §f${camera.location.world?.name} (${camera.location.blockX}, ${camera.location.blockY}, ${camera.location.blockZ})",
"",
"§eClick to remove this camera"
)
)
// Store camera ID in item NBT
val meta = cameraItem.itemMeta
meta?.persistentDataContainer?.set(
org.bukkit.NamespacedKey(plugin, "camera-id"),
org.bukkit.persistence.PersistentDataType.STRING,
id
)
cameraItem.itemMeta = meta
inventory.setItem(slot, cameraItem)
slot++
}
}
}
val backItem = createItem(Material.ARROW, "§cBack", listOf("§7Return to main menu"))
inventory.setItem(49, backItem)
// Open inventory for player
player.openInventory(inventory)
}
override fun handleClick(player: Player, item: ItemStack, slot: Int) {
if (slot == 49) {
player.closeInventory()
MainMenu(plugin, player).open()
return
}
if (item.type == Material.ENDER_EYE) {
val meta = item.itemMeta ?: return
val cameraId = meta.persistentDataContainer.get(
org.bukkit.NamespacedKey(plugin, "camera-id"),
org.bukkit.persistence.PersistentDataType.STRING
) ?: return
player.closeInventory()
if (plugin.removeCamera(cameraId, player)) {
player.sendMessage("§aCamera removed successfully!")
} else {
player.sendMessage("§cFailed to remove camera. Make sure you own it or have admin permissions.")
}
ManageCamerasMenu(plugin, player).open()
}
}
private fun createItem(material: Material, name: String, lore: List<String>): ItemStack {
val item = ItemStack(material)
val meta = item.itemMeta
meta?.setDisplayName(name)
meta?.lore = lore
item.itemMeta = meta
return item
}
}

View File

@@ -0,0 +1,2 @@
cameras:
## data is here automatically

View File

@@ -0,0 +1,4 @@
settings:
max-cameras-per-player: 10
camera-range: 50 # Maximum viewing distance

View File

@@ -0,0 +1,24 @@
name: CctvPlugin
version: 1.0.0
main: com.example.cctv.CctvPlugin
api-version: '1.21'
description: CCTV camera system for Minecraft
author: YourName
commands:
cctv:
description: Manage CCTV cameras
usage: /cctv [place|list|remove|help]
permission: cctv.use
permissions:
cctv.use:
description: Allows using the CCTV system
default: true
cctv.place:
description: Allows placing cameras
default: op
cctv.admin:
description: Allows managing all cameras
default: op
children:
cctv.use: true
cctv.place: true

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,2 @@
cameras:
## data is here automatically

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,4 @@
settings:
max-cameras-per-player: 10
camera-range: 50 # Maximum viewing distance

24
target/classes/plugin.yml Normal file
View File

@@ -0,0 +1,24 @@
name: CctvPlugin
version: 1.0.0
main: com.example.cctv.CctvPlugin
api-version: '1.21'
description: CCTV camera system for Minecraft
author: YourName
commands:
cctv:
description: Manage CCTV cameras
usage: /cctv [place|list|remove|help]
permission: cctv.use
permissions:
cctv.use:
description: Allows using the CCTV system
default: true
cctv.place:
description: Allows placing cameras
default: op
cctv.admin:
description: Allows managing all cameras
default: op
children:
cctv.use: true
cctv.place: true

View File

@@ -0,0 +1,3 @@
artifactId=CctvPlugin
groupId=org.CctvPlugin
version=1.0-SNAPSHOT

Binary file not shown.