forked from Upstream/mmocore
more cleanup
This commit is contained in:
parent
3cf8886349
commit
591e92c46c
@ -1,5 +1,10 @@
|
||||
package net.Indyuce.mmocore.listener;
|
||||
|
||||
import net.Indyuce.mmocore.MMOCore;
|
||||
import net.Indyuce.mmocore.api.player.PlayerData;
|
||||
import net.Indyuce.mmocore.manager.ConfigManager;
|
||||
import net.Indyuce.mmocore.manager.SoundManager;
|
||||
import net.Indyuce.mmocore.skill.Skill.SkillInfo;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.GameMode;
|
||||
import org.bukkit.entity.Player;
|
||||
@ -11,150 +16,151 @@ import org.bukkit.event.player.PlayerSwapHandItemsEvent;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.scheduler.BukkitRunnable;
|
||||
|
||||
import net.Indyuce.mmocore.MMOCore;
|
||||
import net.Indyuce.mmocore.api.player.PlayerData;
|
||||
import net.Indyuce.mmocore.skill.Skill.SkillInfo;
|
||||
import net.Indyuce.mmocore.manager.ConfigManager;
|
||||
import net.Indyuce.mmocore.manager.SoundManager;
|
||||
|
||||
public class SpellCast implements Listener {
|
||||
@EventHandler
|
||||
public void a(PlayerSwapHandItemsEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
ConfigManager.SwapAction action = player.isSneaking()
|
||||
? MMOCore.plugin.configManager.sneakingSwapAction
|
||||
: MMOCore.plugin.configManager.normalSwapAction;
|
||||
|
||||
if(action == ConfigManager.SwapAction.VANILLA) return;
|
||||
PlayerData playerData = PlayerData.get(player);
|
||||
event.setCancelled(true);
|
||||
@EventHandler
|
||||
public void a(PlayerSwapHandItemsEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
ConfigManager.SwapAction action = player.isSneaking() ? MMOCore.plugin.configManager.sneakingSwapAction : MMOCore.plugin.configManager.normalSwapAction;
|
||||
|
||||
/*
|
||||
* hotbar swap feature only if the player is sneaking while entering
|
||||
* skill casting mode
|
||||
*/
|
||||
if (action == ConfigManager.SwapAction.HOTBAR_SWAP) {
|
||||
MMOCore.plugin.soundManager.play(player, SoundManager.SoundEvent.HOTBAR_SWAP);
|
||||
for (int j = 0; j < 9; j++) {
|
||||
ItemStack replaced = player.getInventory().getItem(j + 9 * 3);
|
||||
player.getInventory().setItem(j + 9 * 3, player.getInventory().getItem(j));
|
||||
player.getInventory().setItem(j, replaced);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Vanilla action does nothing
|
||||
if (action == ConfigManager.SwapAction.VANILLA)
|
||||
return;
|
||||
|
||||
if (!((!MMOCore.plugin.configManager.canCreativeCast && player.getGameMode() == GameMode.CREATIVE) || player.getGameMode() == GameMode.SPECTATOR)
|
||||
&& !playerData.isCasting() && playerData.getBoundSkills().size() > 0) {
|
||||
playerData.skillCasting = new SkillCasting(playerData);
|
||||
MMOCore.plugin.soundManager.play(player, SoundManager.SoundEvent.SPELL_CAST_BEGIN);
|
||||
}
|
||||
}
|
||||
// Always cancel event if it's not the vanilla action
|
||||
event.setCancelled(true);
|
||||
|
||||
public class SkillCasting extends BukkitRunnable implements Listener {
|
||||
private final PlayerData playerData;
|
||||
/*
|
||||
* Hotbar swap feature, this entirely switches the
|
||||
* player's hotbar with their 9 lowest inventory slots
|
||||
*/
|
||||
if (action == ConfigManager.SwapAction.HOTBAR_SWAP) {
|
||||
MMOCore.plugin.soundManager.play(player, SoundManager.SoundEvent.HOTBAR_SWAP);
|
||||
for (int j = 0; j < 9; j++) {
|
||||
ItemStack replaced = player.getInventory().getItem(j + 9 * 3);
|
||||
player.getInventory().setItem(j + 9 * 3, player.getInventory().getItem(j));
|
||||
player.getInventory().setItem(j, replaced);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
private final String ready = MMOCore.plugin.configManager.getSimpleMessage("casting.action-bar.ready").message();
|
||||
private final String onCooldown = MMOCore.plugin.configManager.getSimpleMessage("casting.action-bar.on-cooldown").message();
|
||||
private final String noMana = MMOCore.plugin.configManager.getSimpleMessage("casting.action-bar.no-mana").message();
|
||||
private final String split = MMOCore.plugin.configManager.getSimpleMessage("casting.split").message();
|
||||
// Enter spell casting
|
||||
PlayerData playerData = PlayerData.get(player);
|
||||
if (player.getGameMode() != GameMode.SPECTATOR
|
||||
&& (MMOCore.plugin.configManager.canCreativeCast || player.getGameMode() != GameMode.CREATIVE)
|
||||
&& !playerData.isCasting()
|
||||
&& playerData.getBoundSkills().size() > 0) {
|
||||
playerData.skillCasting = new SkillCasting(playerData);
|
||||
MMOCore.plugin.soundManager.play(player, SoundManager.SoundEvent.SPELL_CAST_BEGIN);
|
||||
}
|
||||
}
|
||||
|
||||
private int j;
|
||||
public class SkillCasting extends BukkitRunnable implements Listener {
|
||||
private final PlayerData playerData;
|
||||
|
||||
public SkillCasting(PlayerData playerData) {
|
||||
this.playerData = playerData;
|
||||
private final String ready = MMOCore.plugin.configManager.getSimpleMessage("casting.action-bar.ready").message();
|
||||
private final String onCooldown = MMOCore.plugin.configManager.getSimpleMessage("casting.action-bar.on-cooldown").message();
|
||||
private final String noMana = MMOCore.plugin.configManager.getSimpleMessage("casting.action-bar.no-mana").message();
|
||||
private final String split = MMOCore.plugin.configManager.getSimpleMessage("casting.split").message();
|
||||
|
||||
Bukkit.getPluginManager().registerEvents(this, MMOCore.plugin);
|
||||
runTaskTimer(MMOCore.plugin, 0, 1);
|
||||
}
|
||||
private int j;
|
||||
|
||||
@EventHandler()
|
||||
public void onSkillCast(PlayerItemHeldEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
if(!playerData.isOnline()) return;
|
||||
if (!event.getPlayer().equals(playerData.getPlayer()))
|
||||
return;
|
||||
public SkillCasting(PlayerData playerData) {
|
||||
this.playerData = playerData;
|
||||
|
||||
/*
|
||||
* when the event is cancelled, another playerItemHeldEvent is
|
||||
* called and previous and next slots are equal. the event must not
|
||||
* listen to that non-player called event.
|
||||
*/
|
||||
if (event.getPreviousSlot() == event.getNewSlot())
|
||||
return;
|
||||
Bukkit.getPluginManager().registerEvents(this, MMOCore.plugin);
|
||||
runTaskTimer(MMOCore.plugin, 0, 1);
|
||||
}
|
||||
|
||||
event.setCancelled(true);
|
||||
int slot = event.getNewSlot() + (event.getNewSlot() >= player.getInventory().getHeldItemSlot() ? -1 : 0);
|
||||
@EventHandler()
|
||||
public void onSkillCast(PlayerItemHeldEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
if (!playerData.isOnline()) return;
|
||||
if (!event.getPlayer().equals(playerData.getPlayer()))
|
||||
return;
|
||||
|
||||
/*
|
||||
* the event is called again soon after the first since when
|
||||
* cancelling the first one, the player held item slot must go back
|
||||
* to the previous one.
|
||||
*/
|
||||
if (slot >= 0 && playerData.hasSkillBound(slot))
|
||||
playerData.cast(playerData.getBoundSkill(slot));
|
||||
}
|
||||
/*
|
||||
* when the event is cancelled, another playerItemHeldEvent is
|
||||
* called and previous and next slots are equal. the event must not
|
||||
* listen to that non-player called event.
|
||||
*/
|
||||
if (event.getPreviousSlot() == event.getNewSlot())
|
||||
return;
|
||||
|
||||
@EventHandler
|
||||
public void stopCasting(PlayerSwapHandItemsEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
ConfigManager.SwapAction action = player.isSneaking()
|
||||
? MMOCore.plugin.configManager.sneakingSwapAction
|
||||
: MMOCore.plugin.configManager.normalSwapAction;
|
||||
if(action != ConfigManager.SwapAction.SPELL_CAST || !playerData.isOnline()) return;
|
||||
if (event.getPlayer().equals(playerData.getPlayer())) {
|
||||
MMOCore.plugin.soundManager.play(player, SoundManager.SoundEvent.SPELL_CAST_END);
|
||||
MMOCore.plugin.configManager.getSimpleMessage("casting.no-longer").send(playerData.getPlayer());
|
||||
close();
|
||||
}
|
||||
}
|
||||
event.setCancelled(true);
|
||||
int slot = event.getNewSlot() + (event.getNewSlot() >= player.getInventory().getHeldItemSlot() ? -1 : 0);
|
||||
|
||||
private void close() {
|
||||
playerData.skillCasting = null;
|
||||
HandlerList.unregisterAll(this);
|
||||
cancel();
|
||||
}
|
||||
/*
|
||||
* the event is called again soon after the first since when
|
||||
* cancelling the first one, the player held item slot must go back
|
||||
* to the previous one.
|
||||
*/
|
||||
if (slot >= 0 && playerData.hasSkillBound(slot))
|
||||
playerData.cast(playerData.getBoundSkill(slot));
|
||||
}
|
||||
|
||||
private String getFormat(PlayerData data) {
|
||||
StringBuilder str = new StringBuilder();
|
||||
if(!data.isOnline()) return str.toString();
|
||||
for (int j = 0; j < data.getBoundSkills().size(); j++) {
|
||||
SkillInfo skill = data.getBoundSkill(j);
|
||||
str.append((str.length() == 0) ? "" : split).append((onCooldown(data, skill)
|
||||
? onCooldown .replace("{cooldown}", "" + data.getSkillData().getCooldown(skill) / 1000)
|
||||
: noMana(data, skill) ? noMana : ready).replace("{index}", "" + (j + 1 + (data.getPlayer().getInventory().getHeldItemSlot()
|
||||
<= j ? 1 : 0))).replace("{skill}", data.getBoundSkill(j).getSkill().getName()));
|
||||
}
|
||||
@EventHandler
|
||||
public void stopCasting(PlayerSwapHandItemsEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
ConfigManager.SwapAction action = player.isSneaking()
|
||||
? MMOCore.plugin.configManager.sneakingSwapAction
|
||||
: MMOCore.plugin.configManager.normalSwapAction;
|
||||
if (action != ConfigManager.SwapAction.SPELL_CAST || !playerData.isOnline()) return;
|
||||
if (event.getPlayer().equals(playerData.getPlayer())) {
|
||||
MMOCore.plugin.soundManager.play(player, SoundManager.SoundEvent.SPELL_CAST_END);
|
||||
MMOCore.plugin.configManager.getSimpleMessage("casting.no-longer").send(playerData.getPlayer());
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
return str.toString();
|
||||
}
|
||||
private void close() {
|
||||
playerData.skillCasting = null;
|
||||
HandlerList.unregisterAll(this);
|
||||
cancel();
|
||||
}
|
||||
|
||||
/*
|
||||
* no longer use index as arguments because data.getBoundSkill(int) has
|
||||
* n-complexity, it has to iterate through a list. using skillInfo
|
||||
* argument uses only one iteration
|
||||
*/
|
||||
private boolean onCooldown(PlayerData data, SkillInfo skill) {
|
||||
return skill.getSkill().hasModifier("cooldown") && data.getSkillData().getCooldown(skill) > 0;
|
||||
}
|
||||
private String getFormat(PlayerData data) {
|
||||
StringBuilder str = new StringBuilder();
|
||||
if (!data.isOnline()) return str.toString();
|
||||
for (int j = 0; j < data.getBoundSkills().size(); j++) {
|
||||
SkillInfo skill = data.getBoundSkill(j);
|
||||
str.append((str.length() == 0) ? "" : split).append((onCooldown(data, skill)
|
||||
? onCooldown.replace("{cooldown}", "" + data.getSkillData().getCooldown(skill) / 1000)
|
||||
: noMana(data, skill) ? noMana : ready).replace("{index}", "" + (j + 1 + (data.getPlayer().getInventory().getHeldItemSlot()
|
||||
<= j ? 1 : 0))).replace("{skill}", data.getBoundSkill(j).getSkill().getName()));
|
||||
}
|
||||
|
||||
private boolean noMana(PlayerData data, SkillInfo skill) {
|
||||
return skill.getSkill().hasModifier("mana") && skill.getModifier("mana", data.getSkillLevel(skill.getSkill())) > data.getMana();
|
||||
}
|
||||
return str.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (!playerData.isOnline() || playerData.getPlayer().isDead()) {
|
||||
close(); return;
|
||||
}
|
||||
/*
|
||||
* no longer use index as arguments because data.getBoundSkill(int) has
|
||||
* n-complexity, it has to iterate through a list. using skillInfo
|
||||
* argument uses only one iteration
|
||||
*/
|
||||
private boolean onCooldown(PlayerData data, SkillInfo skill) {
|
||||
return skill.getSkill().hasModifier("cooldown") && data.getSkillData().getCooldown(skill) > 0;
|
||||
}
|
||||
|
||||
if (j % 20 == 0)
|
||||
playerData.displayActionBar(getFormat(playerData));
|
||||
private boolean noMana(PlayerData data, SkillInfo skill) {
|
||||
return skill.getSkill().hasModifier("mana") && skill.getModifier("mana", data.getSkillLevel(skill.getSkill())) > data.getMana();
|
||||
}
|
||||
|
||||
for (int k = 0; k < 2; k++) {
|
||||
double a = (double) j++ / 5;
|
||||
playerData.getProfess().getCastParticle()
|
||||
.display(playerData.getPlayer().getLocation().add(Math.cos(a), 1 + Math.sin(a / 3) / 1.3, Math.sin(a)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void run() {
|
||||
if (!playerData.isOnline() || playerData.getPlayer().isDead()) {
|
||||
close();
|
||||
return;
|
||||
}
|
||||
|
||||
if (j % 20 == 0)
|
||||
playerData.displayActionBar(getFormat(playerData));
|
||||
|
||||
for (int k = 0; k < 2; k++) {
|
||||
double a = (double) j++ / 5;
|
||||
playerData.getProfess().getCastParticle()
|
||||
.display(playerData.getPlayer().getLocation().add(Math.cos(a), 1 + Math.sin(a / 3) / 1.3, Math.sin(a)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,33 +14,33 @@ config-version: 7
|
||||
# (class, level, etc.) and guild data
|
||||
# (guild names, members, etc.) at a set interval.
|
||||
auto-save:
|
||||
enabled: true
|
||||
interval: 1800 # In seconds (1800 = 30 minutes)
|
||||
enabled: true
|
||||
interval: 1800 # In seconds (1800 = 30 minutes)
|
||||
|
||||
# MySQL Support
|
||||
mysql:
|
||||
enabled: false
|
||||
host: localhost
|
||||
port: 3306
|
||||
database: minecraft
|
||||
user: mmolover
|
||||
pass: ILoveAria
|
||||
properties:
|
||||
cachePrepStmts: true
|
||||
prepStmtCacheSize: 250
|
||||
prepStmtCacheSqlLimit: 2048
|
||||
# Will verbose to the console whenever
|
||||
# data is saved/loaded from the SQL database.
|
||||
debug: false
|
||||
enabled: false
|
||||
host: localhost
|
||||
port: 3306
|
||||
database: minecraft
|
||||
user: mmolover
|
||||
pass: ILoveAria
|
||||
properties:
|
||||
cachePrepStmts: true
|
||||
prepStmtCacheSize: 250
|
||||
prepStmtCacheSqlLimit: 2048
|
||||
# Will verbose to the console whenever
|
||||
# data is saved/loaded from the SQL database.
|
||||
debug: false
|
||||
|
||||
# The default values for all playerdata
|
||||
# All new players will start with these values
|
||||
default-playerdata:
|
||||
level: 1
|
||||
class-points: 0
|
||||
skill-points: 0
|
||||
attribute-points: 0
|
||||
attribute-realloc-points: 0
|
||||
level: 1
|
||||
class-points: 0
|
||||
skill-points: 0
|
||||
attribute-points: 0
|
||||
attribute-realloc-points: 0
|
||||
|
||||
# The list of all conditions which must be met for the
|
||||
# BLOCK REGEN and BLOCK RESTRICTIONS to apply. Set to
|
||||
@ -51,8 +51,8 @@ default-playerdata:
|
||||
#
|
||||
# ^ will enable custom mining server wide
|
||||
custom-mine-conditions:
|
||||
- 'world{name="world,world_nether,world_the_end"}'
|
||||
- 'region{name="example_region,example_region2,__global__"}'
|
||||
- 'world{name="world,world_nether,world_the_end"}'
|
||||
- 'region{name="example_region,example_region2,__global__"}'
|
||||
|
||||
# Set to true to prevent vanilla blocks from being
|
||||
# broken when custom mining conditions are met
|
||||
@ -64,57 +64,57 @@ should-cobblestone-generators-give-exp: false
|
||||
|
||||
loot-chests:
|
||||
|
||||
# Time in seconds it takes for a loot chest to
|
||||
# expire after it was spawned. 600 is 10 minutes.
|
||||
chest-expire-time: 600
|
||||
|
||||
# Interval in seconds before the same player
|
||||
# spawns two loot chests in ANY region.
|
||||
player-cooldown: 600
|
||||
# Time in seconds it takes for a loot chest to
|
||||
# expire after it was spawned. 600 is 10 minutes.
|
||||
chest-expire-time: 600
|
||||
|
||||
# Interval in seconds before the same player
|
||||
# spawns two loot chests in ANY region.
|
||||
player-cooldown: 600
|
||||
|
||||
# Settings for the default action bar
|
||||
action-bar:
|
||||
|
||||
# Whether or not to use the default action bar.
|
||||
# (This doesn't change any other action bars provided by MMOCore.)
|
||||
enabled: true
|
||||
|
||||
# The decimal format for stats (not including stat formats in stats.yml)
|
||||
decimal: "0.#"
|
||||
|
||||
# The amount of miliseconds the bar will be faded
|
||||
#out when displaying other action bars.
|
||||
time-out: 60
|
||||
|
||||
# The amount of ticks before updating the info
|
||||
ticks-to-update: 5
|
||||
|
||||
# How to display the data.
|
||||
format: "&c❤ {health}/{max_health} &f| {mana_icon} {mana}/{max_mana} &f| &7⛨ {armor}"
|
||||
# Whether or not to use the default action bar.
|
||||
# (This doesn't change any other action bars provided by MMOCore.)
|
||||
enabled: true
|
||||
|
||||
# The decimal format for stats (not including stat formats in stats.yml)
|
||||
decimal: "0.#"
|
||||
|
||||
# The amount of miliseconds the bar will be faded
|
||||
#out when displaying other action bars.
|
||||
time-out: 60
|
||||
|
||||
# The amount of ticks before updating the info
|
||||
ticks-to-update: 5
|
||||
|
||||
# How to display the data.
|
||||
format: "&c❤ {health}/{max_health} &f| {mana_icon} {mana}/{max_mana} &f| &7⛨ {armor}"
|
||||
|
||||
party:
|
||||
|
||||
# Edit party buffs here. You may
|
||||
# add as many stats as you want.
|
||||
buff:
|
||||
health-regeneration: 3
|
||||
additional-experience: 5
|
||||
|
||||
# Prefix you need to put in the chat
|
||||
# to talk in the party chat.
|
||||
chat-prefix: '@'
|
||||
# Edit party buffs here. You may
|
||||
# add as many stats as you want.
|
||||
buff:
|
||||
health-regeneration: 3
|
||||
additional-experience: 5
|
||||
|
||||
# The max players that can be in
|
||||
# any given party. Between 2-8.
|
||||
max-players: 8
|
||||
# Prefix you need to put in the chat
|
||||
# to talk in the party chat.
|
||||
chat-prefix: '@'
|
||||
|
||||
# The max players that can be in
|
||||
# any given party. Between 2-8.
|
||||
max-players: 8
|
||||
|
||||
# Redirects vanilla experience obtained to MMOCore
|
||||
# class experience. You can define the % of the vanilla
|
||||
# experience that is being transfered as MMOCore exp.
|
||||
# Requires a SERVER reload when changed.
|
||||
vanilla-exp-redirection:
|
||||
enabled: false
|
||||
ratio: 0.8
|
||||
enabled: false
|
||||
ratio: 0.8
|
||||
|
||||
# Enable this open to override vanilla EXP and display
|
||||
# level progress on the vanilla experience bar.
|
||||
@ -130,25 +130,22 @@ display-main-class-exp-holograms: true
|
||||
|
||||
# Requires a SERVER reload when changed.
|
||||
death-exp-loss:
|
||||
enabled: false
|
||||
enabled: false
|
||||
|
||||
# Percentage of current EXP you lose when dying.
|
||||
percent: 30
|
||||
# Percentage of current EXP you lose when dying.
|
||||
percent: 30
|
||||
|
||||
# Put the "value" of the action you want.
|
||||
# If the value is invalid or empty it will
|
||||
# default to the vanilla minecraft action.
|
||||
# spell_cast
|
||||
# Enters the default spell casting mode
|
||||
# hotbar_swap
|
||||
# Players can swap their hotbar with the 9 inventory slots
|
||||
# right above it by pressing [swap items] while crouching.
|
||||
# THis allows players to have two combat item sets.
|
||||
# - vanilla
|
||||
# The default F action, swapping main hand and offhand.
|
||||
# Modify the keybinds for the 'swap hand items' key (F by default)
|
||||
#
|
||||
# Available actions:
|
||||
# - spell_cast (enters the default spell casting mode)
|
||||
# - vanilla (swaps hand items)
|
||||
# - hotbar_swap (swap the player's horbat with the 9 lowest inventory slots)
|
||||
#
|
||||
# If the action is invalid, it will use 'vanilla' by default
|
||||
swap-keybind:
|
||||
normal: spell_cast
|
||||
sneaking: hotbar_swap
|
||||
normal: spell_cast
|
||||
sneaking: hotbar_swap
|
||||
|
||||
# Set this to true to allow players
|
||||
# in creative mode to enter casting mode
|
||||
@ -163,7 +160,7 @@ prevent-spawner-xp: true
|
||||
|
||||
# Timer for combat log to expire (in seconds)
|
||||
combat-log:
|
||||
timer: 10
|
||||
timer: 10
|
||||
|
||||
# Whether or not the default class should save information (level,
|
||||
# skills, etc.) when selecting a new class
|
||||
@ -172,9 +169,9 @@ save-default-class-info: false
|
||||
# Change this to the name of the color you want for
|
||||
# the different resource bar placeholders
|
||||
resource-bar-colors:
|
||||
stamina-whole: 'GREEN'
|
||||
stamina-half: 'DARK_GREEN'
|
||||
stamina-empty: 'WHITE'
|
||||
stamina-whole: 'GREEN'
|
||||
stamina-half: 'DARK_GREEN'
|
||||
stamina-empty: 'WHITE'
|
||||
|
||||
# Whether or not the admin commands should display
|
||||
# the result of the command when ran.
|
||||
@ -185,11 +182,11 @@ resource-bar-colors:
|
||||
# console - Only verbose when ran from console
|
||||
# false - Never verbose
|
||||
command-verbose:
|
||||
attribute: true
|
||||
class: true
|
||||
experience: true
|
||||
level: true
|
||||
nocd: true
|
||||
points: true
|
||||
reset: true
|
||||
resource: true
|
||||
attribute: true
|
||||
class: true
|
||||
experience: true
|
||||
level: true
|
||||
nocd: true
|
||||
points: true
|
||||
reset: true
|
||||
resource: true
|
Loading…
Reference in New Issue
Block a user