mirror of
https://github.com/WiIIiam278/HuskSync.git
synced 2025-12-24 00:59:18 +00:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
414246f243 | ||
|
|
a3e269c00b | ||
|
|
bf9f29ffe9 | ||
|
|
29bd2e1319 | ||
|
|
2475a9b3c6 | ||
|
|
2a52cc9086 | ||
|
|
237abf9698 | ||
|
|
adbc264532 | ||
|
|
f9cfec7d03 | ||
|
|
29805bfe04 | ||
|
|
8d2e5a6a52 | ||
|
|
d4f61bd646 | ||
|
|
55173be04b | ||
|
|
e7078c9542 |
9
.github/workflows/update_docs.yml
vendored
9
.github/workflows/update_docs.yml
vendored
@@ -19,9 +19,6 @@ jobs:
|
||||
- name: 'Checkout for CI 🛎️'
|
||||
uses: actions/checkout@v4
|
||||
- name: 'Push Docs to Github Wiki 📄️'
|
||||
uses: Andrew-Chen-Wang/github-wiki-action@v3
|
||||
env:
|
||||
WIKI_DIR: 'docs/'
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
GH_MAIL: 'actions@github.com'
|
||||
GH_NAME: 'github-actions[bot]'
|
||||
uses: Andrew-Chen-Wang/github-wiki-action@v4
|
||||
with:
|
||||
path: 'docs'
|
||||
@@ -7,7 +7,7 @@ dependencies {
|
||||
implementation 'net.william278:mapdataapi:1.0.3'
|
||||
implementation 'net.william278:andjam:1.0.2'
|
||||
implementation 'me.lucko:commodore:2.2'
|
||||
implementation 'net.kyori:adventure-platform-bukkit:4.3.1'
|
||||
implementation 'net.kyori:adventure-platform-bukkit:4.3.2'
|
||||
implementation 'dev.triumphteam:triumph-gui:3.1.7'
|
||||
implementation 'space.arim.morepaperlib:morepaperlib:0.4.3'
|
||||
implementation 'de.tr7zw:item-nbt-api:2.12.2'
|
||||
|
||||
@@ -48,6 +48,8 @@ import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static net.william278.husksync.util.BukkitKeyedAdapter.*;
|
||||
|
||||
public abstract class BukkitData implements Data {
|
||||
|
||||
@Override
|
||||
@@ -608,7 +610,7 @@ public abstract class BukkitData implements Data {
|
||||
Map.Entry::getKey,
|
||||
entry -> entry.getValue().entrySet().stream()
|
||||
.flatMap(blockEntry -> {
|
||||
Material material = Material.matchMaterial(blockEntry.getKey());
|
||||
Material material = matchMaterial(blockEntry.getKey());
|
||||
return material != null ? Stream.of(new AbstractMap.SimpleEntry<>(material, blockEntry.getValue())) : Stream.empty();
|
||||
})
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))
|
||||
@@ -622,7 +624,7 @@ public abstract class BukkitData implements Data {
|
||||
Map.Entry::getKey,
|
||||
entry -> entry.getValue().entrySet().stream()
|
||||
.flatMap(itemEntry -> {
|
||||
Material material = Material.matchMaterial(itemEntry.getKey());
|
||||
Material material = matchMaterial(itemEntry.getKey());
|
||||
return material != null ? Stream.of(new AbstractMap.SimpleEntry<>(material, itemEntry.getValue())) : Stream.empty();
|
||||
})
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))
|
||||
@@ -661,20 +663,6 @@ public abstract class BukkitData implements Data {
|
||||
return new StatisticsMap(genericStats, blockStats, itemStats, entityStats);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Statistic matchStatistic(@NotNull String key) {
|
||||
return Arrays.stream(Statistic.values())
|
||||
.filter(stat -> stat.getKey().toString().equals(key))
|
||||
.findFirst().orElse(null);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static EntityType matchEntityType(@NotNull String key) {
|
||||
return Arrays.stream(EntityType.values())
|
||||
.filter(entityType -> entityType.getKey().toString().equals(key))
|
||||
.findFirst().orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply(@NotNull BukkitUser user, @NotNull BukkitHuskSync plugin) throws IllegalStateException {
|
||||
genericStatistics.forEach((stat, value) -> applyStat(user, stat, null, value));
|
||||
@@ -708,7 +696,8 @@ public abstract class BukkitData implements Data {
|
||||
public Map<String, Map<String, Integer>> getBlockStatistics() {
|
||||
return blockStatistics.entrySet().stream().filter(entry -> entry.getKey() != null).collect(
|
||||
TreeMap::new,
|
||||
(m, e) -> m.put(e.getKey().getKey().toString(), convertStatistics(e.getValue())), TreeMap::putAll
|
||||
(m, e) -> getKeyName(e.getKey()).ifPresent(key -> m.put(key, convertStatistics(e.getValue()))),
|
||||
TreeMap::putAll
|
||||
);
|
||||
}
|
||||
|
||||
@@ -717,7 +706,8 @@ public abstract class BukkitData implements Data {
|
||||
public Map<String, Map<String, Integer>> getItemStatistics() {
|
||||
return itemStatistics.entrySet().stream().filter(entry -> entry.getKey() != null).collect(
|
||||
TreeMap::new,
|
||||
(m, e) -> m.put(e.getKey().getKey().toString(), convertStatistics(e.getValue())), TreeMap::putAll
|
||||
(m, e) -> getKeyName(e.getKey()).ifPresent(key -> m.put(key, convertStatistics(e.getValue()))),
|
||||
TreeMap::putAll
|
||||
);
|
||||
}
|
||||
|
||||
@@ -726,7 +716,8 @@ public abstract class BukkitData implements Data {
|
||||
public Map<String, Map<String, Integer>> getEntityStatistics() {
|
||||
return entityStatistics.entrySet().stream().filter(entry -> entry.getKey() != null).collect(
|
||||
TreeMap::new,
|
||||
(m, e) -> m.put(e.getKey().getKey().toString(), convertStatistics(e.getValue())), TreeMap::putAll
|
||||
(m, e) -> getKeyName(e.getKey()).ifPresent(key -> m.put(key, convertStatistics(e.getValue()))),
|
||||
TreeMap::putAll
|
||||
);
|
||||
}
|
||||
|
||||
@@ -734,13 +725,8 @@ public abstract class BukkitData implements Data {
|
||||
private <T extends Keyed> Map<String, Integer> convertStatistics(@NotNull Map<T, Integer> stats) {
|
||||
return stats.entrySet().stream().filter(entry -> entry.getKey() != null).collect(
|
||||
TreeMap::new,
|
||||
(m, e) -> {
|
||||
try {
|
||||
m.put(e.getKey().getKey().toString(), e.getValue());
|
||||
} catch (Throwable t) {
|
||||
// Ignore; skip elements with invalid keys (e.g., legacy materials)
|
||||
}
|
||||
}, TreeMap::putAll
|
||||
(m, e) -> getKeyName(e.getKey()).ifPresent(key -> m.put(key, e.getValue())),
|
||||
TreeMap::putAll
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -143,7 +143,7 @@ public class MpdbMigrator extends Migrator {
|
||||
});
|
||||
plugin.log(Level.INFO, "Migration complete for " + dataToMigrate.size() + " users in " + ((System.currentTimeMillis() - startTime) / 1000) + " seconds!");
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
} catch (Throwable e) {
|
||||
plugin.log(Level.SEVERE, "Error while migrating data: " + e.getMessage() + " - are your source database credentials correct?");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -40,6 +40,8 @@ import java.util.Arrays;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static net.william278.husksync.util.BukkitKeyedAdapter.matchMaterial;
|
||||
|
||||
/**
|
||||
* Bukkit platform implementation of an {@link OnlineUser}
|
||||
*/
|
||||
@@ -80,7 +82,7 @@ public class BukkitUser extends OnlineUser implements BukkitUserDataHolder {
|
||||
public void sendToast(@NotNull MineDown title, @NotNull MineDown description,
|
||||
@NotNull String iconMaterial, @NotNull String backgroundType) {
|
||||
try {
|
||||
final Material material = Material.matchMaterial(iconMaterial);
|
||||
final Material material = matchMaterial(iconMaterial);
|
||||
Toast.builder((BukkitHuskSync) plugin)
|
||||
.setTitle(title.toComponent())
|
||||
.setDescription(description.toComponent())
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync.util;
|
||||
|
||||
import org.bukkit.Keyed;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Statistic;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
|
||||
// Utility class for adapting "Keyed" Bukkit objects
|
||||
public final class BukkitKeyedAdapter {
|
||||
|
||||
@Nullable
|
||||
public static Statistic matchStatistic(@NotNull String key) {
|
||||
try {
|
||||
return Arrays.stream(Statistic.values())
|
||||
.filter(stat -> stat.getKey().toString().equals(key))
|
||||
.findFirst().orElse(null);
|
||||
} catch (Throwable e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static EntityType matchEntityType(@NotNull String key) {
|
||||
try {
|
||||
return Arrays.stream(EntityType.values())
|
||||
.filter(entityType -> entityType.getKey().toString().equals(key))
|
||||
.findFirst().orElse(null);
|
||||
} catch (Throwable e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static Material matchMaterial(@NotNull String key) {
|
||||
try {
|
||||
return Material.matchMaterial(key);
|
||||
} catch (Throwable e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static Optional<String> getKeyName(@NotNull Keyed keyed) {
|
||||
try {
|
||||
return Optional.of(keyed.getKey().toString());
|
||||
} catch (Throwable e) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
package net.william278.husksync.util;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import net.william278.husksync.HuskSync;
|
||||
import net.william278.husksync.adapter.DataAdapter;
|
||||
import net.william278.husksync.data.BukkitData;
|
||||
@@ -43,6 +44,8 @@ import java.time.OffsetDateTime;
|
||||
import java.util.*;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static net.william278.husksync.util.BukkitKeyedAdapter.*;
|
||||
|
||||
public class BukkitLegacyConverter extends LegacyConverter {
|
||||
|
||||
public BukkitLegacyConverter(@NotNull HuskSync plugin) {
|
||||
@@ -197,36 +200,44 @@ public class BukkitLegacyConverter extends LegacyConverter {
|
||||
@NotNull
|
||||
private BukkitData.Statistics readStatisticMaps(@NotNull JSONObject untyped, @NotNull JSONObject blocks,
|
||||
@NotNull JSONObject items, @NotNull JSONObject entities) {
|
||||
final Map<Statistic, Integer> genericStats = new HashMap<>();
|
||||
untyped.keys().forEachRemaining(stat -> genericStats.put(Statistic.valueOf(stat), untyped.getInt(stat)));
|
||||
// Read generic stats
|
||||
final Map<Statistic, Integer> genericStats = Maps.newHashMap();
|
||||
untyped.keys().forEachRemaining(stat -> genericStats.put(matchStatistic(stat), untyped.getInt(stat)));
|
||||
|
||||
final Map<Statistic, Map<Material, Integer>> blockStats = new HashMap<>();
|
||||
blocks.keys().forEachRemaining(stat -> {
|
||||
final JSONObject blockStat = blocks.getJSONObject(stat);
|
||||
final Map<Material, Integer> blockMap = new HashMap<>();
|
||||
blockStat.keys().forEachRemaining(block -> blockMap.put(Material.valueOf(block), blockStat.getInt(block)));
|
||||
blockStats.put(Statistic.valueOf(stat), blockMap);
|
||||
});
|
||||
// Read block & item stats
|
||||
final Map<Statistic, Map<Material, Integer>> blockStats, itemStats;
|
||||
blockStats = readMaterialStatistics(blocks);
|
||||
itemStats = readMaterialStatistics(items);
|
||||
|
||||
final Map<Statistic, Map<Material, Integer>> itemStats = new HashMap<>();
|
||||
items.keys().forEachRemaining(stat -> {
|
||||
final JSONObject itemStat = items.getJSONObject(stat);
|
||||
final Map<Material, Integer> itemMap = new HashMap<>();
|
||||
itemStat.keys().forEachRemaining(item -> itemMap.put(Material.valueOf(item), itemStat.getInt(item)));
|
||||
itemStats.put(Statistic.valueOf(stat), itemMap);
|
||||
});
|
||||
|
||||
final Map<Statistic, Map<EntityType, Integer>> entityStats = new HashMap<>();
|
||||
// Read entity stats
|
||||
final Map<Statistic, Map<EntityType, Integer>> entityStats = Maps.newHashMap();
|
||||
entities.keys().forEachRemaining(stat -> {
|
||||
final JSONObject entityStat = entities.getJSONObject(stat);
|
||||
final Map<EntityType, Integer> entityMap = new HashMap<>();
|
||||
entityStat.keys().forEachRemaining(entity -> entityMap.put(EntityType.valueOf(entity), entityStat.getInt(entity)));
|
||||
entityStats.put(Statistic.valueOf(stat), entityMap);
|
||||
entityStat.keys().forEachRemaining(entity -> entityMap.put(matchEntityType(entity), entityStat.getInt(entity)));
|
||||
entityStats.put(matchStatistic(stat), entityMap);
|
||||
});
|
||||
|
||||
return BukkitData.Statistics.from(genericStats, blockStats, itemStats, entityStats);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Map<Statistic, Map<Material, Integer>> readMaterialStatistics(@NotNull JSONObject items) {
|
||||
final Map<Statistic, Map<Material, Integer>> itemStats = Maps.newHashMap();
|
||||
items.keys().forEachRemaining(stat -> {
|
||||
final JSONObject itemStat = items.getJSONObject(stat);
|
||||
final Map<Material, Integer> itemMap = Maps.newHashMap();
|
||||
itemStat.keys().forEachRemaining(item -> {
|
||||
final Material material = matchMaterial(item);
|
||||
if (material != null) {
|
||||
itemMap.put(material, itemStat.getInt(item));
|
||||
}
|
||||
});
|
||||
itemStats.put(matchStatistic(stat), itemMap);
|
||||
});
|
||||
return itemStats;
|
||||
}
|
||||
|
||||
// Deserialize a legacy item stack array
|
||||
@NotNull
|
||||
public ItemStack[] deserializeLegacyItemStacks(@NotNull String items) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
husksync {
|
||||
update;
|
||||
about;
|
||||
status;
|
||||
reload;
|
||||
}
|
||||
@@ -18,7 +18,7 @@ dependencies {
|
||||
}
|
||||
|
||||
compileOnly 'net.kyori:adventure-api:4.15.0'
|
||||
compileOnly 'net.kyori:adventure-platform-api:4.3.1'
|
||||
compileOnly 'net.kyori:adventure-platform-api:4.3.2'
|
||||
compileOnly 'org.jetbrains:annotations:24.1.0'
|
||||
compileOnly 'com.github.plan-player-analytics:Plan:5.5.2272'
|
||||
compileOnly "redis.clients:jedis:$jedis_version"
|
||||
|
||||
@@ -21,6 +21,9 @@ package net.william278.husksync.command;
|
||||
|
||||
import de.themoep.minedown.adventure.MineDown;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.JoinConfiguration;
|
||||
import net.kyori.adventure.text.event.HoverEvent;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.kyori.adventure.text.format.TextColor;
|
||||
import net.william278.desertwell.about.AboutMenu;
|
||||
import net.william278.desertwell.util.UpdateChecker;
|
||||
@@ -28,10 +31,12 @@ import net.william278.husksync.HuskSync;
|
||||
import net.william278.husksync.migrator.Migrator;
|
||||
import net.william278.husksync.user.CommandUser;
|
||||
import net.william278.husksync.user.OnlineUser;
|
||||
import org.apache.commons.text.WordUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -39,6 +44,7 @@ public class HuskSyncCommand extends Command implements TabProvider {
|
||||
|
||||
private static final Map<String, Boolean> SUB_COMMANDS = Map.of(
|
||||
"about", false,
|
||||
"status", true,
|
||||
"reload", true,
|
||||
"migrate", true,
|
||||
"update", true
|
||||
@@ -92,6 +98,13 @@ public class HuskSyncCommand extends Command implements TabProvider {
|
||||
|
||||
switch (subCommand) {
|
||||
case "about" -> executor.sendMessage(aboutMenu.toComponent());
|
||||
case "status" -> {
|
||||
getPlugin().getLocales().getLocale("system_status_header").ifPresent(executor::sendMessage);
|
||||
executor.sendMessage(Component.join(
|
||||
JoinConfiguration.newlines(),
|
||||
Arrays.stream(StatusLine.values()).map(s -> s.get(plugin)).toList()
|
||||
));
|
||||
}
|
||||
case "reload" -> {
|
||||
try {
|
||||
plugin.loadConfigs();
|
||||
@@ -182,4 +195,63 @@ public class HuskSyncCommand extends Command implements TabProvider {
|
||||
};
|
||||
}
|
||||
|
||||
private enum StatusLine {
|
||||
PLUGIN_VERSION(plugin -> Component.text("v" + plugin.getPluginVersion().toStringWithoutMetadata())
|
||||
.appendSpace().append(plugin.getPluginVersion().getMetadata().isBlank() ? Component.empty()
|
||||
: Component.text("(build " + plugin.getPluginVersion().getMetadata() + ")"))),
|
||||
PLATFORM_TYPE(plugin -> Component.text(WordUtils.capitalizeFully(plugin.getPlatformType()))),
|
||||
LANGUAGE(plugin -> Component.text(plugin.getSettings().getLanguage())),
|
||||
MINECRAFT_VERSION(plugin -> Component.text(plugin.getMinecraftVersion().toString())),
|
||||
JAVA_VERSION(plugin -> Component.text(System.getProperty("java.version"))),
|
||||
JAVA_VENDOR(plugin -> Component.text(System.getProperty("java.vendor"))),
|
||||
SYNC_MODE(plugin -> Component.text(WordUtils.capitalizeFully(plugin.getSettings().getSyncMode().toString()))),
|
||||
DELAY_LATENCY(plugin -> Component.text(plugin.getSettings().getNetworkLatencyMilliseconds() + "ms")),
|
||||
SERVER_NAME(plugin -> Component.text(plugin.getServerName())),
|
||||
DATABASE_TYPE(plugin -> Component.text(plugin.getSettings().getDatabaseType().getDisplayName())),
|
||||
IS_DATABASE_LOCAL(plugin -> getLocalhostBoolean(plugin.getSettings().getMySqlHost())),
|
||||
USING_REDIS_SENTINEL(plugin -> getBoolean(!plugin.getSettings().getRedisSentinelMaster().isBlank())),
|
||||
USING_REDIS_PASSWORD(plugin -> getBoolean(!plugin.getSettings().getRedisPassword().isBlank())),
|
||||
REDIS_USING_SSL(plugin -> getBoolean(plugin.getSettings().redisUseSsl())),
|
||||
IS_REDIS_LOCAL(plugin -> getLocalhostBoolean(plugin.getSettings().getRedisHost())),
|
||||
DATA_TYPES(plugin -> Component.join(
|
||||
JoinConfiguration.commas(true),
|
||||
plugin.getRegisteredDataTypes().stream().map(i -> {
|
||||
boolean enabled = plugin.getSettings().isSyncFeatureEnabled(i);
|
||||
return Component.textOfChildren(Component
|
||||
.text(i.toString()).appendSpace().append(Component.text(enabled ? '✔' : '❌')))
|
||||
.color(enabled ? NamedTextColor.GREEN : NamedTextColor.RED)
|
||||
.hoverEvent(HoverEvent.showText(Component.text(enabled ? "Enabled" : "Disabled")));
|
||||
}).toList()
|
||||
));
|
||||
|
||||
private final Function<HuskSync, Component> supplier;
|
||||
|
||||
StatusLine(@NotNull Function<HuskSync, Component> supplier) {
|
||||
this.supplier = supplier;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Component get(@NotNull HuskSync plugin) {
|
||||
return Component
|
||||
.text("•").appendSpace()
|
||||
.append(Component.text(
|
||||
WordUtils.capitalizeFully(name().replaceAll("_", " ")),
|
||||
TextColor.color(0x848484)
|
||||
))
|
||||
.append(Component.text(':')).append(Component.space().color(NamedTextColor.WHITE))
|
||||
.append(supplier.apply(plugin));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static Component getBoolean(boolean value) {
|
||||
return Component.text(value ? "Yes" : "No", value ? NamedTextColor.GREEN : NamedTextColor.RED);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static Component getLocalhostBoolean(@NotNull String value) {
|
||||
return getBoolean(value.equals("127.0.0.1") || value.equals("0.0.0.0")
|
||||
|| value.equals("localhost") || value.equals("::1"));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -115,7 +115,7 @@ public class DataDumper {
|
||||
} else {
|
||||
return "(Failed to upload to logs site, got: " + connection.getResponseCode() + ")";
|
||||
}
|
||||
} catch (Exception e) {
|
||||
} catch (Throwable e) {
|
||||
plugin.log(Level.SEVERE, "Failed to upload data to logs site", e);
|
||||
}
|
||||
return "(Failed to upload to logs site)";
|
||||
|
||||
@@ -48,6 +48,7 @@ save_cause_converted_from_v2: 'converted from v2'
|
||||
up_to_date: '[HuskSync](#00fb9a bold) [| You are running the latest version of HuskSync (v%1%).](#00fb9a)'
|
||||
update_available: '[HuskSync](#ff7e5e bold) [| A new version of HuskSync is available: v%1% (running: v%2%).](#ff7e5e)'
|
||||
reload_complete: '[HuskSync](#00fb9a bold) [| Презаредихме конфигурацията и файловете със съобщения.](#00fb9a)\n[⚠ Ensure config files are up-to-date on all servers!](#00fb9a)\n[A restart is needed for config changes to take effect.](#00fb9a italic)'
|
||||
system_status_header: '[HuskSync](#00fb9a bold) [| System status report:](#00fb9a)'
|
||||
error_invalid_syntax: '[Грешка:](#ff3300) [Неправилен синтаксис. Използвайте:](#ff7e5e) [%1%](#ff7e5e italic show_text=&#ff7e5e&Click to suggest suggest_command=%1%)'
|
||||
error_invalid_player: '[Грешка:](#ff3300) [Не можахме да открием играч с това име.](#ff7e5e)'
|
||||
error_no_permission: '[Грешка:](#ff3300) [Нямате право да използвате тази команда](#ff7e5e)'
|
||||
|
||||
@@ -48,6 +48,7 @@ save_cause_converted_from_v2: 'Import von v2'
|
||||
reload_complete: '[HuskSync](#00fb9a bold) [| Die Konfigurations- und Sprachdateien wurden neu geladen.](#00fb9a)\n[⚠ Stelle sicher, dass die Konfigurationsdateien auf allen Servern aktuell sind!](#00fb9a)\n[Ein Neustart wird benötigt, damit Konfigurations-Änderungen wirkbar werden.](#00fb9a italic)'
|
||||
up_to_date: '[HuskSync](#00fb9a bold) [| Du verwendest die neuste Version von HuskSync (v%1%).](#00fb9a)'
|
||||
update_available: '[HuskSync](#ff7e5e bold) [| Eine neue Version von HuskSync ist verfügbar: v%1% (Aktuelle Version: v%2%).](#ff7e5e)'
|
||||
system_status_header: '[HuskSync](#00fb9a bold) [| System status report:](#00fb9a)'
|
||||
error_invalid_syntax: '[Fehler:](#ff3300) [Falsche Syntax. Nutze:](#ff7e5e) [%1%](#ff7e5e italic show_text=&#ff7e5e&Click to suggest suggest_command=%1%)'
|
||||
error_invalid_player: '[Fehler:](#ff3300) [Es konnte kein Spieler mit diesem Namen gefunden werden.](#ff7e5e)'
|
||||
error_no_permission: '[Fehler:](#ff3300) [Du hast nicht die benötigten Berechtigungen um diesen Befehl auszuführen](#ff7e5e)'
|
||||
|
||||
@@ -48,6 +48,7 @@ save_cause_converted_from_v2: 'converted from v2'
|
||||
up_to_date: '[HuskSync](#00fb9a bold) [| You are running the latest version of HuskSync (v%1%).](#00fb9a)'
|
||||
update_available: '[HuskSync](#ff7e5e bold) [| A new version of HuskSync is available: v%1% (running: v%2%).](#ff7e5e)'
|
||||
reload_complete: '[HuskSync](#00fb9a bold) [| Reloaded config and message files.](#00fb9a)\n[⚠ Ensure config files are up-to-date on all servers!](#00fb9a)\n[A restart is needed for config changes to take effect.](#00fb9a italic)'
|
||||
system_status_header: '[HuskSync](#00fb9a bold) [| System status report:](#00fb9a)'
|
||||
error_invalid_syntax: '[Error:](#ff3300) [Incorrect syntax. Usage:](#ff7e5e) [%1%](#ff7e5e italic show_text=&#ff7e5e&Click to suggest suggest_command=%1%)'
|
||||
error_invalid_player: '[Error:](#ff3300) [Could not find a player by that name.](#ff7e5e)'
|
||||
error_no_permission: '[Error:](#ff3300) [You do not have permission to execute this command](#ff7e5e)'
|
||||
|
||||
@@ -48,6 +48,7 @@ save_cause_converted_from_v2: 'converted from v2'
|
||||
up_to_date: '[HuskSync](#00fb9a bold) [| You are running the latest version of HuskSync (v%1%).](#00fb9a)'
|
||||
update_available: '[HuskSync](#ff7e5e bold) [| A new version of HuskSync is available: v%1% (running: v%2%).](#ff7e5e)'
|
||||
reload_complete: '[HuskSync](#00fb9a bold) [| Recargada la configuración y los archivos de lenguaje.](#00fb9a)\n[⚠ Ensure config files are up-to-date on all servers!](#00fb9a)\n[A restart is needed for config changes to take effect.](#00fb9a italic)'
|
||||
system_status_header: '[HuskSync](#00fb9a bold) [| System status report:](#00fb9a)'
|
||||
error_invalid_syntax: '[Error:](#ff3300) [Sintanxis incorrecta. Usa:](#ff7e5e) [%1%](#ff7e5e italic show_text=&#ff7e5e&Click to suggest suggest_command=%1%)'
|
||||
error_invalid_player: '[Error:](#ff3300) [No se ha podido encontrar un jugador con ese nombre.](#ff7e5e)'
|
||||
error_no_permission: '[Error:](#ff3300) [No tienes permisos para ejecutar este comando.](#ff7e5e)'
|
||||
|
||||
@@ -48,6 +48,7 @@ save_cause_converted_from_v2: 'converted from v2'
|
||||
up_to_date: '[HuskSync](#00fb9a bold) [| Il plugin è all''ultima versione disponibile (v%1%).](#00fb9a)'
|
||||
update_available: '[HuskSync](#ff7e5e bold) [| Disponibile una nuova versione: v%1% (running: v%2%).](#ff7e5e)'
|
||||
reload_complete: '[HuskSync](#00fb9a bold) [| Configurazione e messaggi ricaricati.](#00fb9a)\n[⚠ Ensure config files are up-to-date on all servers!](#00fb9a)\n[A restart is needed for config changes to take effect.](#00fb9a italic)'
|
||||
system_status_header: '[HuskSync](#00fb9a bold) [| System status report:](#00fb9a)'
|
||||
error_invalid_syntax: '[Errore:](#ff3300) [Sintassi errata. Usa:](#ff7e5e) [%1%](#ff7e5e italic show_text=&#ff7e5e&Click to suggest suggest_command=%1%)'
|
||||
error_invalid_player: '[Errore:](#ff3300) [Impossibile trovare un giocatore con questo nome.](#ff7e5e)'
|
||||
error_no_permission: '[Errore:](#ff3300) [Non hai il permesso di usare questo comando](#ff7e5e)'
|
||||
|
||||
@@ -48,6 +48,7 @@ save_cause_converted_from_v2: 'converted from v2'
|
||||
up_to_date: '[HuskSync](#00fb9a bold) [| HuskSyncの最新バージョンを実行しています(v%1%).](#00fb9a)'
|
||||
update_available: '[HuskSync](#ff7e5e bold) [| HuskSyncの最新バージョンが更新されています: v%1% (実行中: v%2%).](#ff7e5e)'
|
||||
reload_complete: '[HuskSync](#00fb9a bold) [| 設定ファイルとメッセージファイルを再読み込みしました。](#00fb9a)\n[⚠ すべてのサーバーで設定ファイルが最新であることを確認してください!](#00fb9a)\n[設定の変更を有効にするには再起動が必要です。](#00fb9a italic)'
|
||||
system_status_header: '[HuskSync](#00fb9a bold) [| System status report:](#00fb9a)'
|
||||
error_invalid_syntax: '[Error:](#ff3300) [構文が正しくありません。使用法:](#ff7e5e) [%1%](#ff7e5e italic show_text=&#ff7e5e&クリックでサジェスト suggest_command=%1%)'
|
||||
error_invalid_player: '[Error:](#ff3300) [そのプレイヤーは見つかりませんでした](#ff7e5e)'
|
||||
error_no_permission: '[Error:](#ff3300) [このコマンドを実行する権限がありません](#ff7e5e)'
|
||||
|
||||
@@ -48,6 +48,7 @@ save_cause_converted_from_v2: 'converted from v2'
|
||||
up_to_date: '[HuskSync](#00fb9a bold) [| 가장 최신 버전의 HuskSync를 실행 중입니다 (v%1%).](#00fb9a)'
|
||||
update_available: '[HuskSync](#ff7e5e bold) [| 새로운 버전의 HuskSync가 존재합니다: v%1% (현재 버전: v%2%).](#ff7e5e)'
|
||||
reload_complete: '[HuskSync](#00fb9a bold) [| 콘피그와 메시지 파일을 다시 불러왔습니다.](#00fb9a)\n[⚠ 모든 서버의 컨피그 파일을 변경하였는지 확인하세요!](#00fb9a)\n[몇몇 설정은 재시작 후에 적용됩니다.](#00fb9a italic)'
|
||||
system_status_header: '[HuskSync](#00fb9a bold) [| System status report:](#00fb9a)'
|
||||
error_invalid_syntax: '[오류:](#ff3300) [잘못된 사용법. 사용법:](#ff7e5e) [%1%](#ff7e5e italic show_text=&#ff7e5e&클릭하여 입력할 수 있습니다. suggest_command=%1%)'
|
||||
error_invalid_player: '[오류:](#ff3300) [해당 이름의 사용자를 찾을 수 없습니다.](#ff7e5e)'
|
||||
error_no_permission: '[오류:](#ff3300) [해당 명령어를 사용할 권한이 없습니다.](#ff7e5e)'
|
||||
|
||||
@@ -48,6 +48,7 @@ save_cause_converted_from_v2: 'converted from v2'
|
||||
up_to_date: '[HuskSync](#00fb9a bold) [| Je gebruikt de nieuwste versie van HuskSync (v%1%).](#00fb9a)'
|
||||
update_available: '[HuskSync](#ff7e5e bold) [| Er is een nieuwe versie van HuskSync beschikbaar: v%1% (huidige versie: v%2%).](#ff7e5e)'
|
||||
reload_complete: '[HuskSync](#00fb9a bold) [| Configuratie- en berichtbestanden opnieuw geladen.](#00fb9a)\n[⚠ Controleer of de configuratiebestanden up-to-date zijn op alle servers!](#00fb9a)\n[Een herstart is nodig voor de configuratiewijzigingen van kracht te laten worden.](#00fb9a italic)'
|
||||
system_status_header: '[HuskSync](#00fb9a bold) [| System status report:](#00fb9a)'
|
||||
error_invalid_syntax: '[Error:](#ff3300) [Onjuiste syntaxis. Gebruik:](#ff7e5e) [%1%](#ff7e5e italic show_text=&#ff7e5e&Click to suggest suggest_command=%1%)'
|
||||
error_invalid_player: '[Error:](#ff3300) [Kan geen speler met die naam vinden.](#ff7e5e)'
|
||||
error_no_permission: '[Error:](#ff3300) [Je hebt geen toestemming om deze opdracht uit te voeren](#ff7e5e)'
|
||||
|
||||
@@ -48,6 +48,7 @@ save_cause_converted_from_v2: 'converted from v2'
|
||||
up_to_date: '[HuskSync](#00fb9a bold) [| You are running the latest version of HuskSync (v%1%).](#00fb9a)'
|
||||
update_available: '[HuskSync](#ff7e5e bold) [| A new version of HuskSync is available: v%1% (running: v%2%).](#ff7e5e)'
|
||||
reload_complete: '[HuskSync](#00fb9a bold) [| Arquivos de configuração e mensagens recarregados.](#00fb9a)\n[⚠ Ensure config files are up-to-date on all servers!](#00fb9a)\n[A restart is needed for config changes to take effect.](#00fb9a italic)'
|
||||
system_status_header: '[HuskSync](#00fb9a bold) [| System status report:](#00fb9a)'
|
||||
error_invalid_syntax: '[Error:](#ff3300) [Sintaxe incorreta. Utilize:](#ff7e5e) [%1%](#ff7e5e italic show_text=&#ff7e5e&Click to suggest suggest_command=%1%)'
|
||||
error_invalid_player: '[Error:](#ff3300) [Não foi possível encontrar um jogador com esse nome.](#ff7e5e)'
|
||||
error_no_permission: '[Error:](#ff3300) [Você não tem permissão para executar este comando](#ff7e5e)'
|
||||
|
||||
@@ -48,6 +48,7 @@ save_cause_converted_from_v2: 'конвертация с v2'
|
||||
up_to_date: '[HuskSync](#00fb9a bold) [| Вы используете последнюю версию HuskSync (v%1%).](#00fb9a)'
|
||||
update_available: '[HuskSync](#ff7e5e bold) [| Доступна новая версия HuskSync: v%1% (текущая: v%2%).](#ff7e5e)'
|
||||
reload_complete: '[HuskSync](#00fb9a bold) [| Конфигурация и файлы локализации перезагружены.](#00fb9a)\n[⚠ Убедитесь, что файлы конфигурации обновлены на всех серверах!](#00fb9a)\n[Необходима перезагрузка для вступления изменений конфигурации в силу.](#00fb9a italic)'
|
||||
system_status_header: '[HuskSync](#00fb9a bold) [| System status report:](#00fb9a)'
|
||||
error_invalid_syntax: '[Ошибка:](#ff3300) [Неправильный синтаксис. Используйте:](#ff7e5e) [%1%](#ff7e5e italic show_text=&#ff7e5e&Click to suggest suggest_command=%1%)'
|
||||
error_invalid_player: '[Ошибка:](#ff3300) [Не удалось найти игрока с данным именем.](#ff7e5e)'
|
||||
error_no_permission: '[Ошибка:](#ff3300) [У вас недостаточно прав для выполнения данной команды.](#ff7e5e)'
|
||||
|
||||
@@ -48,6 +48,7 @@ save_cause_converted_from_v2: 'converted from v2'
|
||||
up_to_date: '[HuskSync](#00fb9a bold) [| HuskSync\''in en son sürümünü kullanıyorsunuz (v%1%).](#00fb9a)'
|
||||
update_available: '[HuskSync](#ff7e5e bold) [| HuskSync\''in yeni bir sürümü mevcut: v%1% (kullanılan sürüm: v%2%).](#ff7e5e)'
|
||||
reload_complete: '[HuskSync](#00fb9a bold) [| Yapılandırma ve mesaj dosyaları yeniden yüklendi.](#00fb9a)\n[⚠ Lütfen yapılandırma dosyalarının tüm sunucularda güncel olduğundan emin olun!](#00fb9a)\n[Yapılandırma değişikliklerinin etkili olabilmesi için bir yeniden başlatma gereklidir.](#00fb9a italic)'
|
||||
system_status_header: '[HuskSync](#00fb9a bold) [| System status report:](#00fb9a)'
|
||||
error_invalid_syntax: '[Hata:](#ff3300) [Yanlış sözdizimi. Kullanım:](#ff7e5e) [%1%](#ff7e5e italic show_text=&#ff7e5e&Öneri için tıklayın Suggest_command=%1%)'
|
||||
error_invalid_player: '[Hata:](#ff3300) [Bu isimde bir oyuncu bulunamadı.](#ff7e5e)'
|
||||
error_no_permission: '[Hata:](#ff3300) [Bu komutu gerçekleştirmek için izniniz yok](#ff7e5e)'
|
||||
|
||||
@@ -48,6 +48,7 @@ save_cause_converted_from_v2: 'converted from v2'
|
||||
up_to_date: '[HuskSync](#00fb9a bold) [| You are running the latest version of HuskSync (v%1%).](#00fb9a)'
|
||||
update_available: '[HuskSync](#ff7e5e bold) [| A new version of HuskSync is available: v%1% (running: v%2%).](#ff7e5e)'
|
||||
reload_complete: '[HuskSync](#00fb9a bold) [| Перезавантажено конфіґ та файли повідомлень.](#00fb9a)\n[⚠ Ensure config files are up-to-date on all servers!](#00fb9a)\n[A restart is needed for config changes to take effect.](#00fb9a italic)'
|
||||
system_status_header: '[HuskSync](#00fb9a bold) [| System status report:](#00fb9a)'
|
||||
error_invalid_syntax: '[Помилка:](#ff3300) [Неправильний синтакс. Використання:](#ff7e5e) [%1%](#ff7e5e italic show_text=&#ff7e5e&Click to suggest suggest_command=%1%)'
|
||||
error_invalid_player: '[Помилка:](#ff3300) [Гравця не знайдено](#ff7e5e)'
|
||||
error_no_permission: '[Помилка:](#ff3300) [Ввас немає дозволу на використання цієї команди](#ff7e5e)'
|
||||
|
||||
@@ -34,20 +34,21 @@ list_page_jumper_button: '[%1%](show_text=&7跳转到页面 %1% run_command=%2%
|
||||
list_page_jumper_current_page: '[%1%](#00fb9a)'
|
||||
list_page_jumper_separator: ' '
|
||||
list_page_jumper_group_separator: '…'
|
||||
save_cause_disconnect: 'disconnect'
|
||||
save_cause_world_save: 'world save'
|
||||
save_cause_death: 'death'
|
||||
save_cause_server_shutdown: 'server shutdown'
|
||||
save_cause_inventory_command: 'inventory command'
|
||||
save_cause_enderchest_command: 'enderchest command'
|
||||
save_cause_backup_restore: 'backup restore'
|
||||
save_cause_disconnect: '断开连接'
|
||||
save_cause_world_save: '保存世界'
|
||||
save_cause_death: '死亡'
|
||||
save_cause_server_shutdown: '服务器关闭'
|
||||
save_cause_inventory_command: '背包命令'
|
||||
save_cause_enderchest_command: '末影箱命令'
|
||||
save_cause_backup_restore: '备份还原'
|
||||
save_cause_api: 'API'
|
||||
save_cause_mpdb_migration: 'MPDB migration'
|
||||
save_cause_legacy_migration: 'legacy migration'
|
||||
save_cause_converted_from_v2: 'converted from v2'
|
||||
save_cause_mpdb_migration: 'MPDB迁移'
|
||||
save_cause_legacy_migration: '旧版迁移'
|
||||
save_cause_converted_from_v2: '从v2转换'
|
||||
up_to_date: '[HuskSync](#00fb9a bold) [| 你正在使用最新版本的HuskSync (v%1%)](#00fb9a)'
|
||||
update_available: '[HuskSync](#ff7e5e bold) [| 一个新版本的HuskSync已经可以更新: v%1% (当前: v%2%)](#ff7e5e)'
|
||||
reload_complete: '[HuskSync](#00fb9a bold) [| 插件配置和语言文件已重载.](#00fb9a)\n[⚠ 确保所有服务器上的配置文件都是最新的!](#00fb9a)\n[需要重新启动配置更改才能生效.](#00fb9a italic)'
|
||||
system_status_header: '[HuskSync](#00fb9a bold) [| System status report:](#00fb9a)'
|
||||
error_invalid_syntax: ':](#ff3300) [格式错误, 使用方法:](#ff7e5e) [%1%](#ff7e5e italic show_text=&#ff7e5e&点击建议 suggest_command=%1%)'
|
||||
error_invalid_player: '[错误:](#ff3300) [无法找到目标玩家.](#ff7e5e)'
|
||||
error_no_permission: '[错误:](#ff3300) [你没有执行此指令的权限](#ff7e5e)'
|
||||
|
||||
@@ -48,6 +48,7 @@ save_cause_converted_from_v2: 'converted from v2'
|
||||
up_to_date: '[HuskSync](#00fb9a bold) [| 您運行的是最新版本的 HuskSync (v%1%).](#00fb9a)'
|
||||
update_available: '[HuskSync](#ff7e5e bold) [| 發現可用的新版本: v%1% (running: v%2%).](#ff7e5e)'
|
||||
reload_complete: '[HuskSync](#00fb9a bold) [| 已重新載入配置和訊息文件](#00fb9a)\n[⚠ Ensure config files are up-to-date on all servers!](#00fb9a)\n[A restart is needed for config changes to take effect.](#00fb9a italic)'
|
||||
system_status_header: '[HuskSync](#00fb9a bold) [| System status report:](#00fb9a)'
|
||||
error_invalid_syntax: '[錯誤:](#ff3300) [語法不正確,用法:](#ff7e5e) [%1%](#ff7e5e italic show_text=&#ff7e5e&Click to suggest suggest_command=%1%)'
|
||||
error_invalid_player: '[錯誤:](#ff3300) [找不到這位玩家](#ff7e5e)'
|
||||
error_no_permission: '[錯誤:](#ff3300) [您沒有權限執行這個指令](#ff7e5e)'
|
||||
|
||||
@@ -11,7 +11,7 @@ This page contains a table of HuskSync commands and their required permission no
|
||||
<tbody>
|
||||
<!-- /husksync command -->
|
||||
<tr>
|
||||
<td rowspan="5"><code>/husksync</code></td>
|
||||
<td rowspan="6"><code>/husksync</code></td>
|
||||
<td><code>/husksync</code></td>
|
||||
<td>View & manage plugin system information</td>
|
||||
<td><code>husksync.command.husksync</code></td>
|
||||
@@ -21,6 +21,11 @@ This page contains a table of HuskSync commands and their required permission no
|
||||
<td>View information about the plugin</td>
|
||||
<td><code>husksync.command.husksync.about</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>/husksync status</code></td>
|
||||
<td>View plugin system status information</td>
|
||||
<td><code>husksync.command.husksync.status</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>/husksync reload</code></td>
|
||||
<td>Reload the plugin configuration</td>
|
||||
|
||||
@@ -3,7 +3,7 @@ HuskSync offers two built-in **synchronization modes** that utilise Redis and My
|
||||

|
||||
|
||||
## Available Modes
|
||||
* The `LOCKSTEP` sync mode is the default sync mode. It uses a data checkout system to ensure that all servers are in sync regardless of network latency or tick rate fluctuations. This mode was introduced in HuskSync v3.1
|
||||
* The `LOCKSTEP` sync mode is the default sync mode. It uses a data checkout system to ensure that all servers are in sync regardless of network latency or tick rate fluctuations. This mode was introduced in HuskSync v3.1, and was made the default in v3.2.
|
||||
* The `DELAY` sync mode uses the `network_latency_miliseconds` value to apply a delay before listening to Redis data
|
||||
|
||||
You can change which sync mode you are using by editing the `sync_mode` setting under `synchronization` in `config.yml`.
|
||||
@@ -31,6 +31,8 @@ The `LOCKSTEP` sync mode works as described below:
|
||||
|
||||
Additionally, note that `DATA_CHECKOUT` keys are set with the server ID of the server which "checked out" the data (taken from the `server.yml` config file). On both shutdown and startup, the plugin will clear all `DATA_CHECKOUT` keys for the current server ID (to prevent stale keys in the event of a server crash for instance)
|
||||
|
||||
`LOCKSTEP` has been the default sync mode since HuskSync v3.2, and is recommended for most networks.
|
||||
|
||||
## Delay
|
||||
The `DELAY` sync mode works as described below:
|
||||
* When a user disconnects from a server, a `SERVER_SWITCH` key is immediately set on Redis, followed by a `DATA_UPDATE` key which contains the user's packed and serialized Data Snapshot.
|
||||
@@ -40,6 +42,4 @@ The `DELAY` sync mode works as described below:
|
||||
* If present, it will continuously attempt to read for a `DATA_UPDATE` key; when read, their data will be set from the snapshot deserialized from Redis.
|
||||
* If not present, their data will be pulled from the database (as though they joined the network)
|
||||
|
||||
`DELAY` has been the default sync mode since HuskSync v2.0. In HuskSync v3.1, `LOCKSTEP` was introduced. Since the delay mode has been tested and deployed for the longest, it is still the default, though note this may change in the future.
|
||||
|
||||
However, if your network has a fluctuating tick rate or significant latency (especially if you have servers on different hardware/locations), you may wish to use `LOCKSTEP` instead for a more reliable sync system.
|
||||
If your network has a fluctuating tick rate or significant latency (especially if you have servers on different hardware/locations), you may wish to use `LOCKSTEP` instead for a more reliable sync system.
|
||||
@@ -3,7 +3,7 @@ org.gradle.jvmargs='-Dfile.encoding=UTF-8'
|
||||
org.gradle.daemon=true
|
||||
javaVersion=16
|
||||
|
||||
plugin_version=3.2
|
||||
plugin_version=3.2.1
|
||||
plugin_archive=husksync
|
||||
plugin_description=A modern, cross-server player data synchronization system
|
||||
|
||||
|
||||
Reference in New Issue
Block a user