diff --git a/src/main/java/ru/simsonic/rscPermissions/API/Settings.java b/src/main/java/ru/simsonic/rscPermissions/API/Settings.java index fad44a1..0169798 100644 --- a/src/main/java/ru/simsonic/rscPermissions/API/Settings.java +++ b/src/main/java/ru/simsonic/rscPermissions/API/Settings.java @@ -5,21 +5,25 @@ import ru.simsonic.rscCommonsLibrary.ConnectionMySQL.ConnectionParams; public interface Settings { - public static final String chatPrefix = "{GOLD}[rscp] {_LS}"; - public static final String separator = "."; - public static final String separatorRegExp = "\\."; - public static final String instantiator = "?"; - public static final String textInheriter = "%"; - public static final char groupLevelTab = '┏'; + public static final String chatPrefix = "{GOLD}[rscp] {_LS}"; + public static final String separator = "."; + public static final String separatorRegExp = "\\."; + public static final String instantiator = "?"; + public static final String textInheriter = "%"; + public static final char groupLevelTab = '┏'; public static final boolean decolorizeForConsole = false; public void onLoad(); public void readSettings(); public String getDefaultGroup(); public boolean isDefaultForever(); public boolean isAsteriskOP(); + public boolean isUsingAncestorPrefixes(); public boolean isInMaintenance(); public String getMaintenanceMode(); public void setMaintenanceMode(String mode); + public String getMaintenancePingMsg(); + public String getMaintenanceKickMsg(); + public String getMaintenanceJoinMsg(); public boolean isUseResidence(); public boolean isUseWorldGuard(); public long getRegionFinderGranularity(); diff --git a/src/main/java/ru/simsonic/rscPermissions/Bukkit/BukkitEventListener.java b/src/main/java/ru/simsonic/rscPermissions/Bukkit/BukkitEventListener.java index 6eb0d1c..99a36bb 100644 --- a/src/main/java/ru/simsonic/rscPermissions/Bukkit/BukkitEventListener.java +++ b/src/main/java/ru/simsonic/rscPermissions/Bukkit/BukkitEventListener.java @@ -14,9 +14,9 @@ import org.bukkit.event.player.PlayerLevelChangeEvent; import org.bukkit.event.player.PlayerLoginEvent; import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.event.server.ServerListPingEvent; -import ru.simsonic.rscMinecraftLibrary.Bukkit.GenericChatCodes; import ru.simsonic.rscMinecraftLibrary.Bukkit.Tools; import ru.simsonic.rscPermissions.BukkitPluginMain; +import ru.simsonic.rscPermissions.Engine.Phrases; import ru.simsonic.rscPermissions.Engine.ResolutionResult; public class BukkitEventListener implements Listener @@ -88,11 +88,7 @@ public class BukkitEventListener implements Listener public void onServerPing(ServerListPingEvent event) { if(rscp.settings.isInMaintenance()) - { - final String motd = GenericChatCodes.processStringStatic( - "Server is under maintenance"); - event.setMotd(motd); - } + event.setMotd(rscp.settings.getMaintenancePingMsg()); } private void processMaintenanceLogin(AsyncPlayerPreLoginEvent event, ResolutionResult resolution) { @@ -108,9 +104,7 @@ public class BukkitEventListener implements Listener event.allow(); return; } - final String kickMsg = GenericChatCodes.processStringStatic( - "{_YL}Server is in maintenance mode\nPlease try to connect later..."); - event.disallow(AsyncPlayerPreLoginEvent.Result.KICK_WHITELIST, kickMsg); + event.disallow(AsyncPlayerPreLoginEvent.Result.KICK_WHITELIST, rscp.settings.getMaintenanceJoinMsg()); } private void processLimitedSlotsLogin(AsyncPlayerPreLoginEvent event, ResolutionResult resolution) { @@ -137,9 +131,7 @@ public class BukkitEventListener implements Listener event.allow(); return; } - final String kickMsg = GenericChatCodes.processStringStatic( - "{_LR}Server is too full to allow you enter.\n{_YL}Please try to connect later..."); - event.disallow(AsyncPlayerPreLoginEvent.Result.KICK_WHITELIST, kickMsg); + event.disallow(AsyncPlayerPreLoginEvent.Result.KICK_WHITELIST, Phrases.SERVER_IS_FULL.toString()); } public void setMaintenanceMode(String mMode) { @@ -151,14 +143,13 @@ public class BukkitEventListener implements Listener @Override public void run() { + final String kick = rscp.settings.getMaintenanceKickMsg(); for(Player player : Tools.getOnlinePlayers()) { if(player.hasPermission("rscp.maintenance.*")) continue; if(player.hasPermission("rscp.maintenance." + rscp.settings.getMaintenanceMode())) continue; - final String kick = GenericChatCodes.processStringStatic( - "{_LR}Server is going into maintenance mode.\n{_YL}Please try to connect later..."); player.kickPlayer(kick); } } diff --git a/src/main/java/ru/simsonic/rscPermissions/Bukkit/BukkitPluginConfiguration.java b/src/main/java/ru/simsonic/rscPermissions/Bukkit/BukkitPluginConfiguration.java index 22fc02e..4668d27 100644 --- a/src/main/java/ru/simsonic/rscPermissions/Bukkit/BukkitPluginConfiguration.java +++ b/src/main/java/ru/simsonic/rscPermissions/Bukkit/BukkitPluginConfiguration.java @@ -7,24 +7,30 @@ import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.YamlConfiguration; import ru.simsonic.rscCommonsLibrary.ConnectionMySQL.ConnectionParams; +import ru.simsonic.rscMinecraftLibrary.Bukkit.GenericChatCodes; import ru.simsonic.rscPermissions.API.Settings; import ru.simsonic.rscPermissions.API.TranslationProvider; import ru.simsonic.rscPermissions.BukkitPluginMain; +import ru.simsonic.rscPermissions.Engine.Phrases; public class BukkitPluginConfiguration implements Settings { private final BukkitPluginMain plugin; private String strDefaultGroup = "Default"; private String strMaintenanceMode = ""; + private String strMaintenancePingMsg = Phrases.defaultMaintenancePingMsg; + private String strMaintenanceKickMsg = Phrases.defaultMaintenanceKickMsg; + private String strMaintenanceJoinMsg = Phrases.defaultMaintenanceJoinMsg; private String language = "english"; private boolean bAlwaysInheritDefault = false; private boolean bTreatAsteriskAsOP = true; + private boolean bUsingAncestorPrefixes = true; private boolean bUseMetrics = true; private boolean bUseWorldGuard = true; private boolean bUseResidence = true; private int nAutoReloadDelayTicks = 20 * 900; private int nRegionFinderGranularity = 1000; - public final int CurrentVersion = 3; + public final int CurrentVersion = 4; public BukkitPluginConfiguration(final BukkitPluginMain plugin) { this.plugin = plugin; @@ -42,6 +48,9 @@ public class BukkitPluginConfiguration implements Settings case 2: update_v2_to_v3(config); BukkitPluginMain.consoleLog.info("[rscp] Configuration updated from v2 to v3."); + case 3: + update_v3_to_v4(config); + BukkitPluginMain.consoleLog.info("[rscp] Configuration updated from v3 to v4."); case CurrentVersion: // Current version plugin.saveConfig(); break; @@ -61,20 +70,34 @@ public class BukkitPluginConfiguration implements Settings config.set("settings.language", "english"); config.set("internal.version", 3); } + private void update_v3_to_v4(FileConfiguration config) + { + if(!config.contains("settings.slot-limits")) + { + config.set("settings.slot-limits.administrators", 5); + config.set("settings.slot-limits.premium", 25); + } + config.set("settings.groups-inherit-parent-prefixes", true); + config.set("settings.maintenances.default.ping-motd", Phrases.defaultMaintenancePingMsg); + config.set("settings.maintenances.default.kick-online", Phrases.defaultMaintenancePingMsg); + config.set("settings.maintenances.default.block-join", Phrases.defaultMaintenancePingMsg); + config.set("internal.version", 4); + } @Override public void readSettings() { plugin.reloadConfig(); final FileConfiguration config = plugin.getConfig(); - language = config.getString("settings.language", "english"); - strDefaultGroup = config.getString("settings.default-group", "Default"); - strMaintenanceMode = config.getString("settings.maintenance-mode", ""); - bAlwaysInheritDefault = config.getBoolean("settings.always-inherit-default-group", false); - bTreatAsteriskAsOP = config.getBoolean("settings.treat-asterisk-as-op", true); - bUseMetrics = config.getBoolean("settings.use-metrics", true); - bUseWorldGuard = config.getBoolean("settings.integration.worldguard", true); - bUseResidence = config.getBoolean("settings.integration.residence", true); - nAutoReloadDelayTicks = config.getInt("settings.auto-reload-delay-sec", 900) * 20; + language = config.getString("settings.language", "english"); + strDefaultGroup = config.getString("settings.default-group", "Default"); + strMaintenanceMode = config.getString("settings.maintenance-mode", ""); + bAlwaysInheritDefault = config.getBoolean("settings.always-inherit-default-group", false); + bTreatAsteriskAsOP = config.getBoolean("settings.treat-asterisk-as-op", true); + bUsingAncestorPrefixes = config.getBoolean("settings.groups-inherit-parent-prefixes", true); + bUseWorldGuard = config.getBoolean("settings.integration.worldguard", true); + bUseResidence = config.getBoolean("settings.integration.residence", true); + bUseMetrics = config.getBoolean("settings.use-metrics", true); + nAutoReloadDelayTicks = config.getInt("settings.auto-reload-delay-sec", 900) * 20; nRegionFinderGranularity = config.getInt("settings.region-finder-thread-granularity-msec", 1000); if(nAutoReloadDelayTicks <= 0) nAutoReloadDelayTicks = -1; @@ -100,6 +123,33 @@ public class BukkitPluginConfiguration implements Settings strMaintenanceMode = (mode != null) ? mode : ""; plugin.getConfig().set("settings.maintenance-mode", strMaintenanceMode); plugin.saveConfig(); + if(!"".equals(mode)) + { + strMaintenancePingMsg = GenericChatCodes.processStringStatic(plugin.getConfig().getString( + "settings.maintenances." + strMaintenanceMode.toLowerCase() + ".ping-motd", + Phrases.defaultMaintenancePingMsg).replace("{MMODE}", strMaintenanceMode)); + strMaintenanceKickMsg = GenericChatCodes.processStringStatic(plugin.getConfig().getString( + "settings.maintenances." + strMaintenanceMode.toLowerCase() + ".kick-online", + Phrases.defaultMaintenanceKickMsg).replace("{MMODE}", strMaintenanceMode)); + strMaintenanceJoinMsg = GenericChatCodes.processStringStatic(plugin.getConfig().getString( + "settings.maintenances." + strMaintenanceMode.toLowerCase() + ".block-join", + Phrases.defaultMaintenanceJoinMsg).replace("{MMODE}", strMaintenanceMode)); + } + } + @Override + public String getMaintenancePingMsg() + { + return isInMaintenance() ? strMaintenancePingMsg : ""; + } + @Override + public String getMaintenanceKickMsg() + { + return isInMaintenance() ? strMaintenanceKickMsg : ""; + } + @Override + public String getMaintenanceJoinMsg() + { + return isInMaintenance() ? strMaintenanceJoinMsg : ""; } @Override public boolean isDefaultForever() @@ -111,6 +161,10 @@ public class BukkitPluginConfiguration implements Settings { return bTreatAsteriskAsOP; } + public boolean isUsingAncestorPrefixes() + { + return bUsingAncestorPrefixes; + } @Override public boolean isUseMetrics() { diff --git a/src/main/java/ru/simsonic/rscPermissions/Bukkit/Commands/CommandLock.java b/src/main/java/ru/simsonic/rscPermissions/Bukkit/Commands/CommandLock.java index ed28f47..c99cda6 100644 --- a/src/main/java/ru/simsonic/rscPermissions/Bukkit/Commands/CommandLock.java +++ b/src/main/java/ru/simsonic/rscPermissions/Bukkit/Commands/CommandLock.java @@ -3,6 +3,7 @@ package ru.simsonic.rscPermissions.Bukkit.Commands; import org.bukkit.command.CommandSender; import ru.simsonic.rscMinecraftLibrary.Bukkit.CommandAnswerException; import ru.simsonic.rscPermissions.BukkitPluginMain; +import ru.simsonic.rscPermissions.Engine.Phrases; public class CommandLock { @@ -15,22 +16,17 @@ public class CommandLock { if(sender.hasPermission("rscp.lock")) { - final String mMode = (args.length >= 2) ? args[1] : "default"; - String mmon = "Maintenance mode enabled"; - mmon = rscp.getConfig().getString("language.maintenance.locked.default.mmon", mmon); - mmon = rscp.getConfig().getString("language.maintenance.locked." + mMode + ".mmon", mmon); - rscp.bukkitListener.setMaintenanceMode(mMode); - throw new CommandAnswerException(mmon); + final String mode = (args.length >= 2) ? args[1] : "default"; + rscp.bukkitListener.setMaintenanceMode(mode); + throw new CommandAnswerException(Phrases.MAINTENANCE_ON.toString()); } } public void executeUnlock(CommandSender sender) throws CommandAnswerException { if(sender.hasPermission("rscp.lock")) { - String mmoff = "Maintenance mode disabled"; - mmoff = rscp.getConfig().getString("language.maintenance.unlocked", mmoff); rscp.bukkitListener.setMaintenanceMode(null); - throw new CommandAnswerException(mmoff); + throw new CommandAnswerException(Phrases.MAINTENANCE_OFF.toString()); } } } diff --git a/src/main/java/ru/simsonic/rscPermissions/BukkitPluginMain.java b/src/main/java/ru/simsonic/rscPermissions/BukkitPluginMain.java index 5a1362e..90f553f 100644 --- a/src/main/java/ru/simsonic/rscPermissions/BukkitPluginMain.java +++ b/src/main/java/ru/simsonic/rscPermissions/BukkitPluginMain.java @@ -55,7 +55,8 @@ public final class BukkitPluginMain extends JavaPlugin bukkitListener.onEnable(); internalCache.setDefaultGroup( settings.getDefaultGroup(), - settings.isDefaultForever()); + settings.isDefaultForever(), + settings.isUsingAncestorPrefixes()); Phrases.applyTranslation(settings.getTranslationProvider()); // Restore temporary cached data from json files final DatabaseContents contents = localStorage.retrieveContents(); diff --git a/src/main/java/ru/simsonic/rscPermissions/Engine/InternalCache.java b/src/main/java/ru/simsonic/rscPermissions/Engine/InternalCache.java index 1719f47..17eda3d 100644 --- a/src/main/java/ru/simsonic/rscPermissions/Engine/InternalCache.java +++ b/src/main/java/ru/simsonic/rscPermissions/Engine/InternalCache.java @@ -24,13 +24,15 @@ public class InternalCache private final HashMap entities_u = new HashMap<>(); private final RowInheritance defaultInheritance = new RowInheritance(); private boolean alwaysInheritDefaultGroup = false; + private boolean groupsInheritParentPrefixes = true; private RowEntity implicit_g; private RowEntity implicit_u; - public void setDefaultGroup(String defaultGroup, boolean alwaysInheritDefaultGroup) + public void setDefaultGroup(String defaultGroup, boolean alwaysInheritDefaultGroup, boolean groupsInheritParentPrefixes) { defaultInheritance.parent = defaultGroup; defaultInheritance.deriveInstance(); - this.alwaysInheritDefaultGroup = alwaysInheritDefaultGroup; + this.alwaysInheritDefaultGroup = alwaysInheritDefaultGroup; + this.groupsInheritParentPrefixes = groupsInheritParentPrefixes; } public synchronized void fill(DatabaseContents contents) { @@ -264,12 +266,13 @@ public class InternalCache private ResolutionResult processPrefixesAndSuffixes(ResolutionParams params, List intermediate) { final ResolutionResult result = new ResolutionResult(); + final boolean gipp = groupsInheritParentPrefixes || params.parentEntity.entityType.equals(EntityType.PLAYER); result.prefix = params.parentEntity.prefix; result.suffix = params.parentEntity.suffix; if(result.prefix == null || "".equals(result.prefix)) - result.prefix = "%"; + result.prefix = (gipp ? Settings.textInheriter : ""); if(result.suffix == null || "".equals(result.suffix)) - result.suffix = "%"; + result.suffix = (gipp ? Settings.textInheriter : ""); final StringBuilder sbp = new StringBuilder(); final StringBuilder sbs = new StringBuilder(); for(ResolutionResult inherited : intermediate) diff --git a/src/main/java/ru/simsonic/rscPermissions/Engine/Phrases.java b/src/main/java/ru/simsonic/rscPermissions/Engine/Phrases.java index 11f33ad..01d3a65 100644 --- a/src/main/java/ru/simsonic/rscPermissions/Engine/Phrases.java +++ b/src/main/java/ru/simsonic/rscPermissions/Engine/Phrases.java @@ -17,6 +17,9 @@ public enum Phrases PLUGIN_RELOADED ("generic.reloaded"), PLUGIN_PLAYER_ONLY ("generic.player-only"), PLUGIN_CONSOLE_ONLY("generic.console-only"), + SERVER_IS_FULL ("generic.server-is-full"), + MAINTENANCE_ON ("generic.maintenance-on"), + MAINTENANCE_OFF ("generic.maintenance-off"), INTEGRATION_V_Y ("integration.vault-yes"), INTEGRATION_V_N ("integration.vault-no"), INTEGRATION_WE_Y ("integration.worldedit-yes"), @@ -45,6 +48,9 @@ public enum Phrases HELP_CMD_RELOAD ("help.cmd-reload"), HELP_CMD_HELP ("help.cmd-help"), ; + public final static String defaultMaintenancePingMsg = "{_LR}Maintenance mode"; + public final static String defaultMaintenanceKickMsg = "{_LR}Sorry! Server is going into maintenance mode."; + public final static String defaultMaintenanceJoinMsg = "{_LR}You are not allowed to enter when maintenance is on."; private final String node; private String phrase; private Phrases(String node) diff --git a/src/main/java/ru/simsonic/rscPermissions/IndependentMain.java b/src/main/java/ru/simsonic/rscPermissions/IndependentMain.java index db86d09..5fce9b0 100644 --- a/src/main/java/ru/simsonic/rscPermissions/IndependentMain.java +++ b/src/main/java/ru/simsonic/rscPermissions/IndependentMain.java @@ -42,7 +42,7 @@ public class IndependentMain System.out.println("Permission database is empty, stopping."); return; } - intCache.setDefaultGroup("Default", true); + intCache.setDefaultGroup("Default", true, true); intCache.fill(contents); final ResolutionResult result = intCache.resolvePlayer("87f946d8212440539d685eab07f8e266"); // Sorted output diff --git a/src/main/java/ru/simsonic/rscPermissions/Sponge/SpongePluginConfiguration.java b/src/main/java/ru/simsonic/rscPermissions/Sponge/SpongePluginConfiguration.java index a4cfd25..183c75b 100644 --- a/src/main/java/ru/simsonic/rscPermissions/Sponge/SpongePluginConfiguration.java +++ b/src/main/java/ru/simsonic/rscPermissions/Sponge/SpongePluginConfiguration.java @@ -39,6 +39,11 @@ public class SpongePluginConfiguration implements Settings throw new UnsupportedOperationException("Not supported yet."); } @Override + public boolean isUsingAncestorPrefixes() + { + throw new UnsupportedOperationException("Not supported yet."); + } + @Override public boolean isInMaintenance() { throw new UnsupportedOperationException("Not supported yet."); @@ -54,6 +59,21 @@ public class SpongePluginConfiguration implements Settings throw new UnsupportedOperationException("Not supported yet."); } @Override + public String getMaintenancePingMsg() + { + throw new UnsupportedOperationException("Not supported yet."); + } + @Override + public String getMaintenanceKickMsg() + { + throw new UnsupportedOperationException("Not supported yet."); + } + @Override + public String getMaintenanceJoinMsg() + { + throw new UnsupportedOperationException("Not supported yet."); + } + @Override public boolean isUseResidence() { throw new UnsupportedOperationException("Not supported yet."); diff --git a/src/main/java/ru/simsonic/rscPermissions/SpongePluginMain.java b/src/main/java/ru/simsonic/rscPermissions/SpongePluginMain.java index 7862c2a..382cb0b 100644 --- a/src/main/java/ru/simsonic/rscPermissions/SpongePluginMain.java +++ b/src/main/java/ru/simsonic/rscPermissions/SpongePluginMain.java @@ -18,7 +18,7 @@ import ru.simsonic.rscPermissions.Engine.InternalCache; import ru.simsonic.rscPermissions.Sponge.SpongePermissionManager; import ru.simsonic.rscPermissions.Sponge.SpongePluginConfiguration; -// Documentation for Sponge: https://docs.spongepowered.org/en/index.html +// Documentation for Sponge: https://docs.spongepowered.org/ru/index.html @Plugin(id = "rscPermissions", name = "rscPermissions", version = "0.9.16b") public class SpongePluginMain diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 8050344..0899bb5 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -1,22 +1,28 @@ settings: + language: english default-group: Default always-inherit-default-group: true + groups-inherit-parent-prefixes: true treat-asterisk-as-op: true auto-reload-delay-sec: 900 region-finder-thread-granularity-msec: 1000 - integration: - worldguard: true - residence: true - use-metrics: true connection: database: localhost:3306/minecraft username: user1 password: pass1 prefixes: rscp_ + integration: + worldguard: true + residence: true + use-metrics: true slot-limits: administrators: 5 premium: 25 maintenance-mode: '' - language: english + maintenances: + default: + ping-motd: '{_LR}Maintenance mode' + kick-online: '{_LR}Sorry! Server is going into maintenance mode.' + block-join: '{_LR}You are not allowed to enter when maintenance is on.' internal: - version: 3 + version: 4 diff --git a/src/main/resources/languages/english.yml b/src/main/resources/languages/english.yml index 958399e..2a57cd3 100644 --- a/src/main/resources/languages/english.yml +++ b/src/main/resources/languages/english.yml @@ -1,10 +1,13 @@ generic: - enabled: "[rscp] rscPermissions has been successfully enabled." - disabled: "[rscp] rscPermissions has been disabled." - reloaded: "[rscp] rscPermissions has been reloaded." - metrics: "[rscp] Metrics enabled." - player-only: "{_LR}This command cannot be run from console." - console-only: "{_LR}This command should be run from console." + enabled: "[rscp] rscPermissions has been successfully enabled." + disabled: "[rscp] rscPermissions has been disabled." + reloaded: "[rscp] rscPermissions has been reloaded." + metrics: "[rscp] Metrics enabled." + player-only: "{_LR}This command cannot be run from console." + console-only: "{_LR}This command should be run from console." + server-is-full: "{_LR}Server is full, empty slots are in reserve." + maintenance-on: "{_YL}Server is closed for maintenance now." + maintenance-off: "{_YL}Server is open for players now." integration: vault-yes: "{_LG}Vault was found and integrated." vault-no: "{_LR}There is no Vault found. Vault is highly recommended to be installed on your server." diff --git a/src/main/resources/languages/russian.yml b/src/main/resources/languages/russian.yml index 8685f7c..47adbf2 100644 --- a/src/main/resources/languages/russian.yml +++ b/src/main/resources/languages/russian.yml @@ -1,10 +1,13 @@ generic: - enabled: "[rscp] Плагин rscPermissions успешно включён." - disabled: "[rscp] Плагин rscPermissions выключен." - reloaded: "[rscp] Плагин rscPermissions перезапущен, конфигурация перечитана." - metrics: "[rscp] Включён сбор метрики (mcstats.org)." - player-only: "{_LR}Эта команда не может быть использована из консоли." - console-only: "{_LR}Эта команда может быть использована только из консоли." + enabled: "[rscp] Плагин rscPermissions успешно включён." + disabled: "[rscp] Плагин rscPermissions выключен." + reloaded: "[rscp] Плагин rscPermissions перезапущен, конфигурация перечитана." + metrics: "[rscp] Включён сбор метрики (mcstats.org)." + player-only: "{_LR}Эта команда не может быть использована из консоли." + console-only: "{_LR}Эта команда может быть использована только из консоли." + server-is-full: "{_LR}Сервер заполнен, оставшиеся слоты находятся в резерве." + maintenance-on: "{_YL}Сервер закрыт на техническое обслуживание." + maintenance-off: "{_YL}Сервер открыт для свободного входа игроков." integration: vault-yes: "{_LG}Vault обнаружён и подключён." vault-no: "{_LR}Не удаётся найти плагин Vault. Рекомендуется его скорейшая установка."