Jump to content

Weird Crafting Table-Like Thing; Recipe Help


Hipposgrumm

Recommended Posts

I have been puzzling at this for almost 2 months now. I was wondering how I could make a recipe for something sort of like this:EggingTableDemo.png

It is like a mix of a crafting table mixed with a smithing table. I have slots working, but I don't know how to go about creating a (JSON compatible) recipe.

Here is an example of the JSON structure I would like to use:

{
  "type": "craftable_spawn_eggs:egging",
  "ingredients": [
    {"item":"minecraft:raw_beef"},
    {"tag":"forge:leather"},
    {"tag":"forge:crops/wheat"}
  ],
  "main_ingredient": [
    {"tag": "forge:eggs"}
  ],
  "output": {
    "item": "minecraft:cow_spawn_egg"
  }
}

Shapeless Recipe Code:

Spoiler

Frankensteined from Vanilla Code

package gg.hipposgrumm.craftable_spawn_eggs.recipes;

import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import gg.hipposgrumm.craftable_spawn_eggs.Main;
import it.unimi.dsi.fastutil.ints.IntList;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import net.minecraft.core.NonNullList;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.GsonHelper;
import net.minecraft.world.entity.player.StackedContents;
import net.minecraft.world.inventory.CraftingContainer;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.Ingredient;
import net.minecraft.world.item.crafting.RecipeSerializer;
import net.minecraft.world.level.Level;
import net.minecraftforge.common.crafting.CraftingHelper;
import net.minecraftforge.common.util.RecipeMatcher;

public class EggingRecipeShapeless implements EggingRecipe {
    static final int SIZE = 9;
    private final ResourceLocation id;
    final String group;
    final ItemStack result;
    final NonNullList<Ingredient> main_ingredient;
    final NonNullList<Ingredient> ingredients;
    private final boolean isSimple;

    public EggingRecipeShapeless(ResourceLocation id, String name, ItemStack output, NonNullList<Ingredient> ingredients, NonNullList<Ingredient> main_ingredient) {
        this.id = id;
        this.group = name;
        this.result = output;
        this.main_ingredient = main_ingredient;
        this.ingredients = ingredients;
        this.isSimple = ingredients.stream().allMatch(Ingredient::isSimple);
    }

    public ResourceLocation getId() {
        return this.id;
    }

    public RecipeSerializer<?> getSerializer() {
        return Main.EGGING_RECIPE.get();
    }

    public String getGroup() {
        return this.group;
    }

    public ItemStack getResultItem() {
        return this.result;
    }

    public NonNullList<Ingredient> getIngredients() {
        return this.ingredients;
    }

    public boolean matches(CraftingContainer container, Level world) {
        StackedContents stackedcontents = new StackedContents();
        List<ItemStack> inputs = new ArrayList();
        int i = 0;

        for(int j = 0; j < container.getContainerSize(); ++j) {
            ItemStack itemstack = container.getItem(j);
            if (!itemstack.isEmpty()) {
                ++i;
                if (this.isSimple) {
                    stackedcontents.accountStack(itemstack, 1);
                } else {
                    inputs.add(itemstack);
                }
            }
        }

        boolean var10000;
        label43: {
            if (i == this.ingredients.size()) {
                if (this.isSimple) {
                    if (stackedcontents.canCraft(this, (IntList)null)) {
                        break label43;
                    }
                } else if (RecipeMatcher.findMatches(inputs, this.ingredients) != null) {
                    break label43;
                }
            }

            var10000 = false;
            return var10000;
        }

        var10000 = true;
        return var10000;
    }

    private static ItemStack itemStackFromJson(JsonObject result) {
        return CraftingHelper.getItemStack(result, true, true);
    }

    public ItemStack assemble(CraftingContainer p_44260_) {
        return this.result.copy();
    }

    public boolean canCraftInDimensions(int p_44252_, int p_44253_) {
        return p_44252_ * p_44253_ >= this.ingredients.size();
    }

    public static class Serializer implements RecipeSerializer<EggingRecipeShapeless> {
        private static final ResourceLocation NAME = new ResourceLocation(Main.MODID, "egging");

