diff --git a/common/src/main/java/dev/ftb/mods/ftbchunks/FTBChunks.java b/common/src/main/java/dev/ftb/mods/ftbchunks/FTBChunks.java index f22fe090..1bf08126 100644 --- a/common/src/main/java/dev/ftb/mods/ftbchunks/FTBChunks.java +++ b/common/src/main/java/dev/ftb/mods/ftbchunks/FTBChunks.java @@ -36,6 +36,7 @@ import net.minecraft.server.level.ServerPlayer; import net.minecraft.stats.Stats; import net.minecraft.world.InteractionHand; +import net.minecraft.world.damagesource.DamageSource; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.MobSpawnType; @@ -118,10 +119,11 @@ public FTBChunks() { PlayerEvent.FILL_BUCKET.register(this::fillBucket); PlayerEvent.PLAYER_CLONE.register(this::playerCloned); PlayerEvent.CHANGE_DIMENSION.register(this::playerChangedDimension); - PlayerEvent.ATTACK_ENTITY.register(this::playerAttackEntity); + PlayerEvent.ATTACK_ENTITY.register(this::playerAttackNonLivingEntity); EntityEvent.ENTER_SECTION.register(this::enterSection); EntityEvent.LIVING_CHECK_SPAWN.register(this::checkSpawn); + EntityEvent.LIVING_HURT.register(this::livingEntityHurt); ExplosionEvent.DETONATE.register(this::explosionDetonate); @@ -137,7 +139,7 @@ public FTBChunks() { } } - private EventResult playerAttackEntity(Player player, Level level, Entity entity, InteractionHand interactionHand, @Nullable EntityHitResult entityHitResult) { + private EventResult playerAttackNonLivingEntity(Player player, Level level, Entity entity, InteractionHand interactionHand, @Nullable EntityHitResult entityHitResult) { // note: intentionally does not prevent attacking living entities; // this is for preventing griefing of entities like paintings & item frames if (player instanceof ServerPlayer && !(entity instanceof LivingEntity) && FTBChunksAPI.getManager().protect(player, interactionHand, entity.blockPosition(), Protection.ATTACK_NONLIVING_ENTITY, entity)) { @@ -147,6 +149,22 @@ private EventResult playerAttackEntity(Player player, Level level, Entity entity return EventResult.pass(); } + private EventResult livingEntityHurt(LivingEntity livingEntity, DamageSource damageSource, float damage) { + if (livingEntity.level.isClientSide || !FTBChunksAPI.isManagerLoaded()) return EventResult.pass(); + if (damageSource.getEntity() instanceof ServerPlayer player) { + if (FTBChunksAPI.getManager().protect(player, player.swingingArm, livingEntity.blockPosition(), Protection.ATTACK_LIVING_ENTITY, livingEntity)) { + return EventResult.interruptFalse(); + } + } else if (livingEntity.getType().is(FTBChunksAPI.LIVING_ENTITY_ATTACK_BLACKLIST_TAG)) { // this block protects the entities from unknown sources + ClaimedChunk chunk = FTBChunksAPI.getManager().getChunk(new ChunkDimPos(livingEntity.level, livingEntity.blockPosition())); + if (chunk != null) { + return EventResult.interruptFalse(); + } + } + + return EventResult.pass(); + } + private void playerTickPost(Player player) { if (player.level.isClientSide && player.level.getGameTime() % 20 == 0) { FTBChunks.PROXY.maybeClearDeathpoint(player); @@ -234,6 +252,8 @@ public void loggedOut(ServerPlayer player) { // last player on the team to log out; unforce chunks if the team can't do offline chunk-loading data.updateChunkTickets(false); } + + data.setLastLogoffTime(System.currentTimeMillis()); } private void teamCreated(TeamCreatedEvent teamEvent) { @@ -251,10 +271,9 @@ private void teamSaved(TeamEvent teamEvent) { public EventResult blockLeftClick(Player player, InteractionHand hand, BlockPos pos, Direction face) { // calling architectury stub method //noinspection ConstantConditions - if (player instanceof ServerPlayer && FTBChunksAPI.getManager().protect(player, hand, pos, FTBChunksExpected.getBlockBreakProtection(), null)) { + if (player instanceof ServerPlayer && FTBChunksAPI.getManager().protect(player, hand, pos, FTBChunksExpected.getBlockLeftClickProtection(), null)) { return EventResult.interruptFalse(); } - return EventResult.pass(); } @@ -288,10 +307,9 @@ private EventResult interactEntity(Player player, Entity entity, InteractionHand } public EventResult blockBreak(Level level, BlockPos pos, BlockState blockState, ServerPlayer player, @Nullable IntValue intValue) { - if (FTBChunksAPI.getManager().protect(player, InteractionHand.MAIN_HAND, pos, FTBChunksExpected.getBlockBreakProtection(), null)) { + if (FTBChunksAPI.getManager().protect(player, InteractionHand.MAIN_HAND, pos, FTBChunksExpected.getBlockBreakProtection(), null, true)) { return EventResult.interruptFalse(); } - return EventResult.pass(); } @@ -436,6 +454,7 @@ private void teamConfig(TeamCollectPropertiesEvent event) { event.add(FTBChunksTeamData.ALLOW_ALL_FAKE_PLAYERS); event.add(FTBChunksTeamData.ALLOW_NAMED_FAKE_PLAYERS); event.add(FTBChunksTeamData.ALLOW_FAKE_PLAYERS_BY_ID); + event.add(FTBChunksTeamData.ALLOW_ATTACK_BLACKLISTED_ENTITIES); // block edit/interact properties vary on forge & fabric FTBChunksExpected.getPlatformSpecificProperties(event); diff --git a/common/src/main/java/dev/ftb/mods/ftbchunks/FTBChunksCommands.java b/common/src/main/java/dev/ftb/mods/ftbchunks/FTBChunksCommands.java index fd9499ca..1612c76c 100644 --- a/common/src/main/java/dev/ftb/mods/ftbchunks/FTBChunksCommands.java +++ b/common/src/main/java/dev/ftb/mods/ftbchunks/FTBChunksCommands.java @@ -41,6 +41,7 @@ import net.minecraft.util.Mth; import net.minecraft.world.level.Level; import net.minecraft.world.phys.Vec3; +import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.Comparator; @@ -173,6 +174,14 @@ public static void registerCommands(CommandDispatcher dispat .executes(context -> viewLoadedChunks(context.getSource(), DimensionArgument.getDimension(context, "dimension"))) ) ) + .then(Commands.literal("reset_block_break_counter") + .then(Commands.literal("all") + .executes(context -> resetBlockBreakCounter(context.getSource(), null)) + ) + .then(Commands.argument("team", TeamArgument.create()) + .executes(context -> resetBlockBreakCounter(context.getSource(), TeamArgument.get(context, "team"))) + ) + ) ) .then(Commands.literal("block_color") .requires(source -> source.getServer().isSingleplayer()) @@ -505,4 +514,14 @@ private static ColumnPos toColumn(Vec3 pos) { private static Team selfTeam(CommandSourceStack source) throws CommandSyntaxException { return FTBTeamsAPI.getPlayerTeam(source.getPlayerOrException()); } + + private static int resetBlockBreakCounter(CommandSourceStack source, @Nullable Team team) { + if (team == null) { + FTBChunksAPI.getManager().getAllTeamData().forEach(FTBChunksTeamData::resetBrokenBlocksCounter); + } else { + FTBChunksTeamData data = FTBChunksAPI.getManager().getData(team); + data.resetBrokenBlocksCounter(); + } + return 1; + } } diff --git a/common/src/main/java/dev/ftb/mods/ftbchunks/FTBChunksExpected.java b/common/src/main/java/dev/ftb/mods/ftbchunks/FTBChunksExpected.java index bc80b72f..2dfc4789 100644 --- a/common/src/main/java/dev/ftb/mods/ftbchunks/FTBChunksExpected.java +++ b/common/src/main/java/dev/ftb/mods/ftbchunks/FTBChunksExpected.java @@ -34,4 +34,9 @@ public static Protection getBlockInteractProtection() { public static Protection getBlockBreakProtection() { throw new AssertionError(); } + + @ExpectPlatform + public static Protection getBlockLeftClickProtection() { + throw new AssertionError(); + } } diff --git a/common/src/main/java/dev/ftb/mods/ftbchunks/FTBChunksWorldConfig.java b/common/src/main/java/dev/ftb/mods/ftbchunks/FTBChunksWorldConfig.java index cadc1690..fdc50da1 100644 --- a/common/src/main/java/dev/ftb/mods/ftbchunks/FTBChunksWorldConfig.java +++ b/common/src/main/java/dev/ftb/mods/ftbchunks/FTBChunksWorldConfig.java @@ -38,6 +38,13 @@ public interface FTBChunksWorldConfig { EnumValue PARTY_LIMIT_MODE = CONFIG.getEnum("party_limit_mode", PartyLimitMode.NAME_MAP).comment("Method by which party claim & force-load limits are calculated.","LARGEST: use the limits of the member with the largest limits","SUM: add up all the members' limits","OWNER: use the party owner's limits only","AVERAGE: use the average of all members' limits."); BooleanValue REQUIRE_GAME_STAGE = CONFIG.getBoolean("require_game_stage", false).comment("If true, the player must have the 'ftbchunks_mapping' Game stage to be able to use the map and minimap.\nRequires KubeJS and/or Gamestages to be installed."); BooleanValue LOCATION_MODE_OVERRIDE = CONFIG.getBoolean("location_mode_override", false).comment("If true, \"Location Visibility\" team settings are ignored, and all players can see each other anywhere on the map."); + BooleanValue OFFLINE_PROTECTION_ONLY = CONFIG.getBoolean("offline_protection_only", false).comment("If enabled and disable_protection = false enemy players will ONLY be able to damage your claimed chunks if you or your team mates are online."); + IntValue OFFLINE_PROTECTION_BUFFER = CONFIG.getInt("offline_protection_buffer", 30).comment("If offline_protection_only = true, the time in SECONDS after all members of a team log off, before chunk protection turns on. This setting is meant to discourage combat logging. Set to 0 to disable. Set to -1 for unlimited block breaking if offline_protection_only = true."); + IntValue MAX_DESTROY_BLOCKS_PER_HOUR = CONFIG.getInt("max_destroy_blocks_per_hour", 0).comment("If disable_protection = false, this many blocks can still be destroyed per hour be enemy players. 0 disables this."); + IntValue DESTROY_BLOCKS_COUNT_PERIOD = CONFIG.getInt("destroy_blocks_count_period", 300).comment("If max_destroy_blocks_per_hour > 0, the groups of time in seconds where the number of blocks broken are counted. Groups younger than an hour contribute to the total blocks broken. Groups older than an hour are removed."); + BooleanValue PROTECT_ENTITIES_OFFLINE_ONLY = CONFIG.getBoolean("protect_entities_offline_only", true).comment("Only protect the living entities listed in the living_entity_attack_blacklist tag when all team members are offline."); + BooleanValue PROTECT_NAMED_ENTITIES = CONFIG.getBoolean("protect_named_entities", false).comment("Protect entities that have a name tag."); + BooleanValue PROTECT_UNKNOWN_BLOCK_BREAKER = CONFIG.getBoolean("protect_unknown_block_breaker", true).comment("Protect blocks if the owner of the block breaker is not defined."); static int getMaxClaimedChunks(FTBChunksTeamData playerData, ServerPlayer player) { if (player != null) { diff --git a/common/src/main/java/dev/ftb/mods/ftbchunks/data/ClaimedChunkManager.java b/common/src/main/java/dev/ftb/mods/ftbchunks/data/ClaimedChunkManager.java index cbfefd15..d0e7a98e 100644 --- a/common/src/main/java/dev/ftb/mods/ftbchunks/data/ClaimedChunkManager.java +++ b/common/src/main/java/dev/ftb/mods/ftbchunks/data/ClaimedChunkManager.java @@ -194,6 +194,24 @@ public void setBypassProtection(UUID player, boolean bypass) { * @return true to prevent the interaction, false to permit it */ public boolean protect(@Nullable Entity entity, InteractionHand hand, BlockPos pos, Protection protection, @Nullable Entity targetEntity) { + return protect(entity, hand, pos, protection, targetEntity, false); + } + + /** + * Check if the intended interaction should be prevented from occurring. + * + * @param entity the entity performing the interaction + * @param hand the actor's hand + * @param pos the block position at which the action will be performed + * @param protection the type of protection being checked for + * @param targetEntity the entity being acted upon, if any (e.g. a painting, armor stand etc.) + * @param protectIfNullEntity if true, and entity = null, will return true + * @return true to prevent the interaction, false to permit it + */ + public boolean protect(@Nullable Entity entity, InteractionHand hand, BlockPos pos, Protection protection, @Nullable Entity targetEntity, boolean protectIfNullEntity) { + if (protectIfNullEntity && entity == null && FTBChunksWorldConfig.PROTECT_UNKNOWN_BLOCK_BREAKER.get()) { + return true; + } if (!(entity instanceof ServerPlayer player) || FTBChunksWorldConfig.DISABLE_PROTECTION.get() || player.level == null) { return false; } @@ -267,4 +285,8 @@ public void registerClaim(ChunkDimPos pos, ClaimedChunk chunk) { public void unregisterClaim(ChunkDimPos pos) { claimedChunks.remove(pos); } + + public Collection getAllTeamData() { + return teamData.values(); + } } diff --git a/common/src/main/java/dev/ftb/mods/ftbchunks/data/FTBChunksAPI.java b/common/src/main/java/dev/ftb/mods/ftbchunks/data/FTBChunksAPI.java index 8d05259e..74d51c95 100644 --- a/common/src/main/java/dev/ftb/mods/ftbchunks/data/FTBChunksAPI.java +++ b/common/src/main/java/dev/ftb/mods/ftbchunks/data/FTBChunksAPI.java @@ -22,10 +22,12 @@ */ public class FTBChunksAPI { public static final TagKey EDIT_WHITELIST_TAG = TagKey.create(Registry.BLOCK_REGISTRY, new ResourceLocation(FTBChunks.MOD_ID, "edit_whitelist")); + public static final TagKey EDIT_BLACKLIST_TAG = TagKey.create(Registry.BLOCK_REGISTRY, new ResourceLocation(FTBChunks.MOD_ID, "edit_blacklist")); public static final TagKey INTERACT_WHITELIST_TAG = TagKey.create(Registry.BLOCK_REGISTRY, new ResourceLocation(FTBChunks.MOD_ID, "interact_whitelist")); public static final TagKey RIGHT_CLICK_BLACKLIST_TAG = TagKey.create(Registry.ITEM_REGISTRY, new ResourceLocation(FTBChunks.MOD_ID, "right_click_blacklist")); public static final TagKey RIGHT_CLICK_WHITELIST_TAG = TagKey.create(Registry.ITEM_REGISTRY, new ResourceLocation(FTBChunks.MOD_ID, "right_click_whitelist")); public static final TagKey> ENTITY_INTERACT_WHITELIST_TAG = TagKey.create(Registry.ENTITY_TYPE_REGISTRY, new ResourceLocation(FTBChunks.MOD_ID, "entity_interact_whitelist")); + public static final TagKey> LIVING_ENTITY_ATTACK_BLACKLIST_TAG = TagKey.create(Registry.ENTITY_TYPE_REGISTRY, new ResourceLocation(FTBChunks.MOD_ID, "living_entity_attack_blacklist")); public static final TagKey> NONLIVING_ENTITY_ATTACK_WHITELIST_TAG = TagKey.create(Registry.ENTITY_TYPE_REGISTRY, new ResourceLocation(FTBChunks.MOD_ID, "nonliving_entity_attack_whitelist")); public static final TicketType FORCE_LOADED_TICKET = TicketType.create(FTBChunks.MOD_ID + ":force_loaded", Comparator.comparingLong(ChunkPos::toLong)); diff --git a/common/src/main/java/dev/ftb/mods/ftbchunks/data/FTBChunksTeamData.java b/common/src/main/java/dev/ftb/mods/ftbchunks/data/FTBChunksTeamData.java index 9d8ddb9f..ddf13d1d 100644 --- a/common/src/main/java/dev/ftb/mods/ftbchunks/data/FTBChunksTeamData.java +++ b/common/src/main/java/dev/ftb/mods/ftbchunks/data/FTBChunksTeamData.java @@ -28,6 +28,8 @@ import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; import net.minecraft.nbt.Tag; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.Style; import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.MinecraftServer; @@ -35,6 +37,7 @@ import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.entity.player.Player; import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.state.BlockState; import org.jetbrains.annotations.NotNull; import java.nio.file.Path; @@ -59,6 +62,8 @@ public class FTBChunksTeamData { public static final BooleanProperty ALLOW_MOB_GRIEFING = new BooleanProperty(new ResourceLocation(FTBChunks.MOD_ID, "allow_mob_griefing"), false); public static final PrivacyProperty CLAIM_VISIBILITY = new PrivacyProperty(new ResourceLocation(FTBChunks.MOD_ID, "claim_visibility"), PrivacyMode.PUBLIC); + public static final PrivacyProperty ALLOW_ATTACK_BLACKLISTED_ENTITIES = new PrivacyProperty(new ResourceLocation(FTBChunks.MOD_ID, "allow_attack_blacklisted_entities"), PrivacyMode.ALLIES); + // public static final PrivacyProperty MINIMAP_MODE = new PrivacyProperty(new ResourceLocation(FTBChunks.MOD_ID, "minimap_mode"), PrivacyMode.ALLIES); public static final PrivacyProperty LOCATION_MODE = new PrivacyProperty(new ResourceLocation(FTBChunks.MOD_ID, "location_mode"), PrivacyMode.ALLIES); @@ -76,7 +81,9 @@ public class FTBChunksTeamData { public int prevChunkX = Integer.MAX_VALUE, prevChunkZ = Integer.MAX_VALUE; public String lastChunkID = ""; private long lastLoginTime; + private long lastLogoffTime; private Set fakePlayerNameCache; + private final BrokenBlocksCounter brokenBlocksCounter; public FTBChunksTeamData(ClaimedChunkManager m, Path f, Team t) { manager = m; @@ -88,7 +95,9 @@ public FTBChunksTeamData(ClaimedChunkManager m, Path f, Team t) { extraClaimChunks = 0; extraForceLoadChunks = 0; lastLoginTime = 0L; + lastLogoffTime = 0L; memberData = new HashMap<>(); + brokenBlocksCounter = new BrokenBlocksCounter(); } @Override @@ -282,7 +291,7 @@ public boolean isAlly(UUID p) { return team.isAlly(p); } - public boolean canUse(ServerPlayer p, PrivacyProperty property) { + protected boolean baseUseCheck(ServerPlayer p, PrivacyProperty property, boolean offlineCheck) { PrivacyMode mode = team.getProperty(property); if (mode == PrivacyMode.PUBLIC) { @@ -294,8 +303,42 @@ public boolean canUse(ServerPlayer p, PrivacyProperty property) { } else if (mode == PrivacyMode.ALLIES) { return isAlly(p.getUUID()); } else { - return team.isMember(p.getUUID()); + if (team.isMember(p.getUUID())) return true; + if (offlineCheck && FTBChunksWorldConfig.OFFLINE_PROTECTION_ONLY.get()) { + return canUseOffline(); + } + return false; + } + } + + public boolean canUseOffline() { + if (!team.getOnlineMembers().isEmpty()) { + return true; } + long buffer = FTBChunksWorldConfig.OFFLINE_PROTECTION_BUFFER.get(); + long now = System.currentTimeMillis(); + long timeDiff = now - getLastLogoffTime(); + return timeDiff < buffer * 1000; + } + + public boolean canUse(ServerPlayer p, PrivacyProperty property) { + return baseUseCheck(p, property, true); + } + + public boolean canAttackBlackListedEntity(ServerPlayer p, PrivacyProperty property) { + if (baseUseCheck(p, property, true)) return true; + return FTBChunksWorldConfig.PROTECT_ENTITIES_OFFLINE_ONLY.get() && canUseOffline(); + } + + public boolean canBreak(ServerPlayer p, PrivacyProperty property, boolean leftClick, BlockState state) { + if (baseUseCheck(p, property, false)) return true; + if (FTBChunksWorldConfig.OFFLINE_PROTECTION_ONLY.get() && !canUseOffline()) return false; + if (state.is(FTBChunksAPI.EDIT_BLACKLIST_TAG)) return false; + if (brokenBlocksCounter.canBreakBlock(p, leftClick)) { + if (!leftClick) save(); + return true; + } + return false; } private boolean canFakePlayerUse(Player player, PrivacyMode mode) { @@ -338,6 +381,7 @@ public SNBTCompoundTag serializeNBT() { if (extraClaimChunks > 0 && !(team instanceof PartyTeam)) tag.putInt("extra_claim_chunks", extraClaimChunks); if (extraForceLoadChunks > 0 && !(team instanceof PartyTeam)) tag.putInt("extra_force_load_chunks", extraForceLoadChunks); tag.putLong("last_login_time", lastLoginTime); + tag.putLong("last_logoff_time", lastLogoffTime); CompoundTag chunksTag = new CompoundTag(); for (ClaimedChunk chunk : getClaimedChunks()) { @@ -356,6 +400,8 @@ public SNBTCompoundTag serializeNBT() { tag.put("member_data", memberTag); } + tag.put("broken_blocks_counter", brokenBlocksCounter.serializeNBT()); + return tag; } @@ -365,6 +411,7 @@ public void deserializeNBT(CompoundTag tag) { extraClaimChunks = tag.getInt("extra_claim_chunks"); extraForceLoadChunks = tag.getInt("extra_force_load_chunks"); lastLoginTime = tag.getLong("last_login_time"); + lastLogoffTime = tag.getLong("last_logoff_time"); canForceLoadChunks = null; CompoundTag chunksTag = tag.getCompound("chunks"); @@ -391,6 +438,8 @@ public void deserializeNBT(CompoundTag tag) { e.printStackTrace(); } } + + brokenBlocksCounter.deserializeNBT(tag.getCompound("broken_blocks_counter")); } public int getExtraClaimChunks() { @@ -494,6 +543,15 @@ public long getLastLoginTime() { return lastLoginTime; } + public long getLastLogoffTime() { + return lastLogoffTime; + } + + public void setLastLogoffTime(long when) { + this.lastLogoffTime = when; + save(); + } + public boolean shouldHideClaims() { return getTeam().getProperty(CLAIM_VISIBILITY) != PrivacyMode.PUBLIC; } @@ -641,4 +699,118 @@ public void deleteMemberData(UUID playerId) { save(); } } + + public void resetBrokenBlocksCounter() { + brokenBlocksCounter.reset(); + save(); + } + + public static final Style WARNING_STYLE = Style.EMPTY.withColor(0xFFFF55); + public static final int HOUR_TICKS = 60 * 60 * 20; + + public static class BrokenBlocksCounter { + private final List groups = new ArrayList<>(); + public BrokenBlocksCounter() {} + public boolean canBreakBlock(ServerPlayer p, boolean leftClick) { + long time = p.getLevel().getGameTime(); + int blocks_per_hour = FTBChunksWorldConfig.MAX_DESTROY_BLOCKS_PER_HOUR.get(); + if (blocks_per_hour == -1) + return true; + int total = getTotalBrokenBlocks(time); + if (total >= blocks_per_hour) + return false; + if (!leftClick) { + int group_period_tick = FTBChunksWorldConfig.DESTROY_BLOCKS_COUNT_PERIOD.get() * 20; + BrokenBlocksGroup group = getCurrentGroup(time, group_period_tick); + group.addBrokenBlock(); + if (total+1 >= blocks_per_hour) { + p.sendSystemMessage(Component.translatable("ftbchunks.block_break_limit_reached") + .setStyle(WARNING_STYLE)); + } + } + return true; + } + public int getTotalBrokenBlocks(long time) { + removeOldGroups(time); + int total = 0; + for (BrokenBlocksGroup group : groups) + total += group.getBrokenBlocks(); + return total; + } + private BrokenBlocksGroup getCurrentGroup(long time, int length) { + if (groups.isEmpty()) return addGroup(time, length); + BrokenBlocksGroup group = groups.get(0); + if (!group.isCurrentGroup(time)) return addGroup(time, length); + return group; + } + private BrokenBlocksGroup addGroup(long time, int length) { + BrokenBlocksGroup group = new BrokenBlocksGroup(time, length); + groups.add(0, group); + return group; + } + private void removeOldGroups(long time) { + for (int i = 0; i < groups.size(); ++i) + if (groups.get(i).isOutdated(time)) + groups.remove(i--); + } + public void reset() { + groups.clear(); + } + public SNBTCompoundTag serializeNBT() { + SNBTCompoundTag tag = new SNBTCompoundTag(); + ListTag list = new ListTag(); + for (BrokenBlocksGroup group : groups) list.add(group.serializeNBT()); + tag.put("groups", list); + return tag; + } + public void deserializeNBT(CompoundTag tag) { + ListTag list = tag.getList("groups", 10); + for (int i = 0; i < list.size(); ++i){ + CompoundTag groupNBT = list.getCompound(i); + BrokenBlocksGroup group = new BrokenBlocksGroup(); + group.deserializeNBT(groupNBT); + groups.add(group); + } + } + } + + public static class BrokenBlocksGroup { + private long startTime; + private int brokenBlocks, length; + private BrokenBlocksGroup() {} + public BrokenBlocksGroup(long startTime, int length) { + this.startTime = startTime; + this.length = length; + } + public long getStartTime() { + return startTime; + } + public int getBrokenBlocks() { + return brokenBlocks; + } + public void addBrokenBlock() { + ++brokenBlocks; + } + public int getLength() { + return length; + } + public boolean isOutdated(long time) { + return time - (getStartTime() + getLength()) > HOUR_TICKS; + } + public boolean isCurrentGroup(long time) { + return getStartTime() + getLength() > time; + } + public SNBTCompoundTag serializeNBT() { + SNBTCompoundTag tag = new SNBTCompoundTag(); + tag.putLong("start_time", startTime); + tag.putInt("broken_blocks", brokenBlocks); + tag.putInt("length", length); + return tag; + } + public void deserializeNBT(CompoundTag tag) { + startTime = tag.getLong("start_time"); + brokenBlocks = tag.getInt("broken_blocks"); + length = tag.getInt("length"); + } + } } diff --git a/common/src/main/java/dev/ftb/mods/ftbchunks/data/Protection.java b/common/src/main/java/dev/ftb/mods/ftbchunks/data/Protection.java index 8559ed36..be98691c 100644 --- a/common/src/main/java/dev/ftb/mods/ftbchunks/data/Protection.java +++ b/common/src/main/java/dev/ftb/mods/ftbchunks/data/Protection.java @@ -1,6 +1,7 @@ package dev.ftb.mods.ftbchunks.data; import dev.ftb.mods.ftbchunks.FTBCUtils; +import dev.ftb.mods.ftbchunks.FTBChunksWorldConfig; import net.minecraft.core.BlockPos; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.InteractionHand; @@ -14,6 +15,10 @@ public interface Protection { Protection EDIT_BLOCK = (player, pos, hand, chunk, entity) -> { BlockState blockState = player.level.getBlockState(pos); + if (blockState.is(FTBChunksAPI.EDIT_BLACKLIST_TAG)) { + return ProtectionOverride.CHECK; + } + if (blockState.is(FTBChunksAPI.EDIT_WHITELIST_TAG)) { return ProtectionOverride.ALLOW; } @@ -25,6 +30,34 @@ public interface Protection { return ProtectionOverride.CHECK; }; + Protection BREAK_BLOCK = (player, pos, hand, chunk, entity) -> { + BlockState blockState = player.level.getBlockState(pos); + + if (blockState.is(FTBChunksAPI.EDIT_WHITELIST_TAG)) { + return ProtectionOverride.ALLOW; + } + + if (chunk != null && chunk.teamData.canBreak(player, FTBChunksTeamData.BLOCK_EDIT_MODE, false, blockState)) { + return ProtectionOverride.ALLOW; + } + + return ProtectionOverride.CHECK; + }; + + Protection LEFT_CLICK_BLOCK = (player, pos, hand, chunk, entity) -> { + BlockState blockState = player.level.getBlockState(pos); + + if (blockState.is(FTBChunksAPI.EDIT_WHITELIST_TAG)) { + return ProtectionOverride.ALLOW; + } + + if (chunk != null && chunk.teamData.canBreak(player, FTBChunksTeamData.BLOCK_EDIT_MODE, true, blockState)) { + return ProtectionOverride.ALLOW; + } + + return ProtectionOverride.CHECK; + }; + Protection INTERACT_BLOCK = (player, pos, hand, chunk, entity) -> { BlockState blockState = player.level.getBlockState(pos); @@ -81,6 +114,16 @@ public interface Protection { return ProtectionOverride.CHECK; }; + Protection ATTACK_LIVING_ENTITY = (player, pos, hand, chunk, entity) -> { + if (entity != null && (entity.getType().is(FTBChunksAPI.LIVING_ENTITY_ATTACK_BLACKLIST_TAG) + || (FTBChunksWorldConfig.PROTECT_NAMED_ENTITIES.get() && entity.hasCustomName())) + && chunk != null && !chunk.teamData.canAttackBlackListedEntity(player, FTBChunksTeamData.ALLOW_ATTACK_BLACKLISTED_ENTITIES)) { + return ProtectionOverride.CHECK; + } + + return ProtectionOverride.ALLOW; + }; + // for use on Fabric Protection EDIT_AND_INTERACT_BLOCK = (player, pos, hand, chunk, entity) -> { BlockState blockState = player.level.getBlockState(pos); @@ -96,5 +139,33 @@ public interface Protection { return ProtectionOverride.CHECK; }; + Protection BREAK_BLOCK_FABRIC = (player, pos, hand, chunk, entity) -> { + BlockState blockState = player.level.getBlockState(pos); + + if (blockState.is(FTBChunksAPI.INTERACT_WHITELIST_TAG)) { + return ProtectionOverride.ALLOW; + } + + if (chunk != null && chunk.teamData.canBreak(player, FTBChunksTeamData.BLOCK_EDIT_AND_INTERACT_MODE, false, blockState)) { + return ProtectionOverride.ALLOW; + } + + return ProtectionOverride.CHECK; + }; + + Protection LEFT_CLICK_BLOCK_FABRIC = (player, pos, hand, chunk, entity) -> { + BlockState blockState = player.level.getBlockState(pos); + + if (blockState.is(FTBChunksAPI.INTERACT_WHITELIST_TAG)) { + return ProtectionOverride.ALLOW; + } + + if (chunk != null && chunk.teamData.canBreak(player, FTBChunksTeamData.BLOCK_EDIT_AND_INTERACT_MODE, true, blockState)) { + return ProtectionOverride.ALLOW; + } + + return ProtectionOverride.CHECK; + }; + ProtectionOverride override(ServerPlayer player, BlockPos pos, InteractionHand hand, @Nullable ClaimedChunk chunk, @Nullable Entity entity); } diff --git a/common/src/main/resources/assets/ftbchunks/lang/en_us.json b/common/src/main/resources/assets/ftbchunks/lang/en_us.json index 5514896f..43ebb427 100644 --- a/common/src/main/resources/assets/ftbchunks/lang/en_us.json +++ b/common/src/main/resources/assets/ftbchunks/lang/en_us.json @@ -122,6 +122,8 @@ "ftbteamsconfig.ftbchunks.location_mode.tooltip": "Controls who can see you on the map or minimap (outside the normal vanilla tracking range)", "ftbteamsconfig.ftbchunks.claim_visibility": "Claim Visibility", "ftbteamsconfig.ftbchunks.claim_visibility.tooltip": "Controls who can see your claims on the map or minimap", + "ftbteamsconfig.ftbchunks.allow_attack_blacklisted_entities": "Allow Attack Black Listed Entities", + "ftbteamsconfig.ftbchunks.allow_attack_blacklisted_entities.tooltip": "Allow enemy players to attack living entities the server owner gave protection too. Server owner must enable this setting and modify the entity_attack_blacklist tag.", "ftbchunks.fake_players": "Allow Fake Players", "ftbchunks.fake_players.tooltip": "CHECK: check fake player access like any real player\nDENY: never allow fake players\nALLOW: always allow fake players", "ftbchunks.max_claimed_chunks": "Max Claimed Chunks per Player", @@ -177,5 +179,7 @@ "ftbchunks.claim_result.dimension_forbidden": "Claiming forbidden in this dimension", "ftbchunks.claim_result.not_claimed": "Chunk not claimed", "ftbchunks.claim_result.already_loaded": "Chunk already loaded", - "ftbchunks.claim_result.not_loaded": "Chunk not loaded" + "ftbchunks.claim_result.not_loaded": "Chunk not loaded", + "ftbchunks.cant_break_offline": "You can't break this block while this team is offline!", + "ftbchunks.block_break_limit_reached": "The blocks broken per hour limit for this territory has been reached!" } \ No newline at end of file diff --git a/common/src/main/resources/data/ftbchunks/tags/entity_types/living_entity_attack_blacklist.json b/common/src/main/resources/data/ftbchunks/tags/entity_types/living_entity_attack_blacklist.json new file mode 100644 index 00000000..da9bd3b8 --- /dev/null +++ b/common/src/main/resources/data/ftbchunks/tags/entity_types/living_entity_attack_blacklist.json @@ -0,0 +1,9 @@ +{ + "replace": false, + "values": [ + { + "id": "minecraft:villager", + "required": false + } + ] +} \ No newline at end of file diff --git a/fabric/src/main/java/dev/ftb/mods/ftbchunks/fabric/FTBChunksExpectedImpl.java b/fabric/src/main/java/dev/ftb/mods/ftbchunks/fabric/FTBChunksExpectedImpl.java index 44803677..168e33ab 100644 --- a/fabric/src/main/java/dev/ftb/mods/ftbchunks/fabric/FTBChunksExpectedImpl.java +++ b/fabric/src/main/java/dev/ftb/mods/ftbchunks/fabric/FTBChunksExpectedImpl.java @@ -35,6 +35,10 @@ public static Protection getBlockInteractProtection() { } public static Protection getBlockBreakProtection() { - return Protection.EDIT_AND_INTERACT_BLOCK; + return Protection.BREAK_BLOCK_FABRIC; + } + + public static Protection getBlockLeftClickProtection() { + return Protection.LEFT_CLICK_BLOCK_FABRIC; } } diff --git a/forge/src/main/java/dev/ftb/mods/ftbchunks/forge/FTBChunksExpectedImpl.java b/forge/src/main/java/dev/ftb/mods/ftbchunks/forge/FTBChunksExpectedImpl.java index 15817455..80c0d584 100644 --- a/forge/src/main/java/dev/ftb/mods/ftbchunks/forge/FTBChunksExpectedImpl.java +++ b/forge/src/main/java/dev/ftb/mods/ftbchunks/forge/FTBChunksExpectedImpl.java @@ -29,6 +29,10 @@ public static Protection getBlockInteractProtection() { } public static Protection getBlockBreakProtection() { - return Protection.EDIT_BLOCK; + return Protection.BREAK_BLOCK; + } + + public static Protection getBlockLeftClickProtection() { + return Protection.LEFT_CLICK_BLOCK; } }