diff options
Diffstat (limited to 'src/main')
-rw-r--r-- | src/main/java/net/glease/ggfab/ComponentRecipeLoader.java | 15 | ||||
-rw-r--r-- | src/main/java/net/glease/ggfab/GGItemList.java | 1 | ||||
-rw-r--r-- | src/main/java/net/glease/ggfab/GigaGramFab.java | 2 | ||||
-rw-r--r-- | src/main/java/net/glease/ggfab/mte/MTE_LinkedInputBus.java | 536 | ||||
-rw-r--r-- | src/main/resources/META-INF/ggfab_at.cfg | 3 | ||||
-rw-r--r-- | src/main/resources/assets/ggfab/lang/en_US.lang | 7 |
6 files changed, 562 insertions, 2 deletions
diff --git a/src/main/java/net/glease/ggfab/ComponentRecipeLoader.java b/src/main/java/net/glease/ggfab/ComponentRecipeLoader.java index 76cc11b524..6cf35d97a6 100644 --- a/src/main/java/net/glease/ggfab/ComponentRecipeLoader.java +++ b/src/main/java/net/glease/ggfab/ComponentRecipeLoader.java @@ -1,8 +1,12 @@ package net.glease.ggfab; +import gregtech.api.enums.GT_Values; import gregtech.api.enums.ItemList; import gregtech.api.enums.Materials; import gregtech.api.enums.OrePrefixes; +import gregtech.api.util.GT_OreDictUnificator; +import gregtech.api.util.GT_Utility; +import net.minecraft.item.ItemStack; import net.minecraftforge.fluids.Fluid; import net.minecraftforge.fluids.FluidRegistry; import net.minecraftforge.fluids.FluidStack; @@ -33,5 +37,16 @@ class ComponentRecipeLoader implements Runnable { 1200, 6000 ); + RA.addAssemblerRecipe(new ItemStack[]{ + ItemList.Hatch_Input_Bus_IV.get(1L), + ItemList.Emitter_IV.get(1L), + ItemList.Sensor_IV.get(1L), + GT_OreDictUnificator.get(OrePrefixes.plateDense, Materials.Enderium, 1L), + GT_Utility.getIntegratedCircuit(12), + }, + Materials.Polybenzimidazole.getMolten(144L), + GGItemList.LinkedInputBus.get(1L), + 600, + (int) GT_Values.VP[5]); } } diff --git a/src/main/java/net/glease/ggfab/GGItemList.java b/src/main/java/net/glease/ggfab/GGItemList.java index f3f3655f2f..b54f5d6406 100644 --- a/src/main/java/net/glease/ggfab/GGItemList.java +++ b/src/main/java/net/glease/ggfab/GGItemList.java @@ -11,6 +11,7 @@ import net.minecraft.item.ItemStack; import static gregtech.api.enums.GT_Values.W; public enum GGItemList implements IItemContainer{ + LinkedInputBus, AdvAssLine, ; diff --git a/src/main/java/net/glease/ggfab/GigaGramFab.java b/src/main/java/net/glease/ggfab/GigaGramFab.java index 27248b2522..994fad8893 100644 --- a/src/main/java/net/glease/ggfab/GigaGramFab.java +++ b/src/main/java/net/glease/ggfab/GigaGramFab.java @@ -5,6 +5,7 @@ import cpw.mods.fml.common.event.*; import gregtech.api.GregTech_API; import gregtech.loaders.materialprocessing.ProcessingModSupport; import net.glease.ggfab.mte.MTE_AdvAssLine; +import net.glease.ggfab.mte.MTE_LinkedInputBus; import net.glease.ggfab.nei.IMCForNEI; @Mod(modid = GGConstants.MODID, version = GGConstants.VERSION, name = GGConstants.MODNAME, acceptedMinecraftVersions = "[1.7.10]", dependencies = "required-after:IC2;required-before:gregtech") @@ -20,6 +21,7 @@ public class GigaGramFab { ProcessingModSupport.aTGregSupport =true; GregTech_API.sAfterGTPreload.add(() -> { GGItemList.AdvAssLine.set(new MTE_AdvAssLine(13532, "ggfab.machine.adv_assline", "Advanced Assembly Line").getStackForm(1)); + GGItemList.LinkedInputBus.set(new MTE_LinkedInputBus(13533, "ggfab.machine.linked_input_bus", "Linked Input Bus", 5).getStackForm(1)); }); GregTech_API.sBeforeGTPostload.add(new ComponentRecipeLoader()); ConfigurationHandler.INSTANCE.init(event.getSuggestedConfigurationFile()); diff --git a/src/main/java/net/glease/ggfab/mte/MTE_LinkedInputBus.java b/src/main/java/net/glease/ggfab/mte/MTE_LinkedInputBus.java new file mode 100644 index 0000000000..4ee9bdc940 --- /dev/null +++ b/src/main/java/net/glease/ggfab/mte/MTE_LinkedInputBus.java @@ -0,0 +1,536 @@ +package net.glease.ggfab.mte; + +import com.gtnewhorizons.modularui.api.drawable.Text; +import com.gtnewhorizons.modularui.api.forge.ItemStackHandler; +import com.gtnewhorizons.modularui.api.math.Alignment; +import com.gtnewhorizons.modularui.api.math.Color; +import com.gtnewhorizons.modularui.api.screen.ModularWindow; +import com.gtnewhorizons.modularui.api.screen.UIBuildContext; +import com.gtnewhorizons.modularui.common.internal.wrapper.BaseSlot; +import com.gtnewhorizons.modularui.common.widget.*; +import com.gtnewhorizons.modularui.common.widget.textfield.TextFieldWidget; +import gregtech.api.gui.modularui.GT_UITextures; +import gregtech.api.interfaces.ITexture; +import gregtech.api.interfaces.tileentity.IGregTechTileEntity; +import gregtech.api.metatileentity.MetaTileEntity; +import gregtech.api.metatileentity.implementations.GT_MetaTileEntity_Hatch_InputBus; +import gregtech.api.util.GT_OreDictUnificator; +import gregtech.api.util.GT_Utility; +import net.minecraft.entity.item.EntityItem; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTBase; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.util.ChatComponentTranslation; +import net.minecraft.util.StatCollector; +import net.minecraft.world.WorldSavedData; +import net.minecraftforge.common.util.Constants; + +import java.util.*; + +public class MTE_LinkedInputBus extends GT_MetaTileEntity_Hatch_InputBus { + public static final int SIZE_INVENTORY = 18; + private SharedInventory mRealInventory; + private final ItemStackHandlerProxy handler = new ItemStackHandlerProxy(); + private String mChannel; + private boolean mPrivate; + private State mState; + private WorldSave save; + + public MTE_LinkedInputBus(int id, String name, String nameRegional, int tier) { + super(id, name, nameRegional, tier, 1, new String[]{ + SIZE_INVENTORY + " slot input bus linked together wirelessly", + "Link does not cross world boundary" + }); + } + + public MTE_LinkedInputBus(String aName, int aTier, String[] aDescription, ITexture[][][] aTextures) { + super(aName, aTier, 1, aDescription, aTextures); + } + + @Override + public MetaTileEntity newMetaEntity(IGregTechTileEntity aTileEntity) { + return new MTE_LinkedInputBus(mName, mTier, mDescriptionArray, mTextures); + } + + @Override + public int getCircuitSlot() { + return 0; + } + + @Override + public void addUIWidgets(ModularWindow.Builder builder, UIBuildContext buildContext) { + builder.widget(new TextFieldWidget() + .setSynced(true, true) + .setGetter(() -> mChannel == null ? "" : mChannel) + .setSetter(this::setChannel) + .setTextColor(Color.WHITE.dark(1)) + .setTextAlignment(Alignment.CenterLeft) + .setBackground(GT_UITextures.BACKGROUND_TEXT_FIELD) + .setGTTooltip(() -> mTooltipCache.getData("ggfab.tooltip.linked_input_bus.change_freq_warn")) + .setSize(60, 18) + .setPos(48, 3)) + .widget(new CycleButtonWidget() + .setToggle(this::isPrivate, this::setPrivate) + .setTextureGetter(i -> i == 1 ? GT_UITextures.OVERLAY_BUTTON_CHECKMARK : GT_UITextures.OVERLAY_BUTTON_CROSS) + .setVariableBackground(GT_UITextures.BUTTON_STANDARD_TOGGLE) + .setSynced(true, true) + .setGTTooltip(() -> mTooltipCache.getData("ggfab.tooltip.linked_input_bus.private")) + .setSize(18, 18) + .setPos(150, 3)) + .widget(SlotGroup.ofItemHandler(handler, 9) + .startFromSlot(0) + .endAtSlot(SIZE_INVENTORY - 1) + .background(getGUITextureSet().getItemSlot()) + .slotCreator(i -> new BaseSlot(handler, i, false) { + @Override + public ItemStack getStack() { + return isEnabled() ? super.getStack() : null; + } + + @Override + public boolean isEnabled() { + return mChannel != null; + } + }) + .build() + .setPos(7, 24)) + .widget(new TextWidget(new Text("Private")) + .setPos(110, 3) + .setSize(43, 20)) + .widget(new TextWidget(new Text("Channel")) + .setPos(5, 3) + .setSize(43, 20)); + } + + @Override + public int getCircuitSlotX() { + return 152; + } + + @Override + public ItemStack getStackInSlot(int aIndex) { + if (aIndex == getCircuitSlot()) + return super.getStackInSlot(aIndex); + if (mState != State.Blocked && mChannel != null && mRealInventory != null) { + if (aIndex > 0 && aIndex <= SIZE_INVENTORY) + return mRealInventory.stacks[aIndex - 1]; + } + return null; + } + + @Override + public void setInventorySlotContents(int aIndex, ItemStack aStack) { + if (aIndex == getCircuitSlot()) { + mInventory[0] = GT_Utility.copyAmount(0, aStack); + markDirty(); + } else if (mState != State.Blocked && mChannel != null && mRealInventory != null) { + if (aIndex > 0 && aIndex <= SIZE_INVENTORY) { + mRealInventory.stacks[aIndex - 1] = aStack; + getWorldSave().markDirty(); + } + } + } + + @Override + public ITexture[] getTexturesActive(ITexture aBaseTexture) { + return super.getTexturesActive(aBaseTexture); + } + + @Override + public ITexture[] getTexturesInactive(ITexture aBaseTexture) { + return super.getTexturesInactive(aBaseTexture); + } + + @Override + public boolean canInsertItem(int aIndex, ItemStack aStack, int aSide) { + return isValidSlot(aIndex) + && aStack != null + && mChannel != null + && mRealInventory != null + && aIndex > getCircuitSlot() + && aIndex < SIZE_INVENTORY + 1 + && (mRealInventory.stacks[aIndex - 1] == null || GT_Utility.areStacksEqual(aStack, mRealInventory.stacks[aIndex - 1])) + && allowPutStack(getBaseMetaTileEntity(), aIndex, (byte) aSide, aStack); + } + + @Override + public boolean allowPutStack(IGregTechTileEntity aBaseMetaTileEntity, int aIndex, byte aSide, ItemStack aStack) { + return aSide == getBaseMetaTileEntity().getFrontFacing() + && aIndex != getCircuitSlot() + && (mRecipeMap == null || disableFilter || mRecipeMap.containsInput(aStack)) + && (mRealInventory.disableLimited || limitedAllowPutStack(aIndex, aStack)); + } + + @Override + protected boolean limitedAllowPutStack(int aIndex, ItemStack aStack) { + for (int i = 0; i < SIZE_INVENTORY; i++) + if (GT_Utility.areStacksEqual(GT_OreDictUnificator.get_nocopy(aStack), mRealInventory.stacks[i])) return i == aIndex - 1; + return mRealInventory.stacks[aIndex - 1] == null; + } + + @Override + public boolean canExtractItem(int aIndex, ItemStack aStack, int aSide) { + return false; + } + + @Override + public int getSizeInventory() { + if (mState != State.Blocked && mChannel != null && mRealInventory != null) + return SIZE_INVENTORY + 1; + return 1; + } + + @Override + public void startRecipeProcessing() { + if (mRealInventory == null) return; + if (mRealInventory.used) { + mState = State.Blocked; + } else { + mRealInventory.used = true; + mState = State.Activated; + } + } + + @Override + public void endRecipeProcessing() { + if (mState == State.Activated) { + assert mRealInventory != null; + mRealInventory.used = false; + } + mState = State.Default; + } + + @Override + public void updateSlots() { + if (mChannel == null || mRealInventory == null) return; + for (int i = 0; i < mRealInventory.stacks.length; i++) { + if (mRealInventory.stacks[i] != null + && (mRealInventory.stacks[i].getItem() == null || mRealInventory.stacks[i].stackSize <= 0)) + mRealInventory.stacks[i] = null; + } + if (!mRealInventory.disableSort) fillStacksIntoFirstSlots(); + markDirty(); + getWorldSave().markDirty(); + } + + @Override + protected void fillStacksIntoFirstSlots() { + // sanity check + if (mRealInventory == null) return; + final int L = SIZE_INVENTORY; + HashMap<GT_Utility.ItemId, Integer> slots = new HashMap<>(L); + HashMap<GT_Utility.ItemId, ItemStack> stacks = new HashMap<>(L); + List<GT_Utility.ItemId> order = new ArrayList<>(L); + List<Integer> validSlots = new ArrayList<>(L); + for (int i = 0; i < L; i++) { + validSlots.add(i); + ItemStack s = mRealInventory.stacks[i]; + if (s == null) continue; + GT_Utility.ItemId sID = GT_Utility.ItemId.createNoCopy(s); + slots.merge(sID, s.stackSize, Integer::sum); + if (!stacks.containsKey(sID)) stacks.put(sID, s); + order.add(sID); + mRealInventory.stacks[i] = null; + } + int slotindex = 0; + for (GT_Utility.ItemId sID : order) { + int toSet = slots.get(sID); + if (toSet == 0) continue; + int slot = validSlots.get(slotindex); + slotindex++; + mRealInventory.stacks[slot] = stacks.get(sID).copy(); + toSet = Math.min(toSet, mRealInventory.stacks[slot].getMaxStackSize()); + mRealInventory.stacks[slot].stackSize = toSet; + slots.merge(sID, toSet, (a, b) -> a - b); + } + } + + private void dropItems(ItemStack[] aStacks) { + for (ItemStack stack : aStacks) { + if (!GT_Utility.isStackValid(stack)) continue; + EntityItem ei = new EntityItem(getBaseMetaTileEntity().getWorld(), + getBaseMetaTileEntity().getOffsetX(getBaseMetaTileEntity().getFrontFacing(), 1) + 0.5, + getBaseMetaTileEntity().getOffsetY(getBaseMetaTileEntity().getFrontFacing(), 1) + 0.5, + getBaseMetaTileEntity().getOffsetZ(getBaseMetaTileEntity().getFrontFacing(), 1) + 0.5, + stack); + ei.motionX = ei.motionY = ei.motionZ = 0; + getBaseMetaTileEntity().getWorld().spawnEntityInWorld(ei); + } + } + + @Override + public boolean shouldDropItemAt(int index) { + return mRealInventory != null && mRealInventory.ref <= 1; + } + + @Override + public void onBlockDestroyed() { + super.onBlockDestroyed(); + if (mRealInventory != null) { + if (--mRealInventory.ref <= 0) + getWorldSave().remove(mChannel); + } + } + + @Override + public void saveNBTData(NBTTagCompound aNBT) { + super.saveNBTData(aNBT); + if (mChannel != null) + aNBT.setString("channel", mChannel); + aNBT.setBoolean("private", mPrivate); + } + + @Override + public void loadNBTData(NBTTagCompound aNBT) { + super.loadNBTData(aNBT); + String channel = aNBT.getString("channel"); + if ("".equals(channel)) + channel = null; + this.mChannel = channel; + mPrivate = aNBT.getBoolean("private"); + } + + public String getChannel() { + return mChannel; + } + + @Override + public void onFirstTick(IGregTechTileEntity aBaseMetaTileEntity) { + super.onFirstTick(aBaseMetaTileEntity); + if (mChannel != null) { + mRealInventory = getWorldSave().get(getRealChannel()); + handler.set(mRealInventory.stacks); + } + } + + @Override + public void onScrewdriverRightClick(byte aSide, EntityPlayer aPlayer, float aX, float aY, float aZ) { + if (!getBaseMetaTileEntity() + .getCoverBehaviorAtSideNew(aSide) + .isGUIClickable( + aSide, + getBaseMetaTileEntity().getCoverIDAtSide(aSide), + getBaseMetaTileEntity().getComplexCoverDataAtSide(aSide), + getBaseMetaTileEntity())) return; + if (aPlayer.isSneaking()) { + if (this.mRealInventory == null) { + aPlayer.addChatMessage(new ChatComponentTranslation("ggfab.info.linked_input_bus.no_channel")); + return; + } + if (mRealInventory.disableSort) { + mRealInventory.disableSort = false; + } else { + if (mRealInventory.disableLimited) { + mRealInventory.disableLimited = false; + } else { + mRealInventory.disableSort = true; + mRealInventory.disableLimited = true; + } + } + GT_Utility.sendChatToPlayer( + aPlayer, + StatCollector.translateToLocal("GT5U.hatch.disableSort." + mRealInventory.disableSort) + " " + + StatCollector.translateToLocal("GT5U.hatch.disableLimited." + mRealInventory.disableLimited)); + } else { + this.disableFilter = !this.disableFilter; + GT_Utility.sendChatToPlayer( + aPlayer, StatCollector.translateToLocal("GT5U.hatch.disableFilter." + this.disableFilter)); + } + } + + private String getRealChannel() { + if (mChannel == null) return null; + if (mPrivate) return getBaseMetaTileEntity().getOwnerUuid() + mChannel; + return new UUID(0, 0) + mChannel; + } + + public boolean isPrivate() { + return mPrivate; + } + + public void setPrivate(boolean aPrivate) { + if (aPrivate == mPrivate) return; + if (getBaseMetaTileEntity().isClientSide()) { + mPrivate = aPrivate; + return; + } + if (this.mChannel == null) { + mPrivate = aPrivate; + return; + } + getWorldSave().markDirty(); + if (--this.mRealInventory.ref <= 0) { + // last referrer, drop inventory + dropItems(mRealInventory.stacks); + getWorldSave().remove(getRealChannel()); + } + mPrivate = aPrivate; + mRealInventory = getWorldSave().get(getRealChannel()); + this.handler.set(mRealInventory.stacks); + mRealInventory.ref++; + getWorldSave().markDirty(); + } + + public void setChannel(String aChannel) { + if ("".equals(aChannel)) aChannel = null; + if (getBaseMetaTileEntity().isClientSide()) { + mChannel = aChannel; + return; + } + if (Objects.equals(this.mChannel, aChannel)) return; // noop + if (this.mChannel != null) { + if (--this.mRealInventory.ref <= 0) { + // last referrer, drop inventory + dropItems(mRealInventory.stacks); + getWorldSave().remove(getRealChannel()); + } + } + if (aChannel == null) { + this.mChannel = null; + this.mRealInventory = null; + this.handler.setFake(); + } else { + this.mChannel = aChannel; + this.mRealInventory = getWorldSave().get(getRealChannel()); + this.handler.set(mRealInventory.stacks); + mRealInventory.ref++; + } + getWorldSave().markDirty(); + } + + private WorldSave getWorldSave() { + if (save == null) { + WorldSave save = (WorldSave) getBaseMetaTileEntity().getWorld().loadItemData(WorldSave.class, "LinkedInputBusses"); + if (save == null) { + save = new WorldSave("LinkedInputBusses"); + getBaseMetaTileEntity().getWorld().setItemData(save.mapName, save); + } + this.save = save; + } + return save; + } + + private enum State { + Activated, + Blocked, + Default, + } + + private static class SharedInventory { + private final ItemStack[] stacks; + /** + * Inventory wrapper for ModularUI + */ + private final ItemStackHandler inventoryHandler; + public boolean disableLimited = true; + public boolean disableSort; + private boolean used; + private int ref; + + public SharedInventory() { + this.stacks = new ItemStack[SIZE_INVENTORY]; + inventoryHandler = new ItemStackHandler(stacks); + } + + public SharedInventory(NBTTagCompound tag) { + this.stacks = new ItemStack[SIZE_INVENTORY]; + inventoryHandler = new ItemStackHandler(stacks); + + for (int i = 0; i < SIZE_INVENTORY; i++) { + String key = "" + i; + if (!tag.hasKey(key, Constants.NBT.TAG_COMPOUND)) continue; + stacks[i] = ItemStack.loadItemStackFromNBT(tag.getCompoundTag(key)); + } + + ref = tag.getInteger("ref"); + disableLimited = tag.getBoolean("dl"); + disableSort = tag.getBoolean("ds"); + } + + public NBTTagCompound save() { + NBTTagCompound tag = new NBTTagCompound(); + for (int i = 0; i < SIZE_INVENTORY; i++) { + ItemStack stack = stacks[i]; + if (stack == null) continue; + tag.setTag("" + i, stack.writeToNBT(new NBTTagCompound())); + } + tag.setInteger("ref", ref); + tag.setBoolean("ds", disableSort); + tag.setBoolean("dl", disableLimited); + return tag; + } + } + + public static class WorldSave extends WorldSavedData { + private final Map<String, SharedInventory> data = new HashMap<>(); + + public WorldSave(String p_i2141_1_) { + super(p_i2141_1_); + } + + @Override + public void readFromNBT(NBTTagCompound tag) { + data.clear(); + @SuppressWarnings("unchecked") + Set<Map.Entry<String, NBTBase>> set = tag.tagMap.entrySet(); + for (Map.Entry<String, NBTBase> e : set) { + data.put(e.getKey(), new SharedInventory((NBTTagCompound) e.getValue())); + } + } + + @Override + public void writeToNBT(NBTTagCompound tag) { + for (Map.Entry<String, SharedInventory> e : data.entrySet()) { + if (e.getValue().ref > 0) + tag.setTag(e.getKey(), e.getValue().save()); + } + } + + public SharedInventory get(Object channel) { + return data.computeIfAbsent(channel.toString(), k -> new SharedInventory()); + } + + public void remove(Object channel) { + data.remove(channel.toString()); + markDirty(); + } + } + + private static class ItemStackHandlerProxy extends ItemStackHandler { + private static final ItemStack[] EMPTY = new ItemStack[SIZE_INVENTORY]; + private boolean fake; + + public ItemStackHandlerProxy() { + super(EMPTY); + fake = true; + } + + public void setFake() { + set(EMPTY); + fake = true; + } + + public boolean isFake() { + return fake; + } + + public void set(ItemStack[] stacks) { + this.stacks = Arrays.asList(stacks); + fake = false; + } + + @Override + public NBTTagCompound serializeNBT() { + NBTTagCompound tag = super.serializeNBT(); + tag.setBoolean("fake", fake); + return tag; + } + + @Override + public void deserializeNBT(NBTTagCompound nbt) { + super.deserializeNBT(nbt); + fake = nbt.getBoolean("fake"); + } + } +} diff --git a/src/main/resources/META-INF/ggfab_at.cfg b/src/main/resources/META-INF/ggfab_at.cfg index 4c03ad5bee..a6dc456eb5 100644 --- a/src/main/resources/META-INF/ggfab_at.cfg +++ b/src/main/resources/META-INF/ggfab_at.cfg @@ -1 +1,2 @@ -public net.minecraft.nbt.NBTTagList field_74747_a # tagList
\ No newline at end of file +public net.minecraft.nbt.NBTTagList field_74747_a # tagList +public net.minecraft.nbt.NBTTagCompound field_74784_a # tagMap
\ No newline at end of file diff --git a/src/main/resources/assets/ggfab/lang/en_US.lang b/src/main/resources/assets/ggfab/lang/en_US.lang index 1bdcc6bbe9..7286b99043 100644 --- a/src/main/resources/assets/ggfab/lang/en_US.lang +++ b/src/main/resources/assets/ggfab/lang/en_US.lang @@ -11,6 +11,11 @@ ggfab.waila.advassline.slice=Slice #%s: %s s / %s s ggfab.waila.advassline.slice.small=Slice #%s: %s ticks / %s ticks ggfab.waila.advassline.slice.idle=Slice #%s: Idle ggfab.waila.advassline.slice.stuck=Slice #%s: ยง4STUCK -ggfab.info.advassline.slice=Slice Info + +ggfab.info.linked_input_bus.no_channel=No channel specified yet! ggfab.info.biome=Biome: + +ggfab.tooltip.linked_input_bus.change_freq_warn=Changing channel while this is the last one on this channel will spill all items left! +ggfab.tooltip.linked_input_bus.private=Private channel are not shared with others. +ggfab.tooltip.linked_input_bus.private.1=Changing channel while this is the last one on this channel will spill all items left!
\ No newline at end of file |