Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 29 additions & 5 deletions .github/workflows/verify.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
name: fabric 1.20.1 build and regression
on:
push:
branches: ['fabric/1.20.1']
branches: ["gui/**", "feature/**", 'fabric/1.20.1']
pull_request:
branches: ['fabric/1.20.1']
workflow_dispatch:
jobs:
verify:
strategy:
fail-fast: false
matrix:
flavor: [standalone, create]
env:
CREATE_ARGS: ${{ matrix.flavor == 'create' && '-PwithCreate' || '' }}
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
Expand All @@ -23,9 +29,12 @@ jobs:
- name: Fetch pinned Streams dependency
run: |
mkdir -p libs
curl --fail --location --retry 3 'https://cdn.modrinth.com/data/oLS8HdJ1/versions/fPodpXtQ/StreamsReflowing-1.20.1-fabric-2.15.2.jar' -o 'libs/StreamsReflowing-1.20.1-fabric-2.15.2.jar'
curl --fail --location --retry 3 'https://cdn.modrinth.com/data/oLS8HdJ1/versions/JLNG0xIw/StreamsReflowing-1.20.1-fabric-2.16.2.jar' -o 'libs/StreamsReflowing-1.20.1-fabric-2.16.2.jar'
echo 'ca35d6fbfc3e56a741631dda352bcfa662a48a3f470e7388a192e840d314dfdb libs/StreamsReflowing-1.20.1-fabric-2.16.2.jar' | sha256sum --check
- name: Fetch optional Create fixture
run: python3 tools/fetch_create.py
- name: Build
run: bash gradlew --no-daemon clean build
run: bash gradlew --no-daemon $CREATE_ARGS clean build
- name: Prepare disposable regression world
run: |
mkdir -p run/config
Expand All @@ -34,7 +43,7 @@ jobs:
printf 'level-type=minecraft:flat\ngenerate-structures=false\nonline-mode=false\nview-distance=2\nsimulation-distance=2\n' > run/server.properties
- name: Dedicated server regression
run: |
timeout 300 bash gradlew --no-daemon -Dstreamcontrol.integrationTest=true runServer
timeout 300 bash gradlew --no-daemon $CREATE_ARGS -Dstreamcontrol.integrationTest=true runServer
python3 - <<'CHECK'
import json
data=json.load(open('run/streamcontrol-integration-results.json'))
Expand All @@ -46,8 +55,23 @@ jobs:
if: always()
uses: actions/upload-artifact@v4
with:
name: flow-control-fabric-1.20.1-0.1.5
name: flow-control-fabric-1.20.1-0.2.2-${{ matrix.flavor }}
path: |
build/libs/*.jar
run/streamcontrol-integration-results.json
run/logs/latest.log

- name: Capture client menus and baked block models
run: |
mkdir -p run-client
printf 'onboardAccessibility:false\nguiScale:3\nfullscreen:false\n' > run-client/options.txt
timeout 360 xvfb-run -a bash gradlew --no-daemon $CREATE_ARGS -Dstreamcontrol.uiPreview=true runClient
test -s run-client/screenshots/flow-control-block-models.png
- name: Upload client screenshots
if: always()
uses: actions/upload-artifact@v4
with:
name: flow-control-0.2.2-fabric-1.20.1-${{ matrix.flavor }}-client
path: |
run-client/screenshots/*.png
run-client/logs/latest.log
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,7 @@ validation/
.vscode/
.DS_Store
Thumbs.db

libs/create*.jar
libs/create-compile/
run-client/
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
## 0.1.6 — TNK GUI Rework

![Flow Control](https://i.imgur.com/JGT2lvz.png)

- Restyled Current Generator and Stillwater Anchor using the latest TNK Loot / Progression menu design.
- Slate-blue frames, white outer outlines, inset copper panels and a raised title plate.
- Matching textured buttons, compass selections and slider controls, with visible keyboard focus.
- Embedded the supplied mixed-case menu font for titles; retained Minecraft text for settings and values.
- Preserved eight compass directions, normalized diagonal current strength, connected-water mode, redstone behavior and Apply/Cancel.
- Existing saves, config limits and network packet formats remain compatible with 0.1.5.

# Changelog

## 0.1.5 — Eight-way current compass
Expand Down
19 changes: 14 additions & 5 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,23 +1,32 @@
plugins { id 'fabric-loom' version '1.11.8'; id 'maven-publish' }
version = '0.1.5'
version = '0.2.2'
group='nl.townsandkingdoms.streamcontrol'
base.archivesName='streams-flow-control-fabric-1.20.1'
repositories { mavenCentral() }
java.toolchain.languageVersion=JavaLanguageVersion.of(17)
java.withSourcesJar()
loom {
mixin { defaultRefmapName='streamcontrol.refmap.json' }
runs { server { server(); runDir 'run'; vmArg '-Dstreamcontrol.integrationTest='+System.getProperty('streamcontrol.integrationTest','false'); vmArg '-Xmx3G'; programArg '--nogui' } }
runs { client { client(); runDir 'run-client'; vmArg '-Dstreamcontrol.uiPreview='+System.getProperty('streamcontrol.uiPreview','false'); programArg '--width'; programArg '960'; programArg '--height'; programArg '720' }; server { server(); runDir 'run'; vmArg '-Dstreamcontrol.integrationTest='+System.getProperty('streamcontrol.integrationTest','false'); vmArg '-Xmx3G'; programArg '--nogui' } }
}
dependencies {
minecraft 'com.mojang:minecraft:1.20.1'
mappings loom.officialMojangMappings()
modImplementation 'net.fabricmc:fabric-loader:0.18.4'
modImplementation 'net.fabricmc.fabric-api:fabric-api:0.92.12+1.20.1'
modImplementation files('libs/StreamsReflowing-1.20.1-fabric-2.15.2.jar')
modImplementation files('libs/StreamsReflowing-1.20.1-fabric-2.16.2.jar')
}
tasks.withType(JavaCompile).configureEach { options.encoding='UTF-8'; options.release=17 }
tasks.named('jar') { from('LICENSE') }

tasks.named("jar") { archiveFileName = "flow-control-0.1.5-fabric-1.20.1-dev.jar" }
tasks.named("remapJar") { archiveFileName = "flow-control-0.1.5-fabric-1.20.1.jar" }
tasks.named("jar") { archiveFileName = "flow-control-0.2.2-fabric-1.20.1-dev.jar" }
tasks.named("remapJar") { archiveFileName = "flow-control-0.2.2-fabric-1.20.1.jar" }

dependencies {
modCompileOnly files('libs/create-fabric-6.0.8.1+build.1744-mc1.20.1.jar')
modCompileOnly fileTree('libs/create-compile') { include '*.jar' }
if(providers.gradleProperty('withCreate').isPresent()) {
modRuntimeOnly files('libs/create-fabric-6.0.8.1+build.1744-mc1.20.1.jar')
modRuntimeOnly fileTree('libs/create-compile') { include '*.jar' }
}
}
Original file line number Diff line number Diff line change
@@ -1,27 +1,19 @@
package nl.townsandkingdoms.streamcontrol;
import com.mojang.brigadier.arguments.*;
import net.minecraft.commands.Commands;
import net.minecraft.network.chat.Component;
import com.mojang.brigadier.CommandDispatcher;
import net.minecraft.commands.CommandSourceStack;
public final class ControlCommands {
public static void register(com.mojang.brigadier.CommandDispatcher<net.minecraft.commands.CommandSourceStack> dispatcher) {
public static void register(CommandDispatcher<CommandSourceStack> dispatcher) {
dispatcher.register(Commands.literal("streamcontrol")
.then(Commands.literal("radius").then(Commands.argument("blocks",IntegerArgumentType.integer(1,32)).executes(c -> {
var p = c.getSource().getPlayerOrException(); var s = p.getMainHandItem();
if (!s.is(StreamControl.WAND.get())) return 0;
var n = FlowWand.settings(s); n.putInt("Radius",ControlConfig.radius(IntegerArgumentType.getInteger(c,"blocks"))); FlowWand.saveSettings(s,n); return 1;
})))
.then(Commands.literal("strength").then(Commands.argument("value",DoubleArgumentType.doubleArg(0.05,2)).executes(c -> {
var p = c.getSource().getPlayerOrException(); var s = p.getMainHandItem(); if (!s.is(StreamControl.WAND.get())) return 0;
var n = FlowWand.settings(s); n.putDouble("Strength",ControlConfig.strength(DoubleArgumentType.getDouble(c,"value"))); FlowWand.saveSettings(s,n); return 1;
})))
.then(Commands.literal("status").executes(c -> {
var p = net.minecraft.core.BlockPos.containing(c.getSource().getPosition()); var data = ControlData.get(c.getSource().getLevel()); var r = data.at(p.getX(),p.getY(),p.getZ());
c.getSource().sendSuccess(() -> Component.literal("Flow Control: " + data.all().size() + " regions; here=" + (r == null ? "natural" : r.still() ? "still" : "directed")),false); return 1;
.then(Commands.literal("status").executes(c->{
var p=net.minecraft.core.BlockPos.containing(c.getSource().getPosition());var data=ControlData.get(c.getSource().getLevel());var r=data.at(p.getX(),p.getY(),p.getZ());
c.getSource().sendSuccess(()->Component.literal("Flow Control: "+data.all().size()+" regions; here="+(r==null?"natural":r.still()?"still":"directed")),false);return 1;
}))
.then(Commands.literal("clear").requires(s -> s.hasPermission(2)).executes(c -> {
var level = c.getSource().getLevel(); var data = ControlData.get(level); int count = 0;
for (var r : data.all()) if (!r.controller()) { data.remove(level,r.key()); count++; }
final int removed = count; c.getSource().sendSuccess(() -> Component.literal("Removed " + removed + " wand regions in this dimension."),true); return count;
.then(Commands.literal("clear").requires(s->s.hasPermission(2)).executes(c->{
var level=c.getSource().getLevel();var data=ControlData.get(level);int count=0;
for(var r:data.all()) if(!r.controller()) { data.remove(level,r.key());count++; }
final int removed=count;c.getSource().sendSuccess(()->Component.literal("Removed "+removed+" legacy wand regions in this dimension."),true);return count;
})));
}
}
101 changes: 68 additions & 33 deletions src/main/java/nl/townsandkingdoms/streamcontrol/ControlConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,42 +5,77 @@
import net.minecraft.world.level.storage.LevelResource;
import net.fabricmc.loader.api.FabricLoader;
public final class ControlConfig {
public static final class Value<T> { private volatile T value; public Value(T v){value=v;} public T get(){return value;} public void set(T v){value=v;} }
public static final Value<Boolean> ENABLED=new Value<>(true),WAND_ENABLED=new Value<>(true),OP_ONLY=new Value<>(false),PARTICLES=new Value<>(true);
public static final Value<Integer> MAX_RADIUS=new Value<>(16),DEFAULT_RADIUS=new Value<>(4),VERTICAL=new Value<>(4),MAX_REGIONS=new Value<>(2048),MAX_PLAYER_REGIONS=new Value<>(256);
public static final Value<Double> SPEED=new Value<>(1.0),MAX_SPEED=new Value<>(2.0);
public static int radius(int v){return Math.max(1,Math.min(MAX_RADIUS.get(),v));}
public static double strength(double v){return Double.isFinite(v)?Math.max(0.05,Math.min(MAX_SPEED.get(),v)):Math.min(1,MAX_SPEED.get());}
private static boolean flag(JsonObject j,String k,boolean d){return j.has(k)?j.get(k).getAsBoolean():d;}
private static int integer(JsonObject j,String k,int d,int lo,int hi){return Math.max(lo,Math.min(hi,j.has(k)?j.get(k).getAsInt():d));}
private static double number(JsonObject j,String k,double d){double v=j.has(k)?j.get(k).getAsDouble():d;return Double.isFinite(v)?Math.max(0.05,Math.min(2,v)):d;}
public static final class Value<T> {private volatile T value;public Value(T v){value=v;}public T get(){return value;}public void set(T v){value=v;}}
public static final Value<Boolean> ENABLED=new Value<>(true);
public static final Value<Boolean> WAND_ENABLED=new Value<>(true);
public static final Value<Boolean> OP_ONLY=new Value<>(false);
public static final Value<Boolean> PARTICLES=new Value<>(true);
public static final Value<Integer> MAX_RADIUS=new Value<>(16);
public static final Value<Integer> DEFAULT_RADIUS=new Value<>(4);
public static final Value<Integer> VERTICAL=new Value<>(4);
public static final Value<Double> SPEED=new Value<>(1.0);
public static final Value<Double> MAX_SPEED=new Value<>(2.0);
public static final Value<Integer> MAX_REGIONS=new Value<>(2048);
public static final Value<Integer> MAX_PLAYER_REGIONS=new Value<>(256);
public static final Value<Integer> MAX_VERTICAL=new Value<>(16);
public static final Value<Integer> LINK_RANGE=new Value<>(64);
public static final Value<Integer> SENSOR_MAX_RADIUS=new Value<>(16);
public static final Value<Integer> SENSOR_MAX_VERTICAL=new Value<>(16);
public static final Value<Integer> SENSOR_INTERVAL=new Value<>(20);
public static final Value<Integer> MOORING_MAX_RADIUS=new Value<>(8);
public static final Value<Integer> MOORING_MAX_VERTICAL=new Value<>(8);
public static final Value<Double> RPM_FULL_STRENGTH=new Value<>(128.0);
public static final Value<Double> KINETIC_STRESS=new Value<>(4.0);
public static int radius(int n){return Math.max(1,Math.min(MAX_RADIUS.get(),n));}
public static double strength(double n){return Double.isFinite(n)?Math.max(.05,Math.min(MAX_SPEED.get(),n)):Math.min(1,MAX_SPEED.get());}
public static void load(MinecraftServer server){
ENABLED.set(true);WAND_ENABLED.set(true);OP_ONLY.set(false);PARTICLES.set(true);MAX_RADIUS.set(16);DEFAULT_RADIUS.set(4);VERTICAL.set(4);MAX_REGIONS.set(2048);MAX_PLAYER_REGIONS.set(256);SPEED.set(1.0);MAX_SPEED.set(2.0);
Path path=server.getWorldPath(LevelResource.ROOT).resolve("serverconfig/streamcontrol-server.json");
Path defaults=FabricLoader.getInstance().getGameDir().resolve("defaultconfigs/streamcontrol-server.json");
try {
Files.createDirectories(path.getParent());
if(!Files.exists(path)){
if(Files.isRegularFile(defaults))Files.copy(defaults,path);
else Files.writeString(path,"""
{
"enabled": true,
"wandEnabled": true,
"wandRequiresOperator": false,
"showWandFeedbackParticles": true,
"defaultRadius": 4,
"maxRadius": 16,
"verticalReach": 4,
"defaultStrength": 1.0,
"maxStrength": 2.0,
"maxRegionsPerDimension": 2048,
"maxWandRegionsPerPlayer": 256
}
""");
}
JsonObject j=JsonParser.parseString(Files.readString(path)).getAsJsonObject();
ENABLED.set(flag(j,"enabled",true));WAND_ENABLED.set(flag(j,"wandEnabled",true));OP_ONLY.set(flag(j,"wandRequiresOperator",false));PARTICLES.set(flag(j,"showWandFeedbackParticles",true));
MAX_RADIUS.set(integer(j,"maxRadius",16,1,32));DEFAULT_RADIUS.set(integer(j,"defaultRadius",4,1,32));VERTICAL.set(integer(j,"verticalReach",4,0,16));MAX_REGIONS.set(integer(j,"maxRegionsPerDimension",2048,1,8192));MAX_PLAYER_REGIONS.set(integer(j,"maxWandRegionsPerPlayer",256,1,2048));SPEED.set(number(j,"defaultStrength",1));MAX_SPEED.set(number(j,"maxStrength",2));
}catch(Exception e){org.slf4j.LoggerFactory.getLogger("streamcontrol").error("Cannot load Flow Control settings from {}",path,e);}
Path defaults=FabricLoader.getInstance().getGameDir().resolve("defaultconfigs/streamcontrol-server.json");
if(!Files.exists(path) && Files.isRegularFile(defaults))Files.copy(defaults,path);
JsonObject n=Files.exists(path)?JsonParser.parseString(Files.readString(path)).getAsJsonObject():new JsonObject();
if(!n.has("enabled")) n.addProperty("enabled",true);
ENABLED.set(n.get("enabled").getAsBoolean());
if(!n.has("wandEnabled")) n.addProperty("wandEnabled",true);
WAND_ENABLED.set(n.get("wandEnabled").getAsBoolean());
if(!n.has("wandRequiresOperator")) n.addProperty("wandRequiresOperator",false);
OP_ONLY.set(n.get("wandRequiresOperator").getAsBoolean());
if(!n.has("showWandFeedbackParticles")) n.addProperty("showWandFeedbackParticles",true);
PARTICLES.set(n.get("showWandFeedbackParticles").getAsBoolean());
if(!n.has("maxRadius")) n.addProperty("maxRadius",16);
MAX_RADIUS.set(Math.max(1,Math.min(32,n.get("maxRadius").getAsInt())));
if(!n.has("defaultRadius")) n.addProperty("defaultRadius",4);
DEFAULT_RADIUS.set(Math.max(1,Math.min(32,n.get("defaultRadius").getAsInt())));
if(!n.has("verticalReach")) n.addProperty("verticalReach",4);
VERTICAL.set(Math.max(0,Math.min(16,n.get("verticalReach").getAsInt())));
if(!n.has("defaultStrength")) n.addProperty("defaultStrength",1.0);
SPEED.set(Double.isFinite(n.get("defaultStrength").getAsDouble())?Math.max(0.05,Math.min(2.0,n.get("defaultStrength").getAsDouble())):1.0);
if(!n.has("maxStrength")) n.addProperty("maxStrength",2.0);
MAX_SPEED.set(Double.isFinite(n.get("maxStrength").getAsDouble())?Math.max(0.05,Math.min(2.0,n.get("maxStrength").getAsDouble())):2.0);
if(!n.has("maxRegionsPerDimension")) n.addProperty("maxRegionsPerDimension",2048);
MAX_REGIONS.set(Math.max(1,Math.min(8192,n.get("maxRegionsPerDimension").getAsInt())));
if(!n.has("maxWandRegionsPerPlayer")) n.addProperty("maxWandRegionsPerPlayer",256);
MAX_PLAYER_REGIONS.set(Math.max(1,Math.min(2048,n.get("maxWandRegionsPerPlayer").getAsInt())));
if(!n.has("maxControllerVerticalReach")) n.addProperty("maxControllerVerticalReach",16);
MAX_VERTICAL.set(Math.max(0,Math.min(16,n.get("maxControllerVerticalReach").getAsInt())));
if(!n.has("maxControllerLinkDistance")) n.addProperty("maxControllerLinkDistance",64);
LINK_RANGE.set(Math.max(1,Math.min(256,n.get("maxControllerLinkDistance").getAsInt())));
if(!n.has("maxSensorRadius")) n.addProperty("maxSensorRadius",16);
SENSOR_MAX_RADIUS.set(Math.max(1,Math.min(16,n.get("maxSensorRadius").getAsInt())));
if(!n.has("maxSensorVerticalReach")) n.addProperty("maxSensorVerticalReach",16);
SENSOR_MAX_VERTICAL.set(Math.max(0,Math.min(16,n.get("maxSensorVerticalReach").getAsInt())));
if(!n.has("sensorSampleIntervalTicks")) n.addProperty("sensorSampleIntervalTicks",20);
SENSOR_INTERVAL.set(Math.max(5,Math.min(200,n.get("sensorSampleIntervalTicks").getAsInt())));
if(!n.has("maxMooringRadius")) n.addProperty("maxMooringRadius",8);
MOORING_MAX_RADIUS.set(Math.max(1,Math.min(16,n.get("maxMooringRadius").getAsInt())));
if(!n.has("maxMooringVerticalReach")) n.addProperty("maxMooringVerticalReach",8);
MOORING_MAX_VERTICAL.set(Math.max(0,Math.min(16,n.get("maxMooringVerticalReach").getAsInt())));
if(!n.has("kineticRpmForFullStrength")) n.addProperty("kineticRpmForFullStrength",128.0);
RPM_FULL_STRENGTH.set(Double.isFinite(n.get("kineticRpmForFullStrength").getAsDouble())?Math.max(1.0,Math.min(1024.0,n.get("kineticRpmForFullStrength").getAsDouble())):128.0);
if(!n.has("kineticStressImpact")) n.addProperty("kineticStressImpact",4.0);
KINETIC_STRESS.set(Double.isFinite(n.get("kineticStressImpact").getAsDouble())?Math.max(0.0,Math.min(256.0,n.get("kineticStressImpact").getAsDouble())):4.0);
Files.writeString(path,new GsonBuilder().setPrettyPrinting().create().toJson(n));
}catch(Exception e){throw new IllegalStateException("Cannot load Flow Control settings from "+path,e);}
}
}
Loading
Loading