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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 55 additions & 29 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <amount>` sends Vault currency instead of an item.
* `/kc wishlist <view|add|remove>`
* 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 <amount>` | Send Vault currency instead of an item. |
| `/kc wishlist <view|add|remove>` | 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 <item>`.
* Santas submit gifts using `/kc submit` (items) or `/kc submit currency <amount>` (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 <item>`.
4. Santas submit gifts using `/kc submit` (items) or `/kc submit currency <amount>` (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.
52 changes: 35 additions & 17 deletions src/main/java/me/benrobson/kringlecrate/utils/DateUtils.java
Original file line number Diff line number Diff line change
@@ -1,63 +1,70 @@
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) {
this.plugin = 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);
}

Expand All @@ -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;
}
}
}
43 changes: 5 additions & 38 deletions src/main/java/me/benrobson/kringlecrate/utils/FormatterUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand All @@ -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());
Expand Down
1 change: 1 addition & 0 deletions src/main/resources/config.yml
Original file line number Diff line number Diff line change
@@ -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"