···11+# Disable autocrlf on generated files, they always generate with LF
22+# Add any extra files or paths here to make git stop saying they
33+# are changed when only line endings change.
44+src/generated/**/.cache/* text eol=lf
55+src/generated/**/*.json text eol=lf
···11+// Folder-specific settings
22+//
33+// For a full list of overridable settings, and general information on folder-specific settings,
44+// see the documentation: https://zed.dev/docs/configuring-zed#settings-files
55+{
66+ "lsp": {
77+ "jdtls": {
88+ "settings": {
99+ "jdk_auto_download": true,
1010+ },
1111+ },
1212+ },
1313+}
···11+# Daily Rewards
22+33+Daily Rewards is a mod that lets players claim a configurable reward once every 24 hours.
+192
build.gradle
···11+plugins {
22+ id 'java-library'
33+ id 'maven-publish'
44+ id 'net.neoforged.moddev' version '2.0.141'
55+ id 'idea'
66+}
77+88+tasks.named('wrapper', Wrapper).configure {
99+ // Define wrapper values here so as to not have to always do so when updating gradlew.properties.
1010+ // Switching this to Wrapper.DistributionType.ALL will download the full gradle sources that comes with
1111+ // documentation attached on cursor hover of gradle classes and methods. However, this comes with increased
1212+ // file size for Gradle. If you do switch this to ALL, run the Gradle wrapper task twice afterwards.
1313+ // (Verify by checking gradle/wrapper/gradle-wrapper.properties to see if distributionUrl now points to `-all`)
1414+ distributionType = Wrapper.DistributionType.BIN
1515+}
1616+1717+version = mod_version
1818+group = mod_group_id
1919+2020+sourceSets.main.resources {
2121+ // Include resources generated by data generators.
2222+ srcDir('src/generated/resources')
2323+2424+ // Exclude common development only resources from finalized outputs
2525+ exclude("**/*.bbmodel") // BlockBench project files
2626+ exclude("src/generated/**/.cache") // datagen cache files
2727+}
2828+2929+repositories {
3030+ // Add here additional repositories if required by some of the dependencies below.
3131+}
3232+3333+base {
3434+ archivesName = mod_id
3535+}
3636+3737+// Mojang ships Java 21 to end users in 1.21.1, so mods should target Java 21.
3838+java.toolchain.languageVersion = JavaLanguageVersion.of(21)
3939+4040+neoForge {
4141+ // Specify the version of NeoForge to use.
4242+ version = project.neo_version
4343+4444+ parchment {
4545+ mappingsVersion = project.parchment_mappings_version
4646+ minecraftVersion = project.parchment_minecraft_version
4747+ }
4848+4949+ // This line is optional. Access Transformers are automatically detected
5050+ // accessTransformers = project.files('src/main/resources/META-INF/accesstransformer.cfg')
5151+5252+ // Default run configurations.
5353+ // These can be tweaked, removed, or duplicated as needed.
5454+ runs {
5555+ client {
5656+ client()
5757+5858+ // Comma-separated list of namespaces to load gametests from. Empty = all namespaces.
5959+ systemProperty 'neoforge.enabledGameTestNamespaces', project.mod_id
6060+ }
6161+6262+ server {
6363+ server()
6464+ programArgument '--nogui'
6565+ systemProperty 'neoforge.enabledGameTestNamespaces', project.mod_id
6666+ }
6767+6868+ // This run config launches GameTestServer and runs all registered gametests, then exits.
6969+ // By default, the server will crash when no gametests are provided.
7070+ // The gametest system is also enabled by default for other run configs under the /test command.
7171+ gameTestServer {
7272+ type = "gameTestServer"
7373+ systemProperty 'neoforge.enabledGameTestNamespaces', project.mod_id
7474+ }
7575+7676+ data {
7777+ data()
7878+7979+ // example of overriding the workingDirectory set in configureEach above, uncomment if you want to use it
8080+ // gameDirectory = project.file('run-data')
8181+8282+ // Specify the modid for data generation, where to output the resulting resource, and where to look for existing resources.
8383+ programArguments.addAll '--mod', project.mod_id, '--all', '--output', file('src/generated/resources/').getAbsolutePath(), '--existing', file('src/main/resources/').getAbsolutePath()
8484+ }
8585+8686+ // applies to all the run configs above
8787+ configureEach {
8888+ // Recommended logging data for a userdev environment
8989+ // The markers can be added/remove as needed separated by commas.
9090+ // "SCAN": For mods scan.
9191+ // "REGISTRIES": For firing of registry events.
9292+ // "REGISTRYDUMP": For getting the contents of all registries.
9393+ systemProperty 'forge.logging.markers', 'REGISTRIES'
9494+9595+ // Recommended logging level for the console
9696+ // You can set various levels here.
9797+ // Please read: https://stackoverflow.com/questions/2031163/when-to-use-the-different-log-levels
9898+ logLevel = org.slf4j.event.Level.DEBUG
9999+ }
100100+ }
101101+102102+ mods {
103103+ // define mod <-> source bindings
104104+ // these are used to tell the game which sources are for which mod
105105+ // multi mod projects should define one per mod
106106+ "${mod_id}" {
107107+ sourceSet(sourceSets.main)
108108+ }
109109+ }
110110+}
111111+112112+// Sets up a dependency configuration called 'localRuntime'.
113113+// This configuration should be used instead of 'runtimeOnly' to declare
114114+// a dependency that will be present for runtime testing but that is
115115+// "optional", meaning it will not be pulled by dependents of this mod.
116116+configurations {
117117+ runtimeClasspath.extendsFrom localRuntime
118118+}
119119+120120+dependencies {
121121+ // Example optional mod dependency with JEI
122122+ // The JEI API is declared for compile time use, while the full JEI artifact is used at runtime
123123+ // compileOnly "mezz.jei:jei-${mc_version}-common-api:${jei_version}"
124124+ // compileOnly "mezz.jei:jei-${mc_version}-neoforge-api:${jei_version}"
125125+ // We add the full version to localRuntime, not runtimeOnly, so that we do not publish a dependency on it
126126+ // localRuntime "mezz.jei:jei-${mc_version}-neoforge:${jei_version}"
127127+128128+ // Example mod dependency using a mod jar from ./libs with a flat dir repository
129129+ // This maps to ./libs/coolmod-${mc_version}-${coolmod_version}.jar
130130+ // The group id is ignored when searching -- in this case, it is "blank"
131131+ // implementation "blank:coolmod-${mc_version}:${coolmod_version}"
132132+133133+ // Example mod dependency using a file as dependency
134134+ // implementation files("libs/coolmod-${mc_version}-${coolmod_version}.jar")
135135+136136+ // Example project dependency using a sister or child project:
137137+ // implementation project(":myproject")
138138+139139+ // For more info:
140140+ // http://www.gradle.org/docs/current/userguide/artifact_dependencies_tutorial.html
141141+ // http://www.gradle.org/docs/current/userguide/dependency_management.html
142142+}
143143+144144+// This block of code expands all declared replace properties in the specified resource targets.
145145+// A missing property will result in an error. Properties are expanded using ${} Groovy notation.
146146+var generateModMetadata = tasks.register("generateModMetadata", ProcessResources) {
147147+ var replaceProperties = [
148148+ minecraft_version : minecraft_version,
149149+ minecraft_version_range: minecraft_version_range,
150150+ neo_version : neo_version,
151151+ loader_version_range : loader_version_range,
152152+ mod_id : mod_id,
153153+ mod_name : mod_name,
154154+ mod_license : mod_license,
155155+ mod_version : mod_version,
156156+ ]
157157+ inputs.properties replaceProperties
158158+ expand replaceProperties
159159+ from "src/main/templates"
160160+ into "build/generated/sources/modMetadata"
161161+}
162162+// Include the output of "generateModMetadata" as an input directory for the build
163163+// this works with both building through Gradle and the IDE.
164164+sourceSets.main.resources.srcDir generateModMetadata
165165+// To avoid having to run "generateModMetadata" manually, make it run on every project reload
166166+neoForge.ideSyncTask generateModMetadata
167167+168168+// Example configuration to allow publishing using the maven-publish plugin
169169+publishing {
170170+ publications {
171171+ register('mavenJava', MavenPublication) {
172172+ from components.java
173173+ }
174174+ }
175175+ repositories {
176176+ maven {
177177+ url "file://${project.projectDir}/repo"
178178+ }
179179+ }
180180+}
181181+182182+tasks.withType(JavaCompile).configureEach {
183183+ options.encoding = 'UTF-8' // Use the UTF-8 charset for Java compilation
184184+}
185185+186186+// IDEA no longer automatically downloads sources/javadoc jars for dependencies, so we need to explicitly enable the behavior.
187187+idea {
188188+ module {
189189+ downloadSources = true
190190+ downloadJavadoc = true
191191+ }
192192+}
+39
gradle.properties
···11+# Sets default memory used for gradle commands. Can be overridden by user or command line properties.
22+org.gradle.jvmargs=-Xmx1G
33+org.gradle.daemon=true
44+org.gradle.parallel=true
55+org.gradle.caching=true
66+org.gradle.configuration-cache=true
77+88+#read more on this at https://github.com/neoforged/ModDevGradle?tab=readme-ov-file#better-minecraft-parameter-names--javadoc-parchment
99+# you can also find the latest versions at: https://parchmentmc.org/docs/getting-started
1010+parchment_minecraft_version=1.21.1
1111+parchment_mappings_version=2024.11.17
1212+# Environment Properties
1313+# You can find the latest versions here: https://projects.neoforged.net/neoforged/neoforge
1414+# The Minecraft version must agree with the Neo version to get a valid artifact
1515+minecraft_version=1.21.1
1616+# The Minecraft version range can use any release version of Minecraft as bounds.
1717+# Snapshots, pre-releases, and release candidates are not guaranteed to sort properly
1818+# as they do not follow standard versioning conventions.
1919+minecraft_version_range=[1.21.1]
2020+# The Neo version must agree with the Minecraft version to get a valid artifact
2121+neo_version=21.1.219
2222+# The loader version range can only use the major version of FML as bounds
2323+loader_version_range=[1,)
2424+2525+## Mod Properties
2626+2727+# The unique mod identifier for the mod. Must be lowercase in English locale. Must fit the regex [a-z][a-z0-9_]{1,63}
2828+# Must match the String constant located in the main mod class annotated with @Mod.
2929+mod_id=dailyrewards
3030+# The human-readable display name for the mod.
3131+mod_name=Daily Rewards
3232+# The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default.
3333+mod_license=All Rights Reserved
3434+# The mod version. See https://semver.org/
3535+mod_version=1.0.0
3636+# The group ID for the mod. It is only important when publishing as an artifact to a Maven repository.
3737+# This should match the base package used for the mod sources.
3838+# See https://maven.apache.org/guides/mini/guide-naming-conventions.html
3939+mod_group_id=dev.equinoxx.dailyrewards
···11+@rem
22+@rem Copyright 2015 the original author or authors.
33+@rem
44+@rem Licensed under the Apache License, Version 2.0 (the "License");
55+@rem you may not use this file except in compliance with the License.
66+@rem You may obtain a copy of the License at
77+@rem
88+@rem https://www.apache.org/licenses/LICENSE-2.0
99+@rem
1010+@rem Unless required by applicable law or agreed to in writing, software
1111+@rem distributed under the License is distributed on an "AS IS" BASIS,
1212+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1313+@rem See the License for the specific language governing permissions and
1414+@rem limitations under the License.
1515+@rem
1616+@rem SPDX-License-Identifier: Apache-2.0
1717+@rem
1818+1919+@if "%DEBUG%"=="" @echo off
2020+@rem ##########################################################################
2121+@rem
2222+@rem Gradle startup script for Windows
2323+@rem
2424+@rem ##########################################################################
2525+2626+@rem Set local scope for the variables with windows NT shell
2727+if "%OS%"=="Windows_NT" setlocal
2828+2929+set DIRNAME=%~dp0
3030+if "%DIRNAME%"=="" set DIRNAME=.
3131+@rem This is normally unused
3232+set APP_BASE_NAME=%~n0
3333+set APP_HOME=%DIRNAME%
3434+3535+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
3636+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
3737+3838+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
3939+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
4040+4141+@rem Find java.exe
4242+if defined JAVA_HOME goto findJavaFromJavaHome
4343+4444+set JAVA_EXE=java.exe
4545+%JAVA_EXE% -version >NUL 2>&1
4646+if %ERRORLEVEL% equ 0 goto execute
4747+4848+echo. 1>&2
4949+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
5050+echo. 1>&2
5151+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
5252+echo location of your Java installation. 1>&2
5353+5454+goto fail
5555+5656+:findJavaFromJavaHome
5757+set JAVA_HOME=%JAVA_HOME:"=%
5858+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
5959+6060+if exist "%JAVA_EXE%" goto execute
6161+6262+echo. 1>&2
6363+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
6464+echo. 1>&2
6565+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
6666+echo location of your Java installation. 1>&2
6767+6868+goto fail
6969+7070+:execute
7171+@rem Setup the command line
7272+7373+set CLASSPATH=
7474+7575+7676+@rem Execute Gradle
7777+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
7878+7979+:end
8080+@rem End local scope for the variables with windows NT shell
8181+if %ERRORLEVEL% equ 0 goto mainEnd
8282+8383+:fail
8484+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
8585+rem the _cmd.exe /c_ return code!
8686+set EXIT_CODE=%ERRORLEVEL%
8787+if %EXIT_CODE% equ 0 set EXIT_CODE=1
8888+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
8989+exit /b %EXIT_CODE%
9090+9191+:mainEnd
9292+if "%OS%"=="Windows_NT" endlocal
9393+9494+:omega
+9
settings.gradle
···11+pluginManagement {
22+ repositories {
33+ gradlePluginPortal()
44+ }
55+}
66+77+plugins {
88+ id 'org.gradle.toolchains.foojay-resolver-convention' version '1.0.0'
99+}
···11+package dev.equinoxx.dailyrewards.service;
22+33+import java.util.Map;
44+import java.util.UUID;
55+import java.util.concurrent.ConcurrentHashMap;
66+import net.minecraft.ChatFormatting;
77+import net.minecraft.network.chat.Component;
88+import net.minecraft.network.chat.HoverEvent;
99+import net.minecraft.network.chat.Style;
1010+import net.minecraft.server.level.ServerPlayer;
1111+1212+public final class ReminderService {
1313+1414+ private static final long CLAIM_COOLDOWN_MS = 24L * 60L * 60L * 1000L;
1515+ private static final long REMINDER_INTERVAL_MS = 60L * 1000L;
1616+ private static final long REMINDER_INTERVAL_TICKS =
1717+ REMINDER_INTERVAL_MS / 50L;
1818+1919+ private static final Component READY_MESSAGE = Component.literal(
2020+ "Your daily reward is ready to claim! "
2121+ ).withStyle(ChatFormatting.YELLOW);
2222+2323+ private static final Component CLAIM_NOW = Component.literal(
2424+ "[Claim Now]"
2525+ ).withStyle(
2626+ Style.EMPTY.withColor(ChatFormatting.GREEN)
2727+ .withUnderlined(true)
2828+ .withClickEvent(
2929+ new net.minecraft.network.chat.ClickEvent(
3030+ net.minecraft.network.chat.ClickEvent.Action.RUN_COMMAND,
3131+ "/dailyrewards"
3232+ )
3333+ )
3434+ .withHoverEvent(
3535+ new HoverEvent(
3636+ HoverEvent.Action.SHOW_TEXT,
3737+ Component.literal("Click to run /dailyrewards")
3838+ )
3939+ )
4040+ );
4141+4242+ private final Map<UUID, Long> lastReminderTicks = new ConcurrentHashMap<>();
4343+4444+ // Returns true when the supplied claim timestamp has expired and the player can claim again.
4545+ public boolean hasClaimAvailable(Long lastClaimTimestamp, long now) {
4646+ if (lastClaimTimestamp == null) {
4747+ return true;
4848+ }
4949+5050+ return now - lastClaimTimestamp >= CLAIM_COOLDOWN_MS;
5151+ }
5252+5353+ // Tracks reminder cadence per player so the server does not spam the same message every tick.
5454+ public boolean shouldSendReminder(
5555+ UUID playerId,
5656+ long currentTick,
5757+ long now,
5858+ Long lastClaimTimestamp
5959+ ) {
6060+ if (!hasClaimAvailable(lastClaimTimestamp, now)) {
6161+ return false;
6262+ }
6363+6464+ long lastReminderTick = lastReminderTicks.getOrDefault(playerId, 0L);
6565+ if (currentTick - lastReminderTick < REMINDER_INTERVAL_TICKS) {
6666+ return false;
6767+ }
6868+6969+ lastReminderTicks.put(playerId, currentTick);
7070+ return true;
7171+ }
7272+7373+ // Sends the clickable reminder message shown when a reward is ready.
7474+ public void sendClaimAvailableNotification(ServerPlayer player) {
7575+ player.sendSystemMessage(READY_MESSAGE.copy().append(CLAIM_NOW.copy()));
7676+ }
7777+7878+ // Clears reminder state for one player, usually when they reconnect or claim their reward.
7979+ public void clearReminderState(UUID playerId) {
8080+ lastReminderTicks.remove(playerId);
8181+ }
8282+8383+ // Clears all reminder state when the server stops.
8484+ public void clearAllReminderState() {
8585+ lastReminderTicks.clear();
8686+ }
8787+}
···11+{
22+ "dailyrewards.configuration.title": "Daily Rewards Configuration",
33+ "dailyrewards.configuration.section.dailyrewards.common.toml": "Daily Rewards Configuration",
44+ "dailyrewards.configuration.section.dailyrewards.common.toml.title": "Daily Rewards Configuration",
55+ "dailyrewards.configuration.claimRewards": "Daily Reward Items",
66+ "dailyrewards.configuration.claimRewards.tooltip": "List reward entries in compact `item_id=count` format, for example `minecraft:iron_ingot=1`.",
77+ "dailyrewards.configuration.claimRewards.entry": "Reward Entry",
88+ "dailyrewards.configuration.claimRewards.entry.tooltip": "Use the compact format `item_id=count`. The item ID must be a valid item registry name and the count must be at least 1."
99+}
+29
src/main/templates/META-INF/neoforge.mods.toml
···11+# Metadata for the Daily Rewards mod.
22+# This file is processed by Gradle to produce the final NeoForge mod metadata.
33+44+modLoader = "javafml"
55+loaderVersion = "${loader_version_range}"
66+license = "${mod_license}"
77+88+[[mods]]
99+modId = "${mod_id}"
1010+version = "${mod_version}"
1111+displayName = "${mod_name}"
1212+authors = "Equinoxx"
1313+description = '''
1414+Adds daily claimable rewards for players.
1515+'''
1616+1717+[[dependencies.${mod_id}]]
1818+modId = "neoforge"
1919+type = "required"
2020+versionRange = "[${neo_version},)"
2121+ordering = "NONE"
2222+side = "SERVER"
2323+2424+[[dependencies.${mod_id}]]
2525+modId = "minecraft"
2626+type = "required"
2727+versionRange = "${minecraft_version_range}"
2828+ordering = "NONE"
2929+side = "SERVER"