first commit (checkpoint before destruction)

This commit is contained in:
2020-04-21 19:29:46 +02:00
commit e5a415ddd5
24 changed files with 1265 additions and 0 deletions

View File

@@ -0,0 +1,292 @@
package quimufu.simple_creator;
import com.google.common.collect.Maps;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonObject;
import net.minecraft.block.Block;
import net.minecraft.block.Material;
import net.minecraft.block.MaterialColor;
import net.minecraft.block.piston.PistonBehavior;
import net.minecraft.item.BlockItem;
import net.minecraft.item.Item;
import net.minecraft.item.ItemGroup;
import net.minecraft.sound.BlockSoundGroup;
import net.minecraft.util.Identifier;
import net.minecraft.util.JsonHelper;
import net.minecraft.util.Pair;
import net.minecraft.util.registry.Registry;
import org.apache.logging.log4j.Level;
import java.lang.reflect.Field;
import java.util.Map;
import static quimufu.simple_creator.SimpleCreatorMod.log;
public class BlockResourceLoader extends GenericManualResourceLoader<Pair<Block, Item>> {
private static final Gson GSON = (new GsonBuilder()).setPrettyPrinting().disableHtmlEscaping().create();
private static final String dataType = "blocks";
public static Map<Identifier, Block> blocks = Maps.newHashMap();
public static Map<Identifier, Item> blockItems = Maps.newHashMap();
BlockResourceLoader() {
super(GSON, dataType);
}
@Override
protected void register(Identifier id, Pair<Block, Item> thing) {
Registry.register(Registry.BLOCK, id, thing.getLeft());
Registry.register(Registry.ITEM, id, thing.getRight());
}
@Override
protected void save(Identifier id, Pair<Block, Item> thing) {
blocks.put(id, thing.getLeft());
blockItems.put(id, thing.getRight());
}
@Override
protected Pair<Block, Item> deserialize(Pair<Identifier, JsonObject> e) {
JsonObject jo = e.getRight();
PistonBehavior pistonBehavior;
boolean blocksMovement;
boolean burnable;
boolean breakByHand;
boolean liquid;
boolean replaceable;
boolean solid;
MaterialColor color;
boolean blocksLight;
Material material;
String materialStr = JsonHelper.getString(jo, "material", "stone");
material = getMaterial(materialStr);
// construct block information
BlockSoundGroup soundGroup;
Identifier dropTableId;
boolean collidable;
int luminance;
float resistance;
float hardness;
float slipperiness;
float slowDownMultiplier;
float jumpVelocityMultiplier;
boolean opaque;
String soundGroupStr = JsonHelper.getString(jo, "soundGroup", "stone");
soundGroup = getSoundGroup(soundGroupStr);
String dropTableIdStr = JsonHelper.getString(jo, "dropTableId", null);
dropTableId = getDropTableId(dropTableIdStr);
collidable = JsonHelper.getBoolean(jo, "collidable", true);
luminance = JsonHelper.getInt(jo, "lightLevel", 0);
resistance = JsonHelper.getFloat(jo, "explosionResistance", 6.0F);
hardness = JsonHelper.getFloat(jo, "hardness", 1.5F);
slipperiness = JsonHelper.getFloat(jo, "slipperiness", 0.6F);
slowDownMultiplier = JsonHelper.getFloat(jo, "slowDownMultiplier", 1.0F);
jumpVelocityMultiplier = JsonHelper.getFloat(jo, "jumpVelocityMultiplier", 1.0F);
opaque = JsonHelper.getBoolean(jo, "opaque", true);
// save block information in Block.Settings (!!hacky!!)
Block.Settings bs = Block.Settings.of(material, material.getColor());
Field[] fields = Block.Settings.class.getDeclaredFields();
try {
fields[0].setAccessible(true);
fields[0].set(bs, material);
fields[1].setAccessible(true);
fields[1].set(bs, material.getColor());
fields[2].setAccessible(true);
fields[2].setBoolean(bs, collidable);
fields[3].setAccessible(true);
fields[3].set(bs, soundGroup);
fields[4].setAccessible(true);
fields[4].setInt(bs, luminance);
fields[5].setAccessible(true);
fields[5].setFloat(bs, resistance);
fields[6].setAccessible(true);
fields[6].setFloat(bs, hardness);
fields[8].setAccessible(true);
fields[8].setFloat(bs, slipperiness);
fields[9].setAccessible(true);
fields[9].setFloat(bs, slowDownMultiplier);
fields[10].setAccessible(true);
fields[10].setFloat(bs, jumpVelocityMultiplier);
fields[11].setAccessible(true);
fields[11].set(bs, dropTableId);
fields[12].setAccessible(true);
fields[12].setBoolean(bs, opaque);
} catch (IllegalAccessException ex) {
ex.printStackTrace();
}
// parse item group
String group = JsonHelper.getString(jo, "group", "misc");
ItemGroup g = ItemResourceLoader.findGroup(group);
//create block and corresponding item
Block resB = new Block(bs);
Item resI = new BlockItem(resB, new Item.Settings().group(g));
return new Pair<>(resB, resI);
}
private Identifier getDropTableId(String s) {
if (s == null)
return null;
Identifier i = Identifier.tryParse(s);
if (i == null) {
log(Level.WARN, "Drop table invalid " + s + ", using default");
i = null;
}
return i;
}
private BlockSoundGroup getSoundGroup(String s) {
switch (s.toUpperCase()) {
case "WOOD":
return BlockSoundGroup.WOOD;
case "GRAVEL":
return BlockSoundGroup.GRAVEL;
case "GRASS":
return BlockSoundGroup.GRASS;
case "STONE":
return BlockSoundGroup.STONE;
case "METAL":
return BlockSoundGroup.METAL;
case "GLASS":
return BlockSoundGroup.GLASS;
case "WOOL":
return BlockSoundGroup.WOOL;
case "SAND":
return BlockSoundGroup.SAND;
case "SNOW":
return BlockSoundGroup.SNOW;
case "LADDER":
return BlockSoundGroup.LADDER;
case "ANVIL":
return BlockSoundGroup.ANVIL;
case "SLIME":
return BlockSoundGroup.SLIME;
case "HONEY":
return BlockSoundGroup.HONEY;
case "WET_GRASS":
return BlockSoundGroup.WET_GRASS;
case "CORAL":
return BlockSoundGroup.CORAL;
case "BAMBOO":
return BlockSoundGroup.BAMBOO;
case "BAMBOO_SAPLING":
return BlockSoundGroup.BAMBOO_SAPLING;
case "SCAFFOLDING":
return BlockSoundGroup.SCAFFOLDING;
case "SWEET_BERRY_BUSH":
return BlockSoundGroup.SWEET_BERRY_BUSH;
case "CROP":
return BlockSoundGroup.CROP;
case "STEM":
return BlockSoundGroup.STEM;
case "NETHER_WART":
return BlockSoundGroup.NETHER_WART;
case "LANTERN":
return BlockSoundGroup.LANTERN;
default:
log(Level.WARN, "Sound group " + s + " not found, using stone");
return BlockSoundGroup.STONE;
}
}
private Material getMaterial(String s) {
switch (s.toUpperCase()) {
case "AIR":
return Material.AIR;
case "STRUCTURE_VOID":
return Material.STRUCTURE_VOID;
case "PORTAL":
return Material.PORTAL;
case "CARPET":
return Material.CARPET;
case "PLANT":
return Material.PLANT;
case "UNDERWATER_PLANT":
return Material.UNDERWATER_PLANT;
case "REPLACEABLE_PLANT":
return Material.REPLACEABLE_PLANT;
case "SEAGRASS":
return Material.SEAGRASS;
case "WATER":
return Material.WATER;
case "BUBBLE_COLUMN":
return Material.BUBBLE_COLUMN;
case "LAVA":
return Material.LAVA;
case "SNOW":
return Material.SNOW;
case "FIRE":
return Material.FIRE;
case "PART":
return Material.PART;
case "COBWEB":
return Material.COBWEB;
case "REDSTONE_LAMP":
return Material.REDSTONE_LAMP;
case "CLAY":
return Material.CLAY;
case "EARTH":
return Material.EARTH;
case "ORGANIC":
return Material.ORGANIC;
case "PACKED_ICE":
return Material.PACKED_ICE;
case "SAND":
return Material.SAND;
case "SPONGE":
return Material.SPONGE;
case "SHULKER_BOX":
return Material.SHULKER_BOX;
case "WOOD":
return Material.WOOD;
case "BAMBOO_SAPLING":
return Material.BAMBOO_SAPLING;
case "BAMBOO":
return Material.BAMBOO;
case "WOOL":
return Material.WOOL;
case "TNT":
return Material.TNT;
case "LEAVES":
return Material.LEAVES;
case "GLASS":
return Material.GLASS;
case "ICE":
return Material.ICE;
case "CACTUS":
return Material.CACTUS;
case "STONE":
return Material.STONE;
case "METAL":
return Material.METAL;
case "SNOW_BLOCK":
return Material.SNOW_BLOCK;
case "ANVIL":
return Material.ANVIL;
case "BARRIER":
return Material.BARRIER;
case "PISTON":
return Material.PISTON;
case "UNUSED_PLANT":
return Material.UNUSED_PLANT;
case "PUMPKIN":
return Material.PUMPKIN;
case "EGG":
return Material.EGG;
case "CAKE":
return Material.CAKE;
default:
log(Level.WARN, "Material " + s + " not found, using stone");
return Material.STONE;
}
}
}

