diff --git a/README.md b/README.md index 78912b0..eb2d397 100644 --- a/README.md +++ b/README.md @@ -2,51 +2,77 @@ ## Overview -KringleCrate is a Minecraft plugin designed to enable a Secret Santa gift exchange among players. It features an opt-in system, recipient assignments, wishlist management, item and currency gift submissions, and a redemption system while preserving item metadata such as enchantments, names, and lore. - ---- +KringleCrate is a Minecraft plugin designed to enable a Secret Santa gift exchange among players. It features an opt-in +system, recipient assignments, wishlist management, item and currency gift submissions, and a redemption window that +preserves item metadata such as enchantments, names, and lore. ## Features -1. **Opt-in System**: Players can opt into the Secret Santa event using a command. -2. **Recipient Assignment**: Each participant is randomly assigned a recipient. -3. **Wishlist Support**: Participants can record their gift wishes to help Santas pick the perfect present. -4. **Gift Submission**: Players can submit item gifts while preserving all metadata or send currency directly through Vault. -5. **Gift Redemption**: Players can redeem their items and any accumulated currency during the configured redemption period. ---- +- **Opt-in event flow** with join, reveal, submit, and redeem stages. +- **Recipient assignment** for every participant. +- **Wishlist management** so Santas can pick the perfect present. +- **Item + Vault currency gifts** with metadata retention. +- **Redemption window** that keeps delivery fair and predictable. + +## Requirements + +- Spigot/Paper server compatible with the plugin build. +- Vault (only required if you want currency gifts enabled). +- An economy plugin supported by Vault (for currency gifts). + +## Installation + +1. Drop the plugin jar into your server's `plugins/` folder. +2. Start the server once to generate `config.yml`. +3. Configure event dates/timezone (see below). +4. Restart the server or reload the plugin. ## Configuration +All event dates are ISO-local timestamps (no zone offset). The `event-timezone` is applied to interpret those dates. + ### `config.yml` ```yaml redemption-start: "2024-12-25T00:00:00" redemption-end: "2025-01-01T23:59:59" reveal-date: "2024-12-20T00:00:00" +event-timezone: "Australia/Sydney" ``` +### Timezone behavior + +All event dates (reveal, redemption start/end, and the join/submit/redeem windows derived from them) are interpreted in +the configured `event-timezone`. If the field is missing or invalid, KringleCrate defaults to `Australia/Sydney`. + ## Commands -* `/kc join` - * Opt into the Secret Santa event and create a wishlist profile. -* `/kc reveal` - * Reveal your assigned recipient (after the reveal date or with the `kringlecrate.override` permission). -* `/kc submit` - * Submit the item held in your main hand to your assigned recipient. - * `/kc submit currency ` sends Vault currency instead of an item. -* `/kc wishlist ` - * Manage your gift wishlist once you have joined the event. -* `/kc redeem` - * Redeem stored gifts during the redemption period, including any currency deposits. +| Command | Description | +| --- | --- | +| `/kc join` | Opt into the event and create a wishlist profile. | +| `/kc reveal` | Reveal your recipient after the reveal date (or with override permission). | +| `/kc submit` | Submit the item in your main hand to your recipient. | +| `/kc submit currency ` | Send Vault currency instead of an item. | +| `/kc wishlist ` | Manage your gift wishlist once joined. | +| `/kc redeem` | Redeem stored gifts during the redemption period. | -### Permissions +## Permissions -* `kringlecrate.override`: Allows admins to bypass reveal date restrictions. -* `kringlecrate.redeem`: Grants access to the redeem command (enabled for players by default). +| Permission | Description | +| --- | --- | +| `kringlecrate.override` | Bypass reveal date restrictions (admins). | +| `kringlecrate.redeem` | Use the redeem command (enabled for players by default). | ## How It Works -* Players use `/kc join` to participate in the event. -* The plugin assigns a random recipient to each participant. -* Participants can request presents with `/kc wishlist add `. -* Santas submit gifts using `/kc submit` (items) or `/kc submit currency ` (Vault currency) before the redemption period begins. -* On the reveal date, players can use `/kc reveal` to see their assigned recipient. -* Gifts, including currency, can be redeemed using `/kc redeem` during the redemption period. + +1. Players run `/kc join` to participate. +2. The plugin assigns a random recipient to each participant. +3. Participants add wishes with `/kc wishlist add `. +4. Santas submit gifts using `/kc submit` (items) or `/kc submit currency ` (Vault currency) before the + redemption period begins. +5. On the reveal date, players use `/kc reveal` to see their assigned recipient. +6. Gifts can be redeemed using `/kc redeem` during the redemption period. + +## Notes + +- If Vault or an economy plugin is missing, currency gifts are unavailable but item gifts still work. +- Date messages shown to players include the configured timezone for clarity. diff --git a/src/main/java/me/benrobson/kringlecrate/utils/DateUtils.java b/src/main/java/me/benrobson/kringlecrate/utils/DateUtils.java index c6240c1..cca093d 100644 --- a/src/main/java/me/benrobson/kringlecrate/utils/DateUtils.java +++ b/src/main/java/me/benrobson/kringlecrate/utils/DateUtils.java @@ -1,12 +1,16 @@ package me.benrobson.kringlecrate.utils; import me.benrobson.kringlecrate.KringleCrate; +import java.time.Instant; import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; public class DateUtils { - private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); // Standard format + private static final ZoneId DEFAULT_TIMEZONE = ZoneId.of("Australia/Sydney"); + private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("d MMMM yyyy HH:mm z"); // Standard format private static KringleCrate plugin; public DateUtils(KringleCrate plugin) { @@ -14,50 +18,53 @@ public DateUtils(KringleCrate plugin) { } // Get the reveal date from the config - public static LocalDateTime getRevealDate() { + public static ZonedDateTime getRevealDate() { String dateString = plugin.getConfig().getString("reveal-date"); try { - return LocalDateTime.parse(dateString, DateTimeFormatter.ISO_LOCAL_DATE_TIME); + return LocalDateTime.parse(dateString, DateTimeFormatter.ISO_LOCAL_DATE_TIME) + .atZone(getEventZoneId()); } catch (Exception e) { plugin.getLogger().severe("Invalid reveal date format in config.yml: " + dateString); - return LocalDateTime.now(); // Return the current time if the config value is invalid + return ZonedDateTime.now(getEventZoneId()); // Return the current time if the config value is invalid } } public static boolean isRevealDay() { - LocalDateTime revealDate = FormatterUtils.getRevealDate(); - return !LocalDateTime.now().isBefore(revealDate); + ZonedDateTime revealDate = getRevealDate(); + return !ZonedDateTime.now(getEventZoneId()).isBefore(revealDate); } public static boolean isBeforeRevealDate() { - LocalDateTime revealDate = FormatterUtils.getRevealDate(); - return LocalDateTime.now().isBefore(revealDate); + ZonedDateTime revealDate = getRevealDate(); + return ZonedDateTime.now(getEventZoneId()).isBefore(revealDate); } - public static LocalDateTime getRedemptionStart() { + public static ZonedDateTime getRedemptionStart() { String startDateString = plugin.getConfig().getString("redemption-start"); try { - return LocalDateTime.parse(startDateString, DateTimeFormatter.ISO_LOCAL_DATE_TIME); + return LocalDateTime.parse(startDateString, DateTimeFormatter.ISO_LOCAL_DATE_TIME) + .atZone(getEventZoneId()); } catch (Exception e) { plugin.getLogger().severe("Invalid redemption-start format in config.yml: " + startDateString); - return LocalDateTime.MIN; // Return a minimal value to ensure it won't validate + return ZonedDateTime.ofInstant(Instant.MIN, getEventZoneId()); // Return a minimal value to ensure it won't validate } } - public static LocalDateTime getRedemptionEnd() { + public static ZonedDateTime getRedemptionEnd() { String endDateString = plugin.getConfig().getString("redemption-end"); try { - return LocalDateTime.parse(endDateString, DateTimeFormatter.ISO_LOCAL_DATE_TIME); + return LocalDateTime.parse(endDateString, DateTimeFormatter.ISO_LOCAL_DATE_TIME) + .atZone(getEventZoneId()); } catch (Exception e) { plugin.getLogger().severe("Invalid redemption-end format in config.yml: " + endDateString); - return LocalDateTime.MAX; // Return a maximal value to ensure it won't validate + return ZonedDateTime.ofInstant(Instant.MAX, getEventZoneId()); // Return a maximal value to ensure it won't validate } } public static boolean isInRedemptionPeriod() { - LocalDateTime redemptionStart = getRedemptionStart(); - LocalDateTime redemptionEnd = getRedemptionEnd(); - LocalDateTime now = LocalDateTime.now(); + ZonedDateTime redemptionStart = getRedemptionStart(); + ZonedDateTime redemptionEnd = getRedemptionEnd(); + ZonedDateTime now = ZonedDateTime.now(getEventZoneId()); return !now.isBefore(redemptionStart) && !now.isAfter(redemptionEnd); } @@ -66,4 +73,15 @@ public static String getFormattedRedemptionPeriod() { " to " + getRedemptionEnd().format(formatter); } + + public static ZoneId getEventZoneId() { + String timezoneId = plugin.getConfig().getString("event-timezone", DEFAULT_TIMEZONE.getId()); + try { + return ZoneId.of(timezoneId); + } catch (Exception e) { + plugin.getLogger().severe("Invalid event-timezone in config.yml: " + timezoneId + + ". Falling back to " + DEFAULT_TIMEZONE.getId()); + return DEFAULT_TIMEZONE; + } + } } diff --git a/src/main/java/me/benrobson/kringlecrate/utils/FormatterUtils.java b/src/main/java/me/benrobson/kringlecrate/utils/FormatterUtils.java index d9b8139..ae66243 100644 --- a/src/main/java/me/benrobson/kringlecrate/utils/FormatterUtils.java +++ b/src/main/java/me/benrobson/kringlecrate/utils/FormatterUtils.java @@ -2,55 +2,22 @@ import me.benrobson.kringlecrate.KringleCrate; -import java.time.LocalDateTime; +import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; -import java.time.format.DateTimeParseException; public class FormatterUtils { private static KringleCrate plugin; - private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("d MMMM yyyy"); // Format for display + private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("d MMMM yyyy HH:mm z"); // Format for display public FormatterUtils(KringleCrate plugin) { this.plugin = plugin; } - // Retrieves redemption start time from config (defaults if invalid) - public static LocalDateTime getRedemptionStart() { - String startDateString = plugin.getConfig().getString("redemption-start"); - try { - return LocalDateTime.parse(startDateString, DateTimeFormatter.ISO_LOCAL_DATE_TIME); - } catch (DateTimeParseException | NullPointerException e) { - plugin.getLogger().severe("Invalid redemption-start format in config.yml: " + startDateString); - return LocalDateTime.MIN; // Return a minimal value to ensure it won't validate - } - } - - // Retrieves redemption end time from config (defaults if invalid) - public static LocalDateTime getRedemptionEnd() { - String endDateString = plugin.getConfig().getString("redemption-end"); - try { - return LocalDateTime.parse(endDateString, DateTimeFormatter.ISO_LOCAL_DATE_TIME); - } catch (DateTimeParseException | NullPointerException e) { - plugin.getLogger().severe("Invalid redemption-end format in config.yml: " + endDateString); - return LocalDateTime.MAX; // Return a maximal value to ensure it won't validate - } - } - - public static LocalDateTime getRevealDate() { - String dateString = plugin.getConfig().getString("reveal-date"); - try { - return LocalDateTime.parse(dateString, DateTimeFormatter.ISO_LOCAL_DATE_TIME); - } catch (DateTimeParseException e) { - plugin.getLogger().severe("Invalid reveal date format in config.yml: " + dateString); - return LocalDateTime.now(); // Return current date-time if invalid - } - } - public static String getFormattedRedemptionPeriod() { try { - LocalDateTime redemptionStart = DateUtils.getRedemptionStart(); - LocalDateTime redemptionEnd = DateUtils.getRedemptionEnd(); + ZonedDateTime redemptionStart = DateUtils.getRedemptionStart(); + ZonedDateTime redemptionEnd = DateUtils.getRedemptionEnd(); return "from " + redemptionStart.format(formatter) + " to " + redemptionEnd.format(formatter); } catch (Exception e) { plugin.getLogger().severe("Error formatting redemption period: " + e.getMessage()); @@ -60,7 +27,7 @@ public static String getFormattedRedemptionPeriod() { public static String getFormattedRevealDate() { try { - LocalDateTime revealDate = DateUtils.getRevealDate(); + ZonedDateTime revealDate = DateUtils.getRevealDate(); return revealDate.format(formatter); } catch (Exception e) { plugin.getLogger().severe("Error formatting reveal date: " + e.getMessage()); diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 07f6fcf..dff40b7 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -1,3 +1,4 @@ redemption-start: "2024-12-25T00:00:00" redemption-end: "2025-01-01T23:59:59" reveal-date: "2024-12-20T00:00:00" +event-timezone: "Australia/Sydney"