diff --git a/src/de/steamwar/sql/BannedUserIPs.java b/src/de/steamwar/sql/BannedUserIPs.java new file mode 100644 index 0000000..9a31099 --- /dev/null +++ b/src/de/steamwar/sql/BannedUserIPs.java @@ -0,0 +1,66 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2023 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.sql; + +import de.steamwar.sql.internal.Field; +import de.steamwar.sql.internal.SelectStatement; +import de.steamwar.sql.internal.Statement; +import de.steamwar.sql.internal.Table; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.sql.Timestamp; +import java.util.List; + +@AllArgsConstructor +public class BannedUserIPs { + + private static final Table table = new Table<>(BannedUserIPs.class); + + private static final SelectStatement getByID = table.selectFields("UserID"); + private static final SelectStatement getByIP = new SelectStatement<>(table, "SELECT * FROM BannedUserIPs WHERE IP = ? ORDER BY Timestamp DESC"); + private static final Statement banIP = table.insert(Table.PRIMARY); + private static final Statement unbanIPs = table.deleteFields("UserID"); + + @Getter + @Field(keys = {Table.PRIMARY}) + private final int userID; + @Getter + @Field(def = "CURRENT_TIMESTAMP") + private final Timestamp timestamp; + @Field(keys = {Table.PRIMARY}) + private final String ip; + + public static List get(int userID) { + return getByID.listSelect(userID); + } + + public static List get(String ip) { + return getByIP.listSelect(ip); + } + + public static void banIP(int userID, String ip){ + banIP.update(userID, ip); + } + + public static void unbanIPs(int userID) { + unbanIPs.update(userID); + } +} diff --git a/src/de/steamwar/sql/BauweltMember.java b/src/de/steamwar/sql/BauweltMember.java index aa3ed93..87393e8 100644 --- a/src/de/steamwar/sql/BauweltMember.java +++ b/src/de/steamwar/sql/BauweltMember.java @@ -21,6 +21,7 @@ package de.steamwar.sql; import de.steamwar.sql.internal.Field; import de.steamwar.sql.internal.SelectStatement; +import de.steamwar.sql.internal.Statement; import de.steamwar.sql.internal.Table; import lombok.Getter; @@ -36,6 +37,12 @@ public class BauweltMember { private static final Table table = new Table<>(BauweltMember.class); private static final SelectStatement getMember = table.select(Table.PRIMARY); private static final SelectStatement getMembers = table.selectFields("BauweltID"); + private static final Statement update = table.insertAll(); + private static final Statement delete = table.delete(Table.PRIMARY); + + public static void addMember(UUID ownerID, UUID memberID) { + new BauweltMember(SteamwarUser.get(ownerID).getId(), SteamwarUser.get(memberID).getId(), false, false).updateDB(); + } public static BauweltMember getBauMember(UUID ownerID, UUID memberID){ return getBauMember(SteamwarUser.get(ownerID).getId(), SteamwarUser.get(memberID).getId()); @@ -43,7 +50,7 @@ public class BauweltMember { public static BauweltMember getBauMember(int ownerID, int memberID){ BauweltMember member = memberCache.get(memberID); - if(member != null) + if(member != null && member.bauweltID == ownerID) return member; return getMember.select(ownerID, memberID); } @@ -63,11 +70,11 @@ public class BauweltMember { @Field(keys = {Table.PRIMARY}) private final int memberID; @Getter - @Field - private final boolean worldEdit; + @Field(def = "0") + private boolean worldEdit; @Getter - @Field - private final boolean world; + @Field(def = "0") + private boolean world; public BauweltMember(int bauweltID, int memberID, boolean worldEdit, boolean world) { this.bauweltID = bauweltID; @@ -76,4 +83,22 @@ public class BauweltMember { this.world = world; memberCache.put(memberID, this); } + + public void setWorldEdit(boolean worldEdit) { + this.worldEdit = worldEdit; + updateDB(); + } + + public void setWorld(boolean world) { + this.world = world; + updateDB(); + } + + private void updateDB(){ + update.update(bauweltID, memberID, worldEdit, world); + } + + public void remove(){ + delete.update(bauweltID, memberID); + } } diff --git a/src/de/steamwar/sql/CheckedSchematic.java b/src/de/steamwar/sql/CheckedSchematic.java index 71fe24a..e173d41 100644 --- a/src/de/steamwar/sql/CheckedSchematic.java +++ b/src/de/steamwar/sql/CheckedSchematic.java @@ -21,6 +21,7 @@ package de.steamwar.sql; import de.steamwar.sql.internal.Field; import de.steamwar.sql.internal.SelectStatement; +import de.steamwar.sql.internal.Statement; import de.steamwar.sql.internal.Table; import lombok.AllArgsConstructor; import lombok.Getter; @@ -33,11 +34,25 @@ public class CheckedSchematic { private static final Table table = new Table<>(CheckedSchematic.class); private static final SelectStatement statusOfNode = new SelectStatement<>(table, "SELECT * FROM CheckedSchematic WHERE NodeId = ? AND DeclineReason != 'Prüfvorgang abgebrochen' ORDER BY EndTime DESC"); + private static final SelectStatement nodeHistory = new SelectStatement<>(table, "SELECT * FROM CheckedSchematic WHERE NodeId = ? AND DeclineReason != '' AND DeclineReason != 'Prüfvorgang abgebrochen' ORDER BY EndTime DESC"); + private static final Statement insert = table.insertAll(); + + public static void create(int nodeId, String name, int owner, int validator, Timestamp startTime, Timestamp endTime, String reason){ + insert.update(nodeId, owner, name, validator, startTime, endTime, reason); + } + + public static void create(SchematicNode node, int validator, Timestamp startTime, Timestamp endTime, String reason){ + create(node.getId(), node.getName(), node.getOwner(), validator, startTime, endTime, reason); + } public static List getLastDeclinedOfNode(int node){ return statusOfNode.listSelect(node); } + public static List previousChecks(SchematicNode node) { + return nodeHistory.listSelect(node.getId()); + } + @Field(nullable = true) private final Integer nodeId; @Field diff --git a/src/de/steamwar/sql/Event.java b/src/de/steamwar/sql/Event.java index a4c30e6..7985232 100644 --- a/src/de/steamwar/sql/Event.java +++ b/src/de/steamwar/sql/Event.java @@ -26,17 +26,41 @@ import lombok.AllArgsConstructor; import lombok.Getter; import java.sql.Timestamp; +import java.time.Instant; +import java.util.List; @AllArgsConstructor public class Event { private static final Table table = new Table<>(Event.class); + + private static final SelectStatement byCurrent = new SelectStatement<>(table, "SELECT * FROM Event WHERE Start < now() AND End > now()"); private static final SelectStatement byId = table.select(Table.PRIMARY); + private static final SelectStatement byName = table.select("eventName"); + private static final SelectStatement byComing = new SelectStatement<>(table, "SELECT * FROM Event WHERE Start > now()"); + + private static Event current = null; + + public static Event get(){ + if(current != null && current.now()) + return current; + + current = byCurrent.select(); + return current; + } public static Event get(int eventID){ return byId.select(eventID); } + public static Event get(String eventName) { + return byName.select(eventName); + } + + public static List getComing() { + return byComing.listSelect(); + } + @Getter @Field(keys = {Table.PRIMARY}, autoincrement = true) private final int eventID; @@ -72,4 +96,9 @@ public class Event { public SchematicType getSchematicType() { return schemType; } + + private boolean now() { + Instant now = Instant.now(); + return now.isAfter(start.toInstant()) && now.isBefore(end.toInstant()); + } } diff --git a/src/de/steamwar/sql/EventFight.java b/src/de/steamwar/sql/EventFight.java index 60e4831..eed7f51 100644 --- a/src/de/steamwar/sql/EventFight.java +++ b/src/de/steamwar/sql/EventFight.java @@ -26,18 +26,41 @@ import de.steamwar.sql.internal.Table; import lombok.AllArgsConstructor; import lombok.Getter; +import java.sql.Timestamp; +import java.util.*; + +import static java.time.temporal.ChronoUnit.SECONDS; + @AllArgsConstructor -public class EventFight { +public class EventFight implements Comparable { private static final Table table = new Table<>(EventFight.class); private static final SelectStatement byId = table.select(Table.PRIMARY); + private static final SelectStatement allComing = new SelectStatement<>(table, "SELECT * FROM EventFight WHERE StartTime > now() ORDER BY StartTime ASC"); + private static final SelectStatement event = new SelectStatement<>(table, "SELECT * FROM EventFight WHERE EventID = ? ORDER BY StartTime ASC"); + private static final Statement reschedule = table.update(Table.PRIMARY, "StartTime"); private static final Statement setResult = table.update(Table.PRIMARY, "Ergebnis"); private static final Statement setFight = table.update(Table.PRIMARY, "Fight"); + private static final Queue fights = new PriorityQueue<>(); + public static EventFight get(int fightID) { return byId.select(fightID); } + public static void loadAllComingFights() { + fights.clear(); + fights.addAll(allComing.listSelect()); + } + + public static List getEvent(int eventID) { + return event.listSelect(eventID); + } + + public static Queue getFights() { + return fights; + } + @Getter @Field private final int eventID; @@ -46,6 +69,15 @@ public class EventFight { private final int fightID; @Getter @Field + private Timestamp startTime; + @Getter + @Field + private final String spielmodus; + @Getter + @Field + private final String map; + @Getter + @Field private final int teamBlue; @Getter @Field @@ -69,4 +101,32 @@ public class EventFight { this.fight = fight; setFight.update(fight, fightID); } + + public boolean hasFinished() { + return fight != 0; + } + + public void reschedule() { + startTime = Timestamp.from(new Date().toInstant().plus(30, SECONDS)); + reschedule.update(startTime, fightID); + } + + @Override + public int hashCode(){ + return fightID; + } + + @Override + public boolean equals(Object o){ + if(o == null) + return false; + if(!(o instanceof EventFight)) + return false; + return fightID == ((EventFight) o).fightID; + } + + @Override + public int compareTo(EventFight o) { + return startTime.compareTo(o.startTime); + } } diff --git a/src/de/steamwar/sql/Fight.java b/src/de/steamwar/sql/Fight.java index 0f9eae6..efa230d 100644 --- a/src/de/steamwar/sql/Fight.java +++ b/src/de/steamwar/sql/Fight.java @@ -20,24 +20,49 @@ package de.steamwar.sql; import de.steamwar.sql.internal.Field; +import de.steamwar.sql.internal.SelectStatement; import de.steamwar.sql.internal.Statement; import de.steamwar.sql.internal.Table; import lombok.AllArgsConstructor; +import lombok.Getter; import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; @AllArgsConstructor public class Fight { private static final Table table = new Table<>(Fight.class); + private static final SelectStatement getPage = new SelectStatement<>(table, "SELECT f.*, (b.NodeId IS NULL OR b.AllowReplay) AND (r.NodeId IS NULL OR r.AllowReplay) AS ReplayAllowed, (SELECT COUNT(1) FROM Replay WHERE Replay.FightID = f.FightID) as ReplayAvailable FROM Fight f LEFT OUTER JOIN SchematicNode b ON f.BlueSchem = b.NodeId LEFT OUTER JOIN SchematicNode r ON f.RedSchem = r.NodeId ORDER BY FightID DESC LIMIT ?, ?"); private static final Statement insert = table.insertFields(true, "GameMode", "Server", "StartTime", "Duration", "BlueLeader", "RedLeader", "BlueSchem", "RedSchem", "Win", "WinCondition"); + public static List getPage(int page, int elementsPerPage) { + List fights = getPage.listSelect(page * elementsPerPage, elementsPerPage); + + List fightPlayers = FightPlayer.batchGet(fights.stream().map(f -> f.fightID)); + for(Fight fight : fights) { + fight.initPlayers(fightPlayers); + } + + SteamwarUser.batchCache(fightPlayers.stream().map(FightPlayer::getUserID).collect(Collectors.toSet())); + return fights; + } + + public static int create(String gamemode, String server, Timestamp starttime, int duration, int blueleader, int redleader, Integer blueschem, Integer redschem, int win, String wincondition){ + return insert.insertGetKey(gamemode, server, starttime, duration, blueleader, redleader, blueschem, redschem, win, wincondition); + } + + @Getter @Field(keys = {Table.PRIMARY}, autoincrement = true) private final int fightID; @Field private final String gameMode; + @Getter @Field private final String server; + @Getter @Field private final Timestamp startTime; @Field @@ -50,12 +75,50 @@ public class Fight { private final Integer blueSchem; @Field(nullable = true) private final Integer redSchem; + @Getter @Field private final int win; @Field private final String wincondition; + @Field // Virtual field for easy select + private final boolean replayAllowed; + @Field // Virtual field for easy select + private final boolean replayAvailable; - public static int create(String gamemode, String server, Timestamp starttime, int duration, int blueleader, int redleader, Integer blueschem, Integer redschem, int win, String wincondition){ - return insert.insertGetKey(gamemode, server, starttime, duration, blueleader, redleader, blueschem, redschem, win, wincondition); + @Getter + private final List bluePlayers = new ArrayList<>(); + @Getter + private final List redPlayers = new ArrayList<>(); + + public SchematicType getSchemType() { + return SchematicType.fromDB(gameMode); + } + + public SteamwarUser getBlueLeader() { + return SteamwarUser.get(blueLeader); + } + + public SteamwarUser getRedLeader() { + return SteamwarUser.get(redLeader); + } + + public boolean replayAllowed() { + return replayExists() && replayAllowed; + } + + public boolean replayExists() { + return getSchemType() != null && replayAvailable; + } + + private void initPlayers(List fightPlayers) { + for(FightPlayer fp : fightPlayers) { + if(fp.getFightID() != fightID) + continue; + + if(fp.getTeam() == 1) + bluePlayers.add(fp); + else + redPlayers.add(fp); + } } } diff --git a/src/de/steamwar/sql/FightPlayer.java b/src/de/steamwar/sql/FightPlayer.java index cc529b8..57b977c 100644 --- a/src/de/steamwar/sql/FightPlayer.java +++ b/src/de/steamwar/sql/FightPlayer.java @@ -20,20 +20,30 @@ package de.steamwar.sql; import de.steamwar.sql.internal.Field; +import de.steamwar.sql.internal.SelectStatement; import de.steamwar.sql.internal.Statement; import de.steamwar.sql.internal.Table; import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; @AllArgsConstructor public class FightPlayer { private static final Table table = new Table<>(FightPlayer.class); private static final Statement create = table.insertAll(); + private static final SelectStatement batchGet = new SelectStatement<>(table, "SELECT * FROM FightPlayer WHERE FightID IN ?"); + @Getter @Field(keys = {Table.PRIMARY}) private final int fightID; + @Getter @Field(keys = {Table.PRIMARY}) private final int userID; + @Getter @Field private final int team; @Field @@ -46,4 +56,10 @@ public class FightPlayer { public static void create(int fightID, int userID, boolean blue, String kit, int kills, boolean isOut) { create.update(fightID, userID, blue ? 1 : 2, kit, kills, isOut); } + + public static List batchGet(Stream fightIds) { + try (SelectStatement batch = new SelectStatement<>(table, "SELECT * FROM FightPlayer WHERE FightID IN (" + fightIds.map(Object::toString).collect(Collectors.joining(", ")) + ")")) { + return batch.listSelect(); + } + } } diff --git a/src/de/steamwar/sql/IgnoreSystem.java b/src/de/steamwar/sql/IgnoreSystem.java new file mode 100644 index 0000000..d6ce26a --- /dev/null +++ b/src/de/steamwar/sql/IgnoreSystem.java @@ -0,0 +1,59 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2023 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.sql; + +import de.steamwar.sql.internal.Field; +import de.steamwar.sql.internal.SelectStatement; +import de.steamwar.sql.internal.Statement; +import de.steamwar.sql.internal.Table; +import lombok.AllArgsConstructor; + +import java.sql.ResultSet; +import java.util.UUID; + +@AllArgsConstructor +public class IgnoreSystem { + + private static final Table table = new Table<>(IgnoreSystem.class, "IgnoredPlayers"); + private static final SelectStatement select = table.select(Table.PRIMARY); + private static final Statement insert = table.insertAll(); + private static final Statement delete = table.delete(Table.PRIMARY); + + @Field(keys = {Table.PRIMARY}) + private final int ignorer; + @Field(keys = {Table.PRIMARY}) + private final int ignored; + + public static boolean isIgnored(UUID ignorer, UUID ignored){ + return isIgnored(SteamwarUser.get(ignorer), SteamwarUser.get(ignored)); + } + + public static boolean isIgnored(SteamwarUser ignorer, SteamwarUser ignored) { + return select.select(ResultSet::next, ignorer.getId(), ignored.getId()); + } + + public static void ignore(SteamwarUser ignorer, SteamwarUser ignored) { + insert.update(ignorer.getId(), ignored.getId()); + } + + public static void unIgnore(SteamwarUser ignorer, SteamwarUser ignored) { + delete.update(ignorer.getId(), ignored.getId()); + } +} diff --git a/src/de/steamwar/sql/Mod.java b/src/de/steamwar/sql/Mod.java new file mode 100644 index 0000000..2a4e698 --- /dev/null +++ b/src/de/steamwar/sql/Mod.java @@ -0,0 +1,101 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2023 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.sql; + +import de.steamwar.sql.internal.*; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.List; + +@AllArgsConstructor +public class Mod { + + static { + SqlTypeMapper.ordinalEnumMapper(Platform.class); + SqlTypeMapper.ordinalEnumMapper(ModType.class); + } + + private static final Table table = new Table<>(Mod.class, "Mods"); + private static final SelectStatement get = table.select(Table.PRIMARY); + private static final SelectStatement findFirst = new SelectStatement<>(table, "SELECT * FROM Mods WHERE ModType = 0 LIMIT 1"); + private static final SelectStatement getPageOfType = new SelectStatement<>(table, "SELECT * FROM Mods WHERE ModType = ? ORDER BY ModName DESC LIMIT ?, ?"); + private static final Statement insert = table.insert(Table.PRIMARY); + private static final Statement set = table.update(Table.PRIMARY, "ModType"); + + public static Mod get(String name, Platform platform) { + return get.select(platform, name); + } + + public static Mod getOrCreate(String name, Platform platform) { + Mod mod = get(name, platform); + if(mod != null) + return mod; + + insert.update(platform, name); + return new Mod(platform, name, ModType.UNKLASSIFIED); + } + + public static List getAllModsFiltered(int page, int elementsPerPage, Mod.ModType filter) { + return Mod.getPageOfType.listSelect(filter, page * elementsPerPage, elementsPerPage); + } + + public static Mod findFirstMod() { + return findFirst.select(); + } + + @Getter + @Field(keys = {Table.PRIMARY}) + private final Platform platform; + @Getter + @Field(keys = {Table.PRIMARY}) + private final String modName; + @Getter + @Field(def = "0") + private ModType modType; + + public void setModType(Mod.ModType modType) { + set.update(modType, platform, modName); + this.modType = modType; + } + + public enum Platform { + FORGE, + LABYMOD, + FABRIC + } + + public enum ModType { + UNKLASSIFIED("7"), + GREEN("a"), + YELLOW("e"), + RED("c"), + YOUTUBER_ONLY("6"); + + ModType(String colorcode) { + this.colorcode = colorcode; + } + private final String colorcode; + + public String getColorCode() { + return colorcode; + } + } +} diff --git a/src/de/steamwar/sql/PollAnswer.java b/src/de/steamwar/sql/PollAnswer.java new file mode 100644 index 0000000..933f253 --- /dev/null +++ b/src/de/steamwar/sql/PollAnswer.java @@ -0,0 +1,77 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2023 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.sql; + +import de.steamwar.sql.internal.Field; +import de.steamwar.sql.internal.SelectStatement; +import de.steamwar.sql.internal.Statement; +import de.steamwar.sql.internal.Table; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; + +import java.util.HashMap; +import java.util.Map; + +@AllArgsConstructor +public class PollAnswer { + + @Getter + @Setter + private static String currentPoll; + + private static final Table table = new Table<>(PollAnswer.class); + + private static final SelectStatement get = table.select(Table.PRIMARY); + private static final Statement getResults = new Statement("SELECT Count(UserID) AS Times, Answer FROM PollAnswer WHERE Question = ? GROUP BY Answer ORDER BY Times ASC"); + private static final Statement insert = table.insertAll(); + + @Field(keys = {Table.PRIMARY}) + private final int userID; + @Field(keys = {Table.PRIMARY}) + private final String question; + @Field(def = "0") + private int answer; + + public static PollAnswer get(int userID) { + PollAnswer answer = get.select(userID, currentPoll); + if(answer == null) + return new PollAnswer(userID, currentPoll, 0); + return answer; + } + + public static Map getCurrentResults() { + return getResults.select(rs -> { + Map retMap = new HashMap<>(); + while (rs.next()) + retMap.put(rs.getInt("Answer")-1, rs.getInt("Times")); + return retMap; + }, currentPoll); + } + + public boolean hasAnswered(){ + return answer != 0; + } + + public void setAnswer(int answer){ + this.answer = answer; + insert.update(userID, question, answer); + } +} diff --git a/src/de/steamwar/sql/Punishment.java b/src/de/steamwar/sql/Punishment.java index 086f445..5960dce 100644 --- a/src/de/steamwar/sql/Punishment.java +++ b/src/de/steamwar/sql/Punishment.java @@ -19,16 +19,16 @@ package de.steamwar.sql; -import de.steamwar.sql.internal.Field; -import de.steamwar.sql.internal.SelectStatement; -import de.steamwar.sql.internal.SqlTypeMapper; -import de.steamwar.sql.internal.Table; +import de.steamwar.sql.internal.*; import lombok.AllArgsConstructor; import lombok.Getter; +import lombok.RequiredArgsConstructor; import java.sql.Timestamp; +import java.time.Instant; import java.time.format.DateTimeFormatter; import java.util.Date; +import java.util.List; import java.util.Map; import java.util.function.Consumer; import java.util.stream.Collectors; @@ -40,13 +40,48 @@ public class Punishment { SqlTypeMapper.nameEnumMapper(PunishmentType.class); } + public static final Timestamp PERMA_TIME = Timestamp.from(Instant.ofEpochSecond(946674800)); + private static final Table table = new Table<>(Punishment.class, "Punishments"); private static final SelectStatement getPunishments = new SelectStatement<>(table, "SELECT * FROM Punishments WHERE PunishmentId IN (SELECT MAX(PunishmentId) FROM Punishments WHERE UserId = ? GROUP BY Type)"); private static final SelectStatement getPunishment = new SelectStatement<>(table, "SELECT * FROM Punishments WHERE UserId = ? AND Type = ? ORDER BY PunishmentId DESC LIMIT 1"); + private static final SelectStatement getAllPunishments = new SelectStatement<>(table, "SELECT * FROM Punishments WHERE UserId = ? ORDER BY `PunishmentId` DESC"); + private static final Statement insert = table.insertFields(true, "UserId", "Punisher", "Type", "EndTime", "Perma", "Reason"); + + public static Punishment getPunishmentOfPlayer(int user, PunishmentType type) { + return getPunishment.select(user, type); + } + + public static Map getPunishmentsOfPlayer(int user) { + return getPunishments.listSelect(user).stream().collect(Collectors.toMap(Punishment::getType, punishment -> punishment)); + } + + public static List getAllPunishmentsOfPlayer(int user) { + return getAllPunishments.listSelect(user); + } + + public static boolean isPunished(SteamwarUser user, Punishment.PunishmentType type, Consumer callback) { + Punishment punishment = Punishment.getPunishmentOfPlayer(user.getId(), type); + if(punishment == null || !punishment.isCurrent()) { + return false; + } else { + callback.accept(punishment); + return true; + } + } + + public static Punishment createPunishment(int user, int executor, PunishmentType type, String reason, Timestamp endTime, boolean perma) { + if(perma && !endTime.equals(PERMA_TIME)) { + throw new IllegalArgumentException("Permanent punishments must have an end time of `Punishment.PERMA_TIME`"); + } + int punishmentId = insert.insertGetKey(user, executor, type.name(), endTime, perma, reason); + return new Punishment(punishmentId, user, executor, type, Timestamp.from(Instant.now()), endTime, perma, reason); + } @Field(keys = {Table.PRIMARY}, autoincrement = true) private final int punishmentId; @Field + @Getter private final int userId; @Field @Getter @@ -67,24 +102,6 @@ public class Punishment { @Getter private final String reason; - public static Punishment getPunishmentOfPlayer(int user, PunishmentType type) { - return getPunishment.select(user, type); - } - - public static Map getPunishmentsOfPlayer(int user) { - return getPunishments.listSelect(user).stream().collect(Collectors.toMap(Punishment::getType, punishment -> punishment)); - } - - public static boolean isPunished(SteamwarUser user, Punishment.PunishmentType type, Consumer callback) { - Punishment punishment = Punishment.getPunishmentOfPlayer(user.getId(), type); - if(punishment == null || !punishment.isCurrent()) { - return false; - } else { - callback.accept(punishment); - return true; - } - } - @Deprecated // Not multiling, misleading title public String getBantime(Timestamp endTime, boolean perma) { if (perma) { @@ -94,15 +111,12 @@ public class Punishment { } } - public int getUserId() { - return userId; - } - public boolean isCurrent() { return isPerma() || getEndTime().after(new Date()); } @AllArgsConstructor + @RequiredArgsConstructor @Getter public enum PunishmentType { Ban(false, "BAN_TEAM", "BAN_PERMA", "BAN_UNTIL", "UNBAN_ERROR", "UNBAN"), @@ -110,7 +124,10 @@ public class Punishment { NoSchemReceiving(false, "NOSCHEMRECEIVING_TEAM", "NOSCHEMRECEIVING_PERMA", "NOSCHEMRECEIVING_UNTIL", "UNNOSCHEMRECEIVING_ERROR", "UNNOSCHEMRECEIVING"), NoSchemSharing(false, "NOSCHEMSHARING_TEAM", "NOSCHEMSHARING_PERMA", "NOSCHEMSHARING_UNTIL", "UNNOSCHEMSHARING_ERROR", "UNNOSCHEMSHARING"), NoSchemSubmitting(true, "NOSCHEMSUBMITTING_TEAM", "NOSCHEMSUBMITTING_PERMA", "NOSCHEMSUBMITTING_UNTIL", "UNNOSCHEMSUBMITTING_ERROR", "UNNOSCHEMSUBMITTING"), - NoDevServer(true, "NODEVSERVER_TEAM", "NODEVSERVER_PERMA", "NODEVSERVER_UNTIL", "UNNODEVSERVER_ERROR", "UNNODEVSERVER"); + NoDevServer(true, "NODEVSERVER_TEAM", "NODEVSERVER_PERMA", "NODEVSERVER_UNTIL", "UNNODEVSERVER_ERROR", "UNNODEVSERVER"), + NoFightServer(false, "NOFIGHTSERVER_TEAM", "NOFIGHTSERVER_PERMA", "NOFIGHTSERVER_UNTIL", "UNNOFIGHTSERVER_ERROR", "UNNOFIGHTSERVER"), + NoTeamServer(true, "NOTEAMSERVER_TEAM", "NOTEAMSERVER_PERMA", "NOTEAMSERVER_UNTIL", "UNNOTEAMSERVER_ERROR", "UNNOTEAMSERVER"), + Note(false, "NOTE_TEAM", null, null, null, null, true); private final boolean needsAdmin; private final String teamMessage; @@ -118,5 +135,6 @@ public class Punishment { private final String playerMessageUntil; private final String usageNotPunished; private final String unpunishmentMessage; + private boolean multi = false; } } diff --git a/src/de/steamwar/sql/SchemElo.java b/src/de/steamwar/sql/SchemElo.java index 64090ce..0e92cfd 100644 --- a/src/de/steamwar/sql/SchemElo.java +++ b/src/de/steamwar/sql/SchemElo.java @@ -21,14 +21,31 @@ package de.steamwar.sql; import de.steamwar.sql.internal.Field; import de.steamwar.sql.internal.SelectStatement; +import de.steamwar.sql.internal.Statement; import de.steamwar.sql.internal.Table; import lombok.AllArgsConstructor; @AllArgsConstructor public class SchemElo { + private static final int ELO_DEFAULT = 1000; private static final Table table = new Table<>(SchemElo.class); private static final SelectStatement select = table.select(Table.PRIMARY); + private static final Statement setElo = table.insertAll(); + + public static int getElo(SchematicNode node, int season) { + SchemElo elo = select.select(node, season); + return elo != null ? elo.elo : 0; + } + + public static int getCurrentElo(int schemID) { + SchemElo elo = select.select(schemID, Season.getSeason()); + return elo != null ? elo.elo : ELO_DEFAULT; + } + + public static void setElo(int schemID, int elo) { + setElo.update(schemID, elo, Season.getSeason()); + } @Field(keys = {Table.PRIMARY}) private final int schemId; @@ -36,9 +53,4 @@ public class SchemElo { private final int elo; @Field(keys = {Table.PRIMARY}) private final int season; - - public static int getElo(SchematicNode node, int season) { - SchemElo elo = select.select(node, season); - return elo != null ? elo.elo : 0; - } } diff --git a/src/de/steamwar/sql/SchematicType.java b/src/de/steamwar/sql/SchematicType.java index d87065c..43cfb44 100644 --- a/src/de/steamwar/sql/SchematicType.java +++ b/src/de/steamwar/sql/SchematicType.java @@ -20,6 +20,7 @@ package de.steamwar.sql; import de.steamwar.sql.internal.SqlTypeMapper; +import lombok.Getter; import java.util.*; @@ -51,17 +52,26 @@ public class SchematicType { } private final String name; + @Getter private final String kuerzel; private final Type type; private final SchematicType checkType; + @Getter private final String material; + @Getter + private final Date deadline; SchematicType(String name, String kuerzel, Type type, SchematicType checkType, String material){ + this(name, kuerzel, type, checkType, material, null); + } + + SchematicType(String name, String kuerzel, Type type, SchematicType checkType, String material, Date deadline){ this.name = name; this.kuerzel = kuerzel; this.type = type; this.checkType = checkType; this.material = material; + this.deadline = deadline; } public boolean isAssignable(){ @@ -88,14 +98,6 @@ public class SchematicType { return name; } - public String getKuerzel() { - return kuerzel; - } - - public String getMaterial() { - return material; - } - public String toDB(){ return name.toLowerCase(); } diff --git a/src/de/steamwar/sql/Session.java b/src/de/steamwar/sql/Session.java new file mode 100644 index 0000000..18ad2c8 --- /dev/null +++ b/src/de/steamwar/sql/Session.java @@ -0,0 +1,45 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2023 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.sql; + +import de.steamwar.sql.internal.Field; +import de.steamwar.sql.internal.Statement; +import de.steamwar.sql.internal.Table; +import lombok.AllArgsConstructor; + +import java.sql.Timestamp; + +@AllArgsConstructor +public class Session { + + private static final Table table = new Table<>(Session.class); + private static final Statement insert = table.insert(Table.PRIMARY); + + public static void insertSession(int userID, Timestamp startTime){ + insert.update(userID, startTime); + } + + @Field(keys = {Table.PRIMARY}) + private int userId; + @Field(keys = {Table.PRIMARY}) + private Timestamp startTime; + @Field(def = "CURRENT_TIMESTAMP") + private Timestamp endTime; +} diff --git a/src/de/steamwar/sql/SteamwarUser.java b/src/de/steamwar/sql/SteamwarUser.java index 874643f..7ce882d 100644 --- a/src/de/steamwar/sql/SteamwarUser.java +++ b/src/de/steamwar/sql/SteamwarUser.java @@ -22,7 +22,11 @@ package de.steamwar.sql; import de.steamwar.sql.internal.*; import lombok.Getter; +import java.sql.Timestamp; import java.util.*; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.stream.Collectors; public class SteamwarUser { @@ -44,12 +48,16 @@ public class SteamwarUser { private static final SelectStatement byDiscord = table.selectFields("DiscordId"); private static final SelectStatement byTeam = table.selectFields("Team"); private static final SelectStatement getServerTeam = new SelectStatement<>(table, "SELECT * FROM UserData WHERE UserGroup != 'Member' AND UserGroup != 'YouTuber'"); + private static final Statement updateName = table.update(Table.PRIMARY, "UserName"); private static final Statement updateLocale = table.update(Table.PRIMARY, "Locale", "ManualLocale"); private static final Statement updateTeam = table.update(Table.PRIMARY, "Team"); private static final Statement updateLeader = table.update(Table.PRIMARY, "Leader"); private static final Statement updateDiscord = table.update(Table.PRIMARY, "DiscordId"); + private static final Statement getPlaytime = new Statement("SELECT SUM(UNIX_TIMESTAMP(EndTime) - UNIX_TIMESTAMP(StartTime)) as Playtime FROM Session WHERE UserID = ?"); + private static final Statement getFirstjoin = new Statement("SELECT MIN(StartTime) AS FirstJoin FROM Session WHERE UserID = ?"); + private static final Map usersById = new HashMap<>(); private static final Map usersByUUID = new HashMap<>(); private static final Map usersByName = new HashMap<>(); @@ -69,67 +77,6 @@ public class SteamwarUser { usersByUUID.remove(user.getUUID()); } - @Getter - @Field(keys = {Table.PRIMARY}, autoincrement = true) - private final int id; - @Field(keys = {"uuid"}) - private final UUID uuid; - @Getter - @Field - private String userName; - @Getter - @Field(def = "'Member'") - private final UserGroup userGroup; - @Getter - @Field(def = "0") - private int team; - @Field(def = "0") - private boolean leader; - @Field(nullable = true) - private Locale locale; - @Field(def = "0") - private boolean manualLocale; - @Field(keys = {"discordId"}, nullable = true) - private Long discordId; - - public SteamwarUser(int id, UUID uuid, String userName, UserGroup userGroup, int team, boolean leader, Locale locale, boolean manualLocale, Long discordId) { - this.id = id; - this.uuid = uuid; - this.userName = userName; - this.userGroup = userGroup; - this.team = team; - this.leader = leader; - this.locale = locale; - this.manualLocale = manualLocale; - this.discordId = discordId != null && discordId != 0 ? discordId : null; - - usersById.put(id, this); - usersByName.put(userName.toLowerCase(), this); - usersByUUID.put(uuid, this); - if (this.discordId != null) { - usersByDiscord.put(discordId, this); - } - } - - public UUID getUUID() { - return uuid; - } - - public Locale getLocale() { - if(locale != null) - return locale; - return Locale.getDefault(); - } - - public void setLocale(Locale locale, boolean manualLocale) { - if (locale == null || (this.manualLocale && !manualLocale)) - return; - - this.locale = locale; - this.manualLocale = manualLocale; - updateLocale.update(locale.toLanguageTag(), manualLocale, id); - } - public static SteamwarUser get(String userName){ SteamwarUser user = usersByName.get(userName.toLowerCase()); if(user != null) @@ -157,8 +104,22 @@ public class SteamwarUser { return byDiscord.select(discordId); } - public static void createOrUpdateUsername(UUID uuid, String userName) { - insert.update(uuid, userName); + public static SteamwarUser getOrCreate(UUID uuid, String name, Consumer newPlayer, BiConsumer nameUpdate) { + SteamwarUser user = SteamwarUser.get(uuid); + + if (user != null) { + if (!user.userName.equals(name)) { + updateName.update(name, user.id); + nameUpdate.accept(user.userName, name); + user.userName = name; + } + } else { + insert.update(uuid, name); + newPlayer.accept(uuid); + return get(uuid); + } + + return user; } public static List getServerTeam() { @@ -168,4 +129,139 @@ public class SteamwarUser { public static List getTeam(int teamId) { return byTeam.listSelect(teamId); } + + public static void batchCache(Set ids) { + ids.removeIf(usersById::containsKey); + if(ids.isEmpty()) + return; + + try (SelectStatement batch = new SelectStatement<>(table, "SELECT * FROM UserData WHERE id IN (" + ids.stream().map(Object::toString).collect(Collectors.joining(", ")) + ")")) { + batch.listSelect(); + } + } + + @Getter + @Field(keys = {Table.PRIMARY}, autoincrement = true) + private final int id; + @Field(keys = {"uuid"}) + private final UUID uuid; + @Getter + @Field + private String userName; + @Getter + @Field(def = "'Member'") + private final UserGroup userGroup; + @Getter + @Field(def = "0") + private int team; + @Getter + @Field(def = "0") + private boolean leader; + @Field(nullable = true) + private Locale locale; + @Field(def = "0") + private boolean manualLocale; + @Getter + @Field(keys = {"discordId"}, nullable = true) + private Long discordId; + + private final Map punishments; + + public SteamwarUser(int id, UUID uuid, String userName, UserGroup userGroup, int team, boolean leader, Locale locale, boolean manualLocale, Long discordId) { + this.id = id; + this.uuid = uuid; + this.userName = userName; + this.userGroup = userGroup; + this.team = team; + this.leader = leader; + this.locale = locale; + this.manualLocale = manualLocale; + this.discordId = discordId != null && discordId != 0 ? discordId : null; + + usersById.put(id, this); + usersByName.put(userName.toLowerCase(), this); + usersByUUID.put(uuid, this); + if (this.discordId != null) { + usersByDiscord.put(discordId, this); + } + punishments = Punishment.getPunishmentsOfPlayer(id); //TODO load always on Subservers? + } + + public UUID getUUID() { + return uuid; + } + + public Locale getLocale() { + if(locale != null) + return locale; + return Locale.getDefault(); + } + + public Punishment getPunishment(Punishment.PunishmentType type) { + return punishments.getOrDefault(type, null); + } + + public boolean isPunished(Punishment.PunishmentType punishment) { + if (!punishments.containsKey(punishment)) { + return false; + } + if (!punishments.get(punishment).isCurrent()) { + if (punishment == Punishment.PunishmentType.Ban) { + BannedUserIPs.unbanIPs(id); + } + punishments.remove(punishment); + return false; + } + return true; + } + + public double getOnlinetime() { + return getPlaytime.select(rs -> { + if (rs.next() && rs.getBigDecimal("Playtime") != null) + return rs.getBigDecimal("Playtime").doubleValue(); + return 0.0; + }, id); + } + + public Timestamp getFirstjoin() { + return getFirstjoin.select(rs -> { + if (rs.next()) + return rs.getTimestamp("FirstJoin"); + return null; + }, id); + } + + public void punish(Punishment.PunishmentType punishment, Timestamp time, String banReason, int from, boolean perma) { + punishments.remove(punishment); + punishments.put(punishment, Punishment.createPunishment(id, from, punishment, banReason, time, perma)); + } + + public void setTeam(int team) { + this.team = team; + updateTeam.update(team, id); + setLeader(false); + } + + public void setLeader(boolean leader) { + this.leader = leader; + updateLeader.update(leader, id); + } + + public void setLocale(Locale locale, boolean manualLocale) { + if (locale == null || (this.manualLocale && !manualLocale)) + return; + + this.locale = locale; + this.manualLocale = manualLocale; + updateLocale.update(locale.toLanguageTag(), manualLocale, id); + } + + public void setDiscordId(Long discordId) { + usersByDiscord.remove(this.discordId); + this.discordId = discordId; + updateDiscord.update(discordId, id); + if (discordId != null) { + usersByDiscord.put(discordId, this); + } + } } diff --git a/src/de/steamwar/sql/Team.java b/src/de/steamwar/sql/Team.java index 10e6892..8765ad9 100644 --- a/src/de/steamwar/sql/Team.java +++ b/src/de/steamwar/sql/Team.java @@ -21,41 +21,119 @@ package de.steamwar.sql; import de.steamwar.sql.internal.Field; import de.steamwar.sql.internal.SelectStatement; +import de.steamwar.sql.internal.Statement; import de.steamwar.sql.internal.Table; import lombok.AllArgsConstructor; import lombok.Getter; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; @AllArgsConstructor public class Team { - private static final Table table = new Table<>(Team.class); - private static final SelectStatement select = table.select(Table.PRIMARY); + private static final Map teamCache = new HashMap<>(); - @Field(keys = {Table.PRIMARY}) + public static void clear() { + teamCache.clear(); + } + + private static final Table table = new Table<>(Team.class); + private static final SelectStatement byId = table.select(Table.PRIMARY); + private static final SelectStatement byName = new SelectStatement<>(table, "SELECT * FROM Team WHERE (lower(TeamName) = ? OR lower(TeamKuerzel) = ?) AND NOT TeamDeleted"); + private static final SelectStatement all = table.selectFields("TeamDeleted"); + private static final Statement insert = table.insertFields("TeamKuerzel", "TeamName"); + private static final Statement update = table.update(Table.PRIMARY, "TeamKuerzel", "TeamName", "TeamColor", "Address", "Port"); + private static final Statement delete = table.update(Table.PRIMARY, "TeamDeleted"); + private static final Statement getSize = new Statement("SELECT COUNT(id) FROM UserData WHERE Team = ?"); + + @Field(keys = {Table.PRIMARY}, autoincrement = true) @Getter private final int teamId; @Field @Getter - private final String teamKuerzel; + private String teamKuerzel; @Field @Getter - private final String teamName; + private String teamName; @Field(def = "'8'") @Getter - private final String teamColor; + private String teamColor; + @Field(nullable = true) + @Getter + private String address; + @Field(def = "'25565'") + @Getter + private int port; + @Field(def = "0") + private boolean teamDeleted; - private static final Team pub = new Team(0, "PUB", "Öffentlich", "8"); + public static void create(String kuerzel, String name){ + insert.update(kuerzel, name); + } public static Team get(int id) { - if(id == 0) - return pub; - return select.select(id); + return teamCache.computeIfAbsent(id, byId::select); + } + + public static Team get(String name){ + // No cache lookup due to low frequency use + return byName.select(name, name); + } + + public static List getAll(){ + clear(); + List teams = all.listSelect(false); + teams.forEach(team -> teamCache.put(team.getTeamId(), team)); + return teams; } public List getMembers(){ return SteamwarUser.getTeam(teamId).stream().map(SteamwarUser::getId).collect(Collectors.toList()); } + + public int size(){ + return getSize.select(rs -> { + rs.next(); + return rs.getInt("COUNT(id)"); + }, teamId); + } + + public void disband(SteamwarUser user){ + user.setLeader(false); + delete.update(true, teamId); + teamCache.remove(teamId); + TeamTeilnahme.deleteFuture(teamId); + } + + public void setTeamKuerzel(String teamKuerzel) { + this.teamKuerzel = teamKuerzel; + updateDB(); + } + + public void setTeamName(String teamName) { + this.teamName = teamName; + updateDB(); + } + + public void setTeamColor(String teamColor) { + this.teamColor = teamColor; + updateDB(); + } + + public void setAddress(String address) { + this.address = address; + updateDB(); + } + + public void setPort(int port) { + this.port = port; + updateDB(); + } + + private void updateDB(){ + update.update(teamKuerzel, teamName, teamColor, address, port, teamId); + } } diff --git a/src/de/steamwar/sql/TeamTeilnahme.java b/src/de/steamwar/sql/TeamTeilnahme.java index 533b830..f1484da 100644 --- a/src/de/steamwar/sql/TeamTeilnahme.java +++ b/src/de/steamwar/sql/TeamTeilnahme.java @@ -21,6 +21,7 @@ package de.steamwar.sql; import de.steamwar.sql.internal.Field; import de.steamwar.sql.internal.SelectStatement; +import de.steamwar.sql.internal.Statement; import de.steamwar.sql.internal.Table; import lombok.AllArgsConstructor; @@ -34,6 +35,9 @@ public class TeamTeilnahme { private static final SelectStatement select = table.select(Table.PRIMARY); private static final SelectStatement selectTeams = table.selectFields("EventID"); private static final SelectStatement selectEvents = table.selectFields("TeamID"); + private static final Statement insert = table.insert(Table.PRIMARY); + private static final Statement delete = table.delete(Table.PRIMARY); + private static final Statement deleteFuture = new Statement("DELETE t FROM TeamTeilnahme t INNER JOIN Event e ON t.EventID = e.EventID WHERE t.TeamID = ? AND e.Start > NOW()"); @Field(keys = {Table.PRIMARY}) private final int teamId; @@ -44,6 +48,18 @@ public class TeamTeilnahme { return select.select(teamID, eventID) != null; } + public static void teilnehmen(int teamID, int eventID){ + insert.update(teamID, eventID); + } + + public static void notTeilnehmen(int teamID, int eventID){ + delete.update(teamID, eventID); + } + + public static void deleteFuture(int teamID) { + deleteFuture.update(teamID); + } + public static Set getTeams(int eventID){ return selectTeams.listSelect(eventID).stream().map(tt -> Team.get(tt.teamId)).collect(Collectors.toSet()); // suboptimal performance (O(n) database queries) } diff --git a/src/de/steamwar/sql/Tutorial.java b/src/de/steamwar/sql/Tutorial.java new file mode 100644 index 0000000..9febcba --- /dev/null +++ b/src/de/steamwar/sql/Tutorial.java @@ -0,0 +1,94 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2023 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.sql; + +import de.steamwar.sql.internal.Field; +import de.steamwar.sql.internal.SelectStatement; +import de.steamwar.sql.internal.Statement; +import de.steamwar.sql.internal.Table; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.List; +import java.util.stream.Collectors; + +@AllArgsConstructor +public class Tutorial { + + private static final Table table = new Table<>(Tutorial.class); + private static final SelectStatement by_popularity = new SelectStatement<>(table, "SELECT t.*, AVG(r.Stars) AS Stars FROM Tutorial t LEFT OUTER JOIN TutorialRating r ON t.TutorialID = r.TutorialID WHERE t.Released = ? GROUP BY t.TutorialID ORDER BY SUM(r.Stars) DESC LIMIT ?, ?"); + private static final SelectStatement own = new SelectStatement<>(table, "SELECT t.*, AVG(r.Stars) AS Stars FROM Tutorial t LEFT OUTER JOIN TutorialRating r ON t.TutorialID = r.TutorialID WHERE t.Creator = ? GROUP BY t.TutorialID ORDER BY t.TutorialID ASC LIMIT ?, ?"); + private static final SelectStatement by_creator_name = new SelectStatement<>(table, "SELECT t.*, AVG(r.Stars) AS Stars FROM Tutorial t LEFT OUTER JOIN TutorialRating r ON t.TutorialID = r.TutorialID WHERE t.Creator = ? AND t.Name = ? GROUP BY t.TutorialID"); + private static final SelectStatement by_id = new SelectStatement<>(table, "SELECT t.*, AVG(r.Stars) AS Stars FROM Tutorial t LEFT OUTER JOIN TutorialRating r ON t.TutorialID = r.TutorialID WHERE t.TutorialID = ? GROUP BY t.TutorialID"); + private static final Statement rate = new Statement("INSERT INTO TutorialRating (TutorialID, UserID, Stars) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE Stars = VALUES(Stars)"); + private static final Statement create = new Statement("INSERT INTO Tutorial (Creator, Name, Item) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE Item = VALUES(Item), Released = 0"); + private static final Statement release = table.update(Table.PRIMARY, "released"); + private static final Statement delete = table.delete(Table.PRIMARY); + + public static List getPage(int page, int elementsPerPage, boolean released) { + List tutorials = by_popularity.listSelect(released, page * elementsPerPage, elementsPerPage); + SteamwarUser.batchCache(tutorials.stream().map(tutorial -> tutorial.creator).collect(Collectors.toSet())); + return tutorials; + } + + public static List getOwn(int user, int page, int elementsPerPage) { + return own.listSelect(user, page * elementsPerPage, elementsPerPage); + } + + public static Tutorial create(int creator, String name, String item) { + create.update(creator, name, item); + return by_creator_name.select(creator, name); + } + + public static Tutorial get(int id) { + return by_id.select(id); + } + + @Getter + @Field(keys = {Table.PRIMARY}, autoincrement = true) + private final int tutorialId; + @Getter + @Field(keys = {"CreatorName"}) + private final int creator; + @Getter + @Field(keys = {"CreatorName"}) + private final String name; + @Getter + @Field(def = "'BOOK'") + private final String item; + @Getter + @Field(def = "0") + private final boolean released; + @Getter + @Field(def = "0") // Not really a field, but necessary for select generation + private final double stars; + + public void release() { + release.update(1, tutorialId); + } + + public void delete() { + delete.update(tutorialId); + } + + public void rate(int user, int rating) { + rate.update(tutorialId, user, rating); + } +} diff --git a/src/de/steamwar/sql/UserElo.java b/src/de/steamwar/sql/UserElo.java new file mode 100644 index 0000000..5de692c --- /dev/null +++ b/src/de/steamwar/sql/UserElo.java @@ -0,0 +1,178 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2023 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.sql; + +import de.steamwar.sql.internal.Field; +import de.steamwar.sql.internal.SelectStatement; +import de.steamwar.sql.internal.Statement; +import de.steamwar.sql.internal.Table; +import lombok.AllArgsConstructor; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; + +@AllArgsConstructor +public class UserElo { + private static final int ELO_DEFAULT = 1000; + + private static final Map>> gameModeUserEloCache = new ConcurrentHashMap<>(); + private static final Map maxEloCache = new ConcurrentHashMap<>(); + private static final Map emblemCache = new ConcurrentHashMap<>(); + + public static void clear() { + gameModeUserEloCache.clear(); + maxEloCache.clear(); + emblemCache.clear(); + } + + private static final Table table = new Table<>(UserElo.class); + private static final SelectStatement getElo = table.select(Table.PRIMARY); + private static final Statement setElo = table.insertAll(); + private static final Statement maxElo = new Statement("SELECT MAX(Elo) AS MaxElo FROM UserElo WHERE Season = ? AND GameMode = ?"); + + private static final Statement place = new Statement("SELECT COUNT(*) AS Place FROM UserElo WHERE GameMode = ? AND Elo > ? AND Season = ?"); + private static final Statement fightsOfSeason = new Statement("SELECT COUNT(*) AS Fights FROM FightPlayer INNER JOIN Fight F on FightPlayer.FightID = F.FightID WHERE UserID = ? AND GameMode = ? AND UNIX_TIMESTAMP(StartTime) + Duration >= UNIX_TIMESTAMP(?)"); + + @Field(keys = {Table.PRIMARY}) + private final int season; + @Field(keys = {Table.PRIMARY}) + private final String gameMode; + @Field(keys = {Table.PRIMARY}) + private final int userId; + @Field + private final int elo; + + public static int getEloOrDefault(int userID, String gameMode) { + return getElo(userID, gameMode).orElse(ELO_DEFAULT); + } + + public static Optional getElo(int userID, String gameMode) { + return gameModeUserEloCache.computeIfAbsent(gameMode, gm -> new HashMap<>()).computeIfAbsent(userID, uid -> Optional.ofNullable(getElo.select(Season.getSeason(), gameMode, userID)).map(userElo -> userElo.elo)); + } + + public static int getFightsOfSeason(int userID, String gameMode) { + return fightsOfSeason.select(rs -> { + if (rs.next()) + return rs.getInt("Fights"); + return 0; + }, userID, gameMode, Season.getSeasonStart()); + } + + private static int getMaxElo(String gameMode) { + return maxEloCache.computeIfAbsent(gameMode, gm -> maxElo.select(rs -> { + if (rs.next()) + return rs.getInt("MaxElo"); + return 0; + }, Season.getSeason(), gameMode)); + } + + public static void setElo(int userId, String gameMode, int elo) { + emblemCache.remove(userId); + + Optional oldElo = Optional.ofNullable(gameModeUserEloCache.computeIfAbsent(gameMode, gm -> new HashMap<>()).put(userId, Optional.of(elo))).orElse(Optional.empty()); + int maxElo = getMaxElo(gameMode); + if (elo > maxElo || (oldElo.isPresent() && oldElo.get() == maxElo)) { + maxEloCache.remove(gameMode); + emblemCache.clear(); + } + + setElo.update(Season.getSeason(), gameMode, userId, elo); + } + + public static int getPlacement(int elo, String gameMode) { + return place.select(rs -> { + if (rs.next()) + return rs.getInt("Place") + 1; + return -1; + }, gameMode, elo, Season.getSeason()); + } + + public static String getEmblem(SteamwarUser user, List rankedModes) { + return emblemCache.computeIfAbsent(user.getId(), userId -> { + switch( + rankedModes.stream().filter( + mode -> UserElo.getFightsOfSeason(user.getId(), mode) >= 10 + ).map( + mode -> getProgression(user.getId(), mode) + ).max(Integer::compareTo).orElse(0) + ) { + case 0: + return ""; + case 1: + return "§7✧ "; + case 2: + return "§f✦ "; + case 3: + return "§e✶ "; + case 4: + return "§a✷ "; + case 5: + return "§b✸ "; + case 6: + return "§c✹ "; + case 7: + return "§5❂ "; + default: + throw new SecurityException("Progression out of range"); + } + }); + } + + public static String getEmblemProgression(String gameMode, int userId) { + switch (getProgression(userId, gameMode)) { + case 0: + return "§8✧ ✦ ✶ ✷ ✸ ✹ ❂"; + case 1: + return "§7✧ §8✦ ✶ ✷ ✸ ✹ ❂"; + case 2: + return "§8✧ §f✦ §8✶ ✷ ✸ ✹ ❂"; + case 3: + return "§8✧ ✦ §e✶ §8✷ ✸ ✹ ❂"; + case 4: + return "§8✧ ✦ ✶ §a✷ §8✸ ✹ ❂"; + case 5: + return "§8✧ ✦ ✶ ✷ §b✸ §8✹ ❂"; + case 6: + return "§8✧ ✦ ✶ ✷ ✸ §c✹ §8❂"; + case 7: + return "§8✧ ✦ ✶ ✷ ✸ ✹ §5❂"; + default: + throw new SecurityException("Progression is not in range"); + } + } + + private static int getProgression(int userId, String gameMode) { + int elo = getElo(userId, gameMode).orElse(0); + if(elo == 0) + return 0; + int maxElo = getMaxElo(gameMode); + + if (elo > maxElo * 0.99) return 7; + if (elo > maxElo * 0.97) return 6; + if (elo > maxElo * 0.94) return 5; + if (elo > maxElo * 0.88) return 4; + if (elo > maxElo * 0.76) return 3; + if (elo > maxElo * 0.51) return 2; + return 1; + } +} diff --git a/src/de/steamwar/sql/UserGroup.java b/src/de/steamwar/sql/UserGroup.java index cef4fee..aea2dba 100644 --- a/src/de/steamwar/sql/UserGroup.java +++ b/src/de/steamwar/sql/UserGroup.java @@ -1,21 +1,21 @@ -/* - This file is a part of the SteamWar software. - - Copyright (C) 2020 SteamWar.de-Serverteam - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2023 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ package de.steamwar.sql; @@ -24,22 +24,35 @@ import lombok.Getter; @AllArgsConstructor public enum UserGroup { - Admin("§4", "§e", true, true, true), - Developer("§3", "§f", true, true, true), - Moderator("§c", "§f", true, true, true), - Supporter("§9", "§f", false, true, true), - Builder("§2", "§f", false, true, false), - YouTuber("§5", "§f", false, false, false), - Member("§7", "§7", false, false, false); + Admin("§4", "§e", "Admin", true, true, true, true), + Developer("§3", "§f", "Dev", true, true, true, true), + Moderator("§c", "§f", "Mod", true, true, true, true), + Supporter("§9", "§f", "Sup", false, true, true, true), + Builder("§2", "§f", "Arch", false, true, false, true), + YouTuber("§5", "§f", "YT", false, false, false, true), + Member("§7", "§7", "", false, false, false, false); - @Getter - private final String colorCode; - @Getter - private final String chatColorCode; - @Getter - private final boolean adminGroup; - @Getter - private final boolean teamGroup; - @Getter - private final boolean checkSchematics; + @Getter + private final String colorCode; + @Getter + private final String chatColorCode; + @Getter + private final String chatPrefix; + @Getter + private final boolean adminGroup; + @Getter + private final boolean teamGroup; + @Getter + private final boolean checkSchematics; + @Getter + private final boolean privilegedMods; + + public static UserGroup getUsergroup(String name) { + for(UserGroup group : values()) { + if(group.name().equalsIgnoreCase(name)) + return group; + } + + throw new IllegalArgumentException(name); + } } \ No newline at end of file diff --git a/src/de/steamwar/sql/internal/Statement.java b/src/de/steamwar/sql/internal/Statement.java index e842d5f..c29f711 100644 --- a/src/de/steamwar/sql/internal/Statement.java +++ b/src/de/steamwar/sql/internal/Statement.java @@ -95,7 +95,7 @@ public class Statement implements AutoCloseable { } } - private static int connectionBudget = MAX_CONNECTIONS; + private static volatile int connectionBudget = MAX_CONNECTIONS; public static void closeAll() { synchronized (connections) { @@ -234,7 +234,7 @@ public class Statement implements AutoCloseable { private static Connection aquireConnection() { synchronized (connections) { - while(connections.isEmpty() && connectionBudget == 0) + while(connections.isEmpty() && connectionBudget <= 0) waitOnConnections(); if(!connections.isEmpty()) {