        public Serializer() {}

        public EggingRecipeShapeless fromJson(ResourceLocation location, JsonObject recipe) {
            String s = GsonHelper.getAsString(recipe, "group", "");
            NonNullList<Ingredient> nonnulllist = itemsFromJson(GsonHelper.getAsJsonArray(recipe, "ingredients"));
            if (nonnulllist.isEmpty()) {
                throw new JsonParseException("No ingredients for shapeless recipe");
            } else if (nonnulllist.size() > EggingRecipeShapeless.SIZE) {
                throw new JsonParseException("Too many ingredients for shapeless recipe. The maximum is " + EggingRecipeShapeless.SIZE);
            } else {
                ItemStack itemstack = EggingRecipeShapeless.itemStackFromJson(GsonHelper.getAsJsonObject(recipe, "result"));
                return new EggingRecipeShapeless(location, s, itemstack, nonnulllist, itemsFromJson(GsonHelper.getAsJsonArray(recipe, "main_ingredient")));
            }
        }

        private static NonNullList<Ingredient> itemsFromJson(JsonArray p_44276_) {
            NonNullList<Ingredient> nonnulllist = NonNullList.create();

            for(int i = 0; i < p_44276_.size(); ++i) {
                Ingredient ingredient = Ingredient.fromJson(p_44276_.get(i));
                nonnulllist.add(ingredient);
            }

            return nonnulllist;
        }

        public EggingRecipeShapeless fromNetwork(ResourceLocation location, FriendlyByteBuf buf) {
            String s = buf.readUtf();
            int i = buf.readVarInt();
            NonNullList<Ingredient> nonnulllist = NonNullList.withSize(i, Ingredient.EMPTY);
            NonNullList<Ingredient> nonnullitem = NonNullList.withSize(1, Ingredient.fromNetwork(buf));

            for(int j = 0; j < nonnulllist.size(); ++j) {
                nonnulllist.set(j, Ingredient.fromNetwork(buf));
            }

            ItemStack itemstack = buf.readItem();
            return new EggingRecipeShapeless(location, s, itemstack, nonnulllist, nonnullitem);
        }

        public void toNetwork(FriendlyByteBuf data, EggingRecipeShapeless recipe) {
            data.writeUtf(recipe.group);
            data.writeVarInt(recipe.ingredients.size());
            Iterator var3 = recipe.ingredients.iterator();

            while(var3.hasNext()) {
                Ingredient ingredient = (Ingredient)var3.next();
                ingredient.toNetwork(data);
            }

            data.writeItem(recipe.result);
        }
    }
}

Shaped Recipe:

Spoiler

Also Frankensteined from Vanilla Code

package gg.hipposgrumm.craftable_spawn_eggs.recipes;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonSyntaxException;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import gg.hipposgrumm.craftable_spawn_eggs.Main;
import net.minecraft.core.NonNullList;
import net.minecraft.core.Registry;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.GsonHelper;
import net.minecraft.world.inventory.CraftingContainer;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.crafting.Ingredient;
import net.minecraft.world.item.crafting.RecipeSerializer;
import net.minecraft.world.level.Level;
import net.minecraftforge.common.ForgeHooks;
import net.minecraftforge.common.crafting.CraftingHelper;
import net.minecraftforge.common.crafting.IShapedRecipe;

public class EggingRecipeShaped implements EggingRecipe, IShapedRecipe<CraftingContainer> {
    static int MAX_WIDTH = 3;
    static int MAX_HEIGHT = 3;
    final int width;
    final int height;
    final NonNullList<Ingredient> recipeItems;
    final ItemStack result;
    private final ResourceLocation id;
    final String group;

    public static void setCraftingSize(int width, int height) {
        if (MAX_WIDTH < width) {MAX_WIDTH = width;}
        if (MAX_HEIGHT < height) {MAX_HEIGHT = height;}
    }

