From a91ac6f21483938fef99a2fc356c740d48338e3b Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Mon, 3 Jul 2023 22:10:11 +0100 Subject: [PATCH 1/3] Make turtle tools a little more flexible Turtle tools now accept two additional JSON fields - allowEnchantments: Whether items with enchantments (or any non-standard NBT) can be equipped. - consumesDurability: Whether durability will be consumed. This can be "never" (the current and default behaviour), "always", and "when_enchanted". Closes #1501. --- .../api/turtle/TurtleToolDurability.java | 48 ++++ .../api/turtle/TurtleUpgradeDataProvider.java | 30 +++ .../shared/turtle/core/TurtleBrain.java | 19 -- .../turtle/core/TurtlePlaceCommand.java | 14 +- .../turtle/inventory/UpgradeContainer.java | 2 +- .../shared/turtle/upgrades/TurtleTool.java | 234 ++++++++++++++---- .../turtle/upgrades/TurtleToolSerialiser.java | 11 +- .../shared/platform/FakePlayer.java | 6 + .../shared/platform/FakePlayerExt.java | 6 + 9 files changed, 281 insertions(+), 89 deletions(-) create mode 100644 projects/common-api/src/main/java/dan200/computercraft/api/turtle/TurtleToolDurability.java diff --git a/projects/common-api/src/main/java/dan200/computercraft/api/turtle/TurtleToolDurability.java b/projects/common-api/src/main/java/dan200/computercraft/api/turtle/TurtleToolDurability.java new file mode 100644 index 000000000..c149a185d --- /dev/null +++ b/projects/common-api/src/main/java/dan200/computercraft/api/turtle/TurtleToolDurability.java @@ -0,0 +1,48 @@ +// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers +// +// SPDX-License-Identifier: MPL-2.0 + +package dan200.computercraft.api.turtle; + +import net.minecraft.util.StringRepresentable; +import net.minecraft.world.entity.EquipmentSlot; +import net.minecraft.world.item.ItemStack; + +/** + * Indicates if an equipped turtle item will consume durability. + * + * @see TurtleUpgradeDataProvider.ToolBuilder#consumesDurability(TurtleToolDurability) + */ +public enum TurtleToolDurability implements StringRepresentable { + /** + * The equipped tool always consumes durability when using. + */ + ALWAYS("always"), + + /** + * The equipped tool consumes durability if it is {@linkplain ItemStack#isEnchanted() enchanted} or has + * {@linkplain ItemStack#getAttributeModifiers(EquipmentSlot) custom attribute modifiers}. + */ + WHEN_ENCHANTED("when_enchanted"), + + /** + * The equipped tool never consumes durability. Tools which have been damaged cannot be used as upgrades. + */ + NEVER("never"); + + private final String serialisedName; + + /** + * The codec which may be used for serialising/deserialising {@link TurtleToolDurability}s. + */ + public static final StringRepresentable.EnumCodec CODEC = StringRepresentable.fromEnum(TurtleToolDurability::values); + + TurtleToolDurability(String serialisedName) { + this.serialisedName = serialisedName; + } + + @Override + public String getSerializedName() { + return serialisedName; + } +} diff --git a/projects/common-api/src/main/java/dan200/computercraft/api/turtle/TurtleUpgradeDataProvider.java b/projects/common-api/src/main/java/dan200/computercraft/api/turtle/TurtleUpgradeDataProvider.java index 7aef277d6..a2048e969 100644 --- a/projects/common-api/src/main/java/dan200/computercraft/api/turtle/TurtleUpgradeDataProvider.java +++ b/projects/common-api/src/main/java/dan200/computercraft/api/turtle/TurtleUpgradeDataProvider.java @@ -13,8 +13,10 @@ import net.minecraft.data.DataGenerator; import net.minecraft.data.PackOutput; import net.minecraft.resources.ResourceLocation; import net.minecraft.tags.TagKey; +import net.minecraft.world.entity.EquipmentSlot; import net.minecraft.world.entity.ai.attributes.Attributes; import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.block.Block; import javax.annotation.Nullable; @@ -61,6 +63,8 @@ public abstract class TurtleUpgradeDataProvider extends UpgradeDataProvider breakable; + private boolean allowEnchantments = false; + private TurtleToolDurability consumesDurability = TurtleToolDurability.NEVER; ToolBuilder(ResourceLocation id, TurtleUpgradeSerialiser serialiser, Item toolItem) { this.id = id; @@ -104,6 +108,28 @@ public abstract class TurtleUpgradeDataProvider extends UpgradeDataProvider= 0 && colour <= 0xFFFFFF) { diff --git a/projects/common/src/main/java/dan200/computercraft/shared/turtle/core/TurtlePlaceCommand.java b/projects/common/src/main/java/dan200/computercraft/shared/turtle/core/TurtlePlaceCommand.java index 1e9c9357a..d1b9156b3 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/turtle/core/TurtlePlaceCommand.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/turtle/core/TurtlePlaceCommand.java @@ -74,19 +74,7 @@ public class TurtlePlaceCommand implements TurtleCommand { } } - public static boolean deployCopiedItem( - ItemStack stack, ITurtleAccess turtle, Direction direction, @Nullable Object[] extraArguments, @Nullable ErrorMessage outErrorMessage - ) { - // Create a fake player, and orient it appropriately - var playerPosition = turtle.getPosition().relative(direction); - var turtlePlayer = TurtlePlayer.getWithPosition(turtle, playerPosition, direction); - turtlePlayer.loadInventory(stack); - var result = deploy(stack, turtle, turtlePlayer, direction, extraArguments, outErrorMessage); - turtlePlayer.player().getInventory().clearContent(); - return result; - } - - private static boolean deploy( + public static boolean deploy( ItemStack stack, ITurtleAccess turtle, TurtlePlayer turtlePlayer, Direction direction, @Nullable Object[] extraArguments, @Nullable ErrorMessage outErrorMessage ) { diff --git a/projects/common/src/main/java/dan200/computercraft/shared/turtle/inventory/UpgradeContainer.java b/projects/common/src/main/java/dan200/computercraft/shared/turtle/inventory/UpgradeContainer.java index a0f0b6987..9fff2789a 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/turtle/inventory/UpgradeContainer.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/turtle/inventory/UpgradeContainer.java @@ -59,7 +59,7 @@ class UpgradeContainer implements Container { private ItemStack setUpgradeStack(int slot, @Nullable UpgradeData upgrade) { var stack = upgrade == null ? ItemStack.EMPTY : upgrade.getUpgradeItem(); - lastUpgrade.set(slot, upgrade); + lastUpgrade.set(slot, UpgradeData.copyOf(upgrade)); lastStack.set(slot, stack); return stack; } diff --git a/projects/common/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleTool.java b/projects/common/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleTool.java index 7f75e7456..564fcbb6c 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleTool.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleTool.java @@ -6,6 +6,7 @@ package dan200.computercraft.shared.turtle.upgrades; import dan200.computercraft.api.ComputerCraftTags; import dan200.computercraft.api.turtle.*; +import dan200.computercraft.core.util.Nullability; import dan200.computercraft.shared.platform.PlatformHelper; import dan200.computercraft.shared.turtle.TurtleUtil; import dan200.computercraft.shared.turtle.core.TurtlePlaceCommand; @@ -17,14 +18,19 @@ import net.minecraft.core.Direction; import net.minecraft.nbt.CompoundTag; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; import net.minecraft.tags.TagKey; +import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionResult; import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.MobType; import net.minecraft.world.entity.ai.attributes.Attributes; import net.minecraft.world.entity.decoration.ArmorStand; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.enchantment.EnchantmentHelper; import net.minecraft.world.level.BlockGetter; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.Block; @@ -33,6 +39,8 @@ import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.phys.EntityHitResult; import javax.annotation.Nullable; +import java.util.Objects; +import java.util.function.Function; import static net.minecraft.nbt.Tag.TAG_COMPOUND; import static net.minecraft.nbt.Tag.TAG_LIST; @@ -43,31 +51,39 @@ public class TurtleTool extends AbstractTurtleUpgrade { final ItemStack item; final float damageMulitiplier; - @Nullable - final TagKey breakable; + final boolean allowsEnchantments; + final TurtleToolDurability consumesDurability; + final @Nullable TagKey breakable; - public TurtleTool(ResourceLocation id, String adjective, Item craftItem, ItemStack toolItem, float damageMulitiplier, @Nullable TagKey breakable) { + public TurtleTool( + ResourceLocation id, String adjective, Item craftItem, ItemStack toolItem, float damageMulitiplier, + boolean allowsEnchantments, TurtleToolDurability consumesDurability, @Nullable TagKey breakable + ) { super(id, TurtleUpgradeType.TOOL, adjective, new ItemStack(craftItem)); item = toolItem; this.damageMulitiplier = damageMulitiplier; + this.allowsEnchantments = allowsEnchantments; + this.consumesDurability = consumesDurability; this.breakable = breakable; } @Override public boolean isItemSuitable(ItemStack stack) { - var tag = stack.getTag(); - if (tag == null || tag.isEmpty()) return true; - - // Check we've not got anything vaguely interesting on the item. We allow other mods to add their - // own NBT, with the understanding such details will be lost to the mist of time. - if (stack.isDamaged() || stack.isEnchanted()) return false; - if (tag.contains("AttributeModifiers", TAG_LIST) && !tag.getList("AttributeModifiers", TAG_COMPOUND).isEmpty()) { - return false; - } - + if (consumesDurability == TurtleToolDurability.NEVER && stack.isDamaged()) return false; + if (!allowsEnchantments && isEnchanted(stack)) return false; return true; } + private static boolean isEnchanted(ItemStack stack) { + return !stack.isEmpty() && isEnchanted(stack.getTag()); + } + + private static boolean isEnchanted(@Nullable CompoundTag tag) { + if (tag == null || tag.isEmpty()) return false; + return (tag.contains(ItemStack.TAG_ENCH, TAG_LIST) && !tag.getList(ItemStack.TAG_ENCH, TAG_COMPOUND).isEmpty()) + || (tag.contains("AttributeModifiers", TAG_LIST) && !tag.getList("AttributeModifiers", TAG_COMPOUND).isEmpty()); + } + @Override public CompoundTag getUpgradeData(ItemStack stack) { // Just use the current item's tag. @@ -83,11 +99,64 @@ public class TurtleTool extends AbstractTurtleUpgrade { return item; } + private ItemStack getToolStack(ITurtleAccess turtle, TurtleSide side) { + var item = getCraftingItem(); + var tag = turtle.getUpgradeNBTData(side); + if (!tag.isEmpty()) item.setTag(tag); + return item.copy(); + } + + private void setToolStack(ITurtleAccess turtle, TurtleSide side, ItemStack stack) { + var tag = turtle.getUpgradeNBTData(side); + + var useDurability = switch (consumesDurability) { + case NEVER -> false; + case WHEN_ENCHANTED -> isEnchanted(tag); + case ALWAYS -> true; + }; + if (!useDurability) return; + + // If the tool has broken, remove the upgrade! + if (stack.isEmpty()) { + turtle.setUpgradeWithData(side, null); + return; + } + + // If the tool has changed, no clue what's going on. + if (stack.getItem() != item.getItem()) return; + + var itemTag = stack.getTag(); + + // Early return if the item hasn't changed to avoid redundant syncs with the client. + if ((itemTag == null && tag.isEmpty()) || Objects.equals(itemTag, tag)) return; + + if (itemTag == null) { + tag.getAllKeys().clear(); + } else { + for (var key : itemTag.getAllKeys()) tag.put(key, Nullability.assertNonNull(itemTag.get(key))); + tag.getAllKeys().removeIf(x -> !itemTag.contains(x)); + } + + turtle.updateUpgradeNBTData(side); + } + + private T withEquippedItem(ITurtleAccess turtle, TurtleSide side, Direction direction, Function action) { + var turtlePlayer = TurtlePlayer.getWithPosition(turtle, turtle.getPosition(), direction); + turtlePlayer.loadInventory(getToolStack(turtle, side)); + + var result = action.apply(turtlePlayer); + + setToolStack(turtle, side, turtlePlayer.player().getItemInHand(InteractionHand.MAIN_HAND)); + turtlePlayer.player().getInventory().clearContent(); + + return result; + } + @Override public TurtleCommandResult useTool(ITurtleAccess turtle, TurtleSide side, TurtleVerb verb, Direction direction) { return switch (verb) { - case ATTACK -> attack(turtle, direction); - case DIG -> dig(turtle, direction); + case ATTACK -> attack(turtle, side, direction); + case DIG -> dig(turtle, side, direction); }; } @@ -102,16 +171,14 @@ public class TurtleTool extends AbstractTurtleUpgrade { } /** - * Attack an entity. This is a very cut down version of {@link Player#attack(Entity)}, which doesn't handle - * enchantments, knockback, etc... Unfortunately we can't call attack directly as damage calculations are rather - * different (and we don't want to play sounds/particles). + * Attack an entity. * * @param turtle The current turtle. + * @param side The side the tool is on. * @param direction The direction we're attacking in. * @return Whether an attack occurred. - * @see Player#attack(Entity) */ - private TurtleCommandResult attack(ITurtleAccess turtle, Direction direction) { + private TurtleCommandResult attack(ITurtleAccess turtle, TurtleSide side, Direction direction) { // Create a fake player, and orient it appropriately var world = turtle.getLevel(); var position = turtle.getPosition(); @@ -123,10 +190,11 @@ public class TurtleTool extends AbstractTurtleUpgrade { var turtlePos = player.position(); var rayDir = player.getViewVector(1.0f); var hit = WorldUtil.clip(world, turtlePos, rayDir, 1.5, null); + var attacked = false; if (hit instanceof EntityHitResult entityHit) { // Load up the turtle's inventory - var stackCopy = item.copy(); - turtlePlayer.loadInventory(stackCopy); + var stack = getToolStack(turtle, side); + turtlePlayer.loadInventory(stack); var hitEntity = entityHit.getEntity(); @@ -134,62 +202,120 @@ public class TurtleTool extends AbstractTurtleUpgrade { DropConsumer.set(hitEntity, TurtleUtil.dropConsumer(turtle)); // Attack the entity - var attacked = false; var result = PlatformHelper.get().canAttackEntity(player, hitEntity); if (result.consumesAction()) { attacked = true; } else if (result == InteractionResult.PASS && hitEntity.isAttackable() && !hitEntity.skipAttackInteraction(player)) { - var damage = (float) player.getAttributeValue(Attributes.ATTACK_DAMAGE) * damageMulitiplier; - if (damage > 0.0f) { - var source = player.damageSources().playerAttack(player); - if (hitEntity instanceof ArmorStand) { - // Special case for armor stands: attack twice to guarantee destroy - hitEntity.hurt(source, damage); - if (hitEntity.isAlive()) hitEntity.hurt(source, damage); - attacked = true; - } else { - if (hitEntity.hurt(source, damage)) attacked = true; - } - } + attacked = attack(player, direction, hitEntity); } // Stop claiming drops TurtleUtil.stopConsuming(turtle); - // Put everything we collected into the turtles inventory, then return + // Put everything we collected into the turtles inventory. + setToolStack(turtle, side, player.getItemInHand(InteractionHand.MAIN_HAND)); player.getInventory().clearContent(); - if (attacked) return TurtleCommandResult.success(); } - return TurtleCommandResult.failure("Nothing to attack here"); + return attacked ? TurtleCommandResult.success() : TurtleCommandResult.failure("Nothing to attack here"); } - private TurtleCommandResult dig(ITurtleAccess turtle, Direction direction) { - if (PlatformHelper.get().hasToolUsage(item) && TurtlePlaceCommand.deployCopiedItem(item.copy(), turtle, direction, null, null)) { - return TurtleCommandResult.success(); + /** + * Attack an entity. This is a copy of {@link Player#attack(Entity)}, with some unwanted features removed (sweeping + * edge). This is a little limited. + *

