001/* 002 * PlotSquared, a land and world management plugin for Minecraft. 003 * Copyright (C) IntellectualSites <https://intellectualsites.com> 004 * Copyright (C) IntellectualSites team and contributors 005 * 006 * This program is free software: you can redistribute it and/or modify 007 * it under the terms of the GNU General Public License as published by 008 * the Free Software Foundation, either version 3 of the License, or 009 * (at your option) any later version. 010 * 011 * This program is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 014 * GNU General Public License for more details. 015 * 016 * You should have received a copy of the GNU General Public License 017 * along with this program. If not, see <https://www.gnu.org/licenses/>. 018 */ 019package com.plotsquared.core.command; 020 021import com.google.inject.Inject; 022import com.plotsquared.core.configuration.Settings; 023import com.plotsquared.core.configuration.caption.TranslatableCaption; 024import com.plotsquared.core.database.DBFunc; 025import com.plotsquared.core.events.PlotRateEvent; 026import com.plotsquared.core.events.TeleportCause; 027import com.plotsquared.core.permissions.Permission; 028import com.plotsquared.core.player.PlotPlayer; 029import com.plotsquared.core.plot.Plot; 030import com.plotsquared.core.plot.Rating; 031import com.plotsquared.core.plot.flag.implementations.DoneFlag; 032import com.plotsquared.core.util.EventDispatcher; 033import com.plotsquared.core.util.TabCompletions; 034import com.plotsquared.core.util.query.PlotQuery; 035import com.plotsquared.core.util.task.TaskManager; 036import net.kyori.adventure.text.Component; 037import net.kyori.adventure.text.minimessage.tag.Tag; 038import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; 039import org.checkerframework.checker.nullness.qual.NonNull; 040 041import java.util.Collection; 042import java.util.Collections; 043import java.util.HashMap; 044import java.util.LinkedList; 045import java.util.List; 046import java.util.UUID; 047import java.util.stream.Collectors; 048 049@CommandDeclaration(command = "like", 050 permission = "plots.like", 051 usage = "/plot like [next | purge]", 052 category = CommandCategory.INFO, 053 requiredType = RequiredType.PLAYER) 054public class Like extends SubCommand { 055 056 private final EventDispatcher eventDispatcher; 057 058 @Inject 059 public Like(final @NonNull EventDispatcher eventDispatcher) { 060 this.eventDispatcher = eventDispatcher; 061 } 062 063 /** 064 * Get the likes to dislike ratio of a plot as a percentage (in decimal form) 065 * 066 * @param plot plot 067 * @return likes to dislike ratio, returns zero if the plot has no likes 068 */ 069 public static double getLikesPercentage(final Plot plot) { 070 if (!plot.hasRatings()) { 071 return 0; 072 } 073 final Collection<Boolean> reactions = plot.getLikes().values(); 074 double numLikes = 0, numDislikes = 0; 075 for (final boolean reaction : reactions) { 076 if (reaction) { 077 numLikes += 1; 078 } else { 079 numDislikes += 1; 080 } 081 } 082 if (numLikes == 0 && numDislikes == 0) { 083 return 0D; 084 } else if (numDislikes == 0) { 085 return 1.0D; 086 } 087 return numLikes / (numLikes + numDislikes); 088 } 089 090 protected boolean handleLike( 091 final PlotPlayer<?> player, String[] args, 092 final boolean like 093 ) { 094 final UUID uuid = player.getUUID(); 095 if (args.length == 1) { 096 switch (args[0].toLowerCase()) { 097 case "next" -> { 098 final List<Plot> plots = PlotQuery.newQuery().whereBasePlot().asList(); 099 plots.sort((p1, p2) -> { 100 double v1 = getLikesPercentage(p1); 101 double v2 = getLikesPercentage(p2); 102 if (v1 == v2) { 103 return -0; 104 } 105 return v2 > v1 ? 1 : -1; 106 }); 107 for (final Plot plot : plots) { 108 if ((!Settings.Done.REQUIRED_FOR_RATINGS || DoneFlag.isDone(plot)) && plot 109 .isBasePlot() && !plot.getLikes().containsKey(uuid)) { 110 plot.teleportPlayer(player, TeleportCause.COMMAND_LIKE, result -> { 111 }); 112 player.sendMessage(TranslatableCaption.of("tutorial.rate_this")); 113 return true; 114 } 115 } 116 player.sendMessage(TranslatableCaption.of("invalid.found_no_plots")); 117 return true; 118 } 119 case "purge" -> { 120 final Plot plot = player.getCurrentPlot(); 121 if (plot == null) { 122 player.sendMessage(TranslatableCaption.of("errors.not_in_plot")); 123 return false; 124 } 125 if (!player.hasPermission(Permission.PERMISSION_ADMIN_COMMAND_PURGE_RATINGS, true)) { 126 return false; 127 } 128 plot.clearRatings(); 129 player.sendMessage(TranslatableCaption.of("ratings.ratings_purged")); 130 return true; 131 } 132 } 133 } 134 final Plot plot = player.getCurrentPlot(); 135 if (plot == null) { 136 player.sendMessage(TranslatableCaption.of("errors.not_in_plot")); 137 return false; 138 } 139 if (!plot.hasOwner()) { 140 player.sendMessage(TranslatableCaption.of("ratings.rating_not_owned")); 141 return false; 142 } 143 if (plot.isOwner(player.getUUID())) { 144 player.sendMessage(TranslatableCaption.of("ratings.rating_not_your_own")); 145 return false; 146 } 147 if (Settings.Done.REQUIRED_FOR_RATINGS && !DoneFlag.isDone(plot)) { 148 player.sendMessage(TranslatableCaption.of("ratings.rating_not_done")); 149 return false; 150 } 151 final Runnable run = () -> { 152 final Boolean oldRating = plot.getLikes().get(uuid); 153 if (oldRating != null) { 154 player.sendMessage( 155 TranslatableCaption.of("ratings.rating_already_exists"), 156 TagResolver.resolver("plot", Tag.inserting(Component.text(plot.getId().toString()))) 157 ); 158 return; 159 } 160 final int rating; 161 if (like) { 162 rating = 10; 163 } else { 164 rating = 1; 165 } 166 plot.addRating(uuid, new Rating(rating)); 167 final PlotRateEvent event = 168 this.eventDispatcher.callRating(player, plot, new Rating(rating)); 169 if (event.getRating() != null) { 170 plot.addRating(uuid, event.getRating()); 171 if (like) { 172 player.sendMessage( 173 TranslatableCaption.of("ratings.rating_liked"), 174 TagResolver.resolver("plot", Tag.inserting(Component.text(plot.getId().toString()))) 175 ); 176 } else { 177 player.sendMessage( 178 TranslatableCaption.of("ratings.rating_disliked"), 179 TagResolver.resolver("plot", Tag.inserting(Component.text(plot.getId().toString()))) 180 ); 181 } 182 } 183 }; 184 if (plot.getSettings().getRatings() == null) { 185 if (!Settings.Enabled_Components.RATING_CACHE) { 186 TaskManager.runTaskAsync(() -> { 187 plot.getSettings().setRatings(DBFunc.getRatings(plot)); 188 run.run(); 189 }); 190 return true; 191 } 192 plot.getSettings().setRatings(new HashMap<>()); 193 } 194 run.run(); 195 return true; 196 } 197 198 @Override 199 public boolean onCommand(PlotPlayer<?> player, String[] args) { 200 return handleLike(player, args, true); 201 } 202 203 @Override 204 public Collection<Command> tab(final PlotPlayer<?> player, final String[] args, final boolean space) { 205 if (args.length == 1) { 206 final List<String> completions = new LinkedList<>(); 207 if (player.hasPermission(Permission.PERMISSION_ADMIN_COMMAND_PURGE_RATINGS)) { 208 completions.add("purge"); 209 } 210 final List<Command> commands = completions.stream().filter(completion -> completion 211 .toLowerCase() 212 .startsWith(args[0].toLowerCase())) 213 .map(completion -> new Command(null, true, completion, "", RequiredType.PLAYER, CommandCategory.INFO) { 214 }).collect(Collectors.toCollection(LinkedList::new)); 215 if (player.hasPermission(Permission.PERMISSION_RATE) && args[0].length() > 0) { 216 commands.addAll(TabCompletions.completePlayers(player, args[0], Collections.emptyList())); 217 } 218 return commands; 219 } 220 return TabCompletions.completePlayers(player, String.join(",", args).trim(), Collections.emptyList()); 221 } 222 223}