View File

@@ -0,0 +1,124 @@
/*package quimufu.simple_creator;
import net.minecraft.block.Block;
import net.minecraft.block.Material;
import net.minecraft.block.MaterialColor;
import net.minecraft.sound.BlockSoundGroup;
import net.minecraft.util.Identifier;
public class FlexibleBlockSettings extends Block {
public Material getMaterial() {
return material;
}
public BlockSoundGroup getSoundGroup() {
return soundGroup;
}
@Override
public Identifier getDropTableId() {
return dropTableId;
}
public void setDropTableId(Identifier dropTableId) {
this.dropTableId = dropTableId;
}
public boolean isCollidable() {
return collidable;
}
public void setCollidable(boolean collidable) {
this.collidable = collidable;
}
public int getLuminance() {
return luminance;
}
public void setLuminance(int luminance) {
this.luminance = luminance;
}
public float getResistance() {
return resistance;
}
public void setResistance(float resistance) {
this.resistance = resistance;
}
public float getHardness() {
return hardness;
}
public void setHardness(float hardness) {
this.hardness = hardness;
}
@Override
public float getSlipperiness() {
return slipperiness;
}
public void setSlipperiness(float slipperiness) {
this.slipperiness = slipperiness;
}
public float getSlowDownMultiplier() {
return slowDownMultiplier;
}
public void setSlowDownMultiplier(float slowDownMultiplier) {
this.slowDownMultiplier = slowDownMultiplier;
}
@Override
public float getJumpVelocityMultiplier() {
return jumpVelocityMultiplier;
}
public void setJumpVelocityMultiplier(float jumpVelocityMultiplier) {
this.jumpVelocityMultiplier = jumpVelocityMultiplier;
}
public boolean isOpaque() {
return opaque;
}
public void setOpaque(boolean opaque) {
this.opaque = opaque;
}
public MaterialColor getMaterialColor() {
return materialColor;
}
public void setMaterialColor(MaterialColor materialColor) {
this.materialColor = materialColor;
}
public boolean isRandomTicks() {
return randomTicks;
}
public void setRandomTicks(boolean randomTicks) {
this.randomTicks = randomTicks;
}
public boolean isDynamicBounds() {
return dynamicBounds;
}
public void setDynamicBounds(boolean dynamicBounds) {
this.dynamicBounds = dynamicBounds;
}
public FlexibleBlockSettings() {
super(Settings.of(Material.AIR));
}
}
*/