+ * Ideally we'd use attack directly (if other mods mixin to that method, we won't support their features). + * Unfortunately,that doesn't give us any feedback to whether the attack occurred or not (and we don't want to play + * sounds/particles). + * + * @param player The fake player doing the attacking. + * @param direction The direction the turtle is attacking. + * @param entity The entity to attack. + * @return Whether we attacked or not. + * @see Player#attack(Entity) + */ + private boolean attack(ServerPlayer player, Direction direction, Entity entity) { + var baseDamage = (float) player.getAttributeValue(Attributes.ATTACK_DAMAGE) * damageMulitiplier; + var bonusDamage = EnchantmentHelper.getDamageBonus( + player.getItemInHand(InteractionHand.MAIN_HAND), entity instanceof LivingEntity target ? target.getMobType() : MobType.UNDEFINED + ); + var damage = baseDamage + bonusDamage; + if (damage <= 0) return false; + + var knockBack = EnchantmentHelper.getKnockbackBonus(player); + + // We follow the logic in Player.attack of setting the entity on fire before attacking, so it's burning when it + // (possibly) dies. + var fireAspect = EnchantmentHelper.getFireAspect(player); + var onFire = false; + if (entity instanceof LivingEntity target && fireAspect > 0 && !target.isOnFire()) { + onFire = true; + target.setSecondsOnFire(1); } - var level = (ServerLevel) turtle.getLevel(); - var turtlePosition = turtle.getPosition(); + var source = player.damageSources().playerAttack(player); + if (!entity.hurt(source, damage)) { + // If we failed to damage the entity, undo us setting the entity on fire. + if (onFire) entity.clearFire(); + return false; + } - var blockPosition = turtlePosition.relative(direction); + // Special case for armor stands: attack twice to guarantee destroy + if (entity.isAlive() && entity instanceof ArmorStand) entity.hurt(source, damage); + + // Apply knockback + if (knockBack > 0) { + if (entity instanceof LivingEntity target) { + target.knockback(knockBack * 0.5, -direction.getStepX(), -direction.getStepZ()); + } else { + entity.push(direction.getStepX() * knockBack * 0.5, 0.1, direction.getStepZ() * knockBack * 0.5); + } + } + + // Apply remaining enchantments + if (entity instanceof LivingEntity target) EnchantmentHelper.doPostHurtEffects(target, player); + EnchantmentHelper.doPostDamageEffects(player, entity); + + // Damage the original item stack. + if (entity instanceof LivingEntity target) { + player.getItemInHand(InteractionHand.MAIN_HAND).hurtEnemy(target, player); + } + + // Apply fire aspect + if (entity instanceof LivingEntity target && fireAspect > 0 && !target.isOnFire()) { + target.setSecondsOnFire(4 * fireAspect); + } + + return true; + } + + private TurtleCommandResult dig(ITurtleAccess turtle, TurtleSide side, Direction direction) { + var level = (ServerLevel) turtle.getLevel(); + + var blockPosition = turtle.getPosition().relative(direction); if (level.isEmptyBlock(blockPosition) || WorldUtil.isLiquidBlock(level, blockPosition)) { return TurtleCommandResult.failure("Nothing to dig here"); } - var turtlePlayer = TurtlePlayer.getWithPosition(turtle, turtlePosition, direction); - turtlePlayer.loadInventory(item.copy()); + return withEquippedItem(turtle, side, direction, turtlePlayer -> { + var stack = turtlePlayer.player().getItemInHand(InteractionHand.MAIN_HAND); - // Check if we can break the block - var breakable = checkBlockBreakable(level, blockPosition, turtlePlayer); - if (!breakable.isSuccess()) return breakable; + // Right-click the block when using a shovel/hoe. + if (PlatformHelper.get().hasToolUsage(item) && TurtlePlaceCommand.deploy(stack, turtle, turtlePlayer, direction, null, null)) { + return TurtleCommandResult.success(); + } - DropConsumer.set(level, blockPosition, TurtleUtil.dropConsumer(turtle)); - var broken = !turtlePlayer.isBlockProtected(level, blockPosition) && turtlePlayer.player().gameMode.destroyBlock(blockPosition); - TurtleUtil.stopConsuming(turtle); + // Check if we can break the block + var breakable = checkBlockBreakable(level, blockPosition, turtlePlayer); + if (!breakable.isSuccess()) return breakable; - // Check spawn protection - return broken ? TurtleCommandResult.success() : TurtleCommandResult.failure("Cannot break protected block"); + // And break it! + DropConsumer.set(level, blockPosition, TurtleUtil.dropConsumer(turtle)); + var broken = !turtlePlayer.isBlockProtected(level, blockPosition) && turtlePlayer.player().gameMode.destroyBlock(blockPosition); + TurtleUtil.stopConsuming(turtle); + + return broken ? TurtleCommandResult.success() : TurtleCommandResult.failure("Cannot break protected block"); + }); } private static boolean isTriviallyBreakable(BlockGetter reader, BlockPos pos, BlockState state) { diff --git a/projects/common/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleToolSerialiser.java b/projects/common/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleToolSerialiser.java index 7a635c214..f46fdfcb2 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleToolSerialiser.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleToolSerialiser.java @@ -5,6 +5,7 @@ package dan200.computercraft.shared.turtle.upgrades; import com.google.gson.JsonObject; +import dan200.computercraft.api.turtle.TurtleToolDurability; import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser; import dan200.computercraft.api.upgrades.UpgradeBase; import dan200.computercraft.shared.platform.RegistryWrappers; @@ -28,6 +29,8 @@ public final class TurtleToolSerialiser implements TurtleUpgradeSerialiser breakable = null; if (object.has("breakable")) { @@ -35,7 +38,7 @@ public final class TurtleToolSerialiser implements TurtleUpgradeSerialiser Date: Tue, 4 Jul 2023 23:03:03 +0000 Subject: [PATCH 2/3] Translations for Russian (ru_ru) Translations for French Co-authored-by: chesiren Co-authored-by: ego-rick --- .../assets/computercraft/lang/fr_fr.json | 12 ++++++++++- .../assets/computercraft/lang/ru_ru.json | 20 ++++++++++++++++++- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/projects/common/src/main/resources/assets/computercraft/lang/fr_fr.json b/projects/common/src/main/resources/assets/computercraft/lang/fr_fr.json index 621ff053a..2ee5b546e 100644 --- a/projects/common/src/main/resources/assets/computercraft/lang/fr_fr.json +++ b/projects/common/src/main/resources/assets/computercraft/lang/fr_fr.json @@ -217,5 +217,15 @@ "gui.computercraft.config.http.enabled.tooltip": "Active l'API \"http\" sur les ordinateurs. Cela désactive également les programmes \"pastebin\" et \"wget\",\nsur lesquels de nombreux utilisateurs comptent. Il est recommandé de laisser cette option activée et\nd'utiliser l'option de configuration \"rules\" pour imposer un contrôle plus précis.", "gui.computercraft.config.peripheral.modem_range.tooltip": "La portée des modems sans fil à basse altitude par temps dégagé, en mètres.\nPlage : 0 ~ 100000", "gui.computercraft.config.peripheral.monitor_bandwidth.tooltip": "La limite de la quantité de données du moniteur pouvant être envoyées *par tick*. Note :\n - La bande passante est mesurée avant la compression, donc les données envoyées\n au client sont plus petites.\n - Cela ignore le nombre de joueurs auxquels un paquet est envoyé. La mise à jour d'un\n moniteur pour un joueur consomme la même limite de bande passante que l'envoi à 20.\n - Un moniteur de taille normale envoie ~25ko de données. Ainsi, la valeur par défaut (1Mo) permet \n à environ 40 moniteurs d'être mis à jour en un seul tick.\nMettre à 0 pour désactiver.\nPlage : > 0", - "gui.computercraft.config.http.rules.tooltip": "Une liste de règles qui contrôlent le comportement de l'API \"http\" pour des domaines\nou des IP spécifiques. Chaque règle est un élément avec un 'hôte' à comparer et une série\nde propriétés. Les règles sont évaluées dans l'ordre, ce qui signifie que les règles antérieures\nremplacent les suivantes.\nL'hôte peut être un nom de domaine (\"pastebin.com\"), un astérisque (\"*.pastebin.com\") ou\nune notation CIDR (\"127.0.0.0/8\").\nS'il n'y a pas de règles, le domaine est bloqué." + "gui.computercraft.config.http.rules.tooltip": "Une liste de règles qui contrôlent le comportement de l'API \"http\" pour des domaines\nou des IP spécifiques. Chaque règle est un élément avec un 'hôte' à comparer et une série\nde propriétés. Les règles sont évaluées dans l'ordre, ce qui signifie que les règles antérieures\nremplacent les suivantes.\nL'hôte peut être un nom de domaine (\"pastebin.com\"), un astérisque (\"*.pastebin.com\") ou\nune notation CIDR (\"127.0.0.0/8\").\nS'il n'y a pas de règles, le domaine est bloqué.", + "gui.computercraft.config.http.proxy.host.tooltip": "Le nom d'hôte ou l'adresse IP du serveur proxy.", + "gui.computercraft.config.http.proxy.tooltip": "Tunnelise les requêtes HTTP et websocket via un serveur proxy. Affecte uniquement\nles règles HTTP avec \"use_proxy\" défini sur true (désactivé par défaut).\nSi l'authentification est requise pour le proxy, créez un fichier \"computercraft-proxy.pw\"\ndans le même dossier que \"computercraft-server.toml\", contenant le\nnom d'utilisateur et mot de passe séparés par deux-points, par ex. \"monutilisateur:monmotdepasse\". Pour\nProxy SOCKS4, seul le nom d'utilisateur est requis.", + "gui.computercraft.config.upload_max_size.tooltip": "La taille limite de téléversement de fichier, en octets. Doit être compris entre 1 Kio et 16 Mio.\nGardez à l'esprit que les téléversements sont traités en un seul clic - les fichiers volumineux ou\nde mauvaises performances réseau peuvent bloquer le thread du réseau. Et attention à l'espace disque !\nPlage : 1024 ~ 16777216", + "gui.computercraft.config.http.proxy": "Proxy", + "gui.computercraft.config.http.proxy.host": "Nom d'hôte", + "gui.computercraft.config.http.proxy.port": "Port", + "gui.computercraft.config.http.proxy.port.tooltip": "Le port du serveur proxy.\nPlage : 1 ~ 65536", + "gui.computercraft.config.http.proxy.type": "Type de proxy", + "gui.computercraft.config.http.proxy.type.tooltip": "Le type de proxy à utiliser.\nValeurs autorisées : HTTP, HTTPS, SOCKS4, SOCKS5", + "gui.computercraft.config.upload_max_size": "Taille limite de téléversement de fichiers (octets)" } diff --git a/projects/common/src/main/resources/assets/computercraft/lang/ru_ru.json b/projects/common/src/main/resources/assets/computercraft/lang/ru_ru.json index 8495d2376..66a7390ca 100644 --- a/projects/common/src/main/resources/assets/computercraft/lang/ru_ru.json +++ b/projects/common/src/main/resources/assets/computercraft/lang/ru_ru.json @@ -177,5 +177,23 @@ "gui.computercraft.config.turtle.need_fuel": "Включить механику топлива", "gui.computercraft.config.turtle.normal_fuel_limit": "Лимит топлива Черепашек", "gui.computercraft.config.turtle.normal_fuel_limit.tooltip": "Лимит топлива для Черепашек.\nОграничение: > 0", - "gui.computercraft.config.turtle.tooltip": "Разные настройки, связанные с черепашками." + "gui.computercraft.config.turtle.tooltip": "Разные настройки, связанные с черепашками.", + "gui.computercraft.config.http.proxy.port": "Порт", + "gui.computercraft.config.http.proxy.port.tooltip": "Порт прокси-сервера.\nДиапазон: 1 ~ 65536", + "gui.computercraft.config.http.proxy.host": "Имя хоста", + "gui.computercraft.config.http.proxy": "Proxy", + "gui.computercraft.config.http.proxy.host.tooltip": "Имя хоста или IP-адрес прокси-сервера.", + "gui.computercraft.config.http.proxy.tooltip": "Туннелирует HTTP-запросы и запросы websocket через прокси-сервер. Влияет только на HTTP\nправила с параметром \"use_proxy\" в значении true (отключено по умолчанию).\nЕсли для прокси-сервера требуется аутентификация, создайте \"computercraft-proxy.pw\"\nфайл в том же каталоге, что и \"computercraft-server.toml\", содержащий имя\nпользователя и пароль, разделенные двоеточием, например \"myuser:mypassword\". Для\nпрокси-серверов SOCKS4 требуется только имя пользователя.", + "gui.computercraft.config.http.proxy.type": "Тип прокси-сервера", + "gui.computercraft.config.http.proxy.type.tooltip": "Тип используемого прокси-сервера.\nДопустимые значения: HTTP, HTTPS, SOCKS4, SOCKS5", + "gui.computercraft.upload.no_response.msg": "Ваш компьютер не использовал переданные вами файлы. Возможно, вам потребуется запустить программу %s и повторить попытку.", + "tracking_field.computercraft.max": "%s (максимальное)", + "tracking_field.computercraft.count": "%s (количество)", + "gui.computercraft.config.http.rules": "Разрешающие/запрещающие правила", + "gui.computercraft.config.http.websocket_enabled": "Включить веб-сокеты", + "gui.computercraft.config.http.websocket_enabled.tooltip": "Включить использование http веб-сокетов. Для этого необходимо, чтобы параметр «http_enable» был true.", + "gui.computercraft.config.log_computer_errors": "Регистрировать ошибки компьютера", + "gui.computercraft.config.log_computer_errors.tooltip": "Регистрировать исключения, вызванные периферийными устройствами и другими объектами Lua. Это облегчает\nдля авторам модов устранение проблем, но может привести к спаму в логах, если люди будут использовать\nглючные методы.", + "gui.computercraft.config.maximum_open_files": "Максимальное количество файлов, открытых на одном компьютере", + "gui.computercraft.config.http.rules.tooltip": "Список правил, которые контролируют поведение «http» API для определенных доменов или\nIP-адресов. Каждое правило представляет собой элемент с «узлом» для сопоставления и набором\nсвойств. Правила оцениваются по порядку, то есть более ранние правила перевешивают\nболее поздние.\nХост может быть доменным именем (\"pastebin.com\"), wildcard-сертификатом (\"*.pastebin.com\") или\nнотацией CIDR (\"127.0.0.0/8\").\nЕсли правил нет, домен блокируется." } From e337a637122b1619d0985a2a578104e77fbcbb2d Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Wed, 5 Jul 2023 20:57:56 +0100 Subject: [PATCH 3/3] Bump FG and Loom Also split up CI steps a little, so that it's more clear what failed. --- .github/workflows/main-ci.yml | 39 ++++++++++--------- .../gradle/common/util/runs/RunConfigSetup.kt | 31 +++------------ gradle/libs.versions.toml | 4 +- 3 files changed, 29 insertions(+), 45 deletions(-) diff --git a/.github/workflows/main-ci.yml b/.github/workflows/main-ci.yml index 8baff3d9c..d94091eb9 100644 --- a/.github/workflows/main-ci.yml +++ b/.github/workflows/main-ci.yml @@ -8,16 +8,16 @@ jobs: runs-on: ubuntu-latest steps: - - name: Clone repository + - name: 📥 Clone repository uses: actions/checkout@v3 - - name: Set up Java + - name: 📥 Set up Java uses: actions/setup-java@v3 with: java-version: 17 distribution: 'temurin' - - name: Setup Gradle + - name: 📥 Setup Gradle uses: gradle/gradle-build-action@v2 with: cache-read-only: ${{ !startsWith(github.ref, 'refs/heads/mc-') }} @@ -27,42 +27,45 @@ jobs: mkdir -p ~/.gradle echo "org.gradle.daemon=false" >> ~/.gradle/gradle.properties - - name: Build with Gradle + - name: ⚒️ Build run: ./gradlew assemble || ./gradlew assemble - - name: Download assets for game tests + - name: 💡 Lint + uses: pre-commit/action@v3.0.0 + + - name: 🧪 Run tests + run: ./gradlew test validateMixinNames checkChangelog + + - name: 📥 Download assets for game tests run: ./gradlew downloadAssets || ./gradlew downloadAssets - - name: Run tests and linters - run: ./gradlew build + - name: 🧪 Run integration tests + run: ./gradlew runGametest - - name: Run client tests + - name: 🧪 Run client tests run: ./gradlew runGametestClient # Not checkClient, as no point running rendering tests. # These are a little flaky on GH actions: its useful to run them, but don't break the build. continue-on-error: true - - name: Prepare Jars + - name: 🧪 Parse test reports + run: ./tools/parse-reports.py + if: ${{ failure() }} + + - name: 📦 Prepare Jars run: | # Find the main jar and append the git hash onto it. mkdir -p jars find projects/forge/build/libs projects/fabric/build/libs -type f -regex '.*[0-9.]+\(-SNAPSHOT\)?\.jar$' -exec bash -c 'cp {} "jars/$(basename {} .jar)-$(git rev-parse HEAD).jar"' \; - - name: Upload Jar + - name: 📤 Upload Jar uses: actions/upload-artifact@v3 with: name: CC-Tweaked path: ./jars - - name: Upload coverage + - name: 📤 Upload coverage uses: codecov/codecov-action@v3 - - name: Parse test reports - run: ./tools/parse-reports.py - if: ${{ failure() }} - - - name: Run linters - uses: pre-commit/action@v3.0.0 - build-core: strategy: fail-fast: false diff --git a/buildSrc/src/main/kotlin/net/minecraftforge/gradle/common/util/runs/RunConfigSetup.kt b/buildSrc/src/main/kotlin/net/minecraftforge/gradle/common/util/runs/RunConfigSetup.kt index 50e33a84b..a74d0008d 100644 --- a/buildSrc/src/main/kotlin/net/minecraftforge/gradle/common/util/runs/RunConfigSetup.kt +++ b/buildSrc/src/main/kotlin/net/minecraftforge/gradle/common/util/runs/RunConfigSetup.kt @@ -30,41 +30,22 @@ internal fun setRunConfigInternal(project: Project, spec: JavaExecSpec, config: val originalTask = project.tasks.named(config.taskName, MinecraftRunTask::class.java) // Add argument and JVM argument via providers, to be as lazy as possible with fetching artifacts. - fun lazyTokens(): MutableMap> { - return RunConfigGenerator.configureTokensLazy( - project, config, RunConfigGenerator.mapModClassesToGradle(project, config), - originalTask.get().minecraftArtifacts.files, - originalTask.get().runtimeClasspathArtifacts.files, - ) - } + val lazyTokens = RunConfigGenerator.configureTokensLazy( + project, config, RunConfigGenerator.mapModClassesToGradle(project, config), + originalTask.get().minecraftArtifacts, + originalTask.get().runtimeClasspathArtifacts, + ) spec.argumentProviders.add( CommandLineArgumentProvider { - RunConfigGenerator.getArgsStream(config, lazyTokens(), false).toList() + RunConfigGenerator.getArgsStream(config, lazyTokens, false).toList() }, ) spec.jvmArgumentProviders.add( CommandLineArgumentProvider { - val lazyTokens = lazyTokens() (if (config.isClient) config.jvmArgs + originalTask.get().additionalClientArgs.get() else config.jvmArgs).map { config.replace(lazyTokens, it) } + config.properties.map { (k, v) -> "-D${k}=${config.replace(lazyTokens, v)}" } }, ) - // We can't configure environment variables lazily, so we do these now with a more minimal lazyTokens set. - val lazyTokens = mutableMapOf>() - for ((k, v) in config.tokens) lazyTokens[k] = Supplier { v } - for ((k, v) in config.lazyTokens) lazyTokens[k] = v - lazyTokens.compute( - "source_roots", - { _, sourceRoots -> - Supplier { - val modClasses = RunConfigGenerator.mapModClassesToGradle(project, config) - (when (sourceRoots) { - null -> modClasses - else -> Stream.concat(sourceRoots.get().split(File.pathSeparator).stream(), modClasses) - }).distinct().collect(Collectors.joining(File.pathSeparator)) - } - }, - ) for ((key, value) in config.environment) spec.environment(key, config.replace(lazyTokens, value)) } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9152cadaa..d3e1f4fc4 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -53,8 +53,8 @@ checkstyle = "10.3.4" curseForgeGradle = "1.0.14" errorProne-core = "2.18.0" errorProne-plugin = "3.0.1" -fabric-loom = "1.2.7" -forgeGradle = "6.0.6" +fabric-loom = "1.3.7" +forgeGradle = "6.0.8" githubRelease = "2.2.12" ideaExt = "1.1.6" illuaminate = "0.1.0-28-ga7efd71"