    public EggingRecipeShaped(ResourceLocation id, String group, int width, int height, NonNullList<Ingredient> inputs, ItemStack output) {
        this.id = id;
        this.group = group;
        this.width = width;
        this.height = height;
        this.recipeItems = inputs;
        this.result = output;
    }

    public ResourceLocation getId() {return this.id;}

    public RecipeSerializer<?> getSerializer() {return Main.EGGING_RECIPE_SHAPED.get();}

    public String getGroup() {return this.group;}

    public ItemStack getResultItem() {return this.result;}

    public NonNullList<Ingredient> getIngredients() {return this.recipeItems;}

    public boolean canCraftInDimensions(int width, int height) {
        return width >= this.width && height >= this.height;
    }

    public boolean matches(CraftingContainer container, Level world) {
        for(int i = 0; i <= container.getWidth() - this.width; ++i) {
            for(int j = 0; j <= container.getHeight() - this.height; ++j) {
                if (this.matches(container, i, j, true)) {return true;}
                if (this.matches(container, i, j, false)) {return true;}
            }
        }return false;
    }

    private boolean matches(CraftingContainer container, int posHorizontal, int posVertical, boolean isMatch) {
        for(int i = 0; i < container.getWidth(); ++i) {
            for(int j = 0; j < container.getHeight(); ++j) {
                int k = i - posHorizontal;
                int l = j - posVertical;
                Ingredient ingredient = Ingredient.EMPTY;
                if (k >= 0 && l >= 0 && k < this.width && l < this.height) {
                    if (isMatch) {
                        ingredient = (Ingredient)this.recipeItems.get(this.width - k - 1 + l * this.width);
                    } else {
                        ingredient = (Ingredient)this.recipeItems.get(k + l * this.width);
                    }
                }

                if (!ingredient.test(container.getItem(i + j * container.getWidth()))) {
                    return false;
                }
            }
        }

        return true;
    }

    public ItemStack assemble(CraftingContainer container) {return this.getResultItem().copy();}

    public int getWidth() {return this.width;}

    public int getRecipeWidth() {return this.getWidth();}

    public int getHeight() {return this.height;}

    public int getRecipeHeight() {return this.getHeight();}

    static NonNullList<Ingredient> dissolvePattern(String[] recipe, Map<String, Ingredient> ingredients, int width, int height) {
        NonNullList<Ingredient> nonnulllist = NonNullList.withSize(width * height, Ingredient.EMPTY);
        Set<String> set = Sets.newHashSet(ingredients.keySet());
        set.remove(" ");

        for(int i = 0; i < recipe.length; ++i) {
            for(int j = 0; j < recipe[i].length(); ++j) {
                String s = recipe[i].substring(j, j + 1);
                Ingredient ingredient = (Ingredient)ingredients.get(s);
                if (ingredient == null) {
                    throw new JsonSyntaxException("Pattern references symbol '" + s + "' but it's not defined in the key");
                }

                set.remove(s);
                nonnulllist.set(j + width * i, ingredient);
            }
        }

        if (!set.isEmpty()) {
            throw new JsonSyntaxException("Key defines symbols that aren't used in pattern: " + set);
        } else {
            return nonnulllist;
        }
    }

    @VisibleForTesting
    static String[] shrink(String... p_44187_) {
        int i = Integer.MAX_VALUE;
        int j = 0;
        int k = 0;
        int l = 0;

        for(int i1 = 0; i1 < p_44187_.length; ++i1) {
            String s = p_44187_[i1];
            i = Math.min(i, firstNonSpace(s));
            int j1 = lastNonSpace(s);
            j = Math.max(j, j1);
            if (j1 < 0) {
                if (k == i1) {
                    ++k;
                }

                ++l;
            } else {
                l = 0;
            }
        }

        if (p_44187_.length == l) {
            return new String[0];
        } else {
            String[] astring = new String[p_44187_.length - l - k];

            for(int k1 = 0; k1 < astring.length; ++k1) {
                astring[k1] = p_44187_[k1 + k].substring(i, j + 1);
            }

            return astring;
        }
    }