View File

@@ -0,0 +1,33 @@
package quimufu.simple_creator;
import net.minecraft.block.Material;
import net.minecraft.block.MaterialColor;
import net.minecraft.block.piston.PistonBehavior;
public class FlexibleMaterialBuilder extends Material.Builder {
public FlexibleMaterialBuilder(MaterialColor color) {
super(color);
}
@Override
public Material.Builder blocksPistons() {
return super.blocksPistons();
}
@Override
public Material.Builder burnable() {
return super.burnable();
}
@Override
public Material.Builder destroyedByPiston() {
return super.destroyedByPiston();
}
@Override
public Material.Builder requiresTool() {
return super.requiresTool();
}
}

View File

@@ -0,0 +1,94 @@
package quimufu.simple_creator;
import com.google.common.collect.Lists;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import net.fabricmc.fabric.impl.resource.loader.ModResourcePackCreator;
import net.minecraft.resource.*;
import net.minecraft.util.Identifier;
import net.minecraft.util.JsonHelper;
import net.minecraft.util.Pair;
import org.apache.logging.log4j.Level;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import static quimufu.simple_creator.SimpleCreatorMod.log;
public abstract class GenericManualResourceLoader<T> {
private Gson GSON;
private String dataType;
GenericManualResourceLoader(Gson gson, String dt) {
GSON = gson;
dataType = dt;
}
protected void loadItems(ArrayList<Pair<Identifier, JsonObject>> itemJsonList) {
log(Level.INFO, "Start loading " + dataType);
for (Pair<Identifier, JsonObject> e : itemJsonList) {
Identifier id = e.getLeft();
log(Level.INFO, "Loading " + dataType.substring(0,dataType.length()-1) + " " + id);
T thing = deserialize(e);
save(id, thing);
log(Level.INFO, "Registering " + dataType.substring(0,dataType.length()-1) + " " + id);
register(id, thing);
}
log(Level.INFO, "Finished loading " + dataType);
}
protected abstract void register(Identifier id, T thing);
protected abstract T deserialize(Pair<Identifier, JsonObject> e);
protected abstract void save(Identifier id, T item);
public void load() {
ResourcePackManager<ResourcePackProfile> resourcePackManager = new ResourcePackManager<>(ResourcePackProfile::new);
resourcePackManager.registerProvider(new VanillaDataPackProvider());
resourcePackManager.registerProvider(new FileResourcePackProvider(new File("./datapacks")));
resourcePackManager.registerProvider(new ModResourcePackCreator(ResourceType.SERVER_DATA));
resourcePackManager.scanPacks();
List<ResourcePackProfile> ep = Lists.newArrayList(resourcePackManager.getEnabledProfiles());
for (ResourcePackProfile rpp : resourcePackManager.getProfiles()) {
if (!ep.contains(rpp)) {
rpp.getInitialPosition().insert(ep, rpp, resourcePackProfile -> resourcePackProfile, false);
}
}
resourcePackManager.setEnabledProfiles(ep);
ArrayList<Pair<Identifier, JsonObject>> itemJsonList = new ArrayList<>();
for (ResourcePackProfile rpp : resourcePackManager.getEnabledProfiles()) {
ResourcePack rp = rpp.createResourcePack();
log(Level.INFO, "Loading ResourcePack " + rp.getName());
for (String ns : rp.getNamespaces(ResourceType.SERVER_DATA)) {
log(Level.INFO, "Loading namespace " + ns);
Collection<Identifier> ressurces = rp.findResources(ResourceType.SERVER_DATA, ns, dataType, 5, s -> s.endsWith(".json"));
for (Identifier id : ressurces) {
Identifier idNice = new Identifier(id.getNamespace(), getName(id));
try {
InputStream is = rp.open(ResourceType.SERVER_DATA, id);
Reader r = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8));
JsonObject jo = JsonHelper.deserialize(GSON, r, JsonObject.class);
if (jo != null)
itemJsonList.add(new Pair<>(idNice, jo));
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
loadItems(itemJsonList);
}
private String getName(Identifier id) {
String path = id.getPath();
int startLength = dataType.length() + 1;
int endLength = ".json".length();
return path.substring(startLength, path.length() - endLength);
}
}

View File

@@ -0,0 +1,149 @@
package quimufu.simple_creator;
import com.google.common.collect.Maps;
import com.google.gson.*;
import net.minecraft.entity.effect.StatusEffect;
import net.minecraft.entity.effect.StatusEffectInstance;
import net.minecraft.item.FoodComponent;
import net.minecraft.item.Item;
import net.minecraft.item.ItemGroup;
import net.minecraft.util.Identifier;
import net.minecraft.util.JsonHelper;
import net.minecraft.util.Pair;
import net.minecraft.util.Rarity;
import net.minecraft.util.registry.Registry;
import org.apache.logging.log4j.Level;
import java.util.Arrays;
import java.util.Map;
import static quimufu.simple_creator.SimpleCreatorMod.log;
public class ItemResourceLoader extends GenericManualResourceLoader<Item> {
private static final Gson GSON = (new GsonBuilder()).setPrettyPrinting().disableHtmlEscaping().create();
private static final String dataType = "items";
public static Map<Identifier, Item> items = Maps.newHashMap();
ItemResourceLoader() {
super(GSON, dataType);
}
@Override
protected void register(Identifier id, Item thing) {
Registry.register(Registry.ITEM, id, thing);
}
public Item deserialize(Pair<Identifier, JsonObject> e) {
JsonObject jo = e.getRight();
String group = JsonHelper.getString(jo, "group", "misc");
ItemGroup g = findGroup(group);
int durability = JsonHelper.getInt(jo, "durability", 0);
byte stackSize = JsonHelper.getByte(jo, "stackSize", (byte) 1);
boolean isFood = JsonHelper.hasElement(jo, "food");
String rarity = JsonHelper.getString(jo, "rarity", "common");
Item.Settings settings = new Item.Settings();
settings.group(g);
if (isFood) {
if (durability != 0) {
log(Level.WARN, "durability does not work with food");
log(Level.WARN, "ignoring");
}
settings.maxCount(stackSize);
JsonObject jsonFoodObject = JsonHelper.getObject(jo, "food");
settings.food(deserializeFoodComponent(jsonFoodObject));
} else if (durability != 0 && stackSize != 1) {
log(Level.WARN, "durability and stackSize do not work together");
log(Level.WARN, "ignoring stackSize");
settings.maxDamage(durability);
} else {
if (durability != 0) {
settings.maxDamage(durability);
} else {
settings.maxCount(stackSize);
}
}
settings.rarity(findRarity(rarity));
return new Item(settings);
}
@Override
protected void save(Identifier id, Item item) {
items.put(id, item);
}
private static FoodComponent deserializeFoodComponent(JsonObject jo) {
FoodComponent fc;
FoodComponent.Builder fcb = new FoodComponent.Builder();
fcb.hunger(JsonHelper.getInt(jo, "hunger", 4));
fcb.saturationModifier(JsonHelper.getFloat(jo, "saturationModifier", 0.3F));
if (JsonHelper.getBoolean(jo, "isAlwaysEdible", false))
fcb.alwaysEdible();
if (JsonHelper.getBoolean(jo, "isWolfFood", false))
fcb.meat();
if (JsonHelper.getBoolean(jo, "isFast", false))
fcb.snack();
if (JsonHelper.hasArray(jo, "effects")) {
JsonArray jsonEffectsArray = JsonHelper.getArray(jo, "effects");
deserializeEffects(fcb, jsonEffectsArray);
}
fc = fcb.build();
return fc;
}
private static void deserializeEffects(FoodComponent.Builder fcb, JsonArray ja) {
for (JsonElement e : ja) {
StatusEffect type;
int duration = 0;
int amplifier = 0;
boolean ambient = false;
boolean visible = true;
float chance = 1.F;
JsonObject jo = JsonHelper.asObject(e, "effects");
String effect = JsonHelper.getString(jo, "effect");
Identifier ei = Identifier.tryParse(effect);
if (ei != null) {
StatusEffect se = Registry.STATUS_EFFECT.get(ei);
if (se != null) {
type = se;
} else {
log(Level.WARN, "Effect " + ei + " not found, skipping");
continue;
}
} else {
log(Level.WARN, "Effect id " + effect + " invalid, skipping");
continue;
}
duration = JsonHelper.getInt(jo, "duration", duration);
amplifier = JsonHelper.getInt(jo, "amplifier", amplifier);
ambient = JsonHelper.getBoolean(jo, "ambient", ambient);
visible = JsonHelper.getBoolean(jo, "visible", visible);
chance = JsonHelper.getFloat(jo, "chance", chance);
fcb.statusEffect(new StatusEffectInstance(type, duration, amplifier, ambient, visible), chance);
}
}
private static Rarity findRarity(String filter) {
for (Rarity r : Rarity.values()) {
if (r.name().toLowerCase().equals(filter.toLowerCase()))
return r;
}
log(Level.WARN, "Rarity " + filter + " not found, using common");
log(Level.INFO, "Valid groups:" + Arrays.stream(Rarity.values()).map(Rarity::name).map(String::toLowerCase));
return Rarity.COMMON;
}
public static ItemGroup findGroup(String filter) {
for (ItemGroup g : ItemGroup.GROUPS) {
if (g.getName().toLowerCase().equals(filter.toLowerCase())) {
return g;
}
}
log(Level.WARN, "Item Group " + filter + " not found, using misc");
log(Level.INFO, "Valid groups:" + Arrays.stream(ItemGroup.GROUPS).map(ItemGroup::getId));
return ItemGroup.MISC;
}
}

View File

@@ -0,0 +1,29 @@
package quimufu.simple_creator;
import net.fabricmc.api.ModInitializer;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class SimpleCreatorMod implements ModInitializer {
public static Logger LOGGER = LogManager.getLogger();
public static ItemResourceLoader irl = new ItemResourceLoader();
public static BlockResourceLoader brl = new BlockResourceLoader();
public static final String MOD_ID = "simple_creator";
public static final String MOD_NAME = "Simple Item/Block Creator";
@Override
public void onInitialize() {
log(Level.INFO, "Initializing");
irl.load();
brl.load();
}
public static void log(Level level, String message){
LOGGER.log(level, "["+MOD_NAME+"] " + message);
}
}

View File

@@ -0,0 +1,10 @@
{
"variants": {
"": [
{ "model": "block/bedrock" },
{ "model": "block/bedrock_mirrored" },
{ "model": "block/bedrock", "y": 180 },
{ "model": "block/bedrock_mirrored", "y": 180 }
]
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View File

@@ -0,0 +1,3 @@
{
"item.simple_creator.test_item": "Forbidden Fruit"
}

View File

@@ -0,0 +1,3 @@
{
"parent": "block/bedrock"
}

View File

@@ -0,0 +1,6 @@
{
"parent": "item/generated",
"textures": {
"layer0": "item/apple"
}
}

View File

@@ -0,0 +1,15 @@
{
"material": "LAVA",
"mapColor" :
"soundGroup": "wool",
"dropTableId": "minecraft:blocks/diamond_ore",
"collidable": true,
"lightLevel": 1,
"explosionResistance": 5.0,
"hardness": 0.5,
"slipperiness": 0.6,
"slowDownMultiplier": 2.0,
"jumpVelocityMultiplier": 2.0,
"opaque": true,
"group": "food"
}

View File

@@ -0,0 +1,23 @@
{
"group" :"food",
"durability" : 15,
"stackSize": 25,
"rarity" :"rare",
"food": {
"hunger": 4,
"saturationModifier": 0.3,
"isAlwaysEdible": true,
"isWolfFood": false,
"isFast": true,
"effects": [
{
"effect": "minecraft:poison",
"duration": 200,
"amplifier": 1,
"ambient": false,
"visible": true,
"chance": 1.0
}
]
}
}

View File

@@ -0,0 +1,19 @@
{
"type": "minecraft:block",
"pools": [
{
"rolls": 1,
"entries": [
{
"type": "minecraft:item",
"conditions": [
{
"condition": "minecraft:survives_explosion"
}
],
"name": "simple_creator:test_block"
}
]
}
]
}

View File

@@ -0,0 +1,27 @@
{
"schemaVersion": 1,
"id": "simple_creator",
"version": "${version}",
"name": "Simple Item/Block Creator",
"description": "Makes it possible to add simple blocks/items via datapacks.",
"authors": [
"QuImUfu"
],
"contributors": [],
"contact": {},
"license": "MIT",
"icon": "assets/simple_creator/icon.png",
"environment": "*",
"entrypoints": {
"main": [
"quimufu.simple_creator.SimpleCreatorMod"
],
"client": [],
"server": []
},
"mixins": [],
"depends": {
"fabricloader": ">=0.4.0",
"fabric": "*"
}
}