diff --git a/build.gradle b/build.gradle index ead856fad..624b47371 100644 --- a/build.gradle +++ b/build.gradle @@ -30,11 +30,10 @@ buildscript { } plugins { - id "maven-publish" - id "com.diffplug.gradle.spotless" version "4.3.0" - id "com.matthewprenger.cursegradle" version "1.4.0" - id "org.sonarqube" version "2.8" - id "jacoco" + id "maven-publish" + id "com.diffplug.gradle.spotless" version "4.3.0" + id "com.matthewprenger.cursegradle" version "1.4.0" + id "idea" } apply plugin: 'net.minecraftforge.gradle' @@ -45,8 +44,32 @@ repositories { mavenLocal() jcenter() mavenCentral() + maven { // modmaven, maven proxy + name 'modmaven' + url "https://modmaven.k-4u.nl/" + } } +dependencies { + minecraft "net.minecraftforge:forge:${minecraft_version}-${forge_version}" + + // compile against provided APIs + compileOnly "mezz.jei:jei-${minecraft_version}:${jei_version}:api" + compileOnly "mcjty.theoneprobe:TheOneProbe-${minecraft_release}:${minecraft_release}-${top_version}:api" + + // Runtime, Mods + runtimeOnly fg.deobf("mezz.jei:jei-${minecraft_version}:${jei_version}") + runtimeOnly fg.deobf("mcjty.theoneprobe:TheOneProbe-${minecraft_release}:${minecraft_release}-${top_version}") + + // unit test dependencies + testCompile "junit:junit:4.13" + + // Annotation Processors + annotationProcessor 'org.spongepowered:mixin:0.8:processor' +} +group = artifact_group +archivesBaseName = artifact_basename + sourceCompatibility = targetCompatibility = JavaVersion.VERSION_1_8 compileJava { sourceCompatibility = targetCompatibility = JavaVersion.VERSION_1_8 @@ -58,7 +81,8 @@ tasks.withType(JavaCompile) { options.encoding = 'UTF-8' } -// Create version number +/////////////////// +// Version Number version = version_major + "." + version_minor + "." + version_patch ext.pr = System.getenv('PR_NUMBER') ?: "" @@ -76,10 +100,8 @@ if (ext.release) { version = ext.release } - -// Maven group and artifact name -group = artifact_group -archivesBaseName = artifact_basename +ext.isAlpha = project.version.contains("alpha") +ext.isBeta = project.version.contains("beta") sourceSets { api @@ -103,9 +125,8 @@ configurations { apiCompile.extendsFrom(compile) } -apply from: 'gradle/scripts/dependencies.gradle' -apply from: 'gradle/scripts/spotless.gradle' - +//////////////////// +// Forge/Minecraft minecraft { mappings channel: "snapshot", version: project.mcp_mappings @@ -147,18 +168,14 @@ minecraft { } } +/////////// +// Mixins mixin { add sourceSets.main, "appliedenergistics2.mixins.refmap.json" } -jar { - manifest { - attributes([ - "MixinConfigs": "appliedenergistics2.mixins.json" - ]) - } -} - +//////////////// +// Jar Signing def signProps = [:] if (System.getenv("KEY_STORE_FILE")) { signProps['keyStore'] = System.getenv("KEY_STORE_FILE") @@ -181,9 +198,75 @@ task signJar(type: net.minecraftforge.gradle.common.task.SignJar, dependsOn: 're } } -apply from: 'gradle/scripts/artifacts.gradle' -apply from: 'gradle/scripts/curseforge.gradle' +////////////// +// Aritfacts +processResources { + exclude '.cache' +} +jar { + finalizedBy 'reobfJar' + finalizedBy 'signJar' + + from sourceSets.main.output.classesDirs + from sourceSets.api.output.classesDirs + from sourceSets.main.output.resourcesDir + from sourceSets.api.output.resourcesDir + + manifest { + attributes([ + "Specification-Title": "Applied Energistics 2", + "Specification-Vendor": "TeamAppliedEnergistics", + "Specification-Version": "${project.version}", + "Implementation-Title": "${project.name}", + "Implementation-Version": "${project.version}", + "Implementation-Vendor" :"TeamAppliedEnergistics", + "Implementation-Timestamp": new Date().format("yyyy-MM-dd'T'HH:mm:ssZ"), + "MixinConfigs": "appliedenergistics2.mixins.json" + ]) + } +} + +task javadocs(type: Javadoc) { + classpath = sourceSets.main.compileClasspath + source = sourceSets.api.java + include "appeng/api/**" + + options.addStringOption('Xdoclint:none', '-quiet') + options.encoding = 'UTF-8' + options.charSet = 'UTF-8' +} + +task javadocJar(type: Jar, dependsOn: javadocs) { + classifier = "javadoc" + from javadoc.destinationDir +} + +task sourcesJar(type: Jar) { + classifier = "sources" + from sourceSets.main.allJava + from sourceSets.api.allJava +} + +task apiJar(type: Jar) { + classifier = "api" + from sourceSets.api.output + include "appeng/api/**" + + // TODO: when FG bug is fixed, remove allJava from the api jar. + // https://github.com/MinecraftForge/ForgeGradle/issues/369 + // Gradle should be able to pull them from the -sources jar. + from sourceSets.api.allJava +} + +artifacts { + archives javadocJar + archives sourcesJar + archives apiJar +} + +////////////////// +// Maven publish publishing { publications { maven(MavenPublication) { @@ -222,3 +305,53 @@ publishing { } } } + +///////////// +// Spotless +spotless { + java { + target 'src/*/java/appeng/**/*.java' + + indentWithSpaces() + eclipse().configFile 'codeformat/codeformat.xml' + importOrderFile 'codeformat/ae2.importorder' + } + format 'json', { + target 'src/*/resources/**/*.json' + targetExclude 'src/generated/resources/**' + prettier().config(['parser': 'json']) + } +} + +//////////////// +// Curse Forge +if (System.getenv("CURSEFORGE")) { + def cfReleaseType = "release" + if (ext.isAlpha) { + cfReleaseType = "alpha" + } else if (ext.isBeta) { + cfReleaseType = "beta" + } + + curseforge { + apiKey = System.getenv("CURSEFORGE") + project { + id = project.curseforge_project + changelogType = "markdown" + changelog = System.getenv("CHANGELOG") ?: "Please visit our [releases](https://github.com/AppliedEnergistics/Applied-Energistics-2/releases) for a changelog" + releaseType = cfReleaseType + } + } +} + +//////////////// +// IntelliJ Project Import +// The Mixin annotation process does not have an obfuscation source when running through the IntelliJ compiler, +// thus we have to prevent it from being activated as part of importing this Gradle project into IntelliJ. +if (System.getProperty("idea.sync.active") == "true") { + afterEvaluate { + tasks.withType(JavaCompile).all { + it.options.annotationProcessorPath = files() + } + } +} diff --git a/gradle/scripts/artifacts.gradle b/gradle/scripts/artifacts.gradle deleted file mode 100644 index f9503ff9b..000000000 --- a/gradle/scripts/artifacts.gradle +++ /dev/null @@ -1,81 +0,0 @@ -/* - * This file is part of Applied Energistics 2. - * Copyright (c) 2013 - 2014, AlgorithmX2, All rights reserved. - * - * Applied Energistics 2 is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Applied Energistics 2 is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Applied Energistics 2. If not, see . - */ - -processResources { - exclude '.cache' -} - -jar { - finalizedBy 'reobfJar' - finalizedBy 'signJar' - - from sourceSets.main.output.classesDirs - from sourceSets.api.output.classesDirs - from sourceSets.main.output.resourcesDir - from sourceSets.api.output.resourcesDir - - manifest { - attributes([ - "Specification-Title": "Applied Energistics 2", - "Specification-Vendor": "TeamAppliedEnergistics", - "Specification-Version": "${project.version}", - "Implementation-Title": "${project.name}", - "Implementation-Version": "${project.version}", - "Implementation-Vendor" :"TeamAppliedEnergistics", - "Implementation-Timestamp": new Date().format("yyyy-MM-dd'T'HH:mm:ssZ"), - ]) - } -} - -task javadocs(type: Javadoc) { - classpath = sourceSets.main.compileClasspath - source = sourceSets.api.java - include "appeng/api/**" - - options.addStringOption('Xdoclint:none', '-quiet') - options.encoding = 'UTF-8' - options.charSet = 'UTF-8' -} - -task javadocJar(type: Jar, dependsOn: javadocs) { - classifier = "javadoc" - from javadoc.destinationDir -} - -task sourcesJar(type: Jar) { - classifier = "sources" - from sourceSets.main.allJava - from sourceSets.api.allJava -} - -task apiJar(type: Jar) { - classifier = "api" - from sourceSets.api.output - include "appeng/api/**" - - // TODO: when FG bug is fixed, remove allJava from the api jar. - // https://github.com/MinecraftForge/ForgeGradle/issues/369 - // Gradle should be able to pull them from the -sources jar. - from sourceSets.api.allJava -} - -artifacts { - archives javadocJar - archives sourcesJar - archives apiJar -} \ No newline at end of file diff --git a/gradle/scripts/curseforge.gradle b/gradle/scripts/curseforge.gradle deleted file mode 100644 index a925a4506..000000000 --- a/gradle/scripts/curseforge.gradle +++ /dev/null @@ -1,39 +0,0 @@ -/* - * This file is part of Applied Energistics 2. - * Copyright (c) 2020, TeamAppliedEnergistics, All rights reserved. - * - * Applied Energistics 2 is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Applied Energistics 2 is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - *g GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Applied Energistics 2. If not, see . - */ - -def isAlpha = project.version.contains("alpha") -def isBeta = project.version.contains("beta") - -def cfReleaseType = "release" -if (isAlpha) { - cfReleaseType = "alpha" -} else if (isBeta) { - cfReleaseType = "beta" -} - -if (System.getenv("CURSEFORGE")) { - curseforge { - apiKey = System.getenv("CURSEFORGE") - project { - id = project.curseforge_project - changelogType = "markdown" - changelog = System.getenv("CHANGELOG") ?: "Please visit our [releases](https://github.com/AppliedEnergistics/Applied-Energistics-2/releases) for a changelog" - releaseType = cfReleaseType - } - } -} \ No newline at end of file diff --git a/gradle/scripts/dependencies.gradle b/gradle/scripts/dependencies.gradle deleted file mode 100644 index df637187e..000000000 --- a/gradle/scripts/dependencies.gradle +++ /dev/null @@ -1,42 +0,0 @@ -/* - * This file is part of Applied Energistics 2. - * Copyright (c) 2013 - 2014, AlgorithmX2, All rights reserved. - * - * Applied Energistics 2 is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Applied Energistics 2 is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Applied Energistics 2. If not, see . - */ - -repositories { - mavenLocal() - - maven { // modmaven, maven proxy - name 'modmaven' - url "https://modmaven.k-4u.nl/" - } -} - -dependencies { - minecraft "net.minecraftforge:forge:${minecraft_version}-${forge_version}" - - // compile against provided APIs - compileOnly "mezz.jei:jei-${jei_minecraft_version}:${jei_version}:api" - compileOnly "mcjty.theoneprobe:TheOneProbe-${minecraft_release}:${minecraft_release}-${top_version}:api" - - // Runtime, Mods - runtimeOnly fg.deobf("mezz.jei:jei-${jei_minecraft_version}:${jei_version}") - runtimeOnly fg.deobf("mcjty.theoneprobe:TheOneProbe-${minecraft_release}:${minecraft_release}-${top_version}") - //runtimeOnly fg.deobf("team.chisel.ctm:CTM:${ctm_version}") - - // unit test dependencies - testCompile "junit:junit:4.13" -} diff --git a/gradle/scripts/spotless.gradle b/gradle/scripts/spotless.gradle deleted file mode 100644 index ef51ef3cf..000000000 --- a/gradle/scripts/spotless.gradle +++ /dev/null @@ -1,32 +0,0 @@ -/* - * This file is part of Applied Energistics 2. - * Copyright (c) 2013 - 2020, AlgorithmX2, All rights reserved. - * - * Applied Energistics 2 is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Applied Energistics 2 is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - *g GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Applied Energistics 2. If not, see . - */ - -spotless { - java { - target 'src/*/java/appeng/**/*.java' - - indentWithSpaces() - eclipse().configFile 'codeformat/codeformat.xml' - importOrderFile 'codeformat/ae2.importorder' - } - format 'json', { - target 'src/*/resources/**/*.json' - targetExclude 'src/generated/resources/**' - prettier().config(['parser': 'json']) - } -} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 0d4a95168..94336fcae 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 290541c73..f04d6a20a 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.3-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/src/api/java/appeng/api/networking/GridNotification.java b/src/api/java/appeng/api/networking/GridNotification.java index 48c7a29b0..b172f9dd1 100644 --- a/src/api/java/appeng/api/networking/GridNotification.java +++ b/src/api/java/appeng/api/networking/GridNotification.java @@ -28,4 +28,9 @@ public enum GridNotification { * the visible connections for this node have changed, useful for cable. */ CONNECTIONS_CHANGED, + + /** + * the owner of the grid node has changed, and the node needs to be re-saved + */ + OWNER_CHANGED } diff --git a/src/api/java/appeng/api/networking/IGridBlock.java b/src/api/java/appeng/api/networking/IGridBlock.java index 93fd3f37e..3a86fe2eb 100644 --- a/src/api/java/appeng/api/networking/IGridBlock.java +++ b/src/api/java/appeng/api/networking/IGridBlock.java @@ -87,7 +87,8 @@ public interface IGridBlock { AEColor getGridColor(); /** - * Notifies your IGridBlock that changes were made to your connections + * Called by the {@link IGridNode} to notify its {@link IGridBlock} about + * events. */ void onGridNotification(@Nonnull GridNotification notification); diff --git a/src/main/java/appeng/block/networking/CableBusBlock.java b/src/main/java/appeng/block/networking/CableBusBlock.java index 6a9794f33..ace257fa7 100644 --- a/src/main/java/appeng/block/networking/CableBusBlock.java +++ b/src/main/java/appeng/block/networking/CableBusBlock.java @@ -39,6 +39,8 @@ import net.minecraft.item.BlockItemUseContext; import net.minecraft.item.DyeColor; import net.minecraft.item.ItemGroup; import net.minecraft.item.ItemStack; +import net.minecraft.state.IntegerProperty; +import net.minecraft.state.StateContainer; import net.minecraft.tileentity.TileEntity; import net.minecraft.util.ActionResultType; import net.minecraft.util.Direction; @@ -82,8 +84,12 @@ public class CableBusBlock extends AEBaseTileBlock implement private static final ICableBusContainer NULL_CABLE_BUS = new NullCableBusContainer(); + private static final IntegerProperty LIGHT_LEVEL = IntegerProperty.create("light_level", 0, 15); + public CableBusBlock() { - super(defaultProps(AEMaterials.GLASS).notSolid().noDrops().variableOpacity()); + super(defaultProps(AEMaterials.GLASS).notSolid().noDrops().variableOpacity() + .setLightLevel(state -> state.get(LIGHT_LEVEL))); + setDefaultState(getDefaultState().with(LIGHT_LEVEL, 0)); } @Override @@ -126,11 +132,9 @@ public class CableBusBlock extends AEBaseTileBlock implement } @Override - public int getLightValue(final BlockState state, final IBlockReader world, final BlockPos pos) { - if (state.getBlock() != this) { - return state.getBlock().getLightValue(state, world, pos); - } - return this.cb(world, pos).getLightValue(); + protected void fillStateContainer(StateContainer.Builder builder) { + super.fillStateContainer(builder); + builder.add(LIGHT_LEVEL); } @Override @@ -386,4 +390,13 @@ public class CableBusBlock extends AEBaseTileBlock implement } } + @Override + protected BlockState updateBlockStateFromTileEntity(BlockState currentState, CableBusTileEntity te) { + if (currentState.getBlock() != this) { + return currentState; + } + int lightLevel = te.getCableBus().getLightValue(); + return super.updateBlockStateFromTileEntity(currentState, te).with(LIGHT_LEVEL, lightLevel); + } + } diff --git a/src/main/java/appeng/client/gui/AEBaseScreen.java b/src/main/java/appeng/client/gui/AEBaseScreen.java index f45d9391a..21dcb6b50 100644 --- a/src/main/java/appeng/client/gui/AEBaseScreen.java +++ b/src/main/java/appeng/client/gui/AEBaseScreen.java @@ -229,7 +229,7 @@ public abstract class AEBaseScreen extends ContainerS RenderSystem.color4f(1.0F, 1.0F, 1.0F, 1.0F); if (this.getScrollBar() != null) { - this.getScrollBar().draw(this); + this.getScrollBar().draw(matrixStack, this); } this.drawFG(matrixStack, ox, oy, x, y); @@ -291,22 +291,36 @@ public abstract class AEBaseScreen extends ContainerS } } - if (this.getScrollBar() != null) { - this.getScrollBar().click(xCoord - this.guiLeft, yCoord - this.guiTop); + // Forward left mouse button down events to the scrollbar + if (btn == 0 && this.getScrollBar() != null) { + if (this.getScrollBar().mouseDown(xCoord - this.guiLeft, yCoord - this.guiTop)) { + return true; + } } return super.mouseClicked(xCoord, yCoord, btn); } @Override - public boolean mouseDragged(double mouseX, double mouseY, int mouseButton, double dragX, double dragY) { + public boolean mouseReleased(double mouseX, double mouseY, int button) { + // Forward left mouse button up events to the scrollbar + if (button == 0 && this.getScrollBar() != null) { + if (this.getScrollBar().mouseUp(mouseX - this.guiLeft, mouseY - this.guiTop)) { + return true; + } + } + return super.mouseReleased(mouseX, mouseY, button); + } + + @Override + public boolean mouseDragged(double mouseX, double mouseY, int mouseButton, double dragX, double dragY) { final Slot slot = this.getSlot((int) mouseX, (int) mouseY); final ItemStack itemstack = getPlayer().inventory.getItemStack(); if (this.getScrollBar() != null) { // FIXME: Coordinate system of mouseX/mouseY is unclear - this.getScrollBar().click((int) mouseX - this.guiLeft, (int) mouseY - this.guiTop); + this.getScrollBar().mouseDragged((int) mouseX - this.guiLeft, (int) mouseY - this.guiTop); } if (slot instanceof FakeSlot && !itemstack.isEmpty()) { @@ -581,7 +595,7 @@ public abstract class AEBaseScreen extends ContainerS if (slot instanceof SlotME) { final IAEItemStack item = ((SlotME) slot).getAEStack(); if (item != null) { - ((AEBaseContainer) this.container).setTargetStack(item); + this.container.setTargetStack(item); final InventoryAction direction = wheel > 0 ? InventoryAction.ROLL_DOWN : InventoryAction.ROLL_UP; final int times = (int) Math.abs(wheel); final int inventorySize = this.getInventorySlots().size(); @@ -796,6 +810,11 @@ public abstract class AEBaseScreen extends ContainerS public void tick() { super.tick(); + + if (this.getScrollBar() != null) { + this.getScrollBar().tick(); + } + for (IGuiEventListener child : children) { if (child instanceof ITickingWidget) { ((ITickingWidget) child).tick(); diff --git a/src/main/java/appeng/client/gui/implementations/CraftConfirmScreen.java b/src/main/java/appeng/client/gui/implementations/CraftConfirmScreen.java index 771d58cda..76c14ff85 100644 --- a/src/main/java/appeng/client/gui/implementations/CraftConfirmScreen.java +++ b/src/main/java/appeng/client/gui/implementations/CraftConfirmScreen.java @@ -97,6 +97,8 @@ public class CraftConfirmScreen extends AEBaseScreen { addButton(new Button(this.guiLeft + 6, this.guiTop + this.ySize - 25, 50, 20, GuiText.Cancel.text(), btn -> subGui.goBack())); + + this.setScrollBar(); } @Override diff --git a/src/main/java/appeng/client/gui/implementations/InterfaceTerminalScreen.java b/src/main/java/appeng/client/gui/implementations/InterfaceTerminalScreen.java index 1bd3157e3..5d93481cb 100644 --- a/src/main/java/appeng/client/gui/implementations/InterfaceTerminalScreen.java +++ b/src/main/java/appeng/client/gui/implementations/InterfaceTerminalScreen.java @@ -71,8 +71,7 @@ public class InterfaceTerminalScreen extends AEBaseScreen extends AEBas craftingGridOffsetX -= 25; craftingGridOffsetY -= 6; + this.setScrollBar(); + } @Override diff --git a/src/main/java/appeng/client/gui/widgets/EventRepeater.java b/src/main/java/appeng/client/gui/widgets/EventRepeater.java new file mode 100644 index 000000000..8c804c2d8 --- /dev/null +++ b/src/main/java/appeng/client/gui/widgets/EventRepeater.java @@ -0,0 +1,96 @@ +/* + * This file is part of Applied Energistics 2. + * Copyright (c) 2013 - 2014, AlgorithmX2, All rights reserved. + * + * Applied Energistics 2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Applied Energistics 2 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Applied Energistics 2. If not, see . + */ + +package appeng.client.gui.widgets; + +import java.time.Duration; + +/** + * This class can be used to implement repeating events such as holding down a + * button to fire an event repeatedly while the button is still being held, or + * repeatedly scrolling down a page, while the mouse is held down on the + * scrollbar. + */ +public class EventRepeater { + + /** + * -1 if no repeat event is scheduled. Otherwise contains the + * {@link System#nanoTime()} at which the next event should occur. + */ + private long nextEventTime = -1; + + private EventCallback eventCallback = null; + + private final long eventDelay; // In nanoseconds + + private final long eventInterval; // In nanoseconds + + public EventRepeater(Duration delay, Duration interval) { + this.eventDelay = delay.toNanos(); + this.eventInterval = interval.toNanos(); + } + + public void tick() { + if (this.eventCallback == null) { + return; // No event scheduled + } + + // Use nanoTime here because it is monotonically increasing, while + // System.currentTimeMillis is not + long nanoTime = System.nanoTime(); + if (nanoTime < this.nextEventTime) { + return; // Event time not reached + } + + // Before triggering, recompute the next event, since + // the event callback itself may reschedule/cancel, and + // we should not overwrite that + this.nextEventTime = nanoTime + this.eventInterval; + this.eventCallback.trigger(); + } + + /** + * Schedule the given callback to be called after a given initial delay, and + * then after the given interval repeatedly. + * + *

+ * Replaces any previously queued callback. + */ + public void repeat(EventCallback callback) { + long time = System.nanoTime(); + this.eventCallback = callback; + this.nextEventTime = time + eventDelay; + } + + public boolean isRepeating() { + return this.eventCallback != null; + } + + /** + * Stop repeating the event. + */ + public void stop() { + this.eventCallback = null; + } + + @FunctionalInterface + public interface EventCallback { + void trigger(); + } + +} diff --git a/src/main/java/appeng/client/gui/widgets/Scrollbar.java b/src/main/java/appeng/client/gui/widgets/Scrollbar.java index 4b842ed18..a580a2219 100644 --- a/src/main/java/appeng/client/gui/widgets/Scrollbar.java +++ b/src/main/java/appeng/client/gui/widgets/Scrollbar.java @@ -18,17 +18,75 @@ package appeng.client.gui.widgets; -import com.mojang.blaze3d.systems.RenderSystem; +import java.time.Duration; -import net.minecraftforge.fml.client.gui.GuiUtils; +import com.mojang.blaze3d.matrix.MatrixStack; + +import net.minecraft.client.gui.AbstractGui; +import net.minecraft.client.renderer.Rectangle2d; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.MathHelper; import appeng.client.gui.AEBaseScreen; -public class Scrollbar implements IScrollSource { +/** + * Implements a vertical scrollbar using Vanilla's scrollbar handle texture from + * the creative tab. + *

+ * It is expected that the background of the UI contains a pre-baked scrollbar + * track border, and that the exact rectangle of that track is set on this + * object via {@link #setLeft(int)}, {@link #setTop(int)} and + * {@link #setHeight(int)}. While the width of the track can also be set, the + * drawn handle will use vanilla's sprite width (see {@link #HANDLE_WIDTH}. + */ +public class Scrollbar extends AbstractGui implements IScrollSource { + /** + * Width of the scrollbar handle sprite in the source texture. + */ + private static final int HANDLE_WIDTH = 12; + + /** + * Height of the scrollbar handle sprite in the source texture. + */ + private static final int HANDLE_HEIGHT = 15; + + /** + * Texture containing the scrollbar handle sprites. + */ + private static final ResourceLocation TEXTURE = new ResourceLocation("minecraft", + "textures/gui/container/creative_inventory/tabs.png"); + + /** + * Rectangle in the source texture that contains the sprite for an enabled + * handle. + */ + private static final Rectangle2d ENABLED = new Rectangle2d(232, 0, HANDLE_WIDTH, HANDLE_HEIGHT); + + /** + * Rectangle in the source texture that contains the sprite for a disabled + * handle. + */ + private static final Rectangle2d DISABLED = new Rectangle2d(232 + HANDLE_WIDTH, 0, HANDLE_WIDTH, HANDLE_HEIGHT); + + /** + * The screen x-coordinate of the scrollbar's inner track. + */ private int displayX = 0; + + /** + * The screen y-coordinate of the scrollbar's inner track. + */ private int displayY = 0; - private int width = 12; + + /** + * The inner width of the scrollbar track. + */ + private int width = HANDLE_WIDTH; + + /** + * The inner height of the scrollbar track. + */ private int height = 16; private int pageSize = 1; @@ -36,18 +94,58 @@ public class Scrollbar implements IScrollSource { private int minScroll = 0; private int currentScroll = 0; - public void draw(final AEBaseScreen g) { - g.bindTexture("minecraft", "gui/container/creative_inventory/tabs.png"); - RenderSystem.color4f(1.0f, 1.0f, 1.0f, 1.0f); + /** + * True if the scrollbar's handle is currently being dragged. + */ + private boolean dragging; + /** + * The y-coordinate relative to the upper edge of the scrollbar handle, where + * the user pressed the mouse button to drag. While dragging, this is applied as + * an offset to the effective scrollbar position. + */ + private int dragYOffset; + private final EventRepeater eventRepeater = new EventRepeater(Duration.ofMillis(250), Duration.ofMillis(150)); + + /** + * Draws the handle of the scrollbar. + *

+ * The GUI is assumed to already contain a prebaked scrollbar track in its + * background. + */ + public void draw(MatrixStack matrices, final AEBaseScreen g) { + setBlitOffset(g.getBlitOffset()); + + // Draw the track (nice for debugging) + // fill(matrices, displayX, displayY, this.displayX + width, this.displayY + + // height, 0xffff0000); + + g.bindTexture(TEXTURE); + + int yOffset; + Rectangle2d sourceRect; if (this.getRange() == 0) { - GuiUtils.drawTexturedModalRect(this.displayX, this.displayY, 232 + this.width, 0, this.width, 15, - g.getBlitOffset()); + yOffset = 0; + sourceRect = DISABLED; } else { - final int offset = (this.currentScroll - this.minScroll) * (this.height - 15) / this.getRange(); - GuiUtils.drawTexturedModalRect(this.displayX, offset + this.displayY, 232, 0, this.width, 15, - g.getBlitOffset()); + yOffset = getHandleYOffset(); + sourceRect = ENABLED; } + + blit(matrices, this.displayX, this.displayY + yOffset, sourceRect.getX(), sourceRect.getY(), + sourceRect.getWidth(), sourceRect.getHeight()); + } + + /** + * Returns the y-position of the scrollbar handle in relation to the upper edge + * of the scrollbar's track. + */ + private int getHandleYOffset() { + if (getRange() == 0) { + return 0; + } + int availableHeight = this.height - HANDLE_HEIGHT; + return (this.currentScroll - this.minScroll) * availableHeight / this.getRange(); } private int getRange() { @@ -111,24 +209,96 @@ public class Scrollbar implements IScrollSource { return this.currentScroll; } - public void click(final double x, final double y) { - if (this.getRange() == 0) { + public boolean mouseDown(double x, double y) { + this.dragging = false; + + // Clicks to the left or right of the scrollbar don't do anything + if (x < displayX || x >= displayX + width) { + return false; + } + + // Clicks to the top or bottom don't do anything either + int relY = (int) Math.round(y - displayY); + if (relY < 0 || relY >= height) { + return false; + } + + // Do nothing when there's no range, but swallow the event + if (getRange() == 0) { + return true; + } + + int handleYOffset = getHandleYOffset(); + + if (relY < handleYOffset) { + // Clicks above the handle will page up, repeatedly + pageUp(); + eventRepeater.repeat(this::pageUp); + + } else if (relY < handleYOffset + HANDLE_HEIGHT) { + // Clicks on the handle will initiate dragging it + this.dragging = true; + this.dragYOffset = relY - handleYOffset; + } else { + // Clicks below the handle will page down, repeatedly + pageDown(); + eventRepeater.repeat(this::pageDown); + } + + return true; + } + + public boolean mouseUp(double x, double y) { + this.dragging = false; + this.eventRepeater.stop(); + return false; + } + + public void mouseDragged(double x, double y) { + if (this.getRange() == 0 || !this.dragging || this.eventRepeater.isRepeating()) { return; } - if (x > this.displayX && x <= this.displayX + this.width) { - if (y > this.displayY && y <= this.displayY + this.height) { - this.currentScroll = (int) (y - this.displayY); - this.currentScroll = this.minScroll + ((this.currentScroll * 2 * this.getRange() / this.height)); - this.currentScroll = (this.currentScroll + 1) >> 1; - this.applyRange(); - } - } + // Compute the position of the mouse (adjusted for where it grabbed the handle, + // so as if it grabbed + // the upper edge of it) within the scrollable area of the track (minus the + // handle height). + double handleUpperEdgeY = y - this.displayY - this.dragYOffset; + double availableHeight = this.height - HANDLE_HEIGHT; + double position = MathHelper.clamp(handleUpperEdgeY / availableHeight, 0.0, 1.0); + + this.currentScroll = this.minScroll + (int) Math.round(position * this.getRange()); + this.applyRange(); } public void wheel(double delta) { + // Do nothing when there's no range + if (getRange() == 0) { + return; + } + delta = Math.max(Math.min(-delta, 1), -1); this.currentScroll += delta * this.pageSize; this.applyRange(); } + + /** + * Ticks the scrollbar for the purposes of input-repeats (since mouse-downs are + * not repeat-triggered), used to repeatedly page-up or page-down when the mouse + * is held in the area above or below the scrollbar handle. + */ + public void tick() { + this.eventRepeater.tick(); + } + + private void pageUp() { + this.currentScroll -= this.pageSize; + this.applyRange(); + } + + private void pageDown() { + this.currentScroll += this.pageSize; + this.applyRange(); + } + } diff --git a/src/main/java/appeng/me/GridNode.java b/src/main/java/appeng/me/GridNode.java index 8a9699a04..2e0412502 100644 --- a/src/main/java/appeng/me/GridNode.java +++ b/src/main/java/appeng/me/GridNode.java @@ -327,8 +327,9 @@ public class GridNode implements IGridNode, IPathItem { @Override public void setPlayerID(final int playerID) { - if (playerID >= 0) { + if (playerID >= 0 && this.playerID != playerID) { this.playerID = playerID; + gridProxy.onGridNotification(GridNotification.OWNER_CHANGED); } } diff --git a/src/main/java/appeng/me/cache/SecurityCache.java b/src/main/java/appeng/me/cache/SecurityCache.java index 423372570..6a3bbfbe7 100644 --- a/src/main/java/appeng/me/cache/SecurityCache.java +++ b/src/main/java/appeng/me/cache/SecurityCache.java @@ -81,8 +81,16 @@ public class SecurityCache implements ISecurityGrid { private void updateSecurityKey() { final long lastCode = this.securityKey; + /** + * Placing a security station will propagate the security station's owner to all + * connected grid nodes to prevent the network from not reforming due to + * different owners later. + */ + int newOwner = -1; if (this.securityProvider.size() == 1) { - this.securityKey = this.securityProvider.get(0).getSecurityKey(); + ISecurityProvider securityProvider = this.securityProvider.get(0); + this.securityKey = securityProvider.getSecurityKey(); + newOwner = securityProvider.getOwner(); } else { this.securityKey = -1; } @@ -90,7 +98,11 @@ public class SecurityCache implements ISecurityGrid { if (lastCode != this.securityKey) { this.getGrid().postEvent(new MENetworkSecurityChange()); for (final IGridNode n : this.getGrid().getNodes()) { - ((GridNode) n).setLastSecurityKey(this.securityKey); + GridNode gridNode = (GridNode) n; + gridNode.setLastSecurityKey(this.securityKey); + if (gridNode.getPlayerID() != newOwner) { + gridNode.setPlayerID(newOwner); + } } } } diff --git a/src/main/java/appeng/me/helpers/AENetworkProxy.java b/src/main/java/appeng/me/helpers/AENetworkProxy.java index 57f7608a5..5617ef435 100644 --- a/src/main/java/appeng/me/helpers/AENetworkProxy.java +++ b/src/main/java/appeng/me/helpers/AENetworkProxy.java @@ -167,7 +167,6 @@ public class AENetworkProxy implements IGridBlock { * short cut! * * @return grid of node - * * @throws GridAccessException of node or grid is null */ public IGrid getGrid() throws GridAccessException { @@ -280,6 +279,11 @@ public class AENetworkProxy implements IGridBlock { @Override public void onGridNotification(final GridNotification notification) { + if (notification == GridNotification.OWNER_CHANGED) { + gp.saveChanges(); + return; + } + if (this.gp instanceof CablePart) { ((CablePart) this.gp).markForUpdate(); } diff --git a/src/main/java/appeng/me/helpers/IGridProxyable.java b/src/main/java/appeng/me/helpers/IGridProxyable.java index 4f47bdb91..30638a075 100644 --- a/src/main/java/appeng/me/helpers/IGridProxyable.java +++ b/src/main/java/appeng/me/helpers/IGridProxyable.java @@ -28,4 +28,7 @@ public interface IGridProxyable extends IGridHost { DimensionalCoord getLocation(); void gridChanged(); + + void saveChanges(); + } diff --git a/src/main/java/appeng/tile/AEBaseTileEntity.java b/src/main/java/appeng/tile/AEBaseTileEntity.java index 02fc63c1d..9d8430c27 100644 --- a/src/main/java/appeng/tile/AEBaseTileEntity.java +++ b/src/main/java/appeng/tile/AEBaseTileEntity.java @@ -39,10 +39,13 @@ import net.minecraft.network.play.server.SUpdateTileEntityPacket; import net.minecraft.tileentity.TileEntity; import net.minecraft.tileentity.TileEntityType; import net.minecraft.util.Direction; +import net.minecraft.util.math.AxisAlignedBB; import net.minecraft.util.math.BlockPos; import net.minecraft.util.text.ITextComponent; import net.minecraft.util.text.StringTextComponent; import net.minecraft.world.World; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; import net.minecraftforge.client.model.data.IModelData; import net.minecraftforge.items.IItemHandler; @@ -458,4 +461,15 @@ public class AEBaseTileEntity extends TileEntity implements IOrientable, ICommon return new AEModelData(up, forward); } + /** + * AE Tile Entities will generally confine themselves to rendering within the + * bounding block. Forge however would retrieve the collision box here, which is + * very expensive. + */ + @OnlyIn(Dist.CLIENT) + @Override + public AxisAlignedBB getRenderBoundingBox() { + return new AxisAlignedBB(pos, pos.add(1, 1, 1)); + } + }