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.minimessage.Template; 037import org.checkerframework.checker.nullness.qual.NonNull; 038 039import java.util.Collection; 040import java.util.Collections; 041import java.util.HashMap; 042import java.util.LinkedList; 043import java.util.List; 044import java.util.UUID; 045import java.util.stream.Collectors; 046 047@CommandDeclaration(command = "like", 048 permission = "plots.like", 049 usage = "/plot like [next | purge]", 050 category = CommandCategory.INFO, 051 requiredType = RequiredType.PLAYER) 052public class Like extends SubCommand { 053 054 private final EventDispatcher eventDispatcher; 055 056 @Inject 057 public Like(final @NonNull EventDispatcher eventDispatcher) { 058 this.eventDispatcher = eventDispatcher; 059 } 060 061 /** 062 * Get the likes to dislike ratio of a plot as a percentage (in decimal form) 063 * 064 * @param plot plot 065 * @return likes to dislike ratio, returns zero if the plot has no likes 066 */ 067 public static double getLikesPercentage(final Plot plot) { 068 if (!plot.hasRatings()) { 069 return 0; 070 } 071 final Collection<Boolean> reactions = plot.getLikes().values(); 072 double numLikes = 0, numDislikes = 0; 073 for (final boolean reaction : reactions) { 074 if (reaction) { 075 numLikes += 1; 076 } else { 077 numDislikes += 1; 078 } 079 } 080 if (numLikes == 0 && numDislikes == 0) { 081 return 0D; 082 } else if (numDislikes == 0) { 083 return 1.0D; 084 } 085 return numLikes / (numLikes + numDislikes); 086 } 087 088 protected boolean handleLike( 089 final PlotPlayer<?> player, String[] args, 090 final boolean like 091 ) { 092 final UUID uuid = player.getUUID(); 093 if (args.length == 1) { 094 switch (args[0].toLowerCase()) { 095 case "next" -> { 096 final List<Plot> plots = PlotQuery.newQuery().whereBasePlot().asList(); 097 plots.sort((p1, p2) -> { 098 double v1 = getLikesPercentage(p1); 099 double v2 = getLikesPercentage(p2); 100 if (v1 == v2) { 101 return -0; 102 } 103 return v2 > v1 ? 1 : -1; 104 }); 105 for (final Plot plot : plots) { 106 if ((!Settings.Done.REQUIRED_FOR_RATINGS || DoneFlag.isDone(plot)) && plot 107 .isBasePlot() && !plot.getLikes().containsKey(uuid)) { 108 plot.teleportPlayer(player, TeleportCause.COMMAND_LIKE, result -> { 109 }); 110 player.sendMessage(TranslatableCaption.of("tutorial.rate_this")); 111 return true; 112 } 113 } 114 player.sendMessage(TranslatableCaption.of("invalid.found_no_plots")); 115 return true; 116 } 117 case "purge" -> { 118 final Plot plot = player.getCurrentPlot(); 119 if (plot == null) { 120 player.sendMessage(TranslatableCaption.of("errors.not_in_plot")); 121 return false; 122 } 123 if (!player.hasPermission(Permission.PERMISSION_ADMIN_COMMAND_PURGE_RATINGS, true)) { 124 return false; 125 } 126 plot.clearRatings(); 127 player.sendMessage(TranslatableCaption.of("ratings.ratings_purged")); 128 return true; 129 } 130 } 131 } 132 final Plot plot = player.getCurrentPlot(); 133 if (plot == null) { 134 player.sendMessage(TranslatableCaption.of("errors.not_in_plot")); 135 return false; 136 } 137 if (!plot.hasOwner()) { 138 player.sendMessage(TranslatableCaption.of("ratings.rating_not_owned")); 139 return false; 140 } 141 if (plot.isOwner(player.getUUID())) { 142 player.sendMessage(TranslatableCaption.of("ratings.rating_not_your_own")); 143 return false; 144 } 145 if (Settings.Done.REQUIRED_FOR_RATINGS && !DoneFlag.isDone(plot)) { 146 player.sendMessage(TranslatableCaption.of("ratings.rating_not_done")); 147 return false; 148 } 149 final Runnable run = () -> { 150 final Boolean oldRating = plot.getLikes().get(uuid); 151 if (oldRating != null) { 152 player.sendMessage( 153 TranslatableCaption.of("ratings.rating_already_exists"), 154 Template.of("plot", plot.getId().toString()) 155 ); 156 return; 157 } 158 final int rating; 159 if (like) { 160 rating = 10; 161 } else { 162 rating = 1; 163 } 164 plot.addRating(uuid, new Rating(rating)); 165 final PlotRateEvent event = 166 this.eventDispatcher.callRating(player, plot, new Rating(rating)); 167 if (event.getRating() != null) { 168 plot.addRating(uuid, event.getRating()); 169 if (like) { 170 player.sendMessage( 171 TranslatableCaption.of("ratings.rating_liked"), 172 Template.of("plot", plot.getId().toString()) 173 ); 174 } else { 175 player.sendMessage( 176 TranslatableCaption.of("ratings.rating_disliked"), 177 Template.of("plot", plot.getId().toString()) 178 ); 179 } 180 } 181 }; 182 if (plot.getSettings().getRatings() == null) { 183 if (!Settings.Enabled_Components.RATING_CACHE) { 184 TaskManager.runTaskAsync(() -> { 185 plot.getSettings().setRatings(DBFunc.getRatings(plot)); 186 run.run(); 187 }); 188 return true; 189 } 190 plot.getSettings().setRatings(new HashMap<>()); 191 } 192 run.run(); 193 return true; 194 } 195 196 @Override 197 public boolean onCommand(PlotPlayer<?> player, String[] args) { 198 return handleLike(player, args, true); 199 } 200 201 @Override 202 public Collection<Command> tab(final PlotPlayer<?> player, final String[] args, final boolean space) { 203 if (args.length == 1) { 204 final List<String> completions = new LinkedList<>(); 205 if (player.hasPermission(Permission.PERMISSION_ADMIN_COMMAND_PURGE_RATINGS)) { 206 completions.add("purge"); 207 } 208 final List<Command> commands = completions.stream().filter(completion -> completion 209 .toLowerCase() 210 .startsWith(args[0].toLowerCase())) 211 .map(completion -> new Command(null, true, completion, "", RequiredType.PLAYER, CommandCategory.INFO) { 212 }).collect(Collectors.toCollection(LinkedList::new)); 213 if (player.hasPermission(Permission.PERMISSION_RATE) && args[0].length() > 0) { 214 commands.addAll(TabCompletions.completePlayers(player, args[0], Collections.emptyList())); 215 } 216 return commands; 217 } 218 return TabCompletions.completePlayers(player, String.join(",", args).trim(), Collections.emptyList()); 219 } 220 221}