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}