mirror of
https://github.com/Melledy/Nebula.git
synced 2026-09-20 01:29:54 +02:00
Implement ban functionality
- Added ban module - Added Ban and Unban commands, supporting banning and unbanning players through various command parameters. - Added timestamp formatting method in Utils utility class for displaying ban expiration times - Introduced PlayerErrorCode enum defining various error codes including ErrBan - Added dual ban checking for both IP and user during player login - Optimized login failure responses to provide specific error reasons and parameters
This commit is contained in:
@@ -9,6 +9,7 @@ import java.util.concurrent.TimeUnit;
|
||||
import emu.nebula.GameConstants;
|
||||
import emu.nebula.Nebula;
|
||||
import emu.nebula.game.activity.ActivityModule;
|
||||
import emu.nebula.game.ban.BanModule;
|
||||
import emu.nebula.game.gacha.GachaModule;
|
||||
import emu.nebula.game.player.PlayerModule;
|
||||
import emu.nebula.game.scoreboss.ScoreBossModule;
|
||||
@@ -30,6 +31,7 @@ public class GameContext implements Runnable {
|
||||
private final TutorialModule tutorialModule;
|
||||
private final ActivityModule activityModule;
|
||||
private final ScoreBossModule scoreBossModule;
|
||||
private final BanModule banModule;
|
||||
|
||||
// Game loop
|
||||
private final ScheduledExecutorService scheduler;
|
||||
@@ -48,6 +50,7 @@ public class GameContext implements Runnable {
|
||||
this.tutorialModule = new TutorialModule(this);
|
||||
this.activityModule = new ActivityModule(this);
|
||||
this.scoreBossModule = new ScoreBossModule(this);
|
||||
this.banModule = new BanModule(this);
|
||||
|
||||
// Run game loop
|
||||
this.scheduler = Executors.newScheduledThreadPool(1);
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
package emu.nebula.game.ban;
|
||||
|
||||
import dev.morphia.annotations.Entity;
|
||||
import dev.morphia.annotations.Id;
|
||||
import emu.nebula.Nebula;
|
||||
import emu.nebula.database.GameDatabaseObject;
|
||||
import emu.nebula.game.player.PlayerErrorCode;
|
||||
import emu.nebula.proto.Public.Error;
|
||||
import emu.nebula.util.Utils;
|
||||
import lombok.Getter;
|
||||
import java.util.Date;
|
||||
|
||||
@Getter
|
||||
@Entity(value = "bans", useDiscriminator = false)
|
||||
public class BanInfo implements GameDatabaseObject {
|
||||
@Id
|
||||
private String id;
|
||||
|
||||
private int playerUid;
|
||||
private long startTime;
|
||||
private long endTime;
|
||||
private String reason;
|
||||
private String bannedBy;
|
||||
private String ipAddress;
|
||||
|
||||
@Deprecated // Morphia only
|
||||
public BanInfo() {
|
||||
}
|
||||
|
||||
public BanInfo(int playerUid, long endTime, String reason, String bannedBy, String ipAddress) {
|
||||
this.playerUid = playerUid;
|
||||
this.startTime = System.currentTimeMillis();
|
||||
this.endTime = endTime;
|
||||
this.reason = reason;
|
||||
this.bannedBy = bannedBy;
|
||||
this.ipAddress = ipAddress;
|
||||
// Generate ID based on either player UID or IP address
|
||||
this.id = (ipAddress != null && !ipAddress.isEmpty() && playerUid == 0) ? "ip_" + ipAddress :
|
||||
"player_" + playerUid;
|
||||
}
|
||||
|
||||
public boolean isExpired() {
|
||||
return endTime != 0 && endTime < System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public String getExpirationDateString() {
|
||||
if (endTime == 0) {
|
||||
return "Never";
|
||||
}
|
||||
return Utils.formatTimestamp(this.endTime);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save() {
|
||||
GameDatabaseObject.super.save();
|
||||
}
|
||||
|
||||
public Error toProto() {
|
||||
return Error.newInstance()
|
||||
.setCode(PlayerErrorCode.ErrBan.getValue())
|
||||
.addArguments(
|
||||
getExpirationDateString() + "\n" +
|
||||
(this.reason != null ? "\n (" + this.reason + ")" : "\n" + this.id));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,242 @@
|
||||
package emu.nebula.game.ban;
|
||||
|
||||
import emu.nebula.Nebula;
|
||||
import emu.nebula.game.player.Player;
|
||||
import emu.nebula.game.GameContext;
|
||||
import emu.nebula.game.GameContextModule;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class BanModule extends GameContextModule {
|
||||
private final Map<String, BanInfo> cachedIpBans = new ConcurrentHashMap<>();
|
||||
private final Map<Integer, BanInfo> cachedPlayerBans = new ConcurrentHashMap<>();
|
||||
|
||||
public BanModule(GameContext context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ban a player by UID
|
||||
* @param uid Player UID
|
||||
* @param endTime Ban expiration time (0 = permanent)
|
||||
* @param isBanIp Ban Ip
|
||||
* @param reason Ban reason
|
||||
* @param bannedBy Who banned the player
|
||||
*/
|
||||
public void banPlayer(int uid, long endTime, String reason, boolean isBanIp, String bannedBy) {
|
||||
Player player = getGameContext().getPlayerModule().getPlayer(uid);
|
||||
|
||||
// It is only used as a supplement to find the banned IP when unblocking a player
|
||||
String playerBindIp = null;
|
||||
|
||||
if (isBanIp) {
|
||||
if (player != null && player.getSession() != null) {
|
||||
String ipAddress = player.getSession().getIpAddress();
|
||||
if (ipAddress != null && !ipAddress.isEmpty()) {
|
||||
playerBindIp = ipAddress;
|
||||
banIp(ipAddress, endTime, reason, bannedBy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BanInfo banInfo = new BanInfo(uid, endTime, reason, bannedBy, playerBindIp);
|
||||
cachedPlayerBans.put(uid, banInfo);
|
||||
banInfo.save();
|
||||
|
||||
// Kick player
|
||||
if (player != null && player.isLoaded()) {
|
||||
player.setSession(null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ban an IP address
|
||||
* <p>
|
||||
* Please be cautious about disabling IPs
|
||||
* in some regions where IPs are scarce, many people may share a public IP
|
||||
* and restarting the optical cat device will reassign a new IP
|
||||
*
|
||||
* @param ipAddress IP address to ban
|
||||
* @param endTime Ban expiration time (0 = permanent)
|
||||
* @param reason Ban reason
|
||||
* @param bannedBy Who banned the IP
|
||||
*/
|
||||
public void banIp(String ipAddress, long endTime, String reason, String bannedBy) {
|
||||
BanInfo banInfo = new BanInfo(0, endTime, reason, bannedBy, ipAddress);
|
||||
cachedIpBans.put(ipAddress, banInfo);
|
||||
banInfo.save();
|
||||
|
||||
List<Player> playerList = Nebula.getGameContext().getPlayerModule().getCachedPlayers()
|
||||
.values().stream().toList();
|
||||
|
||||
String playerIpAddress;
|
||||
for (Player player : playerList) {
|
||||
playerIpAddress = player.getSession().getIpAddress();
|
||||
if (playerIpAddress == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Kick player
|
||||
if (playerIpAddress.equals(ipAddress)) {
|
||||
player.setSession(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unban a player
|
||||
* @param uid Player UID
|
||||
*/
|
||||
public void unbanPlayer(int uid) {
|
||||
BanInfo banInfo = cachedPlayerBans.remove(uid);
|
||||
if (banInfo == null) {
|
||||
banInfo = getPlayerBanInfo(uid);
|
||||
}
|
||||
|
||||
if (banInfo == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
deleteBan(banInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unban an IP address
|
||||
* @param ipAddress IP address to unban
|
||||
*/
|
||||
public void unbanIp(String ipAddress) {
|
||||
BanInfo banInfo = cachedIpBans.remove(ipAddress);
|
||||
if (banInfo == null) {
|
||||
banInfo = getIpBanInfo(ipAddress);
|
||||
}
|
||||
|
||||
if (banInfo == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
deleteBan(banInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a player is banned
|
||||
* @param uid Player UID
|
||||
* @return True if banned, false otherwise
|
||||
*/
|
||||
public boolean isPlayerBanned(int uid) {
|
||||
BanInfo banInfo = cachedPlayerBans.get(uid);
|
||||
if (banInfo == null) {
|
||||
banInfo = loadPlayerBanFromDatabase(uid);
|
||||
if (banInfo == null) {
|
||||
return false;
|
||||
}
|
||||
cachedPlayerBans.put(uid, banInfo);
|
||||
}
|
||||
|
||||
// Check if ban has expired
|
||||
if (banInfo.isExpired()) {
|
||||
unbanPlayer(uid);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an IP address is banned
|
||||
* @param ipAddress IP address
|
||||
* @return True if banned, false otherwise
|
||||
*/
|
||||
public boolean isIpBanned(String ipAddress) {
|
||||
BanInfo banInfo = cachedIpBans.get(ipAddress);
|
||||
if (banInfo == null) {
|
||||
banInfo = loadIpBanFromDatabase(ipAddress);
|
||||
if (banInfo == null) {
|
||||
return false;
|
||||
}
|
||||
cachedIpBans.put(ipAddress, banInfo);
|
||||
}
|
||||
|
||||
// Check if ban has expired
|
||||
if (banInfo.isExpired()) {
|
||||
unbanIp(ipAddress);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get ban info for a player
|
||||
* @param uid Player UID
|
||||
* @return BanInfo or null if not banned
|
||||
*/
|
||||
public BanInfo getPlayerBanInfo(int uid) {
|
||||
BanInfo banInfo = cachedPlayerBans.get(uid);
|
||||
if (banInfo == null) {
|
||||
banInfo = loadPlayerBanFromDatabase(uid);
|
||||
if (banInfo != null) {
|
||||
cachedPlayerBans.put(uid, banInfo);
|
||||
}
|
||||
}
|
||||
return banInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get ban info for an IP
|
||||
* @param ipAddress IP address
|
||||
* @return BanInfo or null if not banned
|
||||
*/
|
||||
public BanInfo getIpBanInfo(String ipAddress) {
|
||||
BanInfo banInfo = cachedIpBans.get(ipAddress);
|
||||
if (banInfo == null) {
|
||||
banInfo = loadIpBanFromDatabase(ipAddress);
|
||||
if (banInfo != null) {
|
||||
cachedIpBans.put(ipAddress, banInfo);
|
||||
}
|
||||
}
|
||||
return banInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete ban from database
|
||||
*/
|
||||
private void deleteBan(BanInfo banInfo) {
|
||||
try {
|
||||
Nebula.getGameDatabase().delete(banInfo);
|
||||
} catch (Exception e) {
|
||||
Nebula.getLogger().error("Failed to delete ban info from database", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load player ban from database
|
||||
*/
|
||||
private BanInfo loadPlayerBanFromDatabase(int uid) {
|
||||
if (uid == 0)
|
||||
return null;
|
||||
|
||||
try {
|
||||
return Nebula.getGameDatabase().getObjectByField(BanInfo.class, "playerUid", uid);
|
||||
} catch (Exception e) {
|
||||
Nebula.getLogger().error("Failed to load player ban from database", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load IP ban from database
|
||||
*/
|
||||
private BanInfo loadIpBanFromDatabase(String ipAddress) {
|
||||
if (ipAddress == null || ipAddress.isEmpty())
|
||||
return null;
|
||||
|
||||
try {
|
||||
return Nebula.getGameDatabase().getObjectByField(BanInfo.class, "ipAddress", ipAddress);
|
||||
} catch (Exception e) {
|
||||
Nebula.getLogger().error("Failed to load IP ban from database", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
package emu.nebula.game.player;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
// resources\bin\ErrorCode.json
|
||||
// resources\language\en_US\ErrorCode.json
|
||||
|
||||
@Getter
|
||||
public enum PlayerErrorCode {
|
||||
ErrInvalidToken(100101),
|
||||
ErrInvalidTimestamp(100102),
|
||||
ErrInvalidMsgId(100103),
|
||||
ErrInvalidMsgBody(100104),
|
||||
ErrAgentBusy(100105),
|
||||
ErrServiceUnavailable(100106),
|
||||
ErrRequestTimeout(100107),
|
||||
ErrLoginFailed(100108),
|
||||
ErrInvalidVersion(100109),
|
||||
ErrLogin(100110),
|
||||
ErrMask(110101),
|
||||
ErrServerBusy(110102),
|
||||
ErrData(110103),
|
||||
ErrArgs(110104),
|
||||
ErrInternal(110105),
|
||||
ErrTokenExpire(110106),
|
||||
ErrRelogin(110107),
|
||||
ErrInvalidReq(110108),
|
||||
ErrApiDisabled(110109),
|
||||
ErrResUpdated(110110),
|
||||
ErrOperateTooFast(110111),
|
||||
ErrNickLenViolation(110201),
|
||||
ErrNickIllegal(110202),
|
||||
ErrNickForbidden(110203),
|
||||
ErrBan(110205),
|
||||
ErrSignatureForbidden(110206),
|
||||
ErrSignatureLenViolation(110207),
|
||||
ErrSignatureIllegal(110208),
|
||||
ErrTitleNotFound(110209),
|
||||
ErrSurveyNotFound(110210),
|
||||
ErrSurveyNotOpenYet(110211),
|
||||
ErrSurveyHasCompleted(110212),
|
||||
ErrMailMiss(110401),
|
||||
ErrMailRevoked(110402),
|
||||
ErrMailUpdate(110403),
|
||||
ErrMailPinned(110404),
|
||||
ErrQuestNotComplete(110501),
|
||||
ErrQuestClosed(110502),
|
||||
ErrProductNotReady(110601),
|
||||
ErrProductRestockLimit(110602),
|
||||
ErrGoodsNotFound(110603),
|
||||
ErrGoodsLockPurchase(110604),
|
||||
ErrGoodsDelisted(110605),
|
||||
ErrShopNotFound(110606),
|
||||
ErrShopAlreadyClosed(110607),
|
||||
ErrRecvReward(110701),
|
||||
ErrMainlineCondPremission(110702),
|
||||
ErrEnergyHoldLimit(110801),
|
||||
ErrEnergyBuyLimit(110802),
|
||||
ErrEnergyLack(110803),
|
||||
ErrEnergyRestore(110804),
|
||||
ErrGachaSpinLimit(110901),
|
||||
ErrItemUseRefused(111001),
|
||||
ErrRglInProgress(111101),
|
||||
ErrRglCond(111102),
|
||||
ErrRglBuildNotFound(111103),
|
||||
ErrRglBuildLock(111104),
|
||||
ErrFormationIdNeed(111105),
|
||||
ErrFormationFullNeed(111106),
|
||||
ErrBuildNameMinLenLimit(111107),
|
||||
ErrBuildNameMaxLenLimit(111108),
|
||||
ErrBuildNameViolation(111109),
|
||||
ErrOutfitStrengthenMax(111201),
|
||||
ErrOutfitNoData(111202),
|
||||
ErrOutfitBeEquipped(111203),
|
||||
ErrOutfitBeLocked(111204),
|
||||
ErrOutfitPhaseMax(111205),
|
||||
ErrOutfitStarMax(111206),
|
||||
ErrOutfitLevelNotReached(111207),
|
||||
ErrGiftPackageInvalid(111301),
|
||||
ErrGiftPackageExpired(111302),
|
||||
ErrGiftPackError(111303),
|
||||
ErrRedeemCodeReachedLimit(111304),
|
||||
ErrUserExchangeCountReachedLimit(111305),
|
||||
ErrGiftPackageAlreadyExists(111306),
|
||||
ErrGiftPackageInfoInvalid(111307),
|
||||
ErrRedeemCodeItemListEmpty(111308),
|
||||
ErrRedeemCodeCodeItemInvalid(111309),
|
||||
ErrGiftPackageStartTimeError(111310),
|
||||
ErrGiftPackageEndTimeError(111311),
|
||||
ErrGiftPackageRedeemCodeQuantityExceedsLimit(111312),
|
||||
ErrFixedRedeemCodeAlreadyExists(111313),
|
||||
ErrFixedRedeemCodeCannotBeEmpty(111314),
|
||||
ErrRedeemCodeQuantityCannotBeZero(111315),
|
||||
ErrFixedRedeemCodeUserUsageQuantityRequired(111316),
|
||||
ErrRedeemCodeInvalid(111317),
|
||||
ErrRedeemCodeFailed(111318),
|
||||
ErrRedeemCodeAlreadyUsed(111319),
|
||||
ErrRedeemFailed(111320),
|
||||
ErrInviteNotFound(111401),
|
||||
ErrUserInviteCountLimit(111402),
|
||||
ErrUserFriendCountLimit(111403),
|
||||
ErrAlreadyExistFriend(111404),
|
||||
ErrFriendEnergyReceiveMax(110681),
|
||||
ErrMallPackageNotListed(111501),
|
||||
ErrMallPackageDelisted(111502),
|
||||
ErrMallPackageStockLimit(111503),
|
||||
ErrMallPackageOrderLimit(111504),
|
||||
ErrOrderCantRemove(111505),
|
||||
ErrHeartStoneMax(111601),
|
||||
ErrWeaponMaxAdvance(111602),
|
||||
ErrWeaponCondNoMet(111603),
|
||||
ErrCharMaxLevel(111604),
|
||||
ErrCharCondMaxAdvance(111605),
|
||||
ErrCharMaxAdvance(111606),
|
||||
ErrCharSkillMaxLevel(111607),
|
||||
ErrCharSkillCondNotMet(111608),
|
||||
ErrPresentsNoData(111609),
|
||||
ErrPresentsUpgradeMax(111610),
|
||||
ErrPresentsBeEquipped(111611),
|
||||
ErrPresentsBeLocked(111612),
|
||||
ErrNotAllowedRecruit(111613),
|
||||
ErrAffinityMaxLevel(111614),
|
||||
ErrAffinityGiftSendCountLimit(111615),
|
||||
ErrDatingCountLimit(111616),
|
||||
ErrDatingSendCountLimit(111617),
|
||||
ErrCharWorldClassCondNotMet(111618),
|
||||
ErrDatingCharLimit(111619),
|
||||
ErrCharArchiveRewardReceived(111620),
|
||||
ErrReceiveEnergyFriendNotData(111621),
|
||||
ErrBattlePassVersionMismatch(111701),
|
||||
ErrTravelerDuelSeasonEnd(111801),
|
||||
ErrProductionFormulaUnactivated(111901),
|
||||
ErrTalentAlreayActiveated(112001),
|
||||
ErrTalentGroupNotYetOpen(112002),
|
||||
ErrTalentKeyNodesInsufficientQuantity(112003),
|
||||
ErrTalentOrdinaryNodesInsufficientQuantity(112004),
|
||||
ErrTalentListEmpty(112005),
|
||||
ErrTalentResetFrequently(112006),
|
||||
ErrTalentActivationLimit(112007),
|
||||
ErrTalentNotYetActivated(112008),
|
||||
ErrEquipmentNoData(112101),
|
||||
ErrEquipmentUpgradeMax(112102),
|
||||
ErrEquipmentBeEquipped(112103),
|
||||
ErrEquipmentBeLocked(112104),
|
||||
ErrDiscNoData(112201),
|
||||
ErrDiscLevelNotReached(112202),
|
||||
ErrDiscStrengthenMax(112203),
|
||||
ErrDiscPhaseMax(112204),
|
||||
ErrDiscStarMax(112205),
|
||||
ErrStarTowerRankSeasonNoData(112301),
|
||||
ErrStarTowerRankSeasonNotYetOpen(112302),
|
||||
ErrStarTowerRankSeasonEnd(112303),
|
||||
ErrAgentNoData(112401),
|
||||
ErrAgentInProcess(112402),
|
||||
ErrAgentCond(112403),
|
||||
ErrAgentCharOccupied(112404),
|
||||
ErrAgentBuildOccupied(112405),
|
||||
ErrAgentBuildScoreLow(112406),
|
||||
ErrAgentCountLimit(112407),
|
||||
ErrAgentDailyLimit(112408),
|
||||
ErrAgentWeeklyLimit(112409),
|
||||
ErrAgentNotCompleted(112410),
|
||||
ErrVampireSurvivorBuildNum(112501),
|
||||
ErrVampireSurvivorBuildCharRepetition(112502),
|
||||
ErrTowerGrowthGroupActivated(112601),
|
||||
ErrJointDrillNotExist(112701),
|
||||
ErrJointDrillInProgress(112702),
|
||||
ErrJointDrillNotYetOpen(112703),
|
||||
ErrJointDrillAlreadyClosed(112704),
|
||||
ErrScoreBossNotOpen(112801),
|
||||
ErrNickNameResetLimit(112901),
|
||||
ErrCapOverflow(119901),
|
||||
ErrConfig(119902),
|
||||
ErrCondCheck(119903),
|
||||
ErrNotYetOpen(119904),
|
||||
ErrAlreadyClosed(119905),
|
||||
ErrCondNotMet(119906),
|
||||
ErrInsufficientWorldClass(119907),
|
||||
ErrNoRewardsToReceive(119908),
|
||||
ErrAlreayExists(119909),
|
||||
ErrExchangeNotSupported(119910),
|
||||
ErrRequestTooFrequent(119911),
|
||||
ErrAlreaydReceive(119912),
|
||||
ErrLimit(119913),
|
||||
ErrDataUpdated(119914);
|
||||
|
||||
final int value;
|
||||
|
||||
PlayerErrorCode(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user