Clean repo for open source release
Remove build artifacts, dev tool configs, unused dependencies, and third-party source dumps. Add proper README, update .gitignore, clean up Makefile.
This commit is contained in:
407
src/main/java/com/tiedup/remake/bounty/BountyManager.java
Normal file
407
src/main/java/com/tiedup/remake/bounty/BountyManager.java
Normal file
@@ -0,0 +1,407 @@
|
||||
package com.tiedup.remake.bounty;
|
||||
|
||||
import com.tiedup.remake.core.SystemMessageManager;
|
||||
import com.tiedup.remake.core.TiedUpMod;
|
||||
import com.tiedup.remake.core.SettingsAccessor;
|
||||
import java.util.*;
|
||||
import javax.annotation.Nullable;
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.ListTag;
|
||||
import net.minecraft.nbt.Tag;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.saveddata.SavedData;
|
||||
|
||||
/**
|
||||
* World-saved data manager for bounties.
|
||||
*
|
||||
* Phase 17: Bounty System
|
||||
*
|
||||
* Manages all active bounties, handles expiration, delivery rewards,
|
||||
* and stores bounties for offline players.
|
||||
*/
|
||||
public class BountyManager extends SavedData {
|
||||
|
||||
private static final String DATA_NAME = "tiedup_bounties";
|
||||
|
||||
/** Pending rewards expire after 30 real days (in milliseconds). */
|
||||
private static final long PENDING_REWARD_EXPIRATION_MS =
|
||||
30L * 24 * 60 * 60 * 1000;
|
||||
|
||||
// Active bounties
|
||||
private final List<Bounty> bounties = new ArrayList<>();
|
||||
|
||||
// Bounties for offline players (to return reward when they log in)
|
||||
// Stored with timestamp via Bounty.creationTime + durationSeconds
|
||||
private final List<Bounty> pendingRewards = new ArrayList<>();
|
||||
|
||||
// ==================== CONSTRUCTION ====================
|
||||
|
||||
public BountyManager() {}
|
||||
|
||||
public static BountyManager create() {
|
||||
return new BountyManager();
|
||||
}
|
||||
|
||||
public static BountyManager load(CompoundTag tag) {
|
||||
BountyManager manager = new BountyManager();
|
||||
|
||||
// Load active bounties
|
||||
ListTag bountiesTag = tag.getList("bounties", Tag.TAG_COMPOUND);
|
||||
for (int i = 0; i < bountiesTag.size(); i++) {
|
||||
manager.bounties.add(Bounty.load(bountiesTag.getCompound(i)));
|
||||
}
|
||||
|
||||
// Load pending rewards (with expiration cleanup)
|
||||
ListTag pendingTag = tag.getList("pendingRewards", Tag.TAG_COMPOUND);
|
||||
int expiredCount = 0;
|
||||
for (int i = 0; i < pendingTag.size(); i++) {
|
||||
Bounty bounty = Bounty.load(pendingTag.getCompound(i));
|
||||
if (!isPendingRewardExpired(bounty)) {
|
||||
manager.pendingRewards.add(bounty);
|
||||
} else {
|
||||
expiredCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (expiredCount > 0) {
|
||||
TiedUpMod.LOGGER.info(
|
||||
"[BOUNTY] Cleaned up {} expired pending rewards (>30 days)",
|
||||
expiredCount
|
||||
);
|
||||
}
|
||||
|
||||
TiedUpMod.LOGGER.info(
|
||||
"[BOUNTY] Loaded {} active bounties, {} pending rewards",
|
||||
manager.bounties.size(),
|
||||
manager.pendingRewards.size()
|
||||
);
|
||||
|
||||
return manager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompoundTag save(CompoundTag tag) {
|
||||
// Save active bounties
|
||||
ListTag bountiesTag = new ListTag();
|
||||
for (Bounty bounty : bounties) {
|
||||
bountiesTag.add(bounty.save());
|
||||
}
|
||||
tag.put("bounties", bountiesTag);
|
||||
|
||||
// Save pending rewards
|
||||
ListTag pendingTag = new ListTag();
|
||||
for (Bounty bounty : pendingRewards) {
|
||||
pendingTag.add(bounty.save());
|
||||
}
|
||||
tag.put("pendingRewards", pendingTag);
|
||||
|
||||
return tag;
|
||||
}
|
||||
|
||||
// ==================== ACCESS ====================
|
||||
|
||||
/**
|
||||
* Get the BountyManager for a world.
|
||||
*/
|
||||
public static BountyManager get(ServerLevel level) {
|
||||
return level
|
||||
.getDataStorage()
|
||||
.computeIfAbsent(
|
||||
BountyManager::load,
|
||||
BountyManager::create,
|
||||
DATA_NAME
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the BountyManager from a server.
|
||||
*/
|
||||
public static BountyManager get(MinecraftServer server) {
|
||||
ServerLevel overworld = server.overworld();
|
||||
return get(overworld);
|
||||
}
|
||||
|
||||
// ==================== BOUNTY MANAGEMENT ====================
|
||||
|
||||
/**
|
||||
* Get all active bounties (removes expired ones).
|
||||
*/
|
||||
public List<Bounty> getBounties(ServerLevel level) {
|
||||
// Clean up expired bounties
|
||||
Iterator<Bounty> it = bounties.iterator();
|
||||
while (it.hasNext()) {
|
||||
Bounty bounty = it.next();
|
||||
if (bounty.isExpired()) {
|
||||
it.remove();
|
||||
onBountyExpired(level, bounty);
|
||||
}
|
||||
}
|
||||
setDirty();
|
||||
return new ArrayList<>(bounties);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new bounty.
|
||||
*/
|
||||
public void addBounty(Bounty bounty) {
|
||||
bounties.add(bounty);
|
||||
setDirty();
|
||||
TiedUpMod.LOGGER.info(
|
||||
"[BOUNTY] New bounty: {} on {} by {}",
|
||||
bounty.getId(),
|
||||
bounty.getTargetName(),
|
||||
bounty.getClientName()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a bounty by ID.
|
||||
*/
|
||||
@Nullable
|
||||
public Bounty getBountyById(String id) {
|
||||
for (Bounty bounty : bounties) {
|
||||
if (bounty.getId().equals(id)) {
|
||||
return bounty;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel a bounty.
|
||||
* Only the client or an admin can cancel.
|
||||
* If client cancels, they get their reward back.
|
||||
*/
|
||||
public boolean cancelBounty(ServerPlayer player, String bountyId) {
|
||||
Bounty bounty = getBountyById(bountyId);
|
||||
if (bounty == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean isAdmin = player.hasPermissions(2);
|
||||
boolean isClient = bounty.isClient(player.getUUID());
|
||||
|
||||
if (!isClient && !isAdmin) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bounties.remove(bounty);
|
||||
setDirty();
|
||||
|
||||
// Return reward to client (or drop if admin cancelled)
|
||||
if (isClient) {
|
||||
giveReward(player, bounty);
|
||||
broadcastMessage(
|
||||
player.server,
|
||||
player.getName().getString() +
|
||||
" cancelled their bounty on " +
|
||||
bounty.getTargetName()
|
||||
);
|
||||
} else {
|
||||
onBountyExpired(player.serverLevel(), bounty);
|
||||
broadcastMessage(
|
||||
player.server,
|
||||
player.getName().getString() +
|
||||
" (admin) cancelled bounty on " +
|
||||
bounty.getTargetName()
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ==================== DELIVERY ====================
|
||||
|
||||
/**
|
||||
* Try to deliver a captive to a client.
|
||||
* Called when a hunter brings a captive near the bounty client.
|
||||
*
|
||||
* @param hunter The player delivering the captive
|
||||
* @param client The bounty client receiving the captive
|
||||
* @param target The captive being delivered
|
||||
* @return true if bounty was fulfilled
|
||||
*/
|
||||
public boolean tryDeliverCaptive(
|
||||
ServerPlayer hunter,
|
||||
ServerPlayer client,
|
||||
ServerPlayer target
|
||||
) {
|
||||
boolean delivered = false;
|
||||
|
||||
Iterator<Bounty> it = bounties.iterator();
|
||||
while (it.hasNext()) {
|
||||
Bounty bounty = it.next();
|
||||
|
||||
// Skip expired
|
||||
if (bounty.isExpired()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if this bounty matches
|
||||
if (bounty.matches(client.getUUID(), target.getUUID())) {
|
||||
it.remove();
|
||||
setDirty();
|
||||
|
||||
// Give reward to hunter
|
||||
giveReward(hunter, bounty);
|
||||
delivered = true;
|
||||
|
||||
broadcastMessage(
|
||||
hunter.server,
|
||||
hunter.getName().getString() +
|
||||
" delivered " +
|
||||
target.getName().getString() +
|
||||
" to " +
|
||||
client.getName().getString() +
|
||||
" for " +
|
||||
bounty.getRewardDescription() +
|
||||
"!"
|
||||
);
|
||||
|
||||
TiedUpMod.LOGGER.info(
|
||||
"[BOUNTY] Delivered: {} brought {} to {}",
|
||||
hunter.getName().getString(),
|
||||
target.getName().getString(),
|
||||
client.getName().getString()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return delivered;
|
||||
}
|
||||
|
||||
// ==================== EXPIRATION ====================
|
||||
|
||||
/**
|
||||
* Handle bounty expiration.
|
||||
* Returns reward to client if online, otherwise stores for later.
|
||||
*/
|
||||
private void onBountyExpired(ServerLevel level, Bounty bounty) {
|
||||
ServerPlayer client = level
|
||||
.getServer()
|
||||
.getPlayerList()
|
||||
.getPlayer(bounty.getClientId());
|
||||
|
||||
if (client != null) {
|
||||
// Client is online - return reward
|
||||
giveReward(client, bounty);
|
||||
SystemMessageManager.sendChatToPlayer(
|
||||
client,
|
||||
"Your bounty on " +
|
||||
bounty.getTargetName() +
|
||||
" has expired. Reward returned.",
|
||||
ChatFormatting.YELLOW
|
||||
);
|
||||
} else {
|
||||
// Client is offline - store for later
|
||||
pendingRewards.add(bounty);
|
||||
setDirty();
|
||||
}
|
||||
|
||||
TiedUpMod.LOGGER.info(
|
||||
"[BOUNTY] Expired: {} on {}",
|
||||
bounty.getClientName(),
|
||||
bounty.getTargetName()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for pending rewards when a player joins.
|
||||
*/
|
||||
public void onPlayerJoin(ServerPlayer player) {
|
||||
Iterator<Bounty> it = pendingRewards.iterator();
|
||||
while (it.hasNext()) {
|
||||
Bounty bounty = it.next();
|
||||
if (bounty.isClient(player.getUUID())) {
|
||||
giveReward(player, bounty);
|
||||
it.remove();
|
||||
setDirty();
|
||||
|
||||
SystemMessageManager.sendChatToPlayer(
|
||||
player,
|
||||
"Your expired bounty reward has been returned: " +
|
||||
bounty.getRewardDescription(),
|
||||
ChatFormatting.YELLOW
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== VALIDATION ====================
|
||||
|
||||
/**
|
||||
* Check if a player can create a new bounty.
|
||||
*/
|
||||
public boolean canCreateBounty(ServerPlayer player, ServerLevel level) {
|
||||
if (player.hasPermissions(2)) {
|
||||
return true; // Admins bypass limit
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
for (Bounty bounty : bounties) {
|
||||
if (bounty.isClient(player.getUUID())) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
int max = SettingsAccessor.getMaxBounties(level.getGameRules());
|
||||
return count < max;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of active bounties for a player.
|
||||
*/
|
||||
public int getBountyCount(UUID playerId) {
|
||||
int count = 0;
|
||||
for (Bounty bounty : bounties) {
|
||||
if (bounty.isClient(playerId)) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
// ==================== HELPERS ====================
|
||||
|
||||
private void giveReward(ServerPlayer player, Bounty bounty) {
|
||||
ItemStack reward = bounty.getReward();
|
||||
if (!reward.isEmpty()) {
|
||||
if (!player.getInventory().add(reward)) {
|
||||
// Inventory full - drop at feet
|
||||
player.drop(reward, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void broadcastMessage(MinecraftServer server, String message) {
|
||||
server
|
||||
.getPlayerList()
|
||||
.broadcastSystemMessage(
|
||||
Component.literal("[Bounty] " + message).withStyle(
|
||||
ChatFormatting.GOLD
|
||||
),
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a pending reward has been waiting too long (>30 days).
|
||||
* Uses the bounty's original expiration time as baseline.
|
||||
*/
|
||||
private static boolean isPendingRewardExpired(Bounty bounty) {
|
||||
// Calculate when the bounty originally expired
|
||||
// creationTime is in milliseconds, durationSeconds needs conversion
|
||||
long expirationTime =
|
||||
bounty.getCreationTime() + (bounty.getDurationSeconds() * 1000L);
|
||||
long now = System.currentTimeMillis();
|
||||
|
||||
// Check if it's been more than 30 days since expiration
|
||||
return (now - expirationTime) > PENDING_REWARD_EXPIRATION_MS;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user