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.plot; 020 021import com.google.common.collect.ImmutableMap; 022import com.plotsquared.core.configuration.ConfigurationUtil; 023import com.plotsquared.core.configuration.serialization.ConfigurationSerializable; 024import com.plotsquared.core.util.BlockUtil; 025import com.plotsquared.core.util.MathMan; 026import com.plotsquared.core.util.PatternUtil; 027import com.plotsquared.core.util.StringMan; 028import com.sk89q.worldedit.function.pattern.BlockPattern; 029import com.sk89q.worldedit.function.pattern.Pattern; 030import com.sk89q.worldedit.world.block.BlockState; 031import com.sk89q.worldedit.world.block.BlockType; 032import org.checkerframework.checker.nullness.qual.NonNull; 033 034import java.util.Arrays; 035import java.util.Map; 036import java.util.Objects; 037import java.util.regex.Matcher; 038 039/** 040 * A block bucket is a container of block types, where each block 041 * has a specified chance of being randomly picked 042 */ 043@SuppressWarnings({"unused", "WeakerAccess"}) 044public final class BlockBucket implements ConfigurationSerializable { 045 046 private static final java.util.regex.Pattern regex = java.util.regex.Pattern.compile( 047 "((?<namespace>[A-Za-z_]+):)?(?<block>([A-Za-z_]+(\\[?[\\S\\s]+\\])?))(:(?<chance>[0-9]{1,3}))?"); 048 private final StringBuilder input; 049 private boolean compiled; 050 private BlockState single; 051 private Pattern pattern; 052 053 public BlockBucket(final @NonNull BlockType type) { 054 this(type.getId()); 055 this.single = type.getDefaultState(); 056 this.pattern = new BlockPattern(this.single); 057 this.compiled = true; 058 } 059 060 public BlockBucket(final @NonNull BlockState state) { 061 this(state.getAsString()); 062 this.single = state; 063 this.pattern = new BlockPattern(this.single); 064 this.compiled = true; 065 } 066 067 public BlockBucket(final @NonNull String input) { 068 this.input = new StringBuilder(input); 069 } 070 071 public BlockBucket() { 072 this.input = new StringBuilder(); 073 } 074 075 public static BlockBucket withSingle(final @NonNull BlockState block) { 076 final BlockBucket blockBucket = new BlockBucket(); 077 blockBucket.addBlock(block, 100); 078 return blockBucket; 079 } 080 081 public static BlockBucket deserialize(final @NonNull Map<String, Object> map) { 082 if (!map.containsKey("blocks")) { 083 return null; 084 } 085 return ConfigurationUtil.BLOCK_BUCKET.parseString(map.get("blocks").toString()); 086 } 087 088 public void addBlock(final @NonNull BlockState block) { 089 this.addBlock(block, -1); 090 } 091 092 public void addBlock(final @NonNull BlockState block, final int chance) { 093 addBlock(block, (double) chance); 094 } 095 096 private void addBlock(final @NonNull BlockState block, double chance) { 097 if (chance == -1) { 098 chance = 1; 099 } 100 String prefix = input.length() == 0 ? "" : ","; 101 input.append(prefix).append(block).append(":").append(chance); 102 this.compiled = false; 103 } 104 105 public boolean isEmpty() { 106 return input == null || input.length() == 0; 107 } 108 109 public void compile() { 110 if (isCompiled()) { 111 return; 112 } 113 this.compiled = true; 114 String string = this.input.toString(); 115 if (string.isEmpty()) { 116 this.single = null; 117 this.pattern = null; 118 return; 119 } 120 // Convert legacy format 121 boolean legacy = false; 122 String[] blocksStr = string.split(",(?![^\\(\\[]*[\\]\\)])"); 123 if (blocksStr.length == 1) { 124 try { 125 Matcher matcher = regex.matcher(string); 126 if (matcher.find()) { 127 String chanceStr = matcher.group("chance"); 128 String block = matcher.group("block"); 129 //noinspection PointlessNullCheck 130 if (chanceStr != null && block != null && !MathMan.isInteger(block) && MathMan 131 .isInteger(chanceStr)) { 132 String namespace = matcher.group("namespace"); 133 string = (namespace == null ? "" : namespace + ":") + block; 134 } 135 } 136 this.single = BlockUtil.get(string); 137 this.pattern = new BlockPattern(single); 138 return; 139 } catch (Exception ignore) { 140 } 141 } 142 for (int i = 0; i < blocksStr.length; i++) { 143 String entry = blocksStr[i]; 144 Matcher matcher = regex.matcher(entry); 145 if (matcher.find()) { 146 String chanceStr = matcher.group("chance"); 147 //noinspection PointlessNullCheck 148 if (chanceStr != null && MathMan.isInteger(chanceStr)) { 149 String[] parts = entry.split(":"); 150 parts = Arrays.copyOf(parts, parts.length - 1); 151 entry = chanceStr + "%" + StringMan.join(parts, ":"); 152 blocksStr[i] = entry; 153 legacy = true; 154 } 155 } 156 } 157 if (legacy) { 158 string = StringMan.join(blocksStr, ","); 159 } 160 pattern = PatternUtil.parse(null, string); 161 } 162 163 public boolean isCompiled() { 164 return this.compiled; 165 } 166 167 public Pattern toPattern() { 168 this.compile(); 169 return this.pattern; 170 } 171 172 @Override 173 public String toString() { 174 return input.toString(); 175 } 176 177 public boolean isAir() { 178 compile(); 179 return isEmpty() || (single != null && single.getBlockType().getMaterial().isAir()); 180 } 181 182 @Override 183 public Map<String, Object> serialize() { 184 if (!isCompiled()) { 185 compile(); 186 } 187 return ImmutableMap.of("blocks", this.toString()); 188 } 189 190 public boolean equals(final Object o) { 191 if (o == this) { 192 return true; 193 } 194 if (!(o instanceof final BlockBucket other)) { 195 return false; 196 } 197 final Object this$input = this.input; 198 final Object other$input = other.input; 199 return Objects.equals(this$input, other$input); 200 } 201 202 public int hashCode() { 203 final int PRIME = 59; 204 int result = 1; 205 final Object $input = this.input; 206 result = result * PRIME + ($input == null ? 43 : $input.hashCode()); 207 return result; 208 } 209 210 private static final class Range { 211 212 private final int min; 213 private final int max; 214 private final boolean automatic; 215 216 public Range(int min, int max, boolean automatic) { 217 this.min = min; 218 this.max = max; 219 this.automatic = automatic; 220 } 221 222 public int getWeight() { 223 return max - min; 224 } 225 226 public boolean isInRange(final int num) { 227 return num <= max && num >= min; 228 } 229 230 public int getMin() { 231 return this.min; 232 } 233 234 public int getMax() { 235 return this.max; 236 } 237 238 public boolean equals(final Object o) { 239 if (o == this) { 240 return true; 241 } 242 if (!(o instanceof final Range other)) { 243 return false; 244 } 245 if (this.getMin() != other.getMin()) { 246 return false; 247 } 248 if (this.getMax() != other.getMax()) { 249 return false; 250 } 251 if (this.isAutomatic() != other.isAutomatic()) { 252 return false; 253 } 254 return true; 255 } 256 257 public int hashCode() { 258 final int PRIME = 59; 259 int result = 1; 260 result = result * PRIME + this.getMin(); 261 result = result * PRIME + this.getMax(); 262 result = result * PRIME + (this.isAutomatic() ? 79 : 97); 263 return result; 264 } 265 266 public boolean isAutomatic() { 267 return this.automatic; 268 } 269 270 } 271 272}