├── .gitignore ├── screenshots ├── 1.png ├── 2.png ├── 3.png └── 4.png ├── src └── Taylcd │ └── ReportUI │ ├── task │ └── SaveTask.php │ ├── event │ ├── PlayerReportEvent.php │ ├── Listener.php │ └── ReportProcessedEvent.php │ └── ReportUI.php ├── plugin.yml ├── README.md └── resources ├── config.yml └── language.yml /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/* -------------------------------------------------------------------------------- /screenshots/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/q1an1x/ReportUI/HEAD/screenshots/1.png -------------------------------------------------------------------------------- /screenshots/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/q1an1x/ReportUI/HEAD/screenshots/2.png -------------------------------------------------------------------------------- /screenshots/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/q1an1x/ReportUI/HEAD/screenshots/3.png -------------------------------------------------------------------------------- /screenshots/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/q1an1x/ReportUI/HEAD/screenshots/4.png -------------------------------------------------------------------------------- /src/Taylcd/ReportUI/task/SaveTask.php: -------------------------------------------------------------------------------- 1 | getOwner()->save(); 10 | } 11 | } -------------------------------------------------------------------------------- /plugin.yml: -------------------------------------------------------------------------------- 1 | name: ReportUI 2 | author: Taylcd 3 | website: https://github.com/Taylcd/ReportUI 4 | 5 | version: 1.3.1 6 | api: 3.0.2 7 | 8 | main: Taylcd\ReportUI\ReportUI 9 | depend: ["FormAPI"] 10 | 11 | commands: 12 | report: 13 | description: Report a player 14 | permission: report.use 15 | reportadmin: 16 | description: Open Report Panel 17 | permission: report.admin 18 | 19 | permissions: 20 | report.use: 21 | default: true 22 | report.admin: 23 | default: op 24 | report.admin.notification: 25 | default: op -------------------------------------------------------------------------------- /src/Taylcd/ReportUI/event/PlayerReportEvent.php: -------------------------------------------------------------------------------- 1 | player = $player; 17 | $this->reported = $reported; 18 | $this->reason = $reason; 19 | } 20 | 21 | public function getReported(){ 22 | return $this->reported; 23 | } 24 | 25 | public function getReason(){ 26 | return $this->reason; 27 | } 28 | } -------------------------------------------------------------------------------- /src/Taylcd/ReportUI/event/Listener.php: -------------------------------------------------------------------------------- 1 | plugin = $plugin; 13 | } 14 | 15 | public function onPlayerJoin(\pocketmine\event\player\PlayerJoinEvent $event){ 16 | if($this->plugin->getConfig()->get("enable-new-report-notification-on-join", true) && $event->getPlayer()->hasPermission("report.admin.notification") && $count = count($this->plugin->getReports()->getAll())){ 17 | $event->getPlayer()->sendMessage($this->plugin->getMessage('admin.unread-reports', $count)); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ReportUI 2 | A plugin allows players to report harmful behaviors, with a beautiful(?) User-Interface. 3 | 4 | This plugin requires FormAPI(https://github.com/jojoe77777/FormAPI) to run. 5 | 6 | Download compilied plugins here: https://github.com/Taylcd/ReportUI/releases 7 | 8 | **Looking for a custom Bukkit/PocketMine plugin? I can make that for you.** Contact: [Twitter](http://twitter.com/_Taylcd)/Email: this@taylcd.com 9 | 10 | ## Commands 11 | - /report - Open Report GUI 12 | - /report [Player] - Report a player 13 | - /reportadmin - Open Admin GUI 14 | 15 | ## Configuration 16 | You can edit default reasons in config.yml. 17 | 18 | ## ScreenShots 19 | ![1](https://raw.githubusercontent.com/Taylcd/ReportUI/master/screenshots/1.png) 20 | ![2](https://raw.githubusercontent.com/Taylcd/ReportUI/master/screenshots/2.png) 21 | ![3](https://raw.githubusercontent.com/Taylcd/ReportUI/master/screenshots/3.png) 22 | ![4](https://raw.githubusercontent.com/Taylcd/ReportUI/master/screenshots/4.png) 23 | -------------------------------------------------------------------------------- /src/Taylcd/ReportUI/event/ReportProcessedEvent.php: -------------------------------------------------------------------------------- 1 | reported = $reported; 21 | $this->reason = $reason; 22 | $this->processType = $processType; 23 | } 24 | 25 | public function getReported(){ 26 | return $this->reported; 27 | } 28 | 29 | public function getReason(){ 30 | return $this->reason; 31 | } 32 | 33 | public function getProcessType(){ 34 | return $this->processType; 35 | } 36 | } -------------------------------------------------------------------------------- /resources/config.yml: -------------------------------------------------------------------------------- 1 | config-version: 3 2 | 3 | save-period: 600 4 | # Default reasons available when report 5 | reasons: 6 | - Harassment 7 | - Offensive Profile 8 | - Spamming Advertisements 9 | - Suspected Cheater/Hacker 10 | - Impersonating another player 11 | # This will allow players to report with their own reasons. 12 | allow-custom-reason: true 13 | 14 | custom-reason-min-length: 4 15 | 16 | custom-reason-max-length: 20 17 | 18 | allow-reporting-ops: false 19 | 20 | allow-reporting-banned-players: false 21 | 22 | # Plugin will check for new version from Github 23 | check-update: true 24 | 25 | # Player who has the notification permission will receive 26 | # a message if someone got reported. 27 | enable-new-report-notification: true 28 | 29 | # Player who has the notification permission will receive 30 | # a message when they join the server if there's new reports 31 | enable-new-report-notification-on-join: true 32 | 33 | # If enabled, you will have to select an online player from a dropdown list when reporting 34 | # And you can no longer report players that are not online 35 | # This feature will be improved in the future 36 | use-dropdown-select: false -------------------------------------------------------------------------------- /resources/language.yml: -------------------------------------------------------------------------------- 1 | gui: 2 | title: "§eReport" 3 | label: "Please enter the name of the player you want to report, be aware that abusing the report system will get yourself banned.\nYou can also use /report to report." 4 | input: "Player Name" 5 | player-not-found: "§cThis player has never showed up before." 6 | content: "You are now reporting §e%0§f.\nPlease select a reason to report." 7 | cant-report-self: "§eYou can't report yourself!" 8 | custom-reason: "Other" 9 | custom: 10 | label: "You are now reporting §e%0§f.\nPlease enter a reason." 11 | input: "Reason" 12 | select-label: "Please select a player to report, be aware that abusing the report system will get yourself banned.\nYou can also use /report to report." 13 | dropdown: "Select a player" 14 | report: 15 | successful: "§eYour report has been submitted. §7(Reported Player: %0, Reason: %1)" 16 | bad-reason: "§cYour reason was too long or too short." 17 | op: "§cYou don't have permission to report this player." 18 | banned: "§eThis player has already been banned!" 19 | admin: 20 | main-content: "Welcome, administrator!\nPlease select an operation." 21 | delete-by-reporter-content: "Please enter a name, all reports submitted by this player will be deleted." 22 | delete-by-target-content: "Please enter a name, all reports related to this player will be deleted." 23 | name-not-entered: "§cPlease enter player's name!" 24 | deleted-by-reporter: "§aAll %0's reports have been deleted." 25 | deleted-by-target: "§aAll reports related to %0 have been deleted." 26 | deleted: "§aThe reported has been deleted." 27 | banned: "§ePlayer %0 has been banned and all related reports has been deleted." 28 | title: "§eReport Manager" 29 | content: "Welcome, administrator!\nPlease select a report to see the details." 30 | no-report: "\nThere's currently no report to be viewed." 31 | detail: "Player §e%0§f was reported by %1 at %2 for §e%3§f.\nThis player has been reported §e%4§f times on record.\n" 32 | button: 33 | close: "Close" 34 | report: "%0 reported at %1" 35 | delete: "Delete this report" 36 | delete-all: "Delete all related reports" 37 | ban: "§cBan & delete all related reports" 38 | back: "Go back" 39 | view-reports: "View reports" 40 | delete-by-reporter: "Delete reports by reporter" 41 | delete-by-target: "Delete reports by reported" 42 | unread-reports: "§eThere's §f%0§e new report(s)." 43 | new-report: "§eA new report has been submitted by %0. §7(Reported Player: %1, Reason: %2)" 44 | console: 45 | config-outdated: "Looks like you have updated your plugin, your config file was outdated and has been updated automatically, please note that all settings have been reset to default. Your original config file is renamed to config.old.yml." -------------------------------------------------------------------------------- /src/Taylcd/ReportUI/ReportUI.php: -------------------------------------------------------------------------------- 1 | saveDefaultConfig(); 36 | $this->saveResource('language.yml'); 37 | $this->lang = new Config($this->getDataFolder() . 'language.yml', Config::YAML); 38 | $this->reports = new Config($this->getDataFolder() . 'reports.yml', Config::YAML); 39 | 40 | if($this->getConfig()->get("check-update", true)){ 41 | $this->getLogger()->info("Checking update..."); 42 | try{ 43 | if(($version = (new PluginDescription(file_get_contents("https://raw.githubusercontent.com/Taylcd/ReportUI/master/plugin.yml")))->getVersion()) != $this->getDescription()->getVersion()){ 44 | $this->getLogger()->notice("New version $version available! Get it here: " . $this->getDescription()->getWebsite()); 45 | } else { 46 | $this->getLogger()->info("Already up-to-date."); 47 | } 48 | } catch(\Exception $ex) { 49 | $this->getLogger()->warning("Unable to check update."); 50 | } 51 | } 52 | 53 | if($this->getConfig()->get('config-version') < self::CONFIG_VERSION){ 54 | rename($this->getDataFolder() . "config.yml", $this->getDataFolder() . "config.old.yml"); 55 | $this->saveDefaultConfig(); 56 | $this->getConfig()->reload(); 57 | $this->getLogger()->notice($this->getMessage("console.config-outdated")); 58 | } 59 | } 60 | 61 | public function onEnable(){ 62 | $this->FormAPI = $this->getServer()->getPluginManager()->getPlugin("FormAPI"); 63 | if(!$this->FormAPI or $this->FormAPI->isDisabled()){ 64 | $this->getLogger()->warning('Dependency FormAPI not found, disabling...'); 65 | $this->getServer()->getPluginManager()->disablePlugin($this); 66 | } 67 | $this->getServer()->getPluginManager()->registerEvents(new Listener($this), $this); 68 | $this->getScheduler()->scheduleDelayedRepeatingTask(new SaveTask($this), $this->getConfig()->get('save-period', 600) * 20, $this->getConfig()->get('save-period', 600) * 20); 69 | $this->getServer()->getLogger()->info(TextFormat::AQUA . 'ReportUI enabled. ' . TextFormat::GRAY . 'Made by Taylcd with ' . TextFormat::RED . "\xe2\x9d\xa4"); 70 | } 71 | 72 | public function onDisable(){ 73 | $this->save(); 74 | } 75 | 76 | public function save(){ 77 | $this->reports->save(); 78 | } 79 | 80 | /** 81 | * Create a new report 82 | * 83 | * @param string $reporter 84 | * @param string $target 85 | * @param string $reason 86 | */ 87 | public function addReport(string $reporter, string $target, string $reason){ 88 | $reports = $this->reports->getAll(); 89 | array_unshift($reports, [ 90 | 'reporter' => $reporter, 91 | 'target' => $target, 92 | 'reason' => $reason, 93 | 'time' => time() 94 | ]); 95 | $this->reports->setAll($reports); 96 | 97 | if($this->getConfig()->get("enable-new-report-notification", true)){ 98 | foreach($this->getServer()->getOnlinePlayers() as $player){ 99 | if($player->hasPermission("report.admin.notification")){ 100 | $player->sendMessage($this->getMessage("admin.new-report", $reporter, $target, $reason)); 101 | } 102 | } 103 | } 104 | } 105 | 106 | /** 107 | * Delete specific report 108 | * 109 | * @param string $search 110 | * @param $value 111 | */ 112 | public function deleteReport(string $search, $value){ 113 | if($search == "id"){ 114 | $reports = $this->reports->getAll(); 115 | array_splice($reports, $value, 1); 116 | $this->reports->setAll($reports); 117 | }else{ 118 | $reports = $this->reports->getAll(); 119 | for($i = 0; $i < count($reports); $i ++){ 120 | if(strtolower($reports[$i][$search]) == strtolower($value)){ 121 | $i --; 122 | array_splice($reports, $i, 1); 123 | } 124 | } 125 | $this->reports->setAll($reports); 126 | } 127 | } 128 | 129 | /** 130 | * Get all reports on the server 131 | * 132 | * @return Config 133 | */ 134 | public function getReports(){ 135 | return $this->reports; 136 | } 137 | 138 | public function getMessage($key, ...$replacement) : string{ 139 | if(!$message = $this->lang->getNested($key)){ 140 | if($message = (new Config($this->getFile() . "resources/language.yml", Config::YAML))->getNested($key)){ 141 | $this->lang->setNested($key, $message); 142 | $this->lang->save(); 143 | }else{ 144 | $this->getLogger()->warning("Message $key not found."); 145 | } 146 | } 147 | foreach($replacement as $index => $value){ 148 | $message = str_replace("%$index", $value, $message); 149 | } 150 | return $message; 151 | } 152 | 153 | public function onCommand(CommandSender $sender, Command $command, string $label, array $args) : bool{ 154 | if(!$sender instanceof Player){ 155 | $sender->sendMessage(TextFormat::RED . 'This command can only be called in-game.'); 156 | return true; 157 | } 158 | switch($command->getName()){ 159 | case 'report': 160 | if(!isset($args[0])) unset($this->reportCache[$sender->getName()]); 161 | else $this->reportCache[$sender->getName()] = $args[0]; 162 | $this->sendReportGUI($sender); 163 | return true; 164 | case 'reportadmin': 165 | $this->sendAdminGUI($sender); 166 | } 167 | return true; 168 | } 169 | 170 | private function sendReportGUI(Player $sender){ 171 | if(isset($this->reportCache[$sender->getName()])){ 172 | $this->sendReasonSelect($sender); 173 | return; 174 | } 175 | 176 | $form = $this->FormAPI->createCustomForm(function(Player $sender, array $data){ 177 | if(count($data) < 2){ 178 | return; 179 | } 180 | 181 | if($this->getConfig()->get("use-dropdown-select", false)){ 182 | $this->reportCache[$sender->getName()] = $this->dropdownCache[$sender->getName()][$data[1]]; 183 | } else { 184 | $this->reportCache[$sender->getName()] = $data[1]; 185 | } 186 | 187 | $this->sendReasonSelect($sender); 188 | }); 189 | 190 | $form->setTitle($this->getMessage('gui.title')); 191 | if($this->getConfig()->get("use-dropdown-select", false)){ 192 | $form->addLabel($this->getMessage('gui.select-label')); 193 | $this->dropdownCache[$sender->getName()] = []; 194 | foreach($this->getServer()->getOnlinePlayers() as $player){ 195 | if($player->getName() !== null){ 196 | array_push($this->dropdownCache[$sender->getName()], $player->getName()); 197 | } 198 | } 199 | $form->addDropdown($this->getMessage('gui.dropdown'), $this->dropdownCache[$sender->getName()]); 200 | } else { 201 | $form->addLabel($this->getMessage('gui.label')); 202 | $form->addInput($this->getMessage('gui.input')); 203 | } 204 | $form->sendToPlayer($sender); 205 | } 206 | 207 | private function sendReasonSelect(Player $sender){ 208 | $name = $this->reportCache[$sender->getName()]; 209 | if(!$name || !$this->getServer()->getOfflinePlayer($name)->getFirstPlayed()){ 210 | $sender->sendMessage($this->getMessage('gui.player-not-found')); 211 | return; 212 | } 213 | if(strtolower($name) == strtolower($sender->getName())){ 214 | $sender->sendMessage($this->getMessage('gui.cant-report-self')); 215 | return; 216 | } 217 | if($this->getServer()->getOfflinePlayer($this->reportCache[$sender->getName()])->isOp() && !$this->getConfig()->get('allow-reporting-ops')){ 218 | $sender->sendMessage($this->getMessage('report.op')); 219 | return; 220 | } 221 | if($this->getServer()->getOfflinePlayer($this->reportCache[$sender->getName()])->isBanned() && !$this->getConfig()->get('allow-reporting-banned-players')){ 222 | $sender->sendMessage($this->getMessage('report.banned')); 223 | return; 224 | } 225 | 226 | $form = $this->FormAPI->createSimpleForm(function(Player $sender, array $data){ 227 | if($data[0] === null){ 228 | return; 229 | } 230 | if($data[0] == count($this->getConfig()->get('reasons'))){ 231 | if(!$this->getConfig()->get('allow-custom-reason')){ 232 | return; 233 | } 234 | $form = $this->FormAPI->createCustomForm(function(Player $sender, array $data){ 235 | if (count($data) < 2){ 236 | return; 237 | } 238 | if(!$data[1] || strlen($data[1]) < $this->getConfig()->get('custom-reason-min-length', 4) || strlen($data[1]) < $this->getConfig()->get('custom-reason-min-length', 4)){ 239 | $sender->sendMessage($this->getMessage('report.bad-reason')); 240 | return; 241 | } 242 | $this->addReport($sender->getName(), $this->reportCache[$sender->getName()], $data[1]); 243 | $sender->sendMessage($this->getMessage('report.successful', $this->reportCache[$sender->getName()], $data[1])); 244 | }); 245 | $form->setTitle($this->getMessage('gui.title')); 246 | $form->addLabel($this->getMessage('gui.custom.label', $this->reportCache[$sender->getName()])); 247 | $form->addInput($this->getMessage('gui.custom.input')); 248 | $form->sendToPlayer($sender); 249 | return; 250 | } 251 | 252 | $this->getServer()->getPluginManager()->callEvent($ev = new PlayerReportEvent($sender, $this->reportCache[$sender->getName()], $this->getConfig()->get('reasons')[$data[0]] ?? 'None')); 253 | if(!$ev->isCancelled()){ 254 | $this->addReport($sender->getName(), $this->reportCache[$sender->getName()], $this->getConfig()->get('reasons')[$data[0]] ?? 'None'); 255 | $sender->sendMessage($this->getMessage('report.successful', $this->reportCache[$sender->getName()], $this->getConfig()->get('reasons')[$data[0]] ?? 'None')); 256 | } 257 | }); 258 | $form->setTitle($this->getMessage('gui.title')); 259 | $form->setContent($this->getMessage('gui.content', $this->reportCache[$sender->getName()])); 260 | foreach($this->getConfig()->get('reasons') as $reason){ 261 | $form->addButton($reason); 262 | } 263 | if($this->getConfig()->get('allow-custom-reason')){ 264 | $form->addButton($this->getMessage('gui.custom-reason')); 265 | } 266 | $form->sendToPlayer($sender); 267 | } 268 | 269 | private function sendAdminGUI(Player $sender){ 270 | $form = $this->FormAPI->createSimpleForm(function(Player $sender, array $data){ 271 | if($data[0] === null){ 272 | return; 273 | } 274 | switch($data[0]){ 275 | case 0: 276 | $form = $this->FormAPI->createSimpleForm(function(Player $sender, array $data){ 277 | if($data[0] === null || count($this->reports->getAll()) < 1){ 278 | return; 279 | } 280 | $this->adminCache[$sender->getName()] = $data[0]; 281 | 282 | $form = $this->FormAPI->createSimpleForm(function(Player $sender, array $data){ 283 | if($data[0] === null) return; 284 | $report = $this->reports->get($this->adminCache[$sender->getName()]); 285 | switch($data[0]){ 286 | case 0: 287 | $this->getServer()->getPluginManager()->callEvent($ev = new ReportProcessedEvent($report['target'], $report['reason'], ReportProcessedEvent::PROCESS_TYPE_DELETE)); 288 | if(!$ev->isCancelled()){ 289 | $this->deleteReport("id", $this->adminCache[$sender->getName()]); 290 | $sender->sendMessage($this->getMessage('admin.deleted')); 291 | } 292 | return; 293 | case 1: 294 | $this->getServer()->getPluginManager()->callEvent($ev = new ReportProcessedEvent($report['target'], $report['reason'], ReportProcessedEvent::PROCESS_TYPE_DELETE_ALL)); 295 | if(!$ev->isCancelled()){ 296 | $this->deleteReport("target", $report['target']); 297 | $sender->sendMessage($this->getMessage('admin.deleted-by-target', $report['target'])); 298 | } 299 | return; 300 | case 2: 301 | $this->getServer()->getPluginManager()->callEvent($ev = new ReportProcessedEvent($report['target'], $report['reason'], ReportProcessedEvent::PROCESS_TYPE_BAN)); 302 | if(!$ev->isCancelled()){ 303 | if(($player = $this->getServer()->getOfflinePlayer($report['target'])) !== null) $player->setBanned(true); 304 | $this->deleteReport("target", $report['target']); 305 | $sender->sendMessage($this->getMessage('admin.banned', $report['target'])); 306 | } 307 | return; 308 | case 3: 309 | $this->sendAdminGUI($sender); 310 | return; 311 | } 312 | }); 313 | 314 | $report = $this->reports->get($this->adminCache[$sender->getName()]); 315 | $form->setTitle($this->getMessage('admin.title')); 316 | $count = 0; 317 | foreach($this->reports->getAll() as $_report){ 318 | if(strtolower($_report['target']) == strtolower($report['target'])){ 319 | $count ++; 320 | } 321 | } 322 | $form->setContent($this->getMessage('admin.detail', $report['target'], $report['reporter'], date("Y-m-d h:i", $report['time']), $report['reason'], $count)); 323 | $form->addButton($this->getMessage('admin.button.delete')); 324 | $form->addButton($this->getMessage('admin.button.delete-all')); 325 | $form->addButton($this->getMessage('admin.button.ban')); 326 | $form->addButton($this->getMessage('admin.button.back')); 327 | $form->sendToPlayer($sender); 328 | }); 329 | 330 | $form->setTitle($this->getMessage('admin.title')); 331 | $form->setContent($this->getMessage('admin.content')); 332 | $reportExist = false; 333 | foreach($this->reports->getAll() as $report){ 334 | $reportExist = true; 335 | $form->addButton($this->getMessage('admin.button.report', $report['target'], date("Y-m-d h:i", $report['time']))); 336 | } 337 | if(!$reportExist){ 338 | $form->setContent($form->getContent() . $this->getMessage('admin.no-report')); 339 | $form->addButton($this->getMessage('admin.button.close')); 340 | } 341 | $form->sendToPlayer($sender); 342 | break; 343 | case 1: 344 | $form = $this->FormAPI->createCustomForm(function(Player $sender, array $data){ 345 | if(count($data) < 2){ 346 | return; 347 | } 348 | if(!$data[1] || !$this->getServer()->getOfflinePlayer($data[1])->getFirstPlayed()){ 349 | $sender->sendMessage($this->getMessage('gui.player-not-found')); 350 | return; 351 | } 352 | $this->deleteReport("reporter", $data[1]); 353 | $sender->sendMessage($this->getMessage('admin.deleted-by-reporter', $data[1])); 354 | }); 355 | 356 | $form->addLabel($this->getMessage('admin.delete-by-reporter-content')); 357 | $form->addInput($this->getMessage('gui.input')); 358 | $form->sendToPlayer($sender); 359 | break; 360 | case 2: 361 | $form = $this->FormAPI->createCustomForm(function(Player $sender, array $data){ 362 | if(count($data) < 2){ 363 | return; 364 | } 365 | if(!$data[1] || !$this->getServer()->getOfflinePlayer($data[1])->getFirstPlayed()){ 366 | $sender->sendMessage($this->getMessage('gui.player-not-found')); 367 | return; 368 | } 369 | $this->deleteReport("target", $data[1]); 370 | $sender->sendMessage($this->getMessage('admin.deleted-by-target', $data[1])); 371 | }); 372 | 373 | $form->addLabel($this->getMessage('admin.delete-by-target-content')); 374 | $form->addInput($this->getMessage('gui.input')); 375 | $form->sendToPlayer($sender); 376 | break; 377 | } 378 | }); 379 | 380 | $form->setContent($this->getMessage('admin.main-content')); 381 | $form->addButton($this->getMessage('admin.button.view-reports')); 382 | $form->addButton($this->getMessage('admin.button.delete-by-reporter')); 383 | $form->addButton($this->getMessage('admin.button.delete-by-target')); 384 | $form->sendToPlayer($sender); 385 | } 386 | } --------------------------------------------------------------------------------