rewrote weighted pocket system. Pockets now get supplied additional information to calculate their weight
This commit is contained in:
parent
4269ed289e
commit
0af4b02a56
9 changed files with 126 additions and 95 deletions
|
@ -8,6 +8,7 @@ import org.dimdev.dimdoors.DimensionalDoorsInitializer;
|
||||||
import org.dimdev.dimdoors.ModConfig;
|
import org.dimdev.dimdoors.ModConfig;
|
||||||
import org.dimdev.dimdoors.rift.registry.LinkProperties;
|
import org.dimdev.dimdoors.rift.registry.LinkProperties;
|
||||||
import org.dimdev.dimdoors.rift.targets.VirtualTarget;
|
import org.dimdev.dimdoors.rift.targets.VirtualTarget;
|
||||||
|
import org.dimdev.dimdoors.util.PocketGenerationParameters;
|
||||||
import org.dimdev.dimdoors.world.level.DimensionalRegistry;
|
import org.dimdev.dimdoors.world.level.DimensionalRegistry;
|
||||||
import org.dimdev.dimdoors.world.ModDimensions;
|
import org.dimdev.dimdoors.world.ModDimensions;
|
||||||
import org.dimdev.dimdoors.world.pocket.Pocket;
|
import org.dimdev.dimdoors.world.pocket.Pocket;
|
||||||
|
@ -59,11 +60,12 @@ public final class PocketGenerator {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Pocket generateRandomPocketFromGroupV2(ServerWorld world, String group, VirtualLocation virtualLocation, VirtualTarget linkTo, LinkProperties linkProperties) {
|
public static Pocket generateRandomPocketFromGroupV2(ServerWorld world, String group, VirtualLocation virtualLocation, VirtualTarget linkTo, LinkProperties linkProperties) {
|
||||||
return generatePocketV2(world, SchematicV2Handler.getInstance().getRandomPocketFromGroup(group), virtualLocation, linkTo, linkProperties);
|
PocketGenerationParameters parameters = new PocketGenerationParameters(world, group, virtualLocation, linkTo, linkProperties);
|
||||||
|
return generatePocketV2(SchematicV2Handler.getInstance().getRandomPocketFromGroup(group, parameters), parameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Pocket generatePocketV2(ServerWorld world, VirtualPocket virtualPocket, VirtualLocation virtualLocation, VirtualTarget linkTo, LinkProperties linkProperties) {
|
public static Pocket generatePocketV2(VirtualPocket virtualPocket, PocketGenerationParameters parameters) {
|
||||||
return virtualPocket.prepareAndPlacePocket(world, virtualLocation, linkTo, linkProperties);
|
return virtualPocket.prepareAndPlacePocket(parameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -32,7 +32,7 @@ public class PocketTemplateV2 {
|
||||||
private final int size;
|
private final int size;
|
||||||
private final String id;
|
private final String id;
|
||||||
|
|
||||||
public PocketTemplateV2(Schematic schematic, String group, int size, String id, float weight) {
|
public PocketTemplateV2(Schematic schematic, String group, int size, String id) {
|
||||||
this.schematic = schematic;
|
this.schematic = schematic;
|
||||||
this.group = group;
|
this.group = group;
|
||||||
this.size = size;
|
this.size = size;
|
||||||
|
|
|
@ -16,7 +16,8 @@ import com.mojang.serialization.JsonOps;
|
||||||
import net.minecraft.util.Identifier;
|
import net.minecraft.util.Identifier;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
import org.dimdev.dimdoors.util.WeightedSet;
|
import org.dimdev.dimdoors.util.PocketGenerationParameters;
|
||||||
|
import org.dimdev.dimdoors.util.WeightedList;
|
||||||
import org.dimdev.dimdoors.util.schematic.v2.Schematic;
|
import org.dimdev.dimdoors.util.schematic.v2.Schematic;
|
||||||
|
|
||||||
import net.minecraft.nbt.CompoundTag;
|
import net.minecraft.nbt.CompoundTag;
|
||||||
|
@ -27,7 +28,7 @@ public class SchematicV2Handler {
|
||||||
private static final Gson GSON = new GsonBuilder().setLenient().setPrettyPrinting().create();
|
private static final Gson GSON = new GsonBuilder().setLenient().setPrettyPrinting().create();
|
||||||
private static final SchematicV2Handler INSTANCE = new SchematicV2Handler();
|
private static final SchematicV2Handler INSTANCE = new SchematicV2Handler();
|
||||||
private final Map<Identifier, PocketTemplateV2> templates = Maps.newHashMap();
|
private final Map<Identifier, PocketTemplateV2> templates = Maps.newHashMap();
|
||||||
private final Map<String, WeightedSet<VirtualPocket>> templateMap = Maps.newHashMap(); //TODO: un-ugly-fy
|
private final Map<String, WeightedList<VirtualPocket, PocketGenerationParameters>> templateMap = Maps.newHashMap(); //TODO: un-ugly-fy
|
||||||
private final List<PocketGroup> pocketTypes = Lists.newArrayList();
|
private final List<PocketGroup> pocketTypes = Lists.newArrayList();
|
||||||
private static final Random RANDOM = new Random(new Random().nextLong());
|
private static final Random RANDOM = new Random(new Random().nextLong());
|
||||||
private boolean loaded = false;
|
private boolean loaded = false;
|
||||||
|
@ -70,17 +71,17 @@ public class SchematicV2Handler {
|
||||||
*/
|
*/
|
||||||
private void loadResourceSchematics(PocketGroup type) throws URISyntaxException, IOException {
|
private void loadResourceSchematics(PocketGroup type) throws URISyntaxException, IOException {
|
||||||
String group = type.getGroup();
|
String group = type.getGroup();
|
||||||
WeightedSet<VirtualPocket> weightedPockets = new WeightedSet<>();
|
WeightedList<VirtualPocket, PocketGenerationParameters> weightedPockets = new WeightedList<>();
|
||||||
templateMap.put(group, weightedPockets);
|
templateMap.put(group, weightedPockets);
|
||||||
Path basePath = Paths.get(SchematicV2Handler.class.getResource(String.format("/data/dimdoors/pockets/schematic/v2/%s/", group)).toURI());
|
Path basePath = Paths.get(SchematicV2Handler.class.getResource(String.format("/data/dimdoors/pockets/schematic/v2/%s/", group)).toURI());
|
||||||
for (VirtualPocket virtualPocket : type.getEntries()) {
|
for (VirtualPocket virtualPocket : type.getEntries()) {
|
||||||
weightedPockets.add(virtualPocket, virtualPocket.getWeight());
|
weightedPockets.add(virtualPocket);
|
||||||
if (virtualPocket instanceof VirtualSchematicPocket) {
|
if (virtualPocket instanceof VirtualSchematicPocket) {
|
||||||
VirtualSchematicPocket schemPocket = (VirtualSchematicPocket) virtualPocket;
|
VirtualSchematicPocket schemPocket = (VirtualSchematicPocket) virtualPocket;
|
||||||
Path schemPath = basePath.resolve(schemPocket.getName() + ".schem");
|
Path schemPath = basePath.resolve(schemPocket.getName() + ".schem");
|
||||||
CompoundTag schemTag = NbtIo.readCompressed(Files.newInputStream(schemPath));
|
CompoundTag schemTag = NbtIo.readCompressed(Files.newInputStream(schemPath));
|
||||||
Schematic schematic = Schematic.fromTag(schemTag);
|
Schematic schematic = Schematic.fromTag(schemTag);
|
||||||
PocketTemplateV2 template = new PocketTemplateV2(schematic, group, schemPocket.getSize(), schemPocket.getName(), schemPocket.getWeight());
|
PocketTemplateV2 template = new PocketTemplateV2(schematic, group, schemPocket.getSize(), schemPocket.getName());
|
||||||
Identifier templateID = new Identifier("dimdoors", schemPocket.getName());
|
Identifier templateID = new Identifier("dimdoors", schemPocket.getName());
|
||||||
templates.put(templateID, template);
|
templates.put(templateID, template);
|
||||||
schemPocket.setTemplateID(templateID);
|
schemPocket.setTemplateID(templateID);
|
||||||
|
@ -88,16 +89,16 @@ public class SchematicV2Handler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public VirtualPocket getRandomPublicPocket() {
|
public VirtualPocket getRandomPublicPocket(PocketGenerationParameters parameters) {
|
||||||
return getRandomPocketFromGroup("public");
|
return getRandomPocketFromGroup("public", parameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
public VirtualPocket getRandomPrivatePocket() {
|
public VirtualPocket getRandomPrivatePocket(PocketGenerationParameters parameters) {
|
||||||
return getRandomPocketFromGroup("private");
|
return getRandomPocketFromGroup("private", parameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
public VirtualPocket getRandomPocketFromGroup(String group) {
|
public VirtualPocket getRandomPocketFromGroup(String group, PocketGenerationParameters parameters) {
|
||||||
return templateMap.get(group).getRandomWeighted();
|
return templateMap.get(group).getRandomWeighted(parameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static SchematicV2Handler getInstance() {
|
public static SchematicV2Handler getInstance() {
|
||||||
|
|
|
@ -10,11 +10,17 @@ import net.minecraft.util.registry.RegistryKey;
|
||||||
import net.minecraft.util.registry.SimpleRegistry;
|
import net.minecraft.util.registry.SimpleRegistry;
|
||||||
import org.dimdev.dimdoors.rift.registry.LinkProperties;
|
import org.dimdev.dimdoors.rift.registry.LinkProperties;
|
||||||
import org.dimdev.dimdoors.rift.targets.VirtualTarget;
|
import org.dimdev.dimdoors.rift.targets.VirtualTarget;
|
||||||
|
import org.dimdev.dimdoors.util.PocketGenerationParameters;
|
||||||
|
import org.dimdev.dimdoors.util.Weighted;
|
||||||
import org.dimdev.dimdoors.world.pocket.Pocket;
|
import org.dimdev.dimdoors.world.pocket.Pocket;
|
||||||
import org.dimdev.dimdoors.world.pocket.VirtualLocation;
|
import org.dimdev.dimdoors.world.pocket.VirtualLocation;
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
// maybe PocketEntry is a better name? Only realised after I named this that the previous PocketEntry would be redundant.
|
// maybe PocketEntry is a better name? Only realised after I named this that the previous PocketEntry would be redundant.
|
||||||
public abstract class VirtualPocket {
|
public abstract class VirtualPocket implements Weighted<PocketGenerationParameters> {
|
||||||
public static final Registry<VirtualPocketType<? extends VirtualPocket>> REGISTRY = FabricRegistryBuilder.from(new SimpleRegistry<VirtualPocketType<? extends VirtualPocket>>(RegistryKey.ofRegistry(new Identifier("dimdoors", "virtual_pocket_type")), Lifecycle.stable())).buildAndRegister();
|
public static final Registry<VirtualPocketType<? extends VirtualPocket>> REGISTRY = FabricRegistryBuilder.from(new SimpleRegistry<VirtualPocketType<? extends VirtualPocket>>(RegistryKey.ofRegistry(new Identifier("dimdoors", "virtual_pocket_type")), Lifecycle.stable())).buildAndRegister();
|
||||||
public static final Codec<VirtualPocket> CODEC = new Codec<VirtualPocket>() {
|
public static final Codec<VirtualPocket> CODEC = new Codec<VirtualPocket>() {
|
||||||
@Override
|
@Override
|
||||||
|
@ -30,12 +36,10 @@ public abstract class VirtualPocket {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
public abstract Pocket prepareAndPlacePocket(ServerWorld world, VirtualLocation virtualLocation, VirtualTarget linkTo, LinkProperties linkProperties);
|
public abstract Pocket prepareAndPlacePocket(PocketGenerationParameters parameters);
|
||||||
public abstract String toString();
|
public abstract String toString();
|
||||||
// TODO: are equals() and hashCode() necessary?
|
// TODO: are equals() and hashCode() necessary?
|
||||||
public abstract VirtualPocketType<? extends VirtualPocket> getType();
|
public abstract VirtualPocketType<? extends VirtualPocket> getType();
|
||||||
public abstract int getWeight();
|
|
||||||
|
|
||||||
|
|
||||||
public interface VirtualPocketType<T extends VirtualPocket> {
|
public interface VirtualPocketType<T extends VirtualPocket> {
|
||||||
VirtualPocketType<VirtualSchematicPocket> SCHEMATIC = register("dimdoors:schematic", VirtualSchematicPocket.CODEC);
|
VirtualPocketType<VirtualSchematicPocket> SCHEMATIC = register("dimdoors:schematic", VirtualSchematicPocket.CODEC);
|
||||||
|
|
|
@ -8,7 +8,8 @@ import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
import org.dimdev.dimdoors.rift.registry.LinkProperties;
|
import org.dimdev.dimdoors.rift.registry.LinkProperties;
|
||||||
import org.dimdev.dimdoors.rift.targets.VirtualTarget;
|
import org.dimdev.dimdoors.rift.targets.VirtualTarget;
|
||||||
import org.dimdev.dimdoors.util.DimensionalRegistry;
|
import org.dimdev.dimdoors.util.PocketGenerationParameters;
|
||||||
|
import org.dimdev.dimdoors.world.level.DimensionalRegistry;
|
||||||
import org.dimdev.dimdoors.world.pocket.Pocket;
|
import org.dimdev.dimdoors.world.pocket.Pocket;
|
||||||
import org.dimdev.dimdoors.world.pocket.VirtualLocation;
|
import org.dimdev.dimdoors.world.pocket.VirtualLocation;
|
||||||
|
|
||||||
|
@ -18,7 +19,7 @@ public class VirtualSchematicPocket extends VirtualPocket{
|
||||||
public static final Codec<VirtualSchematicPocket> CODEC = RecordCodecBuilder.create(instance -> instance.group(
|
public static final Codec<VirtualSchematicPocket> CODEC = RecordCodecBuilder.create(instance -> instance.group(
|
||||||
Codec.INT.fieldOf("size").forGetter(VirtualSchematicPocket::getSize),
|
Codec.INT.fieldOf("size").forGetter(VirtualSchematicPocket::getSize),
|
||||||
Codec.STRING.fieldOf("id").forGetter(VirtualSchematicPocket::getName),
|
Codec.STRING.fieldOf("id").forGetter(VirtualSchematicPocket::getName),
|
||||||
Codec.INT.optionalFieldOf("weight", 5).forGetter(VirtualSchematicPocket::getWeight)
|
Codec.INT.optionalFieldOf("weight", 5).forGetter(virtualSchematicPocket -> virtualSchematicPocket.getWeight(null))
|
||||||
).apply(instance, VirtualSchematicPocket::new));
|
).apply(instance, VirtualSchematicPocket::new));
|
||||||
|
|
||||||
private final int size;
|
private final int size;
|
||||||
|
@ -49,15 +50,17 @@ public class VirtualSchematicPocket extends VirtualPocket{
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getWeight() {
|
public int getWeight(PocketGenerationParameters parameters){
|
||||||
return this.weight;
|
return this.weight;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Pocket prepareAndPlacePocket(ServerWorld world, VirtualLocation virtualLocation, VirtualTarget linkTo, LinkProperties linkProperties) {
|
public Pocket prepareAndPlacePocket(PocketGenerationParameters parameters) {
|
||||||
|
ServerWorld world = parameters.getWorld();
|
||||||
|
VirtualLocation virtualLocation = parameters.getVirtualLocation();
|
||||||
|
VirtualTarget linkTo = parameters.getLinkTo();
|
||||||
|
LinkProperties linkProperties = parameters.getLinkProperties();
|
||||||
|
|
||||||
PocketTemplateV2 template = SchematicV2Handler.getInstance().getTemplates().get(templateID);
|
PocketTemplateV2 template = SchematicV2Handler.getInstance().getTemplates().get(templateID);
|
||||||
if (template == null) throw new RuntimeException("Pocket template of id " + templateID + " not found!");
|
if (template == null) throw new RuntimeException("Pocket template of id " + templateID + " not found!");
|
||||||
LOGGER.info("Generating pocket from template " + template.getId() + " at virtual location " + virtualLocation);
|
LOGGER.info("Generating pocket from template " + template.getId() + " at virtual location " + virtualLocation);
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
package org.dimdev.dimdoors.util;
|
||||||
|
|
||||||
|
import net.minecraft.server.world.ServerWorld;
|
||||||
|
import org.dimdev.dimdoors.rift.registry.LinkProperties;
|
||||||
|
import org.dimdev.dimdoors.rift.targets.VirtualTarget;
|
||||||
|
import org.dimdev.dimdoors.world.pocket.VirtualLocation;
|
||||||
|
|
||||||
|
public class PocketGenerationParameters {
|
||||||
|
private final ServerWorld world;
|
||||||
|
private final String group;
|
||||||
|
private final VirtualLocation virtualLocation;
|
||||||
|
private final VirtualTarget linkTo;
|
||||||
|
private final LinkProperties linkProperties;
|
||||||
|
|
||||||
|
public PocketGenerationParameters(ServerWorld world, String group, VirtualLocation virtualLocation, VirtualTarget linkTo, LinkProperties linkProperties) {
|
||||||
|
this.world = world;
|
||||||
|
this.group = group;
|
||||||
|
this.virtualLocation = virtualLocation;
|
||||||
|
this.linkTo = linkTo;
|
||||||
|
this.linkProperties = linkProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServerWorld getWorld() {
|
||||||
|
return world;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getGroup() {
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
|
||||||
|
public VirtualLocation getVirtualLocation() {
|
||||||
|
return virtualLocation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public VirtualTarget getLinkTo() {
|
||||||
|
return linkTo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LinkProperties getLinkProperties() {
|
||||||
|
return linkProperties;
|
||||||
|
}
|
||||||
|
}
|
9
src/main/java/org/dimdev/dimdoors/util/Weighted.java
Normal file
9
src/main/java/org/dimdev/dimdoors/util/Weighted.java
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
package org.dimdev.dimdoors.util;
|
||||||
|
|
||||||
|
public interface Weighted<P> {
|
||||||
|
/*
|
||||||
|
Should always return the same number if the same parameters are provided.
|
||||||
|
returned number should always be >= 0
|
||||||
|
*/
|
||||||
|
int getWeight(P parameters);
|
||||||
|
}
|
39
src/main/java/org/dimdev/dimdoors/util/WeightedList.java
Normal file
39
src/main/java/org/dimdev/dimdoors/util/WeightedList.java
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
package org.dimdev.dimdoors.util;
|
||||||
|
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public class WeightedList<T extends Weighted<P>, P> {
|
||||||
|
private final List<T> list;
|
||||||
|
private final Random random = new Random();
|
||||||
|
|
||||||
|
public WeightedList() {
|
||||||
|
this.list = Lists.newArrayList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public T getRandomWeighted(P parameters) {
|
||||||
|
int totalWeight = list.stream().mapToInt(weighted -> weighted.getWeight(parameters)).sum();
|
||||||
|
int cursor = random.nextInt(totalWeight);
|
||||||
|
for (T weighted : list) {
|
||||||
|
cursor -= weighted.getWeight(parameters);
|
||||||
|
if (cursor <= 0) {
|
||||||
|
return weighted; // should never return an entry with weight 0, unless there are only weight 0 entries
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean add(T t) {
|
||||||
|
return list.add(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean remove(T t){
|
||||||
|
return list.remove(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: make weightedList implement List instead
|
||||||
|
public List<T> getList() {
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,69 +0,0 @@
|
||||||
package org.dimdev.dimdoors.util;
|
|
||||||
|
|
||||||
import net.minecraft.util.Pair;
|
|
||||||
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
public class WeightedSet<T> {
|
|
||||||
private final TreeSet<Pair<T, Integer>> set;
|
|
||||||
private final int defaultWeight;
|
|
||||||
private int totalWeight = 0;
|
|
||||||
private boolean dirty = false;
|
|
||||||
private final Random random = new Random();
|
|
||||||
|
|
||||||
public WeightedSet() {
|
|
||||||
this(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
//TODO: ensure default Weight is >= 0?
|
|
||||||
public WeightedSet(int defaultWeight) {
|
|
||||||
this.set = new TreeSet<>((pair1, pair2) -> pair2.getRight().compareTo(pair1.getRight()));
|
|
||||||
this.defaultWeight = defaultWeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void markDirty() {
|
|
||||||
dirty = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateTotalWeight() {
|
|
||||||
if (dirty) totalWeight = set.stream().mapToInt(Pair::getRight).sum();
|
|
||||||
}
|
|
||||||
|
|
||||||
public T getRandomWeighted() {
|
|
||||||
updateTotalWeight();
|
|
||||||
int cursor = random.nextInt(totalWeight);
|
|
||||||
for (Pair<T, Integer> pair : set) {
|
|
||||||
cursor -= pair.getRight();
|
|
||||||
if (cursor <= 0) {
|
|
||||||
return pair.getLeft(); // should never return an entry with weight 0, unless there are only weight 0 entries
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw new RuntimeException(); // either the list is empty, or it somehow
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: ensure weight is >= 0? How about a negativeWeightException?
|
|
||||||
public boolean add(T t, Integer weight) {
|
|
||||||
if (set.add(new Pair<>(t, weight))) {
|
|
||||||
markDirty();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean add(T t) {
|
|
||||||
return add(t, defaultWeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean remove(T t){
|
|
||||||
if (set.remove(set.stream().filter(pair -> pair.getLeft().equals(t)).findFirst().orElse(null))) {
|
|
||||||
markDirty();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<T> getObjectList() {
|
|
||||||
return set.stream().map(Pair::getLeft).collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in a new issue