refactor(player): encapsulate game state behind PlayerSettings, clear VisibilityModifier and HiddenField
- ParkourUser/ParkourPlayer/GeneratorState public mutable fields -> private + short accessors - per-player settings extracted to player/data/PlayerSettings (ParkourPlayer 188 -> 96 lines, PMD-counted methods 20 -> 9); this is what fixed the TooManyMethods violation that the encapsulation itself introduced — by decomposition, not by lowering the threshold - disk storage reads and writes the same canonical 12-key map as the SQL JSON column; legacy key names normalized on read, @Expose dropped from domain classes - 39 settings call sites rewritten across 7 files - HiddenField backlog cleared (Config, ObstacleGenerator, GhostService, Leaderboard, PagedMenu, Reward, ParticleData, World) - checkstyle ratchet: VisibilityModifier, HiddenField, EmptyLineSeparator and MissingDeprecated flipped to error once their counters hit zero; suppressions.xml empty - javac -Xmaxwarns raised so NullAway/Error Prone counts are honest (baseline 165/289, now 164/265) checkstyle 0 errors / 135 warnings, PMD 0 violations, tests 94/0 fail, ./gradlew check green. TODO.md: items 30 and 107 updated, new items 167-170, ПРОГРЕСС-6/7 recorded.
This commit is contained in:
parent
c120309663
commit
02e2947f47
90 changed files with 1103 additions and 660 deletions
|
|
@ -26,7 +26,7 @@ public abstract class LPMenu {
|
|||
|
||||
protected String locale(@NotNull Player player) {
|
||||
final var user = dev.loki.loparkour.player.core.ParkourUser.getUser(player);
|
||||
final String loc = user != null ? user.locale : OptionGeneral.optionsDefaults().get(ParkourOption.LANG);
|
||||
final String loc = user != null ? user.locale() : OptionGeneral.optionsDefaults().get(ParkourOption.LANG);
|
||||
return (loc != null && !loc.isBlank()) ? loc : "en";
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,26 +20,26 @@ public final class Menus {
|
|||
}
|
||||
|
||||
// main
|
||||
public static MainMenu MAIN = new MainMenu();
|
||||
public static final MainMenu MAIN = new MainMenu();
|
||||
|
||||
// play
|
||||
public static PlayMenu PLAY = new PlayMenu();
|
||||
public static SingleMenu SINGLE = new SingleMenu();
|
||||
public static SpectatorMenu SPECTATOR = new SpectatorMenu();
|
||||
public static final PlayMenu PLAY = new PlayMenu();
|
||||
public static final SingleMenu SINGLE = new SingleMenu();
|
||||
public static final SpectatorMenu SPECTATOR = new SpectatorMenu();
|
||||
|
||||
// community
|
||||
public static CommunityMenu COMMUNITY = new CommunityMenu();
|
||||
public static LeaderboardsMenu LEADERBOARDS = new LeaderboardsMenu();
|
||||
public static SingleLeaderboardMenu SINGLE_LEADERBOARD = new SingleLeaderboardMenu();
|
||||
public static final CommunityMenu COMMUNITY = new CommunityMenu();
|
||||
public static final LeaderboardsMenu LEADERBOARDS = new LeaderboardsMenu();
|
||||
public static final SingleLeaderboardMenu SINGLE_LEADERBOARD = new SingleLeaderboardMenu();
|
||||
|
||||
// settings
|
||||
public static SettingsMenu SETTINGS = new SettingsMenu();
|
||||
public static LangMenu LANG = new LangMenu();
|
||||
public static ParkourSettingsMenu PARKOUR_SETTINGS = new ParkourSettingsMenu();
|
||||
public static final SettingsMenu SETTINGS = new SettingsMenu();
|
||||
public static final LangMenu LANG = new LangMenu();
|
||||
public static final ParkourSettingsMenu PARKOUR_SETTINGS = new ParkourSettingsMenu();
|
||||
|
||||
// lobby
|
||||
public static DuelsMenu DUELS = new DuelsMenu();
|
||||
public static LobbyMenu LOBBY = new LobbyMenu();
|
||||
public static PlayerManagementMenu PLAYER_MANAGEMENT = new PlayerManagementMenu();
|
||||
public static final DuelsMenu DUELS = new DuelsMenu();
|
||||
public static final LobbyMenu LOBBY = new LobbyMenu();
|
||||
public static final PlayerManagementMenu PLAYER_MANAGEMENT = new PlayerManagementMenu();
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@ public class PagedMenu {
|
|||
this.title = title;
|
||||
}
|
||||
|
||||
public PagedMenu addToDisplay(List<Item> items) {
|
||||
this.items = items;
|
||||
public PagedMenu addToDisplay(List<Item> menuItems) {
|
||||
items = menuItems;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ public class DuelsMenu extends LPMenu {
|
|||
final Session session = sessions.get(i);
|
||||
final int slot = FIRST_SLOT + (i - startIndex);
|
||||
gui = gui.setItem(slot, sessionItem(player, session), e -> {
|
||||
Modes.DUELS.join(player, session);
|
||||
Modes.duels().join(player, session);
|
||||
player.closeInventory();
|
||||
});
|
||||
}
|
||||
|
|
@ -78,7 +78,7 @@ public class DuelsMenu extends LPMenu {
|
|||
}
|
||||
|
||||
private List<Session> collectOpenSessions() {
|
||||
final int maxPlayers = Modes.DUELS.getMaxPlayers();
|
||||
final int maxPlayers = Modes.duels().getMaxPlayers();
|
||||
final List<Session> sessions = new ArrayList<>();
|
||||
for (final Session session : Divider.SECTIONS.keySet()) {
|
||||
if (isOpenForJoining(session, maxPlayers)) {
|
||||
|
|
@ -89,13 +89,13 @@ public class DuelsMenu extends LPMenu {
|
|||
}
|
||||
|
||||
private boolean isOpenForJoining(Session session, int maxPlayers) {
|
||||
return session.generator instanceof DuelsGenerator generator
|
||||
&& generator.allowJoining
|
||||
return session.generator() instanceof DuelsGenerator generator
|
||||
&& generator.allowJoining()
|
||||
&& session.getPlayers().size() < maxPlayers;
|
||||
}
|
||||
|
||||
private ItemStack sessionItem(@NotNull Player player, @NotNull Session session) {
|
||||
final DuelsGenerator generator = (DuelsGenerator) session.generator;
|
||||
final DuelsGenerator generator = (DuelsGenerator) session.generator();
|
||||
final ItemStack item = new ItemStack(Material.PLAYER_HEAD);
|
||||
applySkullOwner(player, item, generator.owner);
|
||||
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ public class LobbyMenu extends LPMenu {
|
|||
return;
|
||||
}
|
||||
|
||||
final String locale = user.locale;
|
||||
final String locale = user.locale();
|
||||
final String title = Locales.getString(locale, "lobby.name");
|
||||
|
||||
final boolean isHost = !user.session.getPlayers().isEmpty()
|
||||
|
|
@ -60,7 +60,7 @@ public class LobbyMenu extends LPMenu {
|
|||
}
|
||||
|
||||
private ItemStack visibilityItem(ParkourUser user) {
|
||||
final String locale = user.locale;
|
||||
final String locale = user.locale();
|
||||
final List<String> values = Locales.getStringList(locale, "lobby.visibility.values");
|
||||
final String label = switch (user.session.getVisibility()) {
|
||||
case PUBLIC -> values.size() > 2 ? values.get(2) : "Public";
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import dev.loki.loparkour.player.core.ParkourUser;
|
|||
import dev.loki.loparkour.session.core.Session;
|
||||
import dev.loki.loparkour.util.text.ColorUtil;
|
||||
import dev.loki.loparkour.menu.gui.inventory.InventoryGui;
|
||||
import dev.loki.loparkour.player.service.UserRegistry;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.inventory.ClickType;
|
||||
|
|
@ -29,13 +30,13 @@ public class PlayerManagementMenu extends LPMenu {
|
|||
|
||||
@Override
|
||||
public void open(@NotNull Player player) {
|
||||
final ParkourPlayer viewer = ParkourPlayer.getPlayer(player);
|
||||
final ParkourPlayer viewer = UserRegistry.getPlayer(player);
|
||||
if (viewer == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final var session = viewer.session;
|
||||
final String locale = viewer.locale;
|
||||
final String locale = viewer.locale();
|
||||
final String title = Locales.getString(locale, "lobby.player_management.name");
|
||||
|
||||
final List<ParkourUser> others = new ArrayList<>();
|
||||
|
|
@ -102,7 +103,7 @@ public class PlayerManagementMenu extends LPMenu {
|
|||
@NotNull Session session, @NotNull ParkourUser other,
|
||||
@NotNull ClickType click) {
|
||||
if (click == ClickType.LEFT) {
|
||||
Modes.DEFAULT.create(other.player);
|
||||
Modes.defaultMode().create(other.player);
|
||||
other.sendTranslated("lobby.player_management.kicked");
|
||||
viewer.sendTranslated("lobby.player_management.advice");
|
||||
} else if (click == ClickType.RIGHT) {
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ public class SpectatorMenu extends LPMenu {
|
|||
for (int i = startIndex; i < endIndex; i++) {
|
||||
final Session session = sessions.get(i);
|
||||
final int slot = FIRST_SLOT + (i - startIndex); // Slots 10-16
|
||||
gui = gui.setItem(slot, items.get(i), e -> Modes.SPECTATOR.create(player, session));
|
||||
gui = gui.setItem(slot, items.get(i), e -> Modes.spectator().create(player, session));
|
||||
}
|
||||
|
||||
if (currentPage > 0) {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import dev.loki.loparkour.menu.core.Menus;
|
|||
import dev.loki.loparkour.player.core.ParkourPlayer;
|
||||
import dev.loki.loparkour.util.text.ColorUtil;
|
||||
import dev.loki.loparkour.menu.gui.inventory.InventoryGui;
|
||||
import dev.loki.loparkour.player.service.UserRegistry;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
|
@ -30,12 +31,12 @@ public class LangMenu extends LPMenu {
|
|||
|
||||
@Override
|
||||
public void open(@NotNull Player player) {
|
||||
final ParkourPlayer pp = ParkourPlayer.getPlayer(player);
|
||||
final ParkourPlayer pp = UserRegistry.getPlayer(player);
|
||||
if (pp == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final String locale = pp.locale;
|
||||
final String locale = pp.locale();
|
||||
final String title = Locales.getString(locale, "settings.lang.name");
|
||||
|
||||
final List<String> langs = new ArrayList<>(Locales.getLocaleKeys());
|
||||
|
|
@ -52,7 +53,7 @@ public class LangMenu extends LPMenu {
|
|||
item.setItemMeta(meta);
|
||||
}
|
||||
gui = gui.setItem(FIRST_SLOT + i, item, e -> {
|
||||
pp.locale = lang;
|
||||
pp.setLocale(lang);
|
||||
pp.updateHotbar(); // Update hotbar with new language
|
||||
Menus.SETTINGS.open(player);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import dev.loki.loparkour.player.core.ParkourPlayer;
|
|||
import dev.loki.loparkour.style.core.Style;
|
||||
import dev.loki.loparkour.util.text.ColorUtil;
|
||||
import dev.loki.loparkour.menu.gui.inventory.InventoryGui;
|
||||
import dev.loki.loparkour.player.service.UserRegistry;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
|
@ -47,27 +48,27 @@ public class ParkourSettingsMenu extends LPMenu {
|
|||
|
||||
@Override
|
||||
public void open(@NotNull Player player) {
|
||||
final ParkourPlayer pp = ParkourPlayer.getPlayer(player);
|
||||
final ParkourPlayer pp = UserRegistry.getPlayer(player);
|
||||
if (pp == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final String locale = pp.locale;
|
||||
final String locale = pp.locale();
|
||||
final String title = Locales.getString(locale, "settings.name");
|
||||
|
||||
baseGui(title, ROWS_MAIN)
|
||||
.setItem(SLOT_PARTICLES, toggleItem(locale, "particles", pp.particles),
|
||||
e -> toggleSetting(pp, () -> pp.particles = !pp.particles))
|
||||
.setItem(SLOT_SOUND, toggleItem(locale, "sound", pp.sound),
|
||||
e -> toggleSetting(pp, () -> pp.sound = !pp.sound))
|
||||
.setItem(SLOT_SPECIAL_BLOCKS, toggleItem(locale, "special_blocks", pp.useSpecialBlocks),
|
||||
.setItem(SLOT_PARTICLES, toggleItem(locale, "particles", pp.settings().particles()),
|
||||
e -> toggleSetting(pp, () -> pp.settings().setParticles(!pp.settings().particles())))
|
||||
.setItem(SLOT_SOUND, toggleItem(locale, "sound", pp.settings().sound()),
|
||||
e -> toggleSetting(pp, () -> pp.settings().setSound(!pp.settings().sound())))
|
||||
.setItem(SLOT_SPECIAL_BLOCKS, toggleItem(locale, "special_blocks", pp.settings().useSpecialBlocks()),
|
||||
e -> toggleSpecialBlocks(pp))
|
||||
.setItem(SLOT_FALL_MESSAGE, toggleItem(locale, "fall_message", pp.showFallMessage),
|
||||
e -> toggleSetting(pp, () -> pp.showFallMessage = !pp.showFallMessage))
|
||||
.setItem(SLOT_SCOREBOARD, toggleItem(locale, "scoreboard", pp.showScoreboard),
|
||||
e -> toggleSetting(pp, () -> pp.showScoreboard = !pp.showScoreboard))
|
||||
.setItem(SLOT_FALL_MESSAGE, toggleItem(locale, "fall_message", pp.settings().showFallMessage()),
|
||||
e -> toggleSetting(pp, () -> pp.settings().setShowFallMessage(!pp.settings().showFallMessage())))
|
||||
.setItem(SLOT_SCOREBOARD, toggleItem(locale, "scoreboard", pp.settings().showScoreboard()),
|
||||
e -> toggleSetting(pp, () -> pp.settings().setShowScoreboard(!pp.settings().showScoreboard())))
|
||||
.setItem(SLOT_GHOST_RECORDING, toggleItem(locale, "ghost_recording",
|
||||
Boolean.TRUE.equals(pp.ghostRecording)), e -> toggleGhostRecording(pp))
|
||||
Boolean.TRUE.equals(pp.settings().ghostRecording())), e -> toggleGhostRecording(pp))
|
||||
.setItem(SLOT_STYLE, styleItem(pp), e -> openStyleMenu(pp))
|
||||
.setItem(SLOT_SCHEM_DIFF, schemDiffItem(pp), e -> cycleSchematicDifficulty(pp))
|
||||
.setItem(SLOT_CLOSE, closeButton(player), e -> Menus.SETTINGS.open(player))
|
||||
|
|
@ -76,30 +77,30 @@ public class ParkourSettingsMenu extends LPMenu {
|
|||
|
||||
private void toggleSetting(ParkourPlayer pp, Runnable update) {
|
||||
update.run();
|
||||
pp.updateGeneratorSettings(pp.session.generator);
|
||||
pp.updateGeneratorSettings(pp.session.generator());
|
||||
open(pp);
|
||||
}
|
||||
|
||||
private void toggleSpecialBlocks(ParkourPlayer pp) {
|
||||
if (pp.session.generator.state.score == 0) {
|
||||
pp.useSpecialBlocks = !pp.useSpecialBlocks;
|
||||
pp.updateGeneratorSettings(pp.session.generator);
|
||||
if (pp.session.generator().state.score() == 0) {
|
||||
pp.settings().setUseSpecialBlocks(!pp.settings().useSpecialBlocks());
|
||||
pp.updateGeneratorSettings(pp.session.generator());
|
||||
}
|
||||
open(pp);
|
||||
}
|
||||
|
||||
private void cycleSchematicDifficulty(ParkourPlayer pp) {
|
||||
if (pp.session.generator.state.score == 0) {
|
||||
if (pp.session.generator().state.score() == 0) {
|
||||
final List<Double> diffs = List.of(0.0, DIFF_QUARTER, DIFF_HALF, DIFF_THREE_QUARTER, 1.0);
|
||||
final int idx = diffs.indexOf(pp.schematicDifficulty);
|
||||
pp.schematicDifficulty = diffs.get((idx + 1) % diffs.size());
|
||||
pp.updateGeneratorSettings(pp.session.generator);
|
||||
final int idx = diffs.indexOf(pp.settings().schematicDifficulty());
|
||||
pp.settings().setSchematicDifficulty(diffs.get((idx + 1) % diffs.size()));
|
||||
pp.updateGeneratorSettings(pp.session.generator());
|
||||
}
|
||||
open(pp);
|
||||
}
|
||||
|
||||
private void toggleGhostRecording(ParkourPlayer pp) {
|
||||
pp.ghostRecording = !Boolean.TRUE.equals(pp.ghostRecording);
|
||||
pp.settings().setGhostRecording(!Boolean.TRUE.equals(pp.settings().ghostRecording()));
|
||||
pp.save(false);
|
||||
open(pp);
|
||||
}
|
||||
|
|
@ -122,16 +123,16 @@ public class ParkourSettingsMenu extends LPMenu {
|
|||
|
||||
private ItemStack schemDiffItem(ParkourPlayer pp) {
|
||||
final List<Double> diffs = List.of(0.0, DIFF_QUARTER, DIFF_HALF, DIFF_THREE_QUARTER, 1.0);
|
||||
final List<String> values = Locales.getStringList(pp.locale, "settings.parkour_settings.items.schematics.values");
|
||||
final int idx = Math.max(0, diffs.indexOf(pp.schematicDifficulty));
|
||||
final String label = idx < values.size() ? values.get(idx) : String.valueOf(pp.schematicDifficulty);
|
||||
final List<String> values = Locales.getStringList(pp.locale(), "settings.parkour_settings.items.schematics.values");
|
||||
final int idx = Math.max(0, diffs.indexOf(pp.settings().schematicDifficulty()));
|
||||
final String label = idx < values.size() ? values.get(idx) : String.valueOf(pp.settings().schematicDifficulty());
|
||||
final Material[] mats = {Material.RED_STAINED_GLASS_PANE, Material.LIME_STAINED_GLASS_PANE,
|
||||
Material.YELLOW_STAINED_GLASS_PANE, Material.ORANGE_STAINED_GLASS_PANE, Material.SKELETON_SKULL};
|
||||
final ItemStack item = new ItemStack(idx < mats.length ? mats[idx] : Material.PAPER);
|
||||
final ItemMeta meta = item.getItemMeta();
|
||||
if (meta != null) {
|
||||
final String name = Locales.getString(pp.locale, "settings.parkour_settings.items.schematics.name");
|
||||
final String lore = Locales.getString(pp.locale, "settings.parkour_settings.items.schematics.lore");
|
||||
final String name = Locales.getString(pp.locale(), "settings.parkour_settings.items.schematics.name");
|
||||
final String lore = Locales.getString(pp.locale(), "settings.parkour_settings.items.schematics.lore");
|
||||
meta.setDisplayName(ColorUtil.color(name.replace("%s", label)));
|
||||
applyLore(meta, lore, label);
|
||||
item.setItemMeta(meta);
|
||||
|
|
@ -140,13 +141,13 @@ public class ParkourSettingsMenu extends LPMenu {
|
|||
}
|
||||
|
||||
private ItemStack styleItem(ParkourPlayer pp) {
|
||||
final String name = Locales.getString(pp.locale, "settings.parkour_settings.items.styles.item.name");
|
||||
final String lore = Locales.getString(pp.locale, "settings.parkour_settings.items.styles.item.lore");
|
||||
final String name = Locales.getString(pp.locale(), "settings.parkour_settings.items.styles.item.name");
|
||||
final String lore = Locales.getString(pp.locale(), "settings.parkour_settings.items.styles.item.lore");
|
||||
final ItemStack item = new ItemStack(Material.CRAFTING_TABLE);
|
||||
final ItemMeta meta = item.getItemMeta();
|
||||
if (meta != null) {
|
||||
meta.setDisplayName(ColorUtil.color(name.replace("%s", pp.style)));
|
||||
applyLore(meta, lore, pp.style);
|
||||
meta.setDisplayName(ColorUtil.color(name.replace("%s", pp.settings().style())));
|
||||
applyLore(meta, lore, pp.settings().style());
|
||||
item.setItemMeta(meta);
|
||||
}
|
||||
return item;
|
||||
|
|
@ -165,7 +166,7 @@ public class ParkourSettingsMenu extends LPMenu {
|
|||
|
||||
private void openStyleMenu(ParkourPlayer pp) {
|
||||
final Player player = pp.player;
|
||||
final String locale = pp.locale;
|
||||
final String locale = pp.locale();
|
||||
final String title = Locales.getString(locale, "settings.parkour_settings.styles.name");
|
||||
|
||||
InventoryGui gui = baseGui(title, ROWS_STYLE);
|
||||
|
|
@ -187,10 +188,9 @@ public class ParkourSettingsMenu extends LPMenu {
|
|||
item.setItemMeta(meta);
|
||||
}
|
||||
|
||||
final Style s = style;
|
||||
gui = gui.setItem(slot++, item, e -> {
|
||||
pp.style = s.getName();
|
||||
pp.updateGeneratorSettings(pp.session.generator);
|
||||
pp.settings().setStyle(style.getName());
|
||||
pp.updateGeneratorSettings(pp.session.generator());
|
||||
open(pp);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import dev.loki.loparkour.menu.core.screen.ParkourOption;
|
|||
import dev.loki.loparkour.player.core.ParkourPlayer;
|
||||
import dev.loki.loparkour.player.core.ParkourUser;
|
||||
import dev.loki.loparkour.menu.gui.inventory.InventoryGui;
|
||||
import dev.loki.loparkour.player.service.UserRegistry;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
|
|
@ -24,11 +25,11 @@ public class SettingsMenu extends LPMenu {
|
|||
|
||||
InventoryGui gui = baseGui(title, ROWS);
|
||||
|
||||
if (ParkourOption.PARKOUR_SETTINGS.mayPerform(player) && ParkourPlayer.isPlayer(player)) {
|
||||
if (ParkourOption.PARKOUR_SETTINGS.mayPerform(player) && UserRegistry.isPlayer(player)) {
|
||||
gui = gui.setItem(SLOT_PARKOUR_SETTINGS, localeItem(player, "settings.parkour_settings.item"), e -> {
|
||||
final ParkourPlayer pp = ParkourPlayer.getPlayer(player);
|
||||
final ParkourPlayer pp = UserRegistry.getPlayer(player);
|
||||
if (pp != null) {
|
||||
pp.session.generator.menu(pp);
|
||||
pp.session.generator().menu(pp);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -36,7 +37,7 @@ public class SettingsMenu extends LPMenu {
|
|||
if (ParkourOption.LANG.mayPerform(player) && ParkourUser.isUser(player)) {
|
||||
final String langName = Locales.getString(locale, "name");
|
||||
gui = gui.setItem(SLOT_LANG, localeItem(locale, "settings.lang.item", langName), e -> {
|
||||
final ParkourPlayer pp = ParkourPlayer.getPlayer(player);
|
||||
final ParkourPlayer pp = UserRegistry.getPlayer(player);
|
||||
if (pp != null) {
|
||||
Menus.LANG.open(pp);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue