diff options
author | Linnea Gräf <roman.graef@gmail.com> | 2023-11-02 15:28:49 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-11-02 15:28:49 +0100 |
commit | 2a6d3a96d40ca425e661737fb4fc467b2040437b (patch) | |
tree | bb89ff8177ebb3abdc2a047f16b9671bc18cacd8 /src/main/kotlin | |
parent | e1c24ecc30132fc98aa9cf33b928232b2efabcd8 (diff) | |
download | NotEnoughUpdates-2a6d3a96d40ca425e661737fb4fc467b2040437b.tar.gz NotEnoughUpdates-2a6d3a96d40ca425e661737fb4fc467b2040437b.tar.bz2 NotEnoughUpdates-2a6d3a96d40ca425e661737fb4fc467b2040437b.zip |
Add custom TODOs (#870)
Co-authored-by: Lulonaut <lulonaut@lulonaut.tech>
Diffstat (limited to 'src/main/kotlin')
9 files changed, 761 insertions, 2 deletions
diff --git a/src/main/kotlin/io/github/moulberry/notenoughupdates/commands/help/SettingsCommand.kt b/src/main/kotlin/io/github/moulberry/notenoughupdates/commands/help/SettingsCommand.kt index eb5d51b1..40919a8e 100644 --- a/src/main/kotlin/io/github/moulberry/notenoughupdates/commands/help/SettingsCommand.kt +++ b/src/main/kotlin/io/github/moulberry/notenoughupdates/commands/help/SettingsCommand.kt @@ -21,6 +21,7 @@ package io.github.moulberry.notenoughupdates.commands.help import io.github.moulberry.moulconfig.GuiTextures import io.github.moulberry.moulconfig.annotations.ConfigOption +import io.github.moulberry.moulconfig.common.MyResourceLocation import io.github.moulberry.moulconfig.gui.GuiOptionEditor import io.github.moulberry.moulconfig.gui.GuiScreenElementWrapper import io.github.moulberry.moulconfig.gui.MoulConfigEditor @@ -34,7 +35,6 @@ import io.github.moulberry.notenoughupdates.miscfeatures.IQTest import io.github.moulberry.notenoughupdates.options.NEUConfig import io.github.moulberry.notenoughupdates.util.brigadier.* import net.minecraft.client.gui.GuiScreen -import net.minecraft.util.ResourceLocation import net.minecraftforge.fml.common.eventhandler.SubscribeEvent import java.lang.reflect.Field @@ -118,6 +118,7 @@ object SettingsCommand { return object : GuiScreenElementWrapper(createConfigElement(search)) { } } + fun createConfigElement(search: String): MoulConfigEditor<NEUConfig> { val processor = BlockingMoulConfigProcessor() BuiltinMoulConfigGuis.addProcessors(processor) @@ -131,7 +132,8 @@ object SettingsCommand { lastEditor = editor return editor } + init { - GuiTextures.setTextureRoot(ResourceLocation("notenoughupdates:core")) + GuiTextures.setTextureRoot(MyResourceLocation("notenoughupdates", "core")) } } diff --git a/src/main/kotlin/io/github/moulberry/notenoughupdates/events/SidebarChangeEvent.kt b/src/main/kotlin/io/github/moulberry/notenoughupdates/events/SidebarChangeEvent.kt new file mode 100644 index 00000000..0dc4b9f6 --- /dev/null +++ b/src/main/kotlin/io/github/moulberry/notenoughupdates/events/SidebarChangeEvent.kt @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2023 NotEnoughUpdates contributors + * + * This file is part of NotEnoughUpdates. + * + * NotEnoughUpdates 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. + * + * NotEnoughUpdates 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 NotEnoughUpdates. If not, see <https://www.gnu.org/licenses/>. + */ + +package io.github.moulberry.notenoughupdates.events + +class SidebarChangeEvent( + val lines: List<String>, + val lastLines: List<String>, +) : NEUEvent() diff --git a/src/main/kotlin/io/github/moulberry/notenoughupdates/events/TabListChangeEvent.kt b/src/main/kotlin/io/github/moulberry/notenoughupdates/events/TabListChangeEvent.kt new file mode 100644 index 00000000..b5677598 --- /dev/null +++ b/src/main/kotlin/io/github/moulberry/notenoughupdates/events/TabListChangeEvent.kt @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2023 NotEnoughUpdates contributors + * + * This file is part of NotEnoughUpdates. + * + * NotEnoughUpdates 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. + * + * NotEnoughUpdates 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 NotEnoughUpdates. If not, see <https://www.gnu.org/licenses/>. + */ + +package io.github.moulberry.notenoughupdates.events + +class TabListChangeEvent( + val lastLines: List<String>, + val newLines: List<String>, +) : NEUEvent() { +} diff --git a/src/main/kotlin/io/github/moulberry/notenoughupdates/miscgui/customtodos/CustomTodo.kt b/src/main/kotlin/io/github/moulberry/notenoughupdates/miscgui/customtodos/CustomTodo.kt new file mode 100644 index 00000000..7f8c6d1a --- /dev/null +++ b/src/main/kotlin/io/github/moulberry/notenoughupdates/miscgui/customtodos/CustomTodo.kt @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2023 NotEnoughUpdates contributors + * + * This file is part of NotEnoughUpdates. + * + * NotEnoughUpdates 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. + * + * NotEnoughUpdates 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 NotEnoughUpdates. If not, see <https://www.gnu.org/licenses/>. + */ + +package io.github.moulberry.notenoughupdates.miscgui.customtodos + +import com.google.gson.annotations.Expose +import io.github.moulberry.notenoughupdates.util.SBInfo +import io.github.moulberry.notenoughupdates.util.TemplateUtil +import io.github.moulberry.notenoughupdates.util.kotlin.KSerializable + +@KSerializable +data class CustomTodo( + @Expose var label: String, + @Expose var timer: Int, + @Expose var trigger: String, + @Expose var icon: String, + @Expose var isResetOffset: Boolean, + @Expose var triggerTarget: TriggerTarget = TriggerTarget.CHAT, + @Expose var triggerMatcher: TriggerMatcher = TriggerMatcher.CONTAINS, + @Expose var readyAt: MutableMap<String, Long> = mutableMapOf(), + @Expose var enabled: MutableMap<String, Boolean> = mutableMapOf(), +) { + enum class TriggerMatcher { + REGEX, STARTS_WITH, CONTAINS, EQUALS + } + + enum class TriggerTarget { + CHAT, ACTIONBAR, TAB_LIST, SIDEBAR + } + + fun isValid(): Boolean { + return timer >= 0 && !trigger.isBlank() + } + + fun setDoneNow() { + val t = System.currentTimeMillis() + readyAt[SBInfo.getInstance().currentProfile ?: return] = + if (isResetOffset) { + t + DAY - t % DAY + timer * 1000L + } else { + t + timer * 1000L + } + } + + var readyAtOnCurrentProfile: Long? + get() { + return readyAt[SBInfo.getInstance().currentProfile ?: return null] + } + set(value) { + readyAt[SBInfo.getInstance().currentProfile ?: return] = value ?: return + } + + var isEnabledOnCurrentProfile: Boolean + get() { + return enabled[SBInfo.getInstance().currentProfile ?: return true] ?: true + } + set(value) { + enabled[SBInfo.getInstance().currentProfile ?: return] = value + } + + + companion object { + val templatePrefix = "NEU:CUSTOMTODO/" + val DAY = (24 * 60 * 60 * 100) + fun fromTemplate(data: String): CustomTodo? { + return TemplateUtil.maybeDecodeTemplate(templatePrefix, data, CustomTodo::class.java) + ?.also { + it.enabled.clear() + it.readyAt.clear() + } + } + } + + fun toTemplate(): String { + return TemplateUtil.encodeTemplate( + templatePrefix, + this.copy(enabled = mutableMapOf(), readyAt = mutableMapOf()) + ) + } +} diff --git a/src/main/kotlin/io/github/moulberry/notenoughupdates/miscgui/customtodos/CustomTodoEditor.kt b/src/main/kotlin/io/github/moulberry/notenoughupdates/miscgui/customtodos/CustomTodoEditor.kt new file mode 100644 index 00000000..1c5c16ed --- /dev/null +++ b/src/main/kotlin/io/github/moulberry/notenoughupdates/miscgui/customtodos/CustomTodoEditor.kt @@ -0,0 +1,257 @@ +/* + * Copyright (C) 2023 NotEnoughUpdates contributors + * + * This file is part of NotEnoughUpdates. + * + * NotEnoughUpdates 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. + * + * NotEnoughUpdates 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 NotEnoughUpdates. If not, see <https://www.gnu.org/licenses/>. + */ + +package io.github.moulberry.notenoughupdates.miscgui.customtodos + +import io.github.moulberry.moulconfig.common.IItemStack +import io.github.moulberry.moulconfig.forge.ForgeItemStack +import io.github.moulberry.moulconfig.internal.ClipboardUtils +import io.github.moulberry.moulconfig.observer.ObservableList +import io.github.moulberry.moulconfig.xml.Bind +import io.github.moulberry.moulconfig.xml.XMLUniverse +import io.github.moulberry.notenoughupdates.util.SBInfo +import io.github.moulberry.notenoughupdates.util.Utils +import io.github.moulberry.notenoughupdates.util.loadResourceLocation +import net.minecraft.client.Minecraft +import net.minecraft.init.Items +import net.minecraft.item.Item +import net.minecraft.item.ItemStack +import net.minecraft.util.ResourceLocation + +class CustomTodoEditor( + val from: CustomTodo, + val todos: ObservableList<CustomTodoEditor>, + val xmlUniverse: XMLUniverse +) { + @field:Bind + var label: String = from.label + + @field:Bind + var enabled: Boolean = from.isEnabledOnCurrentProfile + + @field:Bind + var timer: String = from.timer.toString() + + @field:Bind + var trigger: String = from.trigger + + @field:Bind + var icon: String = from.icon + + @field:Bind + var isResetOffset: Boolean = from.isResetOffset + + var target = from.triggerTarget + var matchMode = from.triggerMatcher + var lastCustomTodo: CustomTodo? = null + + fun into(): CustomTodo { + val nextCustomTodo = CustomTodo( + label, + timer.toIntOrNull() ?: 0, + trigger, + icon, + isResetOffset, + target, matchMode, + from.readyAt, + from.enabled.toMutableMap().also { it[SBInfo.getInstance().currentProfile ?: return@also] = enabled } + ) + if (nextCustomTodo != lastCustomTodo) { + lastCustomTodo = nextCustomTodo + CustomTodoList(todos, xmlUniverse).save() + } + return nextCustomTodo + } + + @Bind + fun setChat() { + target = CustomTodo.TriggerTarget.CHAT + } + + @Bind + fun setActionbar() { + target = CustomTodo.TriggerTarget.ACTIONBAR + } + + @Bind + fun setSidebar() { + target = CustomTodo.TriggerTarget.SIDEBAR + } + + @Bind + fun setTablist() { + target = CustomTodo.TriggerTarget.TAB_LIST + } + + private fun colorFromBool(b: Boolean): String { + return if (b) "§a" else "§c" + } + + @Bind + fun getChat(): String { + return colorFromBool(target == CustomTodo.TriggerTarget.CHAT) + "Chat" + } + + @Bind + fun getActionbar(): String { + return colorFromBool(target == CustomTodo.TriggerTarget.ACTIONBAR) + "Actionbar" + } + + @Bind + fun getSidebar(): String { + return colorFromBool(target == CustomTodo.TriggerTarget.SIDEBAR) + "Sidebar" + } + + @Bind + fun getTablist(): String { + return colorFromBool(target == CustomTodo.TriggerTarget.TAB_LIST) + "Tablist" + } + + @Bind + fun setRegex() { + matchMode = CustomTodo.TriggerMatcher.REGEX + } + + @Bind + fun setStartsWith() { + matchMode = CustomTodo.TriggerMatcher.STARTS_WITH + } + + @Bind + fun setContains() { + matchMode = CustomTodo.TriggerMatcher.CONTAINS + } + + @Bind + fun setEquals() { + matchMode = CustomTodo.TriggerMatcher.EQUALS + } + + @Bind + fun getRegex(): String { + return colorFromBool(matchMode == CustomTodo.TriggerMatcher.REGEX) + "Regex" + } + + @Bind + fun getStartsWith(): String { + return colorFromBool(matchMode == CustomTodo.TriggerMatcher.STARTS_WITH) + "Starts With" + } + + @Bind + fun getContains(): String { + return colorFromBool(matchMode == CustomTodo.TriggerMatcher.CONTAINS) + "Contains" + } + + @Bind + fun getEquals(): String { + return colorFromBool(matchMode == CustomTodo.TriggerMatcher.EQUALS) + "Equals" + } + + @Bind + fun getItemStack(): IItemStack { + val item = Item.getByNameOrId(icon) ?: (Items.paper) + return ForgeItemStack.of(ItemStack(item)) + } + + @Bind + fun copyTemplate() { + ClipboardUtils.copyToClipboard(into().toTemplate()) + } + + @Bind + fun markAsReady() { + from.readyAtOnCurrentProfile = System.currentTimeMillis() + } + + @Bind + fun markAsCompleted() { + from.setDoneNow() + } + + @Bind + fun getFancyTime(): String { + val tint = timer.toIntOrNull() ?: return "§3Invalid Time" + val timeFormat = Utils.prettyTime(tint * 1000L) + if (isResetOffset) { + return "Reset $timeFormat after 00:00 GMT" + } + return "Reset $timeFormat after completion" + } + + fun changeTimer(value: Int) { + timer = ((timer.toIntOrNull() ?: 0) + value).coerceAtLeast(0).toString() + } + + @Bind + fun plusDay() { + changeTimer(60 * 60 * 24) + } + + @Bind + fun minusDay() { + changeTimer(-60 * 60 * 24) + } + + @Bind + fun minusHour() { + changeTimer(-60 * 60) + } + + @Bind + fun plusHour() { + changeTimer(60 * 60) + } + + @Bind + fun plusMinute() { + changeTimer(60) + } + + @Bind + fun minusMinute() { + changeTimer(-60) + } + + @Bind + fun delete() { + todos.remove(this) + CustomTodoList(todos, xmlUniverse).save() + } + + @Bind + fun getTitle(): String { + return "Editing ${into().label}" + } + + @Bind + fun close() { + Minecraft.getMinecraft().displayGuiScreen( + CustomTodoList( + todos, xmlUniverse + ).open() + ) + } + + @Bind + fun edit() { + Minecraft.getMinecraft().displayGuiScreen( + xmlUniverse.loadResourceLocation(this, ResourceLocation("notenoughupdates:gui/customtodos/edit.xml")) + ) + } +} diff --git a/src/main/kotlin/io/github/moulberry/notenoughupdates/miscgui/customtodos/CustomTodoHud.kt b/src/main/kotlin/io/github/moulberry/notenoughupdates/miscgui/customtodos/CustomTodoHud.kt new file mode 100644 index 00000000..591bcedb --- /dev/null +++ b/src/main/kotlin/io/github/moulberry/notenoughupdates/miscgui/customtodos/CustomTodoHud.kt @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2023 NotEnoughUpdates contributors + * + * This file is part of NotEnoughUpdates. + * + * NotEnoughUpdates 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. + * + * NotEnoughUpdates 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 NotEnoughUpdates. If not, see <https://www.gnu.org/licenses/>. + */ + +package io.github.moulberry.notenoughupdates.miscgui.customtodos + +import io.github.moulberry.notenoughupdates.NotEnoughUpdates +import io.github.moulberry.notenoughupdates.autosubscribe.NEUAutoSubscribe +import io.github.moulberry.notenoughupdates.core.util.StringUtils +import io.github.moulberry.notenoughupdates.events.SidebarChangeEvent +import io.github.moulberry.notenoughupdates.events.TabListChangeEvent +import io.github.moulberry.notenoughupdates.util.Utils +import net.minecraft.util.EnumChatFormatting +import net.minecraftforge.client.event.ClientChatReceivedEvent +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent + +@NEUAutoSubscribe +object CustomTodoHud { + + private fun matchString(todo: CustomTodo, text: String): Boolean { + return when (todo.triggerMatcher) { + CustomTodo.TriggerMatcher.REGEX -> text.matches(todo.trigger.toRegex()) + CustomTodo.TriggerMatcher.STARTS_WITH -> text.startsWith(todo.trigger) + CustomTodo.TriggerMatcher.CONTAINS -> text.contains(todo.trigger) + CustomTodo.TriggerMatcher.EQUALS -> text == todo.trigger + } + } + + @SubscribeEvent + fun onTabList(event: TabListChangeEvent) { + NotEnoughUpdates.INSTANCE.config.hidden.customTodos + .forEach { todo -> + if (todo.triggerTarget != CustomTodo.TriggerTarget.TAB_LIST) return@forEach + event.newLines.forEach { text -> + val doesMatch = matchString(todo, text) + if (doesMatch) { + todo.setDoneNow() + } + } + } + } + + @SubscribeEvent + fun onSidebar(event: SidebarChangeEvent) { + NotEnoughUpdates.INSTANCE.config.hidden.customTodos + .forEach { todo -> + if (todo.triggerTarget != CustomTodo.TriggerTarget.SIDEBAR) return@forEach + event.lines.forEach { text -> + val doesMatch = matchString(todo, text) + if (doesMatch) { + todo.setDoneNow() + } + } + } + } + + @SubscribeEvent + fun onChat(event: ClientChatReceivedEvent) { + val text = StringUtils.cleanColour(event.message.unformattedText) + NotEnoughUpdates.INSTANCE.config.hidden.customTodos + .forEach { + val isCorrectTrigger = when (it.triggerTarget) { + CustomTodo.TriggerTarget.CHAT -> event.type != 2.toByte() + CustomTodo.TriggerTarget.ACTIONBAR -> event.type == 2.toByte() + CustomTodo.TriggerTarget.TAB_LIST -> false + CustomTodo.TriggerTarget.SIDEBAR -> false + } + val doesMatch = matchString(it, text) + if (isCorrectTrigger && doesMatch) + it.setDoneNow() + } + } + + @JvmStatic + fun processInto(strings: MutableList<String>) { + NotEnoughUpdates.INSTANCE.config.hidden.customTodos + .filter { it.isEnabledOnCurrentProfile } + .forEach { + val readyAt = it.readyAtOnCurrentProfile ?: (System.currentTimeMillis() - 1000L) + val until = readyAt - System.currentTimeMillis() + strings.add( + "CUSTOM" + it.icon + ":§3" + it.label + ": " + + if (until <= 0) + EnumChatFormatting.values()[NotEnoughUpdates.INSTANCE.config.miscOverlays.readyColour].toString() + "Ready" + else if (until < 60 * 30 * 1000L) + EnumChatFormatting.values()[NotEnoughUpdates.INSTANCE.config.miscOverlays.verySoonColour].toString() + + Utils.prettyTime(until) + else if (until < 60 * 60 * 1000L) + EnumChatFormatting.values()[NotEnoughUpdates.INSTANCE.config.miscOverlays.soonColour].toString() + + Utils.prettyTime(until) + else if (until < 3 * 60 * 60 * 1000L) + EnumChatFormatting.values()[NotEnoughUpdates.INSTANCE.config.miscOverlays.kindaSoonColour].toString() + + Utils.prettyTime(until) + else + EnumChatFormatting.values()[NotEnoughUpdates.INSTANCE.config.miscOverlays.defaultColour].toString() + + Utils.prettyTime(until) + ) + } + } + +} diff --git a/src/main/kotlin/io/github/moulberry/notenoughupdates/miscgui/customtodos/CustomTodoList.kt b/src/main/kotlin/io/github/moulberry/notenoughupdates/miscgui/customtodos/CustomTodoList.kt new file mode 100644 index 00000000..1b278990 --- /dev/null +++ b/src/main/kotlin/io/github/moulberry/notenoughupdates/miscgui/customtodos/CustomTodoList.kt @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2023 NotEnoughUpdates contributors + * + * This file is part of NotEnoughUpdates. + * + * NotEnoughUpdates 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. + * + * NotEnoughUpdates 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 NotEnoughUpdates. If not, see <https://www.gnu.org/licenses/>. + */ + +package io.github.moulberry.notenoughupdates.miscgui.customtodos + +import io.github.moulberry.moulconfig.internal.ClipboardUtils +import io.github.moulberry.moulconfig.observer.ObservableList +import io.github.moulberry.moulconfig.xml.Bind +import io.github.moulberry.moulconfig.xml.XMLUniverse +import io.github.moulberry.notenoughupdates.NotEnoughUpdates +import io.github.moulberry.notenoughupdates.autosubscribe.NEUAutoSubscribe +import io.github.moulberry.notenoughupdates.events.RegisterBrigadierCommandEvent +import io.github.moulberry.notenoughupdates.util.brigadier.thenExecute +import io.github.moulberry.notenoughupdates.util.brigadier.withHelp +import io.github.moulberry.notenoughupdates.util.loadResourceLocation +import net.minecraft.client.gui.GuiScreen +import net.minecraft.util.ResourceLocation +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent + +class CustomTodoList( + @field:Bind + val todos: ObservableList<CustomTodoEditor>, + val xmlUniverse: XMLUniverse, +) { + @NEUAutoSubscribe + companion object { + @SubscribeEvent + fun onCommand(event: RegisterBrigadierCommandEvent) { + event.command("neutodos", "neucustomtodos") { + thenExecute { + NotEnoughUpdates.INSTANCE.openGui = create().open() + } + }.withHelp("Edit NEUs custom TODOs") + } + + fun create(): CustomTodoList { + val universe = XMLUniverse.getDefaultUniverse() + val list = ObservableList<CustomTodoEditor>(mutableListOf()) + NotEnoughUpdates.INSTANCE.config.hidden.customTodos.forEach { + list.add(CustomTodoEditor(it, list, universe)) + } + return CustomTodoList( + list, + universe + ) + } + } + + fun open(): GuiScreen { + return xmlUniverse.loadResourceLocation(this, ResourceLocation("notenoughupdates:gui/customtodos/overview.xml")) + } + + @Bind + fun pasteTodo() { + val customTodo = CustomTodo.fromTemplate(ClipboardUtils.getClipboardContent()) + ?: return + todos.add(CustomTodoEditor(customTodo, todos, xmlUniverse)) + save() + } + + fun save() { + NotEnoughUpdates.INSTANCE.config.hidden.customTodos = todos.map { it.into() }.toMutableList() + NotEnoughUpdates.INSTANCE.saveConfig() + } + + @Bind + fun addTodo() { + todos.add( + CustomTodoEditor( + CustomTodo( + "Custom Todo # ${todos.size + 1}", + 0, + "", + "", + false, + ), + todos, + xmlUniverse, + ) + ) + save() + } +} diff --git a/src/main/kotlin/io/github/moulberry/notenoughupdates/util/MoulConfig.kt b/src/main/kotlin/io/github/moulberry/notenoughupdates/util/MoulConfig.kt new file mode 100644 index 00000000..9c626627 --- /dev/null +++ b/src/main/kotlin/io/github/moulberry/notenoughupdates/util/MoulConfig.kt @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2023 NotEnoughUpdates contributors + * + * This file is part of NotEnoughUpdates. + * + * NotEnoughUpdates 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. + * + * NotEnoughUpdates 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 NotEnoughUpdates. If not, see <https://www.gnu.org/licenses/>. + */ + +package io.github.moulberry.notenoughupdates.util + +import io.github.moulberry.moulconfig.gui.GuiContext +import io.github.moulberry.moulconfig.gui.GuiScreenElementWrapperNew +import io.github.moulberry.moulconfig.xml.XMLUniverse +import net.minecraft.client.Minecraft +import net.minecraft.client.gui.GuiScreen +import net.minecraft.util.ResourceLocation + + +fun XMLUniverse.loadResourceLocation(obj: Any, resourceLocation: ResourceLocation): GuiScreen { + return GuiScreenElementWrapperNew( + GuiContext( + load( + obj, + Minecraft.getMinecraft().resourceManager.getResource(resourceLocation).inputStream + ) + ) + ) +} diff --git a/src/main/kotlin/io/github/moulberry/notenoughupdates/util/TemplateUtil.kt b/src/main/kotlin/io/github/moulberry/notenoughupdates/util/TemplateUtil.kt new file mode 100644 index 00000000..2cd90acc --- /dev/null +++ b/src/main/kotlin/io/github/moulberry/notenoughupdates/util/TemplateUtil.kt @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2023 NotEnoughUpdates contributors + * + * This file is part of NotEnoughUpdates. + * + * NotEnoughUpdates 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. + * + * NotEnoughUpdates 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 NotEnoughUpdates. If not, see <https://www.gnu.org/licenses/>. + */ + +package io.github.moulberry.notenoughupdates.util + +import com.google.gson.GsonBuilder +import io.github.moulberry.notenoughupdates.util.kotlin.KotlinTypeAdapterFactory +import java.util.* + +object TemplateUtil { + val gson = GsonBuilder() + .registerTypeAdapterFactory(KotlinTypeAdapterFactory) + .create() + + @JvmStatic + fun getTemplatePrefix(data: String): String? { + val decoded = maybeFromBase64Encoded(data) ?: return null + return decoded.replaceAfter("/", "", "").ifBlank { null } + } + + @JvmStatic + fun intoBase64Encoded(raw: String): String { + return Base64.getEncoder().encodeToString(raw.encodeToByteArray()) + } + + private val base64Alphabet = charArrayOf( + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/', '=' + ) + + @JvmStatic + fun maybeFromBase64Encoded(raw: String): String? { + val raw = raw.trim() + if (raw.any { it !in base64Alphabet }) { + return null + } + return try { + Base64.getDecoder().decode(raw).decodeToString() + } catch (ex: Exception) { + null + } + } + + + /** + * Returns a base64 encoded string, truncated such that for all `x`, `x.startsWith(prefix)` implies + * `base64Encoded(x).startsWith(getPrefixComparisonSafeBase64Encoding(prefix))` + * (however, the inverse may not always be true). + */ + @JvmStatic + fun getPrefixComparisonSafeBase64Encoding(prefix: String): String { + val rawEncoded = + Base64.getEncoder().encodeToString(prefix.encodeToByteArray()) + .replace("=", "") + return rawEncoded.substring(0, rawEncoded.length - rawEncoded.length % 4) + } + + @JvmStatic + fun encodeTemplate(sharePrefix: String, data: Any): String { + require(sharePrefix.endsWith("/")) + return intoBase64Encoded(sharePrefix + gson.toJson(data)) + } + + @JvmStatic + fun <T : Any> maybeDecodeTemplate(sharePrefix: String, data: String, type: Class<T>): T? { + require(sharePrefix.endsWith("/")) + val data = data.trim() + if (!data.startsWith(getPrefixComparisonSafeBase64Encoding(sharePrefix))) + return null + val decoded = maybeFromBase64Encoded(data) ?: return null + if (!decoded.startsWith(sharePrefix)) + return null + return try { + gson.fromJson(decoded.substring(sharePrefix.length), type) + } catch (e: Exception) { + null + } + } + +} |