Added Quests and Real Settings (yay)
This commit is contained in:
+5
-19
@@ -10,14 +10,15 @@ base {
|
|||||||
archivesName = project.archives_base_name
|
archivesName = project.archives_base_name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
maven { url 'https://maven.wispforest.io/releases/' }
|
maven { url 'https://maven.wispforest.io/releases/' }
|
||||||
maven { url 'https://jitpack.io' } // required since owo 0.13.0, for a transitive dependency
|
maven { url 'https://jitpack.io' }
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
minecraft "com.mojang:minecraft:26.1.2"
|
minecraft "com.mojang:minecraft:26.1.2"
|
||||||
|
// NO mappings block — 26.1+ ships unobfuscated, mappings aren't used
|
||||||
|
|
||||||
implementation "net.fabricmc:fabric-loader:0.18.4"
|
implementation "net.fabricmc:fabric-loader:0.18.4"
|
||||||
implementation "net.fabricmc.fabric-api:fabric-api:0.154.0+26.1.2"
|
implementation "net.fabricmc.fabric-api:fabric-api:0.154.0+26.1.2"
|
||||||
|
|
||||||
@@ -40,10 +41,6 @@ processResources {
|
|||||||
|
|
||||||
def targetJavaVersion = 25
|
def targetJavaVersion = 25
|
||||||
tasks.withType(JavaCompile).configureEach {
|
tasks.withType(JavaCompile).configureEach {
|
||||||
// ensure that the encoding is set to UTF-8, no matter what the system default is
|
|
||||||
// this fixes some edge cases with special characters not displaying correctly
|
|
||||||
// see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html
|
|
||||||
// If Javadoc is generated, this must be specified in that task too.
|
|
||||||
it.options.encoding = "UTF-8"
|
it.options.encoding = "UTF-8"
|
||||||
if (targetJavaVersion >= 10 || JavaVersion.current().isJava10Compatible()) {
|
if (targetJavaVersion >= 10 || JavaVersion.current().isJava10Compatible()) {
|
||||||
it.options.release.set(targetJavaVersion)
|
it.options.release.set(targetJavaVersion)
|
||||||
@@ -55,9 +52,6 @@ java {
|
|||||||
if (JavaVersion.current() < javaVersion) {
|
if (JavaVersion.current() < javaVersion) {
|
||||||
toolchain.languageVersion = JavaLanguageVersion.of(targetJavaVersion)
|
toolchain.languageVersion = JavaLanguageVersion.of(targetJavaVersion)
|
||||||
}
|
}
|
||||||
// Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task
|
|
||||||
// if it is present.
|
|
||||||
// If you remove this line, sources will not be generated.
|
|
||||||
withSourcesJar()
|
withSourcesJar()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,7 +61,6 @@ jar {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// configure the maven publication
|
|
||||||
publishing {
|
publishing {
|
||||||
publications {
|
publications {
|
||||||
create("mavenJava", MavenPublication) {
|
create("mavenJava", MavenPublication) {
|
||||||
@@ -75,12 +68,5 @@ publishing {
|
|||||||
from components.java
|
from components.java
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
repositories {}
|
||||||
// See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing.
|
}
|
||||||
repositories {
|
|
||||||
// Add repositories to publish to here.
|
|
||||||
// Notice: This block does NOT have the same function as the block in the top level.
|
|
||||||
// The repositories here will be used for publishing your artifact, not for
|
|
||||||
// retrieving dependencies.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -13,6 +13,7 @@ import java.nio.file.Path;
|
|||||||
|
|
||||||
public class Config {
|
public class Config {
|
||||||
|
|
||||||
|
public int fpsP = 6;
|
||||||
public int fpsX = 1;
|
public int fpsX = 1;
|
||||||
public int fpsY = 1;
|
public int fpsY = 1;
|
||||||
public String lastModule = "FPS";
|
public String lastModule = "FPS";
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ public class Module_FPS {
|
|||||||
|
|
||||||
if (toggle && !shown) {
|
if (toggle && !shown) {
|
||||||
fpsContainer.surface(GLASS_PANEL);
|
fpsContainer.surface(GLASS_PANEL);
|
||||||
fpsContainer.padding(Insets.of(6));
|
fpsContainer.padding(Insets.of(Config.get().fpsP));
|
||||||
fpsContainer.child(fpsLabel);
|
fpsContainer.child(fpsLabel);
|
||||||
shown = true;
|
shown = true;
|
||||||
} else if (!toggle && shown) {
|
} else if (!toggle && shown) {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package de.fullrisk.citrusDev.UI;
|
package de.fullrisk.citrusDev.UI;
|
||||||
|
|
||||||
import de.fullrisk.citrusDev.Config;
|
import de.fullrisk.citrusDev.Config;
|
||||||
|
import de.fullrisk.citrusDev.UIHelper.*;
|
||||||
import io.wispforest.owo.ui.base.BaseOwoScreen;
|
import io.wispforest.owo.ui.base.BaseOwoScreen;
|
||||||
import io.wispforest.owo.ui.component.UIComponents;
|
import io.wispforest.owo.ui.component.UIComponents;
|
||||||
import io.wispforest.owo.ui.container.FlowLayout;
|
import io.wispforest.owo.ui.container.FlowLayout;
|
||||||
@@ -12,20 +13,19 @@ import net.minecraft.network.chat.Component;
|
|||||||
import net.minecraft.network.chat.FontDescription;
|
import net.minecraft.network.chat.FontDescription;
|
||||||
import net.minecraft.resources.Identifier;
|
import net.minecraft.resources.Identifier;
|
||||||
import net.minecraft.sounds.SoundEvents;
|
import net.minecraft.sounds.SoundEvents;
|
||||||
import de.fullrisk.citrusDev.UIHelper.ScalableLabel;
|
import de.fullrisk.citrusDev.client.PlaytimeTracker;
|
||||||
import de.fullrisk.citrusDev.UIHelper.ScalableButton;
|
|
||||||
import de.fullrisk.citrusDev.UIHelper.ScaleValue;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.Flow;
|
||||||
|
|
||||||
import static de.fullrisk.citrusDev.UI.Module_FPS.fpsContainer;
|
import static de.fullrisk.citrusDev.UI.Module_FPS.fpsContainer;
|
||||||
import static de.fullrisk.citrusDev.UI.Module_FPS.toggle;
|
import static de.fullrisk.citrusDev.UI.Module_FPS.toggle;
|
||||||
|
|
||||||
public class MyScreen extends BaseOwoScreen<FlowLayout> {
|
public class MyScreen extends BaseOwoScreen<FlowLayout> {
|
||||||
|
|
||||||
private static final Identifier MC_FIVE = Identifier.fromNamespaceAndPath("citrus-dev", "minecraft_five");
|
static final Identifier MC_FIVE = Identifier.fromNamespaceAndPath("citrus-dev", "minecraft_five");
|
||||||
|
|
||||||
private static final Surface GLASS_PANEL = Surface.blur(5f, 15f)
|
static final Surface GLASS_PANEL = Surface.blur(5f, 15f)
|
||||||
.and(Surface.flat(0x40FFFFFF))
|
.and(Surface.flat(0x40FFFFFF))
|
||||||
.and(Surface.outline(0x64de4b));
|
.and(Surface.outline(0x64de4b));
|
||||||
|
|
||||||
@@ -47,6 +47,11 @@ public class MyScreen extends BaseOwoScreen<FlowLayout> {
|
|||||||
|
|
||||||
private record TabDef(String label, Runnable onClick) {}
|
private record TabDef(String label, Runnable onClick) {}
|
||||||
private record SettingRow(String label, FlowLayout control) {}
|
private record SettingRow(String label, FlowLayout control) {}
|
||||||
|
private record QuestData(String title, String description, int requiredPlaytime) {
|
||||||
|
boolean isUnlocked() {
|
||||||
|
return PlaytimeTracker.getCurrentPlaytimeSeconds() >= requiredPlaytime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private FlowLayout content;
|
private FlowLayout content;
|
||||||
private final String initialModule;
|
private final String initialModule;
|
||||||
@@ -101,7 +106,7 @@ public class MyScreen extends BaseOwoScreen<FlowLayout> {
|
|||||||
row.child(sidebar);
|
row.child(sidebar);
|
||||||
|
|
||||||
content = UIContainers.verticalFlow(Sizing.fixed(CONTENT_WIDTH), Sizing.fixed(ROW_HEIGHT));
|
content = UIContainers.verticalFlow(Sizing.fixed(CONTENT_WIDTH), Sizing.fixed(ROW_HEIGHT));
|
||||||
content.gap(6);
|
content.gap(2);
|
||||||
content.padding(Insets.of(4));
|
content.padding(Insets.of(4));
|
||||||
content.horizontalAlignment(HorizontalAlignment.CENTER);
|
content.horizontalAlignment(HorizontalAlignment.CENTER);
|
||||||
content.verticalAlignment(VerticalAlignment.TOP);
|
content.verticalAlignment(VerticalAlignment.TOP);
|
||||||
@@ -125,7 +130,6 @@ public class MyScreen extends BaseOwoScreen<FlowLayout> {
|
|||||||
|
|
||||||
rootComponent.child(panel);
|
rootComponent.child(panel);
|
||||||
|
|
||||||
// Now that content is attached to the tree, decide what to show.
|
|
||||||
if ("FPS".equals(initialModule)) {
|
if ("FPS".equals(initialModule)) {
|
||||||
openModuleSettings("FPS", fpsModuleSettings());
|
openModuleSettings("FPS", fpsModuleSettings());
|
||||||
} else {
|
} else {
|
||||||
@@ -154,7 +158,7 @@ public class MyScreen extends BaseOwoScreen<FlowLayout> {
|
|||||||
content.clearChildren();
|
content.clearChildren();
|
||||||
content.horizontalAlignment(HorizontalAlignment.CENTER);
|
content.horizontalAlignment(HorizontalAlignment.CENTER);
|
||||||
content.verticalAlignment(VerticalAlignment.TOP);
|
content.verticalAlignment(VerticalAlignment.TOP);
|
||||||
buildQuestsPanel(content);
|
QuestsPanel.build(content, CONTENT_WIDTH, ROW_HEIGHT);
|
||||||
}),
|
}),
|
||||||
new TabDef("settings", () -> {
|
new TabDef("settings", () -> {
|
||||||
content.clearChildren();
|
content.clearChildren();
|
||||||
@@ -244,7 +248,6 @@ public class MyScreen extends BaseOwoScreen<FlowLayout> {
|
|||||||
return button;
|
return button;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void buildGeneralPanel(FlowLayout content) {
|
private void buildGeneralPanel(FlowLayout content) {
|
||||||
FlowLayout iconButton = UIContainers.horizontalFlow(Sizing.content(), Sizing.content());
|
FlowLayout iconButton = UIContainers.horizontalFlow(Sizing.content(), Sizing.content());
|
||||||
iconButton.gap(8);
|
iconButton.gap(8);
|
||||||
@@ -281,7 +284,7 @@ public class MyScreen extends BaseOwoScreen<FlowLayout> {
|
|||||||
iconButton.child(textStack);
|
iconButton.child(textStack);
|
||||||
iconButton.child(UIComponents.spacer());
|
iconButton.child(UIComponents.spacer());
|
||||||
|
|
||||||
FlowLayout settingsButton = makeSmallButton(
|
FlowLayout settingsButton = SettingsControls.makeSmallButton(
|
||||||
FLAT,
|
FLAT,
|
||||||
Identifier.fromNamespaceAndPath("citrus-dev", "textures/icons/settings.png"),
|
Identifier.fromNamespaceAndPath("citrus-dev", "textures/icons/settings.png"),
|
||||||
() -> {
|
() -> {
|
||||||
@@ -300,6 +303,7 @@ public class MyScreen extends BaseOwoScreen<FlowLayout> {
|
|||||||
toggle = !toggle;
|
toggle = !toggle;
|
||||||
fpsLabel.color(Color.ofRgb(toggle ? 0xffffff : 0x797b86));
|
fpsLabel.color(Color.ofRgb(toggle ? 0xffffff : 0x797b86));
|
||||||
iconButton.surface(toggle ? GLASS_PANEL : DISABLED);
|
iconButton.surface(toggle ? GLASS_PANEL : DISABLED);
|
||||||
|
CitrusToast.show("Module Enabled", "FPS counter is now active");
|
||||||
|
|
||||||
Minecraft.getInstance().getSoundManager().play(
|
Minecraft.getInstance().getSoundManager().play(
|
||||||
SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK.value(), 2.0F, 2)
|
SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK.value(), 2.0F, 2)
|
||||||
@@ -313,31 +317,23 @@ public class MyScreen extends BaseOwoScreen<FlowLayout> {
|
|||||||
content.child(iconButton);
|
content.child(iconButton);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void buildQuestsPanel(FlowLayout content) {
|
|
||||||
content.child(UIComponents.label(Component.literal("Quests - coming soon")));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void buildSettingsPanel(FlowLayout content) {
|
private void buildSettingsPanel(FlowLayout content) {
|
||||||
content.child(UIComponents.label(Component.literal("Settings")));
|
content.child(UIComponents.label(Component.literal("Settings")));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Opens a per-module settings view inside the "content" container,
|
|
||||||
* replacing whatever was there before. A back arrow returns to General.
|
|
||||||
*/
|
|
||||||
private void openModuleSettings(String title, List<SettingRow> rows) {
|
private void openModuleSettings(String title, List<SettingRow> rows) {
|
||||||
content.clearChildren();
|
content.clearChildren();
|
||||||
|
|
||||||
// re-assert alignment explicitly - clearChildren can reset it
|
|
||||||
content.horizontalAlignment(HorizontalAlignment.LEFT);
|
content.horizontalAlignment(HorizontalAlignment.LEFT);
|
||||||
content.verticalAlignment(VerticalAlignment.TOP);
|
content.verticalAlignment(VerticalAlignment.TOP);
|
||||||
|
|
||||||
FlowLayout header = UIContainers.horizontalFlow(Sizing.fixed(CONTENT_WIDTH - 8), Sizing.content());
|
FlowLayout header = UIContainers.horizontalFlow(Sizing.fixed(CONTENT_WIDTH - 8), Sizing.content());
|
||||||
header.verticalAlignment(VerticalAlignment.CENTER);
|
header.verticalAlignment(VerticalAlignment.CENTER);
|
||||||
header.horizontalAlignment(HorizontalAlignment.LEFT);
|
header.horizontalAlignment(HorizontalAlignment.LEFT);
|
||||||
header.gap(6);
|
header.gap(4);
|
||||||
|
|
||||||
header.child(makeTextButton(FLAT, "<", () -> {
|
header.child(SettingsControls.makeTextButton(FLAT, "<", () -> {
|
||||||
content.clearChildren();
|
content.clearChildren();
|
||||||
content.horizontalAlignment(HorizontalAlignment.CENTER);
|
content.horizontalAlignment(HorizontalAlignment.CENTER);
|
||||||
content.verticalAlignment(VerticalAlignment.TOP);
|
content.verticalAlignment(VerticalAlignment.TOP);
|
||||||
@@ -350,17 +346,32 @@ public class MyScreen extends BaseOwoScreen<FlowLayout> {
|
|||||||
|
|
||||||
content.child(header);
|
content.child(header);
|
||||||
|
|
||||||
|
FlowLayout rowsContainer = UIContainers.verticalFlow(Sizing.fixed(CONTENT_WIDTH - 8), Sizing.content());
|
||||||
|
rowsContainer.gap(1);
|
||||||
|
rowsContainer.verticalAlignment(VerticalAlignment.TOP);
|
||||||
|
rowsContainer.horizontalAlignment(HorizontalAlignment.LEFT);
|
||||||
|
rowsContainer.padding(Insets.of(0));
|
||||||
|
|
||||||
for (SettingRow row : rows) {
|
for (SettingRow row : rows) {
|
||||||
content.child(makeSettingRow(row));
|
FlowLayout settingRow = makeSettingRow(row);
|
||||||
|
settingRow.verticalSizing(Sizing.fixed(16)); // pin each row to a compact fixed height
|
||||||
|
rowsContainer.child(settingRow);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var scrollArea = UIContainers.verticalScroll(
|
||||||
|
Sizing.fixed(CONTENT_WIDTH - 8),
|
||||||
|
Sizing.fixed(ROW_HEIGHT - 24),
|
||||||
|
rowsContainer
|
||||||
|
);
|
||||||
|
content.child(scrollArea);
|
||||||
}
|
}
|
||||||
|
|
||||||
private FlowLayout makeSettingRow(SettingRow setting) {
|
private FlowLayout makeSettingRow(SettingRow setting) {
|
||||||
FlowLayout row = UIContainers.horizontalFlow(Sizing.fixed(CONTENT_WIDTH - 8), Sizing.content());
|
FlowLayout row = UIContainers.horizontalFlow(Sizing.fixed(CONTENT_WIDTH - 8), Sizing.fixed(16));
|
||||||
row.verticalAlignment(VerticalAlignment.TOP);
|
row.verticalAlignment(VerticalAlignment.CENTER);
|
||||||
row.horizontalAlignment(HorizontalAlignment.LEFT);
|
row.horizontalAlignment(HorizontalAlignment.LEFT);
|
||||||
row.padding(Insets.of(2));
|
row.padding(Insets.of(0));
|
||||||
row.gap(6);
|
row.gap(4);
|
||||||
|
|
||||||
row.child(UIComponents.label(Component.literal(setting.label()))
|
row.child(UIComponents.label(Component.literal(setting.label()))
|
||||||
.color(Color.ofRgb(0xa3a3ac)));
|
.color(Color.ofRgb(0xa3a3ac)));
|
||||||
@@ -371,114 +382,28 @@ public class MyScreen extends BaseOwoScreen<FlowLayout> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private List<SettingRow> fpsModuleSettings() {
|
private List<SettingRow> fpsModuleSettings() {
|
||||||
|
List<SettingsControls.ButtonState> positionStates = List.of(
|
||||||
|
new SettingsControls.ButtonState("L-TOP", () -> Module_FPS.setPosition(1, 1)),
|
||||||
|
new SettingsControls.ButtonState("R-TOP", () -> Module_FPS.setPosition(99, 1)),
|
||||||
|
new SettingsControls.ButtonState("R-BOTTOM", () -> Module_FPS.setPosition(99, 99)),
|
||||||
|
new SettingsControls.ButtonState("L-BOTTOM", () -> Module_FPS.setPosition(1, 99))
|
||||||
|
);
|
||||||
|
|
||||||
|
FlowLayout sizeSlider = SettingsControls.makeSlider(
|
||||||
|
FLAT,
|
||||||
|
80,
|
||||||
|
0.0, 20.0,
|
||||||
|
4.0,
|
||||||
|
value -> (int) value + "px",
|
||||||
|
value -> Module_FPS.fpsContainer.gap((int) value)
|
||||||
|
);
|
||||||
|
|
||||||
return List.of(
|
return List.of(
|
||||||
new SettingRow("Position", makeCyclingButton())
|
new SettingRow("Position", SettingsControls.makeCyclingButton(GLASS_PANEL, positionStates)),
|
||||||
|
new SettingRow("Size", sizeSlider)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private record ButtonState(String label, Runnable onSelect) {}
|
|
||||||
|
|
||||||
private FlowLayout makeCyclingButton() {
|
|
||||||
List<ButtonState> states = List.of(
|
|
||||||
new ButtonState("Off", () -> {
|
|
||||||
Module_FPS.setPosition(1, 1);
|
|
||||||
|
|
||||||
}),
|
|
||||||
new ButtonState("Low", () -> {
|
|
||||||
Module_FPS.setPosition(99, 1);
|
|
||||||
|
|
||||||
}),
|
|
||||||
new ButtonState("Medium", () -> {
|
|
||||||
Module_FPS.setPosition(99, 99);
|
|
||||||
|
|
||||||
}),
|
|
||||||
new ButtonState("High", () -> {
|
|
||||||
Module_FPS.setPosition(1, 99);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
int[] currentIndex = {0};
|
|
||||||
|
|
||||||
FlowLayout button = UIContainers.verticalFlow(Sizing.fixed(50), Sizing.fixed(16));
|
|
||||||
button.verticalAlignment(VerticalAlignment.CENTER);
|
|
||||||
button.horizontalAlignment(HorizontalAlignment.CENTER);
|
|
||||||
button.surface(GLASS_PANEL);
|
|
||||||
|
|
||||||
var label = UIComponents.label(Component.literal(states.get(0).label()))
|
|
||||||
.color(Color.ofRgb(0xFFFFFF));
|
|
||||||
button.child(label);
|
|
||||||
|
|
||||||
button.mouseDown().subscribe((click, doubled) -> {
|
|
||||||
Minecraft.getInstance().getSoundManager().play(
|
|
||||||
SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK.value(), 2.0F, 2)
|
|
||||||
);
|
|
||||||
|
|
||||||
currentIndex[0] = (currentIndex[0] + 1) % states.size();
|
|
||||||
ButtonState newState = states.get(currentIndex[0]);
|
|
||||||
|
|
||||||
label.text(Component.literal(newState.label()));
|
|
||||||
newState.onSelect().run();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
button.mouseEnter().subscribe(() -> {
|
|
||||||
label.color(Color.ofRgb(0xFFFFFF));
|
|
||||||
});
|
|
||||||
button.mouseLeave().subscribe(() -> {
|
|
||||||
label.color(Color.ofRgb(0xCCCCCC));
|
|
||||||
});
|
|
||||||
return button;
|
|
||||||
}
|
|
||||||
|
|
||||||
private FlowLayout makeSmallButton(Surface baseSurface, Identifier texture, Runnable onClick) {
|
|
||||||
FlowLayout button = UIContainers.verticalFlow(Sizing.fixed(16), Sizing.fixed(16));
|
|
||||||
button.verticalAlignment(VerticalAlignment.CENTER);
|
|
||||||
button.horizontalAlignment(HorizontalAlignment.CENTER);
|
|
||||||
button.surface(baseSurface);
|
|
||||||
button.child(UIComponents.texture(texture, 0, 0, 12, 12, 12, 12))
|
|
||||||
.horizontalAlignment(HorizontalAlignment.CENTER)
|
|
||||||
.verticalAlignment(VerticalAlignment.CENTER);
|
|
||||||
|
|
||||||
button.mouseEnter().subscribe(() -> button.surface(
|
|
||||||
Surface.blur(5f, 15f).and(Surface.flat(0x70FFFFFF)).and(Surface.outline(0xFFFFFFFF))
|
|
||||||
));
|
|
||||||
button.mouseLeave().subscribe(() -> button.surface(baseSurface));
|
|
||||||
|
|
||||||
button.mouseDown().subscribe((click, doubled) -> {
|
|
||||||
Minecraft.getInstance().getSoundManager().play(
|
|
||||||
SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK.value(), 2.0F, 2)
|
|
||||||
);
|
|
||||||
onClick.run();
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
return button;
|
|
||||||
}
|
|
||||||
|
|
||||||
private FlowLayout makeTextButton(Surface baseSurface, String text, Runnable onClick) {
|
|
||||||
FlowLayout button = UIContainers.verticalFlow(Sizing.content(), Sizing.content());
|
|
||||||
button.verticalAlignment(VerticalAlignment.CENTER);
|
|
||||||
button.horizontalAlignment(HorizontalAlignment.CENTER);
|
|
||||||
button.padding(Insets.of(2, 2, 4, 4));
|
|
||||||
button.surface(baseSurface);
|
|
||||||
button.child(UIComponents.label(Component.literal(text)).color(Color.ofRgb(0xFFFFFF)));
|
|
||||||
|
|
||||||
button.mouseEnter().subscribe(() -> button.surface(
|
|
||||||
Surface.blur(5f, 15f).and(Surface.flat(0x70FFFFFF)).and(Surface.outline(0xFFFFFFFF))
|
|
||||||
));
|
|
||||||
button.mouseLeave().subscribe(() -> button.surface(baseSurface));
|
|
||||||
|
|
||||||
button.mouseDown().subscribe((click, doubled) -> {
|
|
||||||
Minecraft.getInstance().getSoundManager().play(
|
|
||||||
SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK.value(), 2.0F, 2)
|
|
||||||
);
|
|
||||||
onClick.run();
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
return button;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isPauseScreen() {
|
public boolean isPauseScreen() {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -1,5 +1,23 @@
|
|||||||
package de.fullrisk.citrusDev.UI;
|
package de.fullrisk.citrusDev.UI;
|
||||||
|
|
||||||
public class OverviewScreen {
|
import io.wispforest.owo.ui.base.BaseOwoScreen;
|
||||||
|
import io.wispforest.owo.ui.component.UIComponents;
|
||||||
|
import io.wispforest.owo.ui.container.FlowLayout;
|
||||||
|
import io.wispforest.owo.ui.container.UIContainers;
|
||||||
|
import io.wispforest.owo.ui.core.HorizontalAlignment;
|
||||||
|
import io.wispforest.owo.ui.core.OwoUIAdapter;
|
||||||
|
import io.wispforest.owo.ui.core.VerticalAlignment;
|
||||||
|
import net.minecraft.network.chat.Component;
|
||||||
|
import io.wispforest.owo.ui.core.*;
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraft.client.resources.sounds.SimpleSoundInstance;
|
||||||
|
import net.minecraft.network.chat.FontDescription;
|
||||||
|
import net.minecraft.resources.Identifier;
|
||||||
|
import net.minecraft.sounds.SoundEvents;
|
||||||
|
import de.fullrisk.citrusDev.UIHelper.ScalableLabel;
|
||||||
|
import de.fullrisk.citrusDev.UIHelper.ScalableButton;
|
||||||
|
import de.fullrisk.citrusDev.UIHelper.ScaleValue;
|
||||||
|
|
||||||
|
public class OverviewScreen{
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,156 @@
|
|||||||
|
package de.fullrisk.citrusDev.UI;
|
||||||
|
|
||||||
|
import io.wispforest.owo.ui.component.UIComponents;
|
||||||
|
import io.wispforest.owo.ui.container.FlowLayout;
|
||||||
|
import io.wispforest.owo.ui.container.UIContainers;
|
||||||
|
import io.wispforest.owo.ui.core.*;
|
||||||
|
import net.minecraft.network.chat.Component;
|
||||||
|
import net.minecraft.network.chat.FontDescription;
|
||||||
|
import de.fullrisk.citrusDev.client.PlaytimeTracker;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class QuestsPanel {
|
||||||
|
|
||||||
|
private static final int ACCENT = 0xbaff86;
|
||||||
|
|
||||||
|
private record QuestData(String title, String description, int requiredPlaytime) {
|
||||||
|
boolean isUnlocked() {
|
||||||
|
return PlaytimeTracker.getCurrentPlaytimeSeconds() >= requiredPlaytime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void build(FlowLayout content, int contentWidth, int contentHeight) {
|
||||||
|
List<QuestData> quests = quests();
|
||||||
|
int playtime = PlaytimeTracker.getCurrentPlaytimeSeconds();
|
||||||
|
int maxPlaytime = 360;
|
||||||
|
int progress = Math.min(playtime * 100 / maxPlaytime, 100);
|
||||||
|
int barWidth = contentWidth - 8;
|
||||||
|
long unlockedCount = quests.stream().filter(QuestData::isUnlocked).count();
|
||||||
|
|
||||||
|
// --- status row: playtime + percentage ---
|
||||||
|
FlowLayout statusBar = UIContainers.horizontalFlow(Sizing.fixed(barWidth), Sizing.fixed(20));
|
||||||
|
statusBar.gap(4);
|
||||||
|
statusBar.padding(Insets.of(4));
|
||||||
|
statusBar.verticalAlignment(VerticalAlignment.CENTER);
|
||||||
|
statusBar.horizontalAlignment(HorizontalAlignment.LEFT);
|
||||||
|
statusBar.surface(MyScreen.GLASS_PANEL);
|
||||||
|
|
||||||
|
statusBar.child(UIComponents.label(Component.literal("Playtime: " + formatPlaytime(playtime)))
|
||||||
|
.color(Color.ofRgb(0xFFFFFF)));
|
||||||
|
statusBar.child(UIComponents.spacer());
|
||||||
|
statusBar.child(UIComponents.label(Component.literal(progress + "%"))
|
||||||
|
.color(Color.ofRgb(ACCENT)));
|
||||||
|
|
||||||
|
content.child(statusBar);
|
||||||
|
|
||||||
|
// --- progress bar ---
|
||||||
|
FlowLayout barBg = UIContainers.horizontalFlow(Sizing.fixed(barWidth), Sizing.fixed(6));
|
||||||
|
barBg.surface(Surface.flat(0x30FFFFFF).and(Surface.outline(0x30FFFFFF)));
|
||||||
|
barBg.horizontalAlignment(HorizontalAlignment.LEFT);
|
||||||
|
barBg.gap(0);
|
||||||
|
barBg.padding(Insets.of(0));
|
||||||
|
|
||||||
|
int fillWidth = Math.max(barWidth * progress / 100, progress > 0 ? 2 : 0);
|
||||||
|
FlowLayout barFill = UIContainers.horizontalFlow(Sizing.fixed(fillWidth), Sizing.fixed(6));
|
||||||
|
barFill.surface(Surface.flat(0xFF000000 | ACCENT));
|
||||||
|
barBg.child(barFill);
|
||||||
|
|
||||||
|
content.child(barBg);
|
||||||
|
|
||||||
|
// --- unlocked count summary ---
|
||||||
|
FlowLayout summaryRow = UIContainers.horizontalFlow(Sizing.fixed(barWidth), Sizing.content());
|
||||||
|
summaryRow.padding(Insets.of(3, 0, 2, 2));
|
||||||
|
|
||||||
|
summaryRow.child(UIComponents.label(
|
||||||
|
Component.literal("Quests " + unlockedCount + "/" + quests.size())
|
||||||
|
.withStyle(style -> style.withFont(new FontDescription.Resource(MyScreen.MC_FIVE))))
|
||||||
|
.color(Color.ofRgb(0xa3a3ac)));
|
||||||
|
|
||||||
|
content.child(summaryRow);
|
||||||
|
|
||||||
|
// --- quest list (scrollable) ---
|
||||||
|
FlowLayout questList = UIContainers.verticalFlow(Sizing.fixed(barWidth), Sizing.content());
|
||||||
|
questList.gap(3);
|
||||||
|
questList.padding(Insets.of(2, 0, 2, 2));
|
||||||
|
|
||||||
|
for (QuestData quest : quests) {
|
||||||
|
questList.child(buildQuestEntry(quest, contentWidth));
|
||||||
|
}
|
||||||
|
|
||||||
|
int usedHeight = 20 + 6 + 18 + 6; // statusBar + barBg + summaryRow + gaps
|
||||||
|
int scrollHeight = contentHeight - usedHeight;
|
||||||
|
|
||||||
|
var scrollArea = UIContainers.verticalScroll(
|
||||||
|
Sizing.fixed(barWidth),
|
||||||
|
Sizing.fixed(scrollHeight),
|
||||||
|
questList
|
||||||
|
);
|
||||||
|
content.child(scrollArea);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String formatPlaytime(int seconds) {
|
||||||
|
int hours = seconds / 3600;
|
||||||
|
int minutes = (seconds % 3600) / 60;
|
||||||
|
int secs = seconds % 60;
|
||||||
|
if (hours > 0) {
|
||||||
|
return hours + "h " + minutes + "m " + secs + "s";
|
||||||
|
} else if (minutes > 0) {
|
||||||
|
return minutes + "m " + secs + "s";
|
||||||
|
} else {
|
||||||
|
return secs + "s";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<QuestData> quests() {
|
||||||
|
return List.of(
|
||||||
|
new QuestData("First Steps", "Play for 5 minutes", 300),
|
||||||
|
new QuestData("Getting Started", "Play for 30 minutes", 1800),
|
||||||
|
new QuestData("Dedicated Player", "Play for 1 hour", 3600),
|
||||||
|
new QuestData("Veteran", "Play for 3 hours", 10800)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static FlowLayout buildQuestEntry(QuestData quest, int contentWidth) {
|
||||||
|
boolean unlocked = quest.isUnlocked();
|
||||||
|
int entryWidth = contentWidth - 12;
|
||||||
|
|
||||||
|
FlowLayout row = UIContainers.horizontalFlow(Sizing.fixed(entryWidth), Sizing.fixed(32));
|
||||||
|
row.gap(6);
|
||||||
|
row.padding(Insets.of(4));
|
||||||
|
row.verticalAlignment(VerticalAlignment.CENTER);
|
||||||
|
row.horizontalAlignment(HorizontalAlignment.LEFT);
|
||||||
|
row.surface(unlocked
|
||||||
|
? Surface.flat(0x1AFFFFFF).and(Surface.outline(0x30 << 24 | ACCENT))
|
||||||
|
: Surface.flat(0x12000000));
|
||||||
|
|
||||||
|
FlowLayout iconBox = UIContainers.verticalFlow(Sizing.fixed(12), Sizing.fixed(12));
|
||||||
|
iconBox.surface(unlocked
|
||||||
|
? Surface.flat(0xFF000000 | ACCENT)
|
||||||
|
: Surface.flat(0x40FFFFFF));
|
||||||
|
iconBox.horizontalAlignment(HorizontalAlignment.CENTER);
|
||||||
|
iconBox.verticalAlignment(VerticalAlignment.CENTER);
|
||||||
|
iconBox.child(UIComponents.label(Component.literal(unlocked ? "✓" : ""))
|
||||||
|
.color(Color.ofRgb(0x1c1c1c)));
|
||||||
|
row.child(iconBox);
|
||||||
|
|
||||||
|
FlowLayout textStack = UIContainers.verticalFlow(Sizing.fixed(entryWidth - 24), Sizing.content());
|
||||||
|
textStack.gap(1);
|
||||||
|
textStack.horizontalAlignment(HorizontalAlignment.LEFT);
|
||||||
|
textStack.verticalAlignment(VerticalAlignment.TOP);
|
||||||
|
|
||||||
|
var titleLabel = UIComponents.label(Component.literal(quest.title())
|
||||||
|
.withStyle(style -> style.withFont(new FontDescription.Resource(MyScreen.MC_FIVE))))
|
||||||
|
.color(Color.ofRgb(unlocked ? 0xFFFFFF : 0x797b86));
|
||||||
|
textStack.child(titleLabel);
|
||||||
|
|
||||||
|
var descLabel = UIComponents.label(Component.literal(quest.description()))
|
||||||
|
.color(Color.ofRgb(0xa3a3ac))
|
||||||
|
.maxWidth(entryWidth - 24);
|
||||||
|
textStack.child(descLabel);
|
||||||
|
|
||||||
|
row.child(textStack);
|
||||||
|
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
package de.fullrisk.citrusDev.UIHelper;
|
||||||
|
|
||||||
|
import io.wispforest.owo.ui.core.Color;
|
||||||
|
import io.wispforest.owo.ui.core.OwoUIGraphics;
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraft.client.gui.Font;
|
||||||
|
import net.minecraft.client.gui.GuiGraphicsExtractor;
|
||||||
|
import net.minecraft.client.gui.components.toasts.Toast;
|
||||||
|
import net.minecraft.client.gui.components.toasts.ToastManager;
|
||||||
|
import net.minecraft.network.chat.Component;
|
||||||
|
import net.minecraft.client.renderer.RenderPipelines;
|
||||||
|
import net.minecraft.sounds.SoundEvent;
|
||||||
|
import net.minecraft.sounds.SoundEvents;
|
||||||
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
|
public class CitrusToast implements Toast {
|
||||||
|
|
||||||
|
private static final int WIDTH = 180;
|
||||||
|
private static final int HEIGHT = 32;
|
||||||
|
private static final long DEFAULT_DURATION_MS = 3000L;
|
||||||
|
|
||||||
|
private final String title;
|
||||||
|
private final String message;
|
||||||
|
private final long durationMs;
|
||||||
|
|
||||||
|
private Visibility wantedVisibility = Visibility.SHOW;
|
||||||
|
|
||||||
|
private CitrusToast(String title, String message, long durationMs) {
|
||||||
|
this.title = title;
|
||||||
|
this.message = message;
|
||||||
|
this.durationMs = durationMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void show(String title, String message) {
|
||||||
|
show(title, message, DEFAULT_DURATION_MS);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void show(String title, String message, long durationMs) {
|
||||||
|
Minecraft.getInstance().getToastManager().addToast(new CitrusToast(title, message, durationMs));
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public Visibility getWantedVisibility() {
|
||||||
|
return this.wantedVisibility;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update(ToastManager manager, long fullyVisibleForMs) {
|
||||||
|
if (fullyVisibleForMs >= this.durationMs) {
|
||||||
|
this.wantedVisibility = Visibility.HIDE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable SoundEvent getSoundEvent() {
|
||||||
|
return SoundEvents.UI_TOAST_IN;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int width() {
|
||||||
|
return WIDTH;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int height() {
|
||||||
|
return HEIGHT;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void extractRenderState(GuiGraphicsExtractor graphics, Font font, long fullyVisibleForMs) {
|
||||||
|
OwoUIGraphics owo = OwoUIGraphics.of(graphics);
|
||||||
|
|
||||||
|
int x = 0;
|
||||||
|
int y = 0;
|
||||||
|
int w = WIDTH;
|
||||||
|
int h = HEIGHT;
|
||||||
|
|
||||||
|
// glass background, matching GLASS_PANEL style
|
||||||
|
owo.fill(RenderPipelines.GUI, x, y, x + w, y + h, 0x40FFFFFF);
|
||||||
|
owo.drawRectOutline(RenderPipelines.GUI, x, y, w, h, 0x64de4b);
|
||||||
|
|
||||||
|
owo.drawText(Component.literal(this.title), x + 6, y + 6, 1f, 0xFFFFFFFF);
|
||||||
|
owo.drawText(Component.literal(this.message), x + 6, y + 18, 0.8f, 0xFFa3a3ac);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
package de.fullrisk.citrusDev.UIHelper;
|
||||||
|
|
||||||
|
import io.wispforest.owo.ui.component.UIComponents;
|
||||||
|
import io.wispforest.owo.ui.container.FlowLayout;
|
||||||
|
import io.wispforest.owo.ui.container.UIContainers;
|
||||||
|
import io.wispforest.owo.ui.core.*;
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraft.client.resources.sounds.SimpleSoundInstance;
|
||||||
|
import net.minecraft.network.chat.Component;
|
||||||
|
import net.minecraft.resources.Identifier;
|
||||||
|
import net.minecraft.sounds.SoundEvents;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.DoubleConsumer;
|
||||||
|
import java.util.function.DoubleFunction;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public class SettingsControls {
|
||||||
|
|
||||||
|
private static final Surface GLASS_PANEL = Surface.blur(5f, 15f)
|
||||||
|
.and(Surface.flat(0x40FFFFFF))
|
||||||
|
.and(Surface.outline(0x64de4b));
|
||||||
|
|
||||||
|
public record ButtonState(String label, Runnable onSelect) {}
|
||||||
|
|
||||||
|
public static FlowLayout makeSmallButton(Surface baseSurface, Identifier texture, Runnable onClick) {
|
||||||
|
FlowLayout button = UIContainers.verticalFlow(Sizing.fixed(16), Sizing.fixed(16));
|
||||||
|
button.verticalAlignment(VerticalAlignment.CENTER);
|
||||||
|
button.horizontalAlignment(HorizontalAlignment.CENTER);
|
||||||
|
button.surface(baseSurface);
|
||||||
|
button.child(UIComponents.texture(texture, 0, 0, 12, 12, 12, 12))
|
||||||
|
.horizontalAlignment(HorizontalAlignment.CENTER)
|
||||||
|
.verticalAlignment(VerticalAlignment.CENTER);
|
||||||
|
|
||||||
|
button.mouseEnter().subscribe(() -> button.surface(
|
||||||
|
Surface.blur(5f, 15f).and(Surface.flat(0x70FFFFFF)).and(Surface.outline(0xFFFFFFFF))
|
||||||
|
));
|
||||||
|
button.mouseLeave().subscribe(() -> button.surface(baseSurface));
|
||||||
|
|
||||||
|
button.mouseDown().subscribe((click, doubled) -> {
|
||||||
|
Minecraft.getInstance().getSoundManager().play(
|
||||||
|
SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK.value(), 2.0F, 2)
|
||||||
|
);
|
||||||
|
onClick.run();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static FlowLayout makeTextButton(Surface baseSurface, String text, Runnable onClick) {
|
||||||
|
FlowLayout button = UIContainers.verticalFlow(Sizing.content(), Sizing.content());
|
||||||
|
button.verticalAlignment(VerticalAlignment.CENTER);
|
||||||
|
button.horizontalAlignment(HorizontalAlignment.CENTER);
|
||||||
|
button.padding(Insets.of(2, 2, 4, 4));
|
||||||
|
button.surface(baseSurface);
|
||||||
|
button.child(UIComponents.label(Component.literal(text)).color(Color.ofRgb(0xFFFFFF)));
|
||||||
|
|
||||||
|
button.mouseEnter().subscribe(() -> button.surface(
|
||||||
|
Surface.blur(5f, 15f).and(Surface.flat(0x70FFFFFF)).and(Surface.outline(0xFFFFFFFF))
|
||||||
|
));
|
||||||
|
button.mouseLeave().subscribe(() -> button.surface(baseSurface));
|
||||||
|
|
||||||
|
button.mouseDown().subscribe((click, doubled) -> {
|
||||||
|
Minecraft.getInstance().getSoundManager().play(
|
||||||
|
SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK.value(), 2.0F, 2)
|
||||||
|
);
|
||||||
|
onClick.run();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static FlowLayout makeCyclingButton(Surface baseSurface, List<ButtonState> states) {
|
||||||
|
int[] currentIndex = {0};
|
||||||
|
|
||||||
|
FlowLayout button = UIContainers.verticalFlow(Sizing.fixed(50), Sizing.fixed(16));
|
||||||
|
button.verticalAlignment(VerticalAlignment.CENTER);
|
||||||
|
button.horizontalAlignment(HorizontalAlignment.CENTER);
|
||||||
|
button.surface(baseSurface);
|
||||||
|
|
||||||
|
var label = UIComponents.label(Component.literal(states.get(0).label()))
|
||||||
|
.color(Color.ofRgb(0xFFFFFF));
|
||||||
|
button.child(label);
|
||||||
|
|
||||||
|
button.mouseDown().subscribe((click, doubled) -> {
|
||||||
|
Minecraft.getInstance().getSoundManager().play(
|
||||||
|
SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK.value(), 2.0F, 2)
|
||||||
|
);
|
||||||
|
|
||||||
|
currentIndex[0] = (currentIndex[0] + 1) % states.size();
|
||||||
|
ButtonState newState = states.get(currentIndex[0]);
|
||||||
|
|
||||||
|
label.text(Component.literal(newState.label()));
|
||||||
|
newState.onSelect().run();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
button.mouseEnter().subscribe(() -> label.color(Color.ofRgb(0xFFFFFF)));
|
||||||
|
button.mouseLeave().subscribe(() -> label.color(Color.ofRgb(0xCCCCCC)));
|
||||||
|
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static FlowLayout makeSlider(Surface baseSurface, int width, double min, double max, double initial,
|
||||||
|
DoubleFunction<String> labelFormatter, DoubleConsumer onChange) {
|
||||||
|
int labelWidth = 34;
|
||||||
|
int sliderWidth = width - labelWidth - 6;
|
||||||
|
|
||||||
|
FlowLayout wrapper = UIContainers.horizontalFlow(Sizing.fixed(width), Sizing.fixed(16));
|
||||||
|
wrapper.verticalAlignment(VerticalAlignment.CENTER);
|
||||||
|
wrapper.horizontalAlignment(HorizontalAlignment.LEFT);
|
||||||
|
wrapper.padding(Insets.of(2, 2, 4, 0));
|
||||||
|
wrapper.gap(2);
|
||||||
|
|
||||||
|
var valueLabel = UIComponents.label(Component.literal(labelFormatter.apply(initial)));
|
||||||
|
valueLabel.color(Color.ofRgb(0xFFFFFF));
|
||||||
|
valueLabel.horizontalSizing(Sizing.fixed(labelWidth));
|
||||||
|
wrapper.child(valueLabel);
|
||||||
|
|
||||||
|
var slider = UIComponents.discreteSlider(Sizing.fixed(sliderWidth), min, max);
|
||||||
|
slider.horizontalSizing(Sizing.fixed(sliderWidth));
|
||||||
|
slider.verticalSizing(Sizing.fixed(10));
|
||||||
|
slider.setFromDiscreteValue(initial);
|
||||||
|
slider.cursorStyle(CursorStyle.POINTER);
|
||||||
|
slider.mouseDown().subscribe((click, doubled) -> {
|
||||||
|
return doubled;
|
||||||
|
});
|
||||||
|
slider.onChanged().subscribe(value -> {
|
||||||
|
valueLabel.text(Component.literal(labelFormatter.apply(value)));
|
||||||
|
onChange.accept(value);
|
||||||
|
});
|
||||||
|
|
||||||
|
wrapper.child(slider);
|
||||||
|
|
||||||
|
return wrapper;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package de.fullrisk.citrusDev.client;
|
package de.fullrisk.citrusDev.client;
|
||||||
|
|
||||||
|
|
||||||
import de.fullrisk.citrusDev.Config;
|
import de.fullrisk.citrusDev.Config;
|
||||||
import de.fullrisk.citrusDev.UI.Module_FPS;
|
import de.fullrisk.citrusDev.UI.Module_FPS;
|
||||||
import de.fullrisk.citrusDev.UI.MyScreen;
|
import de.fullrisk.citrusDev.UI.MyScreen;
|
||||||
@@ -36,6 +37,10 @@ public class CitrusDevClient implements ClientModInitializer {
|
|||||||
));
|
));
|
||||||
|
|
||||||
ClientTickEvents.END_CLIENT_TICK.register(client -> {
|
ClientTickEvents.END_CLIENT_TICK.register(client -> {
|
||||||
|
if (client.player != null && !client.isPaused()) {
|
||||||
|
PlaytimeTracker.tick();
|
||||||
|
}
|
||||||
|
|
||||||
Module_FPS.tick(); // update every tick, unconditionally
|
Module_FPS.tick(); // update every tick, unconditionally
|
||||||
while (openScreenKey.consumeClick()) {
|
while (openScreenKey.consumeClick()) {
|
||||||
client.setScreen(new MyScreen(null)); // always general tab
|
client.setScreen(new MyScreen(null)); // always general tab
|
||||||
|
|||||||
@@ -0,0 +1,160 @@
|
|||||||
|
// PLACE THIS FILE AT:
|
||||||
|
// src/client/java/de/fullrisk/citrusDev/client/PlaytimeTracker.java
|
||||||
|
//
|
||||||
|
// NOT under src/main/java — MinecraftClient and client.player only exist
|
||||||
|
// on the client source set. That is the actual cause of the
|
||||||
|
// "Cannot resolve symbol" errors, not a mistake in this code.
|
||||||
|
|
||||||
|
package de.fullrisk.citrusDev.client;
|
||||||
|
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.http.HttpClient;
|
||||||
|
import java.net.http.HttpRequest;
|
||||||
|
import java.net.http.HttpResponse;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.UUID;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.google.gson.JsonParser;
|
||||||
|
|
||||||
|
public class PlaytimeTracker {
|
||||||
|
|
||||||
|
//ticks zu backend schicken
|
||||||
|
|
||||||
|
private static final String BACKEND_URL =
|
||||||
|
"http://127.0.0.1:5000/api/heartbeat";
|
||||||
|
|
||||||
|
private static final HttpClient HTTP =
|
||||||
|
HttpClient.newBuilder()
|
||||||
|
.connectTimeout(Duration.ofSeconds(10))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
private static int ticks = 0;
|
||||||
|
private static int currentPlaytimeSeconds = 0;
|
||||||
|
|
||||||
|
public static void tick() {
|
||||||
|
ticks++;
|
||||||
|
|
||||||
|
if (ticks % 100 == 0) {
|
||||||
|
System.out.println("Ticking: " + ticks);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ticks >= 60) {
|
||||||
|
ticks = 0;
|
||||||
|
sendHeartbeat();
|
||||||
|
getPlaytime(); // <-- add this
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void sendHeartbeat() {
|
||||||
|
Minecraft client =
|
||||||
|
Minecraft.getInstance();
|
||||||
|
|
||||||
|
if (client.player == null) {
|
||||||
|
System.out.println("No player yet");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
UUID uuid = client.player.getUUID();
|
||||||
|
|
||||||
|
String json = """
|
||||||
|
{
|
||||||
|
"uuid": "%s"
|
||||||
|
}
|
||||||
|
""".formatted(uuid);
|
||||||
|
|
||||||
|
HttpRequest request = HttpRequest.newBuilder()
|
||||||
|
.uri(URI.create(BACKEND_URL))
|
||||||
|
.timeout(Duration.ofSeconds(10))
|
||||||
|
.header("Content-Type", "application/json")
|
||||||
|
.POST(HttpRequest.BodyPublishers.ofString(json))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
HTTP.sendAsync(request, HttpResponse.BodyHandlers.ofString())
|
||||||
|
.thenAccept(response -> {
|
||||||
|
if (response.statusCode() != 200) {
|
||||||
|
System.out.println("Heartbeat failed: " + response.statusCode());
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.exceptionally(error -> {
|
||||||
|
error.printStackTrace();
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//playtime vom server holen
|
||||||
|
|
||||||
|
private static final String PLAYTIME_URL =
|
||||||
|
"http://localhost:5000/api/player/%s/playtime";
|
||||||
|
|
||||||
|
|
||||||
|
public static void getPlaytime() {
|
||||||
|
|
||||||
|
Minecraft client = Minecraft.getInstance();
|
||||||
|
|
||||||
|
if (client.player == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
UUID uuid = client.player.getUUID();
|
||||||
|
|
||||||
|
HttpRequest request = HttpRequest.newBuilder()
|
||||||
|
.uri(URI.create(
|
||||||
|
PLAYTIME_URL.formatted(uuid)
|
||||||
|
))
|
||||||
|
.GET()
|
||||||
|
.build();
|
||||||
|
|
||||||
|
|
||||||
|
HTTP.sendAsync(
|
||||||
|
request,
|
||||||
|
HttpResponse.BodyHandlers.ofString()
|
||||||
|
)
|
||||||
|
.thenAccept(response -> {
|
||||||
|
|
||||||
|
if (response.statusCode() == 200) {
|
||||||
|
|
||||||
|
String json = response.body();
|
||||||
|
|
||||||
|
JsonObject data =
|
||||||
|
JsonParser.parseString(json)
|
||||||
|
.getAsJsonObject();
|
||||||
|
|
||||||
|
|
||||||
|
currentPlaytimeSeconds =
|
||||||
|
data.get("playtime_seconds")
|
||||||
|
.getAsInt();
|
||||||
|
|
||||||
|
|
||||||
|
// Compare playtime here
|
||||||
|
if (currentPlaytimeSeconds >= 3600) {
|
||||||
|
System.out.println(
|
||||||
|
"Player has more than 1 hour!"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
System.out.println(
|
||||||
|
"Player needs more time"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
System.out.println(
|
||||||
|
"Seconds played: " + currentPlaytimeSeconds
|
||||||
|
);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
System.out.println(
|
||||||
|
"Failed: " + response.statusCode()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getCurrentPlaytimeSeconds() {
|
||||||
|
return currentPlaytimeSeconds;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package de.fullrisk.citrusDev.mixin;
|
||||||
|
|
||||||
|
import io.wispforest.owo.ui.component.SliderComponent;
|
||||||
|
import io.wispforest.owo.ui.core.OwoUIGraphics;
|
||||||
|
import net.minecraft.client.gui.GuiGraphicsExtractor;
|
||||||
|
import net.minecraft.client.gui.components.AbstractSliderButton;
|
||||||
|
import net.minecraft.client.renderer.RenderPipelines;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
|
|
||||||
|
@Mixin(AbstractSliderButton.class)
|
||||||
|
public abstract class SliderComponentMixin {
|
||||||
|
|
||||||
|
@Inject(method = "extractWidgetRenderState", at = @At("HEAD"), cancellable = true)
|
||||||
|
private void citrusDev$renderGlassSlider(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float a, CallbackInfo ci) {
|
||||||
|
Object self = this;
|
||||||
|
if (!(self instanceof SliderComponent slider)) return;
|
||||||
|
|
||||||
|
AbstractSliderButton widget = (AbstractSliderButton) self;
|
||||||
|
|
||||||
|
int x = widget.getX();
|
||||||
|
int y = widget.getY();
|
||||||
|
int w = widget.getWidth();
|
||||||
|
int h = widget.getHeight();
|
||||||
|
|
||||||
|
OwoUIGraphics owo = OwoUIGraphics.of(graphics);
|
||||||
|
|
||||||
|
// glass track
|
||||||
|
owo.fill(RenderPipelines.GUI, x, y, x + w, y + h, 0x40FFFFFF);
|
||||||
|
|
||||||
|
// handle
|
||||||
|
int handleWidth = 4;
|
||||||
|
int handleX = x + (int) (slider.value() * (w - handleWidth));
|
||||||
|
owo.fill(RenderPipelines.GUI, handleX, y, handleX + handleWidth, y + h, 0xFFFFFFFF);
|
||||||
|
|
||||||
|
ci.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@
|
|||||||
"mixins": [
|
"mixins": [
|
||||||
],
|
],
|
||||||
"client": [
|
"client": [
|
||||||
|
"SliderComponentMixin"
|
||||||
],
|
],
|
||||||
"injectors": {
|
"injectors": {
|
||||||
"defaultRequire": 1
|
"defaultRequire": 1
|
||||||
|
|||||||
Reference in New Issue
Block a user