Skip to content

Commit 6279358

Browse files
committed
報酬配布機能追加
1 parent c00b83a commit 6279358

File tree

8 files changed

+329
-3
lines changed

8 files changed

+329
-3
lines changed

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
<groupId>dev.felnull</groupId>
88
<artifactId>Pointed</artifactId>
9-
<version>2.2.0-SNAPSHOT</version>
9+
<version>2.2.1-SNAPSHOT</version>
1010
<packaging>jar</packaging>
1111

1212
<name>Pointed</name>

src/main/java/dev/felnull/pointed/Pointed.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ public void onDisable() {
7676
public void setupCommand(){
7777
getCommand("pt").setExecutor(new PtCommand(pointService, zoneId));
7878
getCommand("ptteam").setExecutor(new PtTeam());
79+
getCommand("reward").setExecutor(new RewardCommand(getRewardAdminService()));
7980
}
8081
public void setupListener(){
8182
Bukkit.getPluginManager().registerEvents(new ChatListener(this), this);
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package dev.felnull.pointed.core.commands;
2+
3+
import dev.felnull.pointed.Pointed;
4+
import dev.felnull.pointed.core.util.Util;
5+
import dev.felnull.pointed.teams.manager.reward.RewardAdminService;
6+
import org.bukkit.Bukkit;
7+
import org.bukkit.command.Command;
8+
import org.bukkit.command.CommandExecutor;
9+
import org.bukkit.command.CommandSender;
10+
import org.bukkit.entity.Player;
11+
12+
import java.sql.SQLException;
13+
14+
public class RewardCommand implements CommandExecutor {
15+
private final RewardAdminService admin;
16+
17+
public RewardCommand(RewardAdminService admin) {
18+
this.admin = admin;
19+
}
20+
21+
@Override
22+
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
23+
if (!(sender instanceof Player p)) { sender.sendMessage("Player only"); return true; }
24+
if(!(Pointed.getInstance().getConfig().getBoolean("reward", false))){
25+
sender.sendMessage("ロビー鯖でのみ有効です");
26+
return true;
27+
}
28+
if (args.length == 1 && args[0].equalsIgnoreCase("claim")) {
29+
Bukkit.getScheduler().runTaskAsynchronously(Pointed.getInstance(), () -> {
30+
try {
31+
int[] res = admin.claimPendingRewards(p.getUniqueId());
32+
int delivered = res[0], remaining = res[1];
33+
Bukkit.getScheduler().runTask(Pointed.getInstance(), () -> {
34+
if (delivered == 0 && remaining == 0) {
35+
p.sendMessage(Util.f("&7受け取れる報酬はありません。"));
36+
} else {
37+
if (delivered > 0) p.sendMessage(Util.f("&a{0}件受け取り完了。", delivered));
38+
if (remaining > 0) p.sendMessage(Util.f("&e空き不足で {0} 件保留です。", remaining));
39+
}
40+
});
41+
} catch (SQLException e) {
42+
Bukkit.getScheduler().runTask(Pointed.getInstance(), () ->
43+
p.sendMessage(Util.f("&cエラー: {0}", e.getMessage())));
44+
}
45+
});
46+
return true;
47+
}
48+
// ヘルプなど
49+
sender.sendMessage("/reward claim");
50+
return true;
51+
}
52+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package dev.felnull.pointed.core.listener;
2+
3+
import dev.felnull.pointed.Pointed;
4+
import dev.felnull.pointed.core.util.Util;
5+
import dev.felnull.pointed.teams.manager.reward.RewardAdminService;
6+
import org.bukkit.Bukkit;
7+
import org.bukkit.event.EventHandler;
8+
import org.bukkit.event.Listener;
9+
import org.bukkit.event.player.PlayerJoinEvent;
10+
11+
import java.sql.SQLException;
12+
import java.util.UUID;
13+
14+
public class RewardPendingAutoDeliverListener implements Listener {
15+
private final RewardAdminService admin;
16+
17+
public RewardPendingAutoDeliverListener(RewardAdminService admin) {
18+
this.admin = admin;
19+
}
20+
21+
@EventHandler
22+
public void onJoin(PlayerJoinEvent e) {
23+
if(!(Pointed.getInstance().getConfig().getBoolean("reward", false))){
24+
return;
25+
}
26+
UUID uuid = e.getPlayer().getUniqueId();
27+
Bukkit.getScheduler().runTaskAsynchronously(Pointed.getInstance(), () -> {
28+
try {
29+
int[] res = admin.claimPendingRewards(uuid);
30+
int delivered = res[0], remaining = res[1];
31+
if (delivered > 0 || remaining > 0) {
32+
Bukkit.getScheduler().runTask(Pointed.getInstance(), () -> {
33+
if (delivered > 0) e.getPlayer().sendMessage(Util.f("&a{0}件の報酬を受け取りました。", delivered));
34+
if (remaining > 0) e.getPlayer().sendMessage(Util.f("&eインベントリの空きが足りません(保留 {0} 件)。空けて /reward claim を実行してください。", remaining));
35+
});
36+
}
37+
} catch (SQLException ex) {
38+
Bukkit.getLogger().warning("[Pointed] claim on login failed: " + ex.getMessage());
39+
}
40+
});
41+
}
42+
}

src/main/java/dev/felnull/pointed/teams/database/TeamTableInitializer.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ public static void initTables() {
4040
" id BIGINT AUTO_INCREMENT PRIMARY KEY" +
4141
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
4242

43+
stmt.executeUpdate("CREATE TABLE IF NOT EXISTS " + Names.t("reward_pending_queue") +
44+
" (id BIGINT AUTO_INCREMENT PRIMARY KEY) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
45+
4346

4447
// ========== 2) 不足カラムを追加 ==========
4548
// team_meta
@@ -71,6 +74,19 @@ public static void initTables() {
7174
"TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP");
7275
addColumnIfNotExists(conn, "rewards", "updated_at",
7376
"TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP");
77+
addColumnIfNotExists(conn, "rewards", "required_slots", "INT NOT NULL DEFAULT 0");
78+
79+
addColumnIfNotExists(conn, "reward_pending_queue", "team_subject_id", "BIGINT NULL");
80+
addColumnIfNotExists(conn, "reward_pending_queue", "scope", "VARCHAR(64) NOT NULL");
81+
addColumnIfNotExists(conn, "reward_pending_queue", "from_date", "DATE NOT NULL");
82+
addColumnIfNotExists(conn, "reward_pending_queue", "to_date", "DATE NOT NULL");
83+
addColumnIfNotExists(conn, "reward_pending_queue", "player_subject_id", "BIGINT NOT NULL");
84+
addColumnIfNotExists(conn, "reward_pending_queue", "reward_id", "INT NULL");
85+
addColumnIfNotExists(conn, "reward_pending_queue", "raw_commands", "TEXT NOT NULL");
86+
addColumnIfNotExists(conn, "reward_pending_queue", "queued_at",
87+
"TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP");
88+
addColumnIfNotExists(conn, "reward_pending_queue", "attempts", "INT NOT NULL DEFAULT 0");
89+
addColumnIfNotExists(conn, "reward_pending_queue", "last_error", "TEXT NULL");
7490

7591
// ========== 3) インデックス・制約 ==========
7692

@@ -122,6 +138,15 @@ public static void initTables() {
122138

123139
ensureIndex(conn, "rewards", "idx_rewards_updated_at", new String[]{"updated_at"});
124140

141+
ensureForeignKey(conn, "reward_pending_queue", "fk_rpq_team",
142+
"team_subject_id", "subjects", "id", "SET NULL", "CASCADE");
143+
ensureForeignKey(conn, "reward_pending_queue", "fk_rpq_player",
144+
"player_subject_id", "subjects", "id", "CASCADE", "CASCADE");
145+
ensureForeignKey(conn, "reward_pending_queue", "fk_rpq_reward",
146+
"reward_id", "rewards", "id", "SET NULL", "CASCADE");
147+
148+
ensureIndex(conn, "reward_pending_queue", "idx_rpq_player", new String[]{"player_subject_id"});
149+
ensureIndex(conn, "reward_pending_queue", "idx_rpq_scope_range", new String[]{"scope","from_date","to_date"});
125150

126151
LOGGER.info("[Pointed] テーブル初期化完了!");
127152

src/main/java/dev/felnull/pointed/teams/manager/reward/RewardAdminService.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,18 @@ void dispatchByBuckets(long teamSubjectId, String scope,
5555
RankRangeType rangeType, LocalDate fromDate, LocalDate toDate,
5656
List<RankBucket> buckets) throws SQLException;
5757

58-
// プレイヤー/チームを直接指定して手動配布(scope=manual固定/期間なし/繰り返しOK)
58+
// プレイヤーを直接指定して手動配布(scope=manual固定/期間なし/繰り返しOK)
5959
void dispatchManual(UUID playerUuid, String playerName, int rewardId) throws SQLException;
6060

61+
/** チーム全員を配布キューに積む(受け取りはログイン時 or /reward claim) */
62+
void enqueueToAllTeamMembers(long teamSubjectId, String scope,
63+
LocalDate fromDate, LocalDate toDate,
64+
int rewardId) throws SQLException;
65+
66+
/** プレイヤーが保留中の報酬を受け取る(インベントリ空きは rewards.required_slots で判定) */
67+
int[] claimPendingRewards(UUID playerUuid) throws SQLException;
68+
69+
6170
/**
6271
* 報酬一覧(ページング・検索・並び替え)。
6372
* @param active null=全て / true=アクティブのみ / false=非アクティブのみ

src/main/java/dev/felnull/pointed/teams/manager/reward/impl/RewardAdminServiceImpl.java

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
import dev.felnull.pointed.teams.manager.reward.RewardAdminService;
88
import dev.felnull.pointed.teams.manager.reward.data.*;
99
import org.bukkit.Bukkit;
10+
import org.bukkit.Material;
11+
import org.bukkit.entity.Player;
12+
import org.bukkit.inventory.ItemStack;
13+
import org.bukkit.inventory.PlayerInventory;
1014

1115
import javax.sql.DataSource;
1216
import java.sql.*;
@@ -830,4 +834,196 @@ public boolean hasEverDistributedRewardToPlayer(long playerSubjectId, int reward
830834
}
831835
}
832836

837+
// ========== 4-1) 一括キュー投入 ==========
838+
@Override
839+
public void enqueueToAllTeamMembers(long teamSubjectId, String scope,
840+
LocalDate fromDate, LocalDate toDate,
841+
int rewardId) throws SQLException {
842+
List<String> cmds = findEnabledCommandsByRewardId(rewardId);
843+
if (cmds.isEmpty()) return;
844+
String raw = String.join("\n", cmds);
845+
846+
final String qMembers =
847+
"SELECT tm.player_subject_id " +
848+
"FROM " + Names.t("team_members") + " tm " +
849+
"WHERE tm.team_subject_id = ?";
850+
851+
final String insQ =
852+
"INSERT INTO " + Names.t("reward_pending_queue") +
853+
" (team_subject_id, scope, from_date, to_date, player_subject_id, reward_id, raw_commands) " +
854+
" VALUES (?, ?, ?, ?, ?, ?, ?)";
855+
856+
try (Connection c = ds.getConnection()) {
857+
c.setAutoCommit(false);
858+
try (PreparedStatement psM = c.prepareStatement(qMembers);
859+
PreparedStatement psQ = c.prepareStatement(insQ)) {
860+
psM.setLong(1, teamSubjectId);
861+
try (ResultSet rs = psM.executeQuery()) {
862+
while (rs.next()) {
863+
int i = 1;
864+
psQ.setLong(i++, teamSubjectId);
865+
psQ.setString(i++, scope);
866+
psQ.setDate(i++, java.sql.Date.valueOf(fromDate));
867+
psQ.setDate(i++, java.sql.Date.valueOf(toDate));
868+
psQ.setLong(i++, rs.getLong(1)); // player_subject_id
869+
psQ.setInt(i++, rewardId);
870+
psQ.setString(i++, raw);
871+
psQ.addBatch();
872+
}
873+
}
874+
psQ.executeBatch();
875+
c.commit();
876+
} catch (SQLException e) { c.rollback(); throw e; }
877+
finally { c.setAutoCommit(true); }
878+
}
879+
}
880+
881+
// ========== 4-2) 受け取り(ログイン時/コマンド) ==========
882+
@Override
883+
public int[] claimPendingRewards(UUID playerUuid) throws SQLException {
884+
Player p = Bukkit.getPlayer(playerUuid);
885+
if (p == null || !p.isOnline()) return new int[]{0, countPendingByUuid(playerUuid)};
886+
887+
try (Connection c = ds.getConnection()) {
888+
Long sid = findPlayerSubjectId(c, playerUuid.toString());
889+
if (sid == null) return new int[]{0, 0};
890+
return tryDeliverPendingForPlayerInternal(c, p, sid);
891+
}
892+
}
893+
894+
// ===== 内部:pending を読み / 実行 / ログ / 削除 =====
895+
896+
private record PendingRow(long id, Long teamId, String scope, LocalDate from, LocalDate to, Integer rewardId,
897+
String rawCommands, int requiredSlots) {
898+
}
899+
900+
private int[] tryDeliverPendingForPlayerInternal(Connection c, Player p, long playerSubjectId) throws SQLException {
901+
c.setAutoCommit(false);
902+
int delivered = 0, remaining = 0;
903+
try {
904+
List<PendingRow> rows = fetchPendingRows(c, playerSubjectId);
905+
906+
for (PendingRow r : rows) {
907+
if (!hasFreeSlots(p.getInventory(), r.requiredSlots)) {
908+
remaining++;
909+
continue;
910+
}
911+
// 実行 → ログ → キュー削除
912+
String executed = runCommandsNow(p, r.rawCommands);
913+
insertDispatchLog(c, r, playerSubjectId, executed);
914+
deleteQueueRow(c, r.id);
915+
delivered++;
916+
}
917+
918+
c.commit();
919+
} catch (SQLException e) {
920+
c.rollback();
921+
throw e;
922+
} finally {
923+
c.setAutoCommit(true);
924+
}
925+
return new int[]{delivered, remaining};
926+
}
927+
928+
private List<PendingRow> fetchPendingRows(Connection c, long playerSubjectId) throws SQLException {
929+
String q =
930+
"SELECT q.id, q.team_subject_id, q.scope, q.from_date, q.to_date, " +
931+
" q.reward_id, q.raw_commands, COALESCE(r.required_slots, 0) AS required_slots " +
932+
"FROM " + Names.t("reward_pending_queue") + " q " +
933+
"LEFT JOIN " + Names.t("rewards") + " r ON r.id = q.reward_id " +
934+
"WHERE q.player_subject_id=? " +
935+
"ORDER BY q.id ASC";
936+
List<PendingRow> out = new ArrayList<>();
937+
try (PreparedStatement ps = c.prepareStatement(q)) {
938+
ps.setLong(1, playerSubjectId);
939+
try (ResultSet rs = ps.executeQuery()) {
940+
while (rs.next()) {
941+
out.add(new PendingRow(
942+
rs.getLong("id"),
943+
(Long)rs.getObject("team_subject_id"),
944+
rs.getString("scope"),
945+
rs.getDate("from_date").toLocalDate(),
946+
rs.getDate("to_date").toLocalDate(),
947+
(Integer)rs.getObject("reward_id"),
948+
rs.getString("raw_commands"),
949+
rs.getInt("required_slots")
950+
));
951+
}
952+
}
953+
}
954+
return out;
955+
}
956+
957+
private static boolean hasFreeSlots(PlayerInventory inv, int need) {
958+
if (need <= 0) return true;
959+
int empty = 0;
960+
for (ItemStack is : inv.getStorageContents()) {
961+
if (is == null || is.getType() == Material.AIR) empty++;
962+
if (empty >= need) return true;
963+
}
964+
return false;
965+
}
966+
967+
private String runCommandsNow(Player p, String rawCommands) {
968+
String playerName = p.getName();
969+
StringBuilder executed = new StringBuilder();
970+
for (String raw : rawCommands.split("\\n")) {
971+
String cmd = raw
972+
.replace("{player}", playerName)
973+
.replace("{playerId}", p.getUniqueId().toString())
974+
.replace("{rank}", "0")
975+
.replace("{points}", "0");
976+
Bukkit.dispatchCommand(Bukkit.getConsoleSender(), cmd);
977+
if (executed.length() > 0) executed.append('\n');
978+
executed.append(cmd);
979+
}
980+
return executed.toString();
981+
}
982+
983+
private void insertDispatchLog(Connection c, PendingRow r, long playerSubjectId, String executed) throws SQLException {
984+
String ins =
985+
"INSERT IGNORE INTO " + Names.t("reward_dispatch_log") +
986+
" (team_subject_id, scope, from_date, to_date, player_subject_id, rank_no, points, reward_id, executed_commands) " +
987+
" VALUES (?, ?, ?, ?, ?, 0, 0, ?, ?)";
988+
try (PreparedStatement ps = c.prepareStatement(ins)) {
989+
int i = 1;
990+
if (r.teamId == null) ps.setNull(i++, Types.BIGINT); else ps.setLong(i++, r.teamId);
991+
ps.setString(i++, r.scope);
992+
ps.setDate(i++, java.sql.Date.valueOf(r.from));
993+
ps.setDate(i++, java.sql.Date.valueOf(r.to));
994+
ps.setLong(i++, playerSubjectId);
995+
if (r.rewardId == null) ps.setNull(i++, Types.INTEGER); else ps.setInt(i++, r.rewardId);
996+
ps.setString(i++, executed);
997+
ps.executeUpdate();
998+
}
999+
}
1000+
1001+
private void deleteQueueRow(Connection c, long id) throws SQLException {
1002+
try (PreparedStatement ps = c.prepareStatement(
1003+
"DELETE FROM " + Names.t("reward_pending_queue") + " WHERE id=?")) {
1004+
ps.setLong(1, id);
1005+
ps.executeUpdate();
1006+
}
1007+
}
1008+
1009+
private int countPendingByUuid(UUID uuid) throws SQLException {
1010+
try (Connection c = ds.getConnection()) {
1011+
Long sid = findPlayerSubjectId(c, uuid.toString());
1012+
if (sid == null) return 0;
1013+
try (PreparedStatement ps = c.prepareStatement(
1014+
"SELECT COUNT(*) FROM " + Names.t("reward_pending_queue") + " WHERE player_subject_id=?")) {
1015+
ps.setLong(1, sid);
1016+
try (ResultSet rs = ps.executeQuery()) { return rs.next() ? rs.getInt(1) : 0; }
1017+
}
1018+
}
1019+
}
1020+
1021+
private Long findPlayerSubjectId(Connection c, String uuid) throws SQLException {
1022+
try (PreparedStatement ps = c.prepareStatement(
1023+
"SELECT id FROM " + Names.t("subjects") + " WHERE type='PLAYER' AND subject_key=?")) {
1024+
ps.setString(1, uuid);
1025+
try (ResultSet rs = ps.executeQuery()) { return rs.next() ? rs.getLong(1) : null; }
1026+
}
1027+
}
1028+
8331029
}

src/main/resources/config.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@ database:
44
database: "pointed"
55
user: "pointed"
66
pass: "password"
7-
table_prefix: "pointed_"
7+
table_prefix: "pointed_"
8+
reward: false

0 commit comments

Comments
 (0)