    public boolean isIncomplete() {
        NonNullList<Ingredient> nonnulllist = this.getIngredients();
        return nonnulllist.isEmpty() || nonnulllist.stream().filter((p_151277_) -> {
            return !p_151277_.isEmpty();
        }).anyMatch((p_151273_) -> {
            return ForgeHooks.hasNoElements(p_151273_);
        });
    }

    private static int firstNonSpace(String p_44185_) {
        int i;
        for(i = 0; i < p_44185_.length() && p_44185_.charAt(i) == ' '; ++i) {
        }

        return i;
    }

    private static int lastNonSpace(String p_44201_) {
        int i;
        for(i = p_44201_.length() - 1; i >= 0 && p_44201_.charAt(i) == ' '; --i) {
        }

        return i;
    }

    static String[] patternFromJson(JsonArray p_44197_) {
        String[] astring = new String[p_44197_.size()];
        if (astring.length > MAX_HEIGHT) {
            throw new JsonSyntaxException("Invalid pattern: too many rows, " + MAX_HEIGHT + " is maximum");
        } else if (astring.length == 0) {
            throw new JsonSyntaxException("Invalid pattern: empty pattern not allowed");
        } else {
            for(int i = 0; i < astring.length; ++i) {
                String s = GsonHelper.convertToString(p_44197_.get(i), "pattern[" + i + "]");
                if (s.length() > MAX_WIDTH) {
                    throw new JsonSyntaxException("Invalid pattern: too many columns, " + MAX_WIDTH + " is maximum");
                }

                if (i > 0 && astring[0].length() != s.length()) {
                    throw new JsonSyntaxException("Invalid pattern: each row must be the same width");
                }

                astring[i] = s;
            }

            return astring;
        }
    }

    static Map<String, Ingredient> keyFromJson(JsonObject p_44211_) {
        Map<String, Ingredient> map = Maps.newHashMap();
        Iterator var2 = p_44211_.entrySet().iterator();

        while(var2.hasNext()) {
            Map.Entry<String, JsonElement> entry = (Map.Entry)var2.next();
            if (((String)entry.getKey()).length() != 1) {
                throw new JsonSyntaxException("Invalid key entry: '" + (String)entry.getKey() + "' is an invalid symbol (must be 1 character only).");
            }

            if (" ".equals(entry.getKey())) {
                throw new JsonSyntaxException("Invalid key entry: ' ' is a reserved symbol.");
            }

            map.put((String)entry.getKey(), Ingredient.fromJson((JsonElement)entry.getValue()));
        }

        map.put(" ", Ingredient.EMPTY);
        return map;
    }

    public static ItemStack itemStackFromJson(JsonObject p_151275_) {
        return CraftingHelper.getItemStack(p_151275_, true, true);
    }

    public static Item itemFromJson(JsonObject p_151279_) {
        String s = GsonHelper.getAsString(p_151279_, "item");
        Item item = (Item)Registry.ITEM.getOptional(new ResourceLocation(s)).orElseThrow(() -> {
            return new JsonSyntaxException("Unknown item '" + s + "'");
        });
        if (item == Items.AIR) {
            throw new JsonSyntaxException("Invalid item: " + s);
        } else {
            return item;
        }
    }

    public static class Serializer implements RecipeSerializer<EggingRecipeShaped> {
        private static final ResourceLocation EGGING_SHAPED = new ResourceLocation(Main.MODID, "egging_shaped");

        public Serializer() {}

        public EggingRecipeShaped fromJson(ResourceLocation recipeFile, JsonObject json) {
            String s = GsonHelper.getAsString(json, "group", "");
            Map<String, Ingredient> map = EggingRecipeShaped.keyFromJson(GsonHelper.getAsJsonObject(json, "key"));
            String[] astring = EggingRecipeShaped.shrink(EggingRecipeShaped.patternFromJson(GsonHelper.getAsJsonArray(json, "pattern")));
            int i = astring[0].length();
            int j = astring.length;
            NonNullList<Ingredient> nonnulllist = EggingRecipeShaped.dissolvePattern(astring, map, i, j);
            ItemStack itemstack = EggingRecipeShaped.itemStackFromJson(GsonHelper.getAsJsonObject(json, "result"));
            return new EggingRecipeShaped(recipeFile, s, i, j, nonnulllist, itemstack);
        }

        public EggingRecipeShaped fromNetwork(ResourceLocation recipeLocation, FriendlyByteBuf data) {
            int i = data.readVarInt();
            int j = data.readVarInt();
            String s = data.readUtf();
            NonNullList<Ingredient> nonnulllist = NonNullList.withSize(i * j, Ingredient.EMPTY);

            for(int k = 0; k < nonnulllist.size(); ++k) {
                nonnulllist.set(k, Ingredient.fromNetwork(data));
            }

            ItemStack itemstack = data.readItem();
            return new EggingRecipeShaped(recipeLocation, s, i, j, nonnulllist, itemstack);
        }

        public void toNetwork(FriendlyByteBuf data, EggingRecipeShaped recipe) {
            data.writeVarInt(recipe.width);
            data.writeVarInt(recipe.height);
            data.writeUtf(recipe.group);
            Iterator var3 = recipe.recipeItems.iterator();

            while(var3.hasNext()) {
                Ingredient ingredient = (Ingredient)var3.next();
                ingredient.toNetwork(data);
            }

            data.writeItem(recipe.result);
        }
    }
}

 

I am also getting a Registry Object Not Present error so it might be that.

Spoiler

Code Involved in Registry:

Spoiler
package gg.hipposgrumm.craftable_spawn_eggs;

import com.mojang.logging.LogUtils;
import gg.hipposgrumm.craftable_spawn_eggs.blocks.EggingTableBlock;
import gg.hipposgrumm.craftable_spawn_eggs.gui.EggingTableGUIHelper;
import gg.hipposgrumm.craftable_spawn_eggs.gui.GUIRegisterer;
import gg.hipposgrumm.craftable_spawn_eggs.recipes.EggingRecipeShaped;
import gg.hipposgrumm.craftable_spawn_eggs.recipes.EggingRecipeShapeless;
import net.minecraft.client.gui.screens.MenuScreens;
import net.minecraft.world.item.BlockItem;
import net.minecraft.world.item.CreativeModeTab;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.crafting.Recipe;
import net.minecraft.world.item.crafting.RecipeSerializer;
import net.minecraft.world.level.block.Block;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent;
import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent;
import net.minecraftforge.event.server.ServerStartingEvent;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import net.minecraftforge.registries.DeferredRegister;
import net.minecraftforge.registries.ForgeRegistries;
import net.minecraftforge.registries.RegistryObject;
import org.slf4j.Logger;

@Mod(Main.MODID)
public class Main {
    public static final String MODID = "craftable_spawn_eggs";
    public static final Logger LOGGER = LogUtils.getLogger();
    public static final DeferredRegister<RecipeSerializer<?>> RECIPE_TYPES = DeferredRegister.create(ForgeRegistries.RECIPE_SERIALIZERS, MODID);

    public static final RegistryObject<RecipeSerializer> EGGING_RECIPE = RECIPE_TYPES.register("egging", () -> new EggingRecipeShapeless.Serializer());
    public static final RegistryObject<RecipeSerializer> EGGING_RECIPE_SHAPED = RECIPE_TYPES.register("egging_shaped", () -> new EggingRecipeShaped.Serializer());

    public Main() {
        IEventBus modEventBus = FMLJavaModLoadingContext.get().getModEventBus();

        modEventBus.addListener(this::commonSetup);

        GUIRegisterer.register(modEventBus);
        RECIPE_TYPES.register(modEventBus);

        MinecraftForge.EVENT_BUS.register(this);
    }

    @Mod.EventBusSubscriber(modid = MODID, bus = Mod.EventBusSubscriber.Bus.MOD)
    public static class ClientModEvents {
        @SubscribeEvent
        public static void onClientSetup(FMLClientSetupEvent event) {
            MenuScreens.register(GUIRegisterer.EGGING_GUI.get(), EggingTableGUIHelper::new);
        }
    }
}

 

Spoiler
package gg.hipposgrumm.craftable_spawn_eggs.recipes;

import java.util.function.Supplier;

import gg.hipposgrumm.craftable_spawn_eggs.Main;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.Container;
import net.minecraft.world.item.crafting.Recipe;
import net.minecraft.world.item.crafting.RecipeType;
import net.minecraftforge.registries.DeferredRegister;
import net.minecraftforge.registries.ForgeRegistries;
import net.minecraftforge.registries.RegistryObject;

public final class RecipeRegisterer {
    public static final DeferredRegister<RecipeType<?>> REGISTRY;
    public static final RegistryObject<RecipeType<EggingRecipe>> EGGING;

    public RecipeRegisterer() {
    }

    private static <T extends EggingRecipe> RegistryObject<RecipeType<T>> register(String name, Supplier<RecipeType<T>> type) {
        return REGISTRY.register(name, type);
    }

    static {
        REGISTRY = DeferredRegister.create(ForgeRegistries.RECIPE_TYPES, Main.MODID);
        EGGING = register("egging", () -> {return RecipeType.simple(new ResourceLocation(Main.MODID, "egging"));});
    }
}

 

Spoiler
package gg.hipposgrumm.craftable_spawn_eggs.recipes;

import net.minecraft.world.inventory.CraftingContainer;
import net.minecraft.world.item.crafting.Recipe;
import net.minecraft.world.item.crafting.RecipeType;

public interface EggingRecipe extends Recipe<CraftingContainer> {
    default RecipeType<?> getType() {return RecipeRegisterer.EGGING.get();}

    public static class Type implements RecipeType<EggingRecipe> {
        private Type() { }
        public static final Type INSTANCE = new Type();
        public static final String ID = "egging";
    }
}

 

 

 

I'm not good at modding, but at least I can read a crash report (well enough). That's something, right?

Link to comment
Share on other sites

Quote

I am also getting a Registry Object Not Present error so it might be that.

Show this error.

Boilerplate:

If you don't post your logs/debug.log we can't help you. For curseforge you need to enable the forge debug.log in its minecraft settings. You should also post your crash report if you have one.

If there is no error in the log file and you don't have a crash report then post the launcher_log.txt from the minecraft folder. Again for curseforge this will be in your curseforge/minecraft/Install

Large files should be posted to a file sharing site like https://gist.github.com  You should also read the support forum sticky post.

Link to comment
Share on other sites

That's not showing the error. That's one line out of context.

Same as your original code dump lacks context. e.g. how can we tell your recipe is in the right place?

Don't dump snippets of code in the forum. Put your code on github where we can see everything, not just the bits you think are important.

And trying to cross reference/search snippets of code behind spoilers is just 10 times more difficult than it needs to be.

 

Obviously from the little information provided by that one line of error,  you have a problem with your static initialisation for RecipeRegisterer.

Either not getting done at all or things are trying to dereference things before it is done.

If you posted the full stacktrace or better the full log we wouldn't have to guess what the exact problem is.

Edited by warjort

Boilerplate:

If you don't post your logs/debug.log we can't help you. For curseforge you need to enable the forge debug.log in its minecraft settings. You should also post your crash report if you have one.

If there is no error in the log file and you don't have a crash report then post the launcher_log.txt from the minecraft folder. Again for curseforge this will be in your curseforge/minecraft/Install

Large files should be posted to a file sharing site like https://gist.github.com  You should also read the support forum sticky post.

Link to comment
Share on other sites

18 hours ago, warjort said:

Don't dump snippets of code in the forum. Put your code on GitHub where we can see everything, not just the bits you think are important.

https://drive.google.com/drive/folders/1zASIqjvGvwVGMi19O9_-ppyTNT0l1mAC?usp=share_link

This edits in real time btw because I am using the Google Drive desktop app and editing directly to drive.

I'm not good at modding, but at least I can read a crash report (well enough). That's something, right?

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Unfortunately, your content contains terms that we do not allow. Please edit your content to remove the highlighted words below.
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Announcements



×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use.