From d64975b95974683cbed5a328da32d353d75dd869 Mon Sep 17 00:00:00 2001 From: Obique PSWG Date: Sat, 23 May 2015 13:13:52 -0500 Subject: [PATCH] Added CORE-67 'Character creation limit per day' --- .../player/CharacterCreationRestriction.java | 111 ++++++++++++++++++ src/services/player/ZoneService.java | 31 +++-- 2 files changed, 131 insertions(+), 11 deletions(-) create mode 100644 src/services/player/CharacterCreationRestriction.java diff --git a/src/services/player/CharacterCreationRestriction.java b/src/services/player/CharacterCreationRestriction.java new file mode 100644 index 000000000..6395fb076 --- /dev/null +++ b/src/services/player/CharacterCreationRestriction.java @@ -0,0 +1,111 @@ +package services.player; + +import java.util.Arrays; +import java.util.Deque; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import resources.player.Player; +import resources.server_info.Log; + +public class CharacterCreationRestriction { + + private static final long TIME_INCREMENT = TimeUnit.DAYS.toMillis(1); + + private final Map restrictions; + private int creationsPerPeriod; + + public CharacterCreationRestriction(int creationsPerPeriod) { + this.restrictions = new HashMap<>(); + this.creationsPerPeriod = creationsPerPeriod; + } + + public void setCreationsPerPeriod(int creationsPerPeriod) { + this.creationsPerPeriod = creationsPerPeriod; + synchronized (restrictions) { + for (PlayerRestriction pr : restrictions.values()) + pr.setCreationsPerPeriod(creationsPerPeriod); + } + } + + public boolean isAbleToCreate(Player player) { + PlayerRestriction pr = getRestriction(player); + return pr.isAbleToCreate(); + } + + public boolean createdCharacter(Player player) { + PlayerRestriction pr = getRestriction(player); + return pr.createdCharacter(); + } + + private PlayerRestriction getRestriction(Player player) { + PlayerRestriction pr = null; + synchronized (restrictions) { + pr = restrictions.get(player.getUserId()); + } + if (pr == null) { + pr = new PlayerRestriction(creationsPerPeriod); + synchronized (restrictions) { + restrictions.put(player.getUserId(), pr); + } + } + return pr; + } + + private static long now() { + return System.currentTimeMillis(); + } + + private static boolean isWithinPeriod(long time) { + long cur = now(); + return time > (cur-TIME_INCREMENT) && time <= cur; + } + + private static class PlayerRestriction { + + private final Deque lastCreations; + private int creationsPerPeriod; + + public PlayerRestriction(int creationsPerPeriod) { + lastCreations = new LinkedList<>(); + setCreationsPerPeriod(creationsPerPeriod); + } + + public void setCreationsPerPeriod(int creationsPerPeriod) { + this.creationsPerPeriod = creationsPerPeriod; + } + + public boolean isAbleToCreate() { + synchronized (lastCreations) { + Log.d("CharacterCreationRestriction", "Size: %d Period: %d", lastCreations.size(), creationsPerPeriod); + if (lastCreations.size() < creationsPerPeriod) + return true; + if (creationsPerPeriod == 0) + return false; + Log.d("CharacterCreationRestriction", "Last: %d Within: %b", lastCreations.getLast(), isWithinPeriod(lastCreations.getLast().longValue())); + return !isWithinPeriod(lastCreations.getLast().longValue()); + } + } + + public boolean createdCharacter() { + synchronized (lastCreations) { + final boolean hitMax = lastCreations.size() >= creationsPerPeriod; + final boolean hackSuccess = hitMax && isWithinPeriod(lastCreations.getLast()); + final long time = now(); + if (hackSuccess) { + final String state = Arrays.toString(lastCreations.toArray(new Long[lastCreations.size()])); + Log.e("CharacterCreationRestriction", "Character created when not allowed! Current time/state: %s/%s", time, state); + } + Log.d("CharacterCreationRestriction", "HitMax: %b HackSuccess: %b", hitMax, hackSuccess); + if (hitMax) + lastCreations.pollLast(); + lastCreations.addFirst(time); + return !hackSuccess; + } + } + + } + +} diff --git a/src/services/player/ZoneService.java b/src/services/player/ZoneService.java index 03f31057e..2e8c1100a 100644 --- a/src/services/player/ZoneService.java +++ b/src/services/player/ZoneService.java @@ -107,6 +107,7 @@ public class ZoneService extends Service { private final ClientFactory clientFac; private final NameFilter nameFilter; private final SWGNameGenerator nameGenerator; + private final CharacterCreationRestriction creationRestriction; private PreparedStatement createCharacter; private PreparedStatement getCharacter; @@ -118,6 +119,7 @@ public class ZoneService extends Service { clientFac = new ClientFactory(); nameFilter = new NameFilter("namegen/bad_word_list.txt", "namegen/reserved_words.txt", "namegen/fiction_reserved.txt"); nameGenerator = new SWGNameGenerator(nameFilter); + creationRestriction = new CharacterCreationRestriction(2); } @Override @@ -281,9 +283,12 @@ public class ZoneService extends Service { private void handleCharCreation(ObjectManager objManager, Player player, ClientCreateCharacter create) { ErrorMessage err = getNameValidity(create.getName(), player.getAccessLevel() != AccessLevel.PLAYER); + if (!creationRestriction.isAbleToCreate(player)) + err = ErrorMessage.NAME_DECLINED_TOO_FAST; if (err == ErrorMessage.NAME_APPROVED) { long characterId = createCharacter(objManager, player, create); if (createCharacterInDb(characterId, create.getName(), player)) { + creationRestriction.createdCharacter(player); System.out.println("[" + player.getUsername() + "] Create Character: " + create.getName() + ". IP: " + create.getAddress() + ":" + create.getPort()); Log.i("ZoneService", "%s created character %s from %s:%d", player.getUsername(), create.getName(), create.getAddress(), create.getPort()); sendPacket(player, new CreateCharacterSuccess(characterId)); @@ -298,17 +303,21 @@ public class ZoneService extends Service { private void sendCharCreationFailure(Player player, ClientCreateCharacter create, ErrorMessage err) { NameFailureReason reason = NameFailureReason.NAME_SYNTAX; - if (err == ErrorMessage.NAME_APPROVED) { // Then it must have been a database error - err = ErrorMessage.NAME_DECLINED_INTERNAL_ERROR; - reason = NameFailureReason.NAME_RETRY; - } else if (err == ErrorMessage.NAME_DECLINED_IN_USE) - reason = NameFailureReason.NAME_IN_USE; - else if (err == ErrorMessage.NAME_DECLINED_EMPTY) - reason = NameFailureReason.NAME_DECLINED_EMPTY; - else if (err == ErrorMessage.NAME_DECLINED_FICTIONALLY_INAPPROPRIATE) - reason = NameFailureReason.NAME_FICTIONALLY_INAPPRORIATE; - else if (err == ErrorMessage.NAME_DECLINED_RESERVED) - reason = NameFailureReason.NAME_DEV_RESERVED; + switch (err) { + case NAME_APPROVED: + err = ErrorMessage.NAME_DECLINED_INTERNAL_ERROR; + reason = NameFailureReason.NAME_RETRY; + break; + case NAME_DECLINED_FICTIONALLY_INAPPROPRIATE: + reason = NameFailureReason.NAME_FICTIONALLY_INAPPRORIATE; + break; + case NAME_DECLINED_IN_USE: reason = NameFailureReason.NAME_IN_USE; break; + case NAME_DECLINED_EMPTY: reason = NameFailureReason.NAME_DECLINED_EMPTY; break; + case NAME_DECLINED_RESERVED: reason = NameFailureReason.NAME_DEV_RESERVED; break; + case NAME_DECLINED_TOO_FAST: reason = NameFailureReason.NAME_TOO_FAST; break; + default: + break; + } System.err.println("ZoneService: Unable to create character [Name: " + create.getName() + " User: " + player.getUsername() + "] and put into database! Reason: " + err); Log.e("ZoneService", "Failed to create character %s for user %s with error %s and reason %s from %s:%d", create.getName(), player.getUsername(), err, reason, create.getAddress(), create.getPort()); sendPacket(player, new CreateCharacterFailure(reason));