diff --git a/src/main/java/org/ultramine/mods/anticheat/AntiXRayServiceImpl.java b/src/main/java/org/ultramine/mods/anticheat/AntiXRayServiceImpl.java new file mode 100644 index 0000000..7718a05 --- /dev/null +++ b/src/main/java/org/ultramine/mods/anticheat/AntiXRayServiceImpl.java @@ -0,0 +1,230 @@ +package org.ultramine.mods.anticheat; + +import cpw.mods.fml.common.eventhandler.SubscribeEvent; +import net.minecraft.block.Block; +import net.minecraft.command.CommandException; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.world.World; +import net.minecraft.world.chunk.Chunk; +import net.minecraftforge.common.DimensionManager; +import net.minecraftforge.oredict.OreDictionary; +import net.openhft.koloboke.collect.map.IntIntMap; +import net.openhft.koloboke.collect.map.hash.HashIntIntMaps; +import net.openhft.koloboke.collect.set.ShortSet; +import net.openhft.koloboke.collect.set.hash.HashShortSets; +import org.ultramine.core.service.EventBusRegisteredService; +import org.ultramine.server.chunk.AntiXRayService; +import org.ultramine.server.chunk.ChunkSnapshot; +import org.ultramine.server.event.SetBlockEvent; +import org.ultramine.server.util.BasicTypeParser; + +import java.util.List; +import java.util.Map; + +public class AntiXRayServiceImpl extends EventBusRegisteredService implements AntiXRayService +{ + private final CanReplaceStrategy strategy; + private final IntIntMap worldProviderToStoneType; + + public AntiXRayServiceImpl(UMACConfig.AntiXRay config) + { + if(config.strategy.equals("replace-ore-to-stone")) + this.strategy = new ReplaceIfContainsStrategy(createBlockSet(config.oreBlocks)); + else if(config.strategy.equals("replace-all-to-stone")) + this.strategy = new ReplaceAllStrategy(); + else + throw new IllegalArgumentException("Unknown antiXRay strategy in settings/anticheat.yml: " + config.strategy); + this.worldProviderToStoneType = createWorldToBlockMap(config.worldProviderToStoneBlock); + } + + private ShortSet createBlockSet(List oreBlocks) + { + ShortSet tempSet = HashShortSets.newUpdatableSet(); + for(String blockStr : oreBlocks) + { + ItemStack is; + try + { + is = BasicTypeParser.parseItemStack(blockStr, true, 1, false); + } catch (CommandException e) { + continue; + } + int id = Item.itemRegistry.getIDForObject(is.getItem()); + if(is.getItemDamage() == OreDictionary.WILDCARD_VALUE) + { + for(int i = 0; i < 16; i++) + addIdMeta(tempSet, id, i); + } + else + { + addIdMeta(tempSet, id, is.getItemDamage()); + } + } + return HashShortSets.newImmutableSet(tempSet); + } + + private void addIdMeta(ShortSet set, int id, int meta) + { + set.add((short)(id | ((meta & 15) << 12))); + } + + private IntIntMap createWorldToBlockMap(Map worldProviderToStoneBlock) + { + IntIntMap tempMap = HashIntIntMaps.newUpdatableMap(); + + for(Map.Entry ent : worldProviderToStoneBlock.entrySet()) + { + ItemStack is; + try + { + is = BasicTypeParser.parseItemStack(ent.getValue(), false, 1, false); + } catch (CommandException e) { + continue; + } + int id = Item.itemRegistry.getIDForObject(is.getItem()); + tempMap.put(ent.getKey().intValue(), (id | ((is.getItemDamage() & 15) << 12))); + } + + return HashIntIntMaps.newImmutableMap(tempMap); + } + + private boolean canReplaceType(int idMeta) + { + return strategy.canReplaceType(idMeta); + } + + private boolean canReplaceType(int id, int meta) + { + return canReplaceType(id | ((meta & 15) << 12)); + } + + private boolean canReplaceType(Block block, int meta) + { + return canReplaceType(Block.blockRegistry.getIDForObject(block), meta); + } + + private boolean canReplaceBlock(ChunkSnapshot chunkSnapshot, int x, int y, int z) + { + int idMeta = chunkSnapshot.getBlockIdAndMeta(x, y, z); + + return + canReplaceType(idMeta) && + chunkSnapshot.getBlock(x-1, y, z) .isOpaqueCube() && + chunkSnapshot.getBlock(x+1, y, z) .isOpaqueCube() && + chunkSnapshot.getBlock(x, y, z-1).isOpaqueCube() && + chunkSnapshot.getBlock(x, y, z+1).isOpaqueCube() && + chunkSnapshot.getBlock(x, y-1, z) .isOpaqueCube() && + chunkSnapshot.getBlock(x, y+1, z) .isOpaqueCube(); + } + + private boolean canReplaceBlock(World world, int x, int y, int z) + { + Block block = world.getBlock(x, y, z); + int meta = world.getBlockMetadata(x, y, z); + + return + canReplaceType(block, meta) && + world.getBlock(x-1, y, z) .isOpaqueCube() && + world.getBlock(x+1, y, z) .isOpaqueCube() && + world.getBlock(x, y, z-1).isOpaqueCube() && + world.getBlock(x, y, z+1).isOpaqueCube() && + world.getBlock(x, y-1, z) .isOpaqueCube() && + world.getBlock(x, y+1, z) .isOpaqueCube(); + } + + private boolean canReplaceBlock(World world, ChunkSnapshot chunkSnapshot, int x, int y, int z) + { + return canReplaceBlock(world, (chunkSnapshot.getX() << 4) | x, y, (chunkSnapshot.getZ() << 4) | z); + } + + @Override + public Integer prepareChunkSync(ChunkSnapshot chunkSnapshot, Chunk chunk) + { + World world = chunk.worldObj; + int stone = worldProviderToStoneType.getOrDefault(DimensionManager.getProviderType(world.provider.dimensionId), 1); + int stoneId = stone & 0xFFF; + int stoneMeta = stone >> 12; + int yMax = Math.min(chunkSnapshot.getTopFilledSegment() + 16, 254); + for(int y = 1; y < yMax; y++) + for(int x = 0; x < 16; x++) + if(canReplaceBlock(world, chunkSnapshot, x, y, 0)) + chunkSnapshot.setBlock(x, y, 0, stoneId, stoneMeta); + for(int y = 1; y < yMax; y++) + for(int x = 0; x < 16; x++) + if(canReplaceBlock(world, chunkSnapshot, x, y, 15)) + chunkSnapshot.setBlock(x, y, 15, stoneId, stoneMeta); + for(int y = 1; y < yMax; y++) + for(int z = 1; z < 15; z++) + if(canReplaceBlock(world, chunkSnapshot, 0, y, z)) + chunkSnapshot.setBlock(0, y, z, stoneId, stoneMeta); + for(int y = 1; y < yMax; y++) + for(int z = 1; z < 15; z++) + if(canReplaceBlock(world, chunkSnapshot, 15, y, z)) + chunkSnapshot.setBlock(15, y, z, stoneId, stoneMeta); + return stone; + } + + @Override + public void prepareChunkAsync(ChunkSnapshot chunkSnapshot, Integer stoneType) + { + int stone = stoneType; + int stoneId = stone & 0xFFF; + int stoneMeta = stone >> 12; + int yMax = Math.min(chunkSnapshot.getTopFilledSegment() + 16, 254); + for(int y = 1; y < yMax; y++) + for(int z = 1; z < 15; z++) + for(int x = 1; x < 15; x++) + if(canReplaceBlock(chunkSnapshot, x, y, z)) + chunkSnapshot.setBlock(x, y, z, stoneId, stoneMeta); + } + + private void checkAndUpdateLocation(World world, int x, int y, int z) + { + if(canReplaceBlock(world, x, y, z)) + world.markBlockForUpdate(x, y, z); + } + + @SubscribeEvent + public void onBlockChange(SetBlockEvent e) + { + if(e.newBlock.isOpaqueCube() || !e.world.getBlock(e.x, e.y, e.z).isOpaqueCube()) + return; + checkAndUpdateLocation(e.world, e.x-1, e.y, e.z); + checkAndUpdateLocation(e.world, e.x+1, e.y, e.z); + checkAndUpdateLocation(e.world, e.x, e.y, e.z-1); + checkAndUpdateLocation(e.world, e.x, e.y, e.z+1); + checkAndUpdateLocation(e.world, e.x, e.y-1, e.z); + checkAndUpdateLocation(e.world, e.x, e.y+1, e.z); + } + + interface CanReplaceStrategy + { + boolean canReplaceType(int idMeta); + } + + private static class ReplaceAllStrategy implements CanReplaceStrategy + { + @Override + public boolean canReplaceType(int idMeta) + { + return true; + } + } + + private static class ReplaceIfContainsStrategy implements CanReplaceStrategy + { + private final ShortSet oreBlocks; + + private ReplaceIfContainsStrategy(ShortSet oreBlocks) + { + this.oreBlocks = oreBlocks; + } + + @Override + public boolean canReplaceType(int idMeta) + { + return oreBlocks.contains((short)idMeta); + } + } +} diff --git a/src/main/java/org/ultramine/mods/anticheat/UMACCommands.java b/src/main/java/org/ultramine/mods/anticheat/UMACCommands.java new file mode 100644 index 0000000..06ea763 --- /dev/null +++ b/src/main/java/org/ultramine/mods/anticheat/UMACCommands.java @@ -0,0 +1,19 @@ +package org.ultramine.mods.anticheat; + +import org.ultramine.commands.Command; +import org.ultramine.commands.CommandContext; + +public class UMACCommands +{ + @Command( + name = "anticheat", + group = "anticheat", + aliases = {"umac"}, + syntax = {"[reload]"} + ) + public static void anticheat(CommandContext ctx) + { + UMAntiCheat.instance().reload(); + ctx.sendMessage("OK"); + } +} diff --git a/src/main/java/org/ultramine/mods/anticheat/UMACConfig.java b/src/main/java/org/ultramine/mods/anticheat/UMACConfig.java new file mode 100644 index 0000000..29c44ad --- /dev/null +++ b/src/main/java/org/ultramine/mods/anticheat/UMACConfig.java @@ -0,0 +1,17 @@ +package org.ultramine.mods.anticheat; + +import java.util.List; +import java.util.Map; + +public class UMACConfig +{ + public AntiXRay antiXRay; + + public static class AntiXRay + { + public boolean enabled; + public String strategy; + public List oreBlocks; + public Map worldProviderToStoneBlock; + } +} diff --git a/src/main/java/org/ultramine/mods/anticheat/UMAntiCheat.java b/src/main/java/org/ultramine/mods/anticheat/UMAntiCheat.java new file mode 100644 index 0000000..d4d5649 --- /dev/null +++ b/src/main/java/org/ultramine/mods/anticheat/UMAntiCheat.java @@ -0,0 +1,82 @@ +package org.ultramine.mods.anticheat; + +import cpw.mods.fml.common.Mod; +import cpw.mods.fml.common.event.FMLModIdMappingEvent; +import cpw.mods.fml.common.event.FMLPreInitializationEvent; +import cpw.mods.fml.common.event.FMLServerStartingEvent; +import org.apache.commons.io.FileUtils; +import org.ultramine.core.service.InjectService; +import org.ultramine.core.service.ServiceManager; +import org.ultramine.core.util.Undoable; +import org.ultramine.server.ConfigurationHandler; +import org.ultramine.server.chunk.AntiXRayService; +import org.ultramine.server.util.Resources; +import org.ultramine.server.util.YamlConfigProvider; + +import java.io.File; +import java.io.IOException; + +@Mod(modid = "UM-AntiCheat", name = "UM-AntiCheat", version = "@version@", acceptableRemoteVersions = "*") +public class UMAntiCheat +{ + @InjectService private static ServiceManager services; + private static UMAntiCheat instance; + + private Undoable onUnload; + + public static UMAntiCheat instance() + { + return instance; + } + + @Mod.EventHandler + public void preInit(FMLPreInitializationEvent e) + { + instance = this; + } + + @Mod.EventHandler + public void serverStarting(FMLServerStartingEvent e) + { + e.registerCommands(UMACCommands.class); +// reload(); + } + + @Mod.EventHandler + public void remap(FMLModIdMappingEvent e) + { + reload(); + } + + public void reload() + { + if(onUnload != null) + { + onUnload.undo(); + onUnload = null; + } + + UMACConfig config = loadConfig(); + if(config.antiXRay.enabled) + onUnload = services.register(AntiXRayService.class, new AntiXRayServiceImpl(config.antiXRay), 100); + } + + private UMACConfig loadConfig() + { + File file = new File(ConfigurationHandler.getSettingDir(), "anticheat.yml"); + if(file.exists()) + { + return YamlConfigProvider.readConfig(file, UMACConfig.class); + } + else + { + String configStr = Resources.getAsString("/assets/um-anticheat/defaults/default-config.yml"); + try { + FileUtils.writeStringToFile(file, configStr); + } catch(IOException e) { + throw new RuntimeException("Failed to create configuration file: " + file.getAbsolutePath(), e); + } + return YamlConfigProvider.readConfig(configStr, UMACConfig.class); + } + } +} diff --git a/src/main/resources/assets/um-anticheat/defaults/default-config.yml b/src/main/resources/assets/um-anticheat/defaults/default-config.yml new file mode 100644 index 0000000..eba8a97 --- /dev/null +++ b/src/main/resources/assets/um-anticheat/defaults/default-config.yml @@ -0,0 +1,63 @@ +antiXRay: + enabled: true + strategy: 'replace-ore-to-stone' # replace-ore-to-stone/replace-all-to-stone + worldProviderToStoneBlock: + '0': 'minecraft:stone' + '-1': 'minecraft:netherrack' + '1': 'minecraft:end_stone' + oreBlocks: + - 'minecraft:gold_ore' + - 'minecraft:iron_ore' + - 'minecraft:coal_ore' + - 'minecraft:lapis_ore' + - 'minecraft:diamond_ore' + - 'minecraft:redstone_ore' + - 'minecraft:emerald_ore' + - 'minecraft:quartz_ore' + - 'IC2:blockOreCopper' + - 'IC2:blockOreTin' + - 'IC2:blockOreLead' + - 'IC2:blockOreUran' + - 'appliedenergistics2:tile.OreQuartz' + - 'appliedenergistics2:tile.OreQuartzCharged' + - 'Railcraft:ore:*' + - 'Thaumcraft:blockCustomOre:*' + - 'Forestry:resources:*' + - 'TConstruct:SearedBrick' + - 'TConstruct:SearedBrick:1' + - 'TConstruct:SearedBrick:2' + - 'TConstruct:SearedBrick:3' + - 'TConstruct:SearedBrick:4' + - 'TConstruct:SearedBrick:5' + - 'TConstruct:GravelOre:*' + - 'TConstruct:ore.berries.one:8' + - 'TConstruct:ore.berries.one:9' + - 'TConstruct:ore.berries.one:10' + - 'TConstruct:ore.berries.one:11' + - 'TConstruct:ore.berries.two:8' + - 'TConstruct:ore.berries.two:9' + - 'Thaumcraft:blockCustomOre:6' + - 'Mekanism:OreBlock' + - 'Mekanism:OreBlock:1' + - 'Mekanism:OreBlock:2' + - 'DraconicEvolution:draconiumOre' + - 'mo:dilithium_ore' + - 'mo:tritanium_ore' + - 'NuclearCraft:blockOre:*' + - 'harvestcraft:salt' + - 'ThermalFoundation:Ore:*' + - 'essentialcraft:oreDrops:*' + - 'GalacticraftCore:tile.gcBlockCore:5' + - 'GalacticraftCore:tile.gcBlockCore:6' + - 'GalacticraftCore:tile.gcBlockCore:7' + - 'GalacticraftCore:tile.gcBlockCore:8' + - 'GalacticraftMars:tile.mars' + - 'GalacticraftMars:tile.mars:1' + - 'GalacticraftMars:tile.mars:2' + - 'GalacticraftMars:tile.mars:3' + - 'GalacticraftMars:tile.mars:4' + - 'GalacticraftMars:tile.asteroidsBlock:3' + - 'GalacticraftMars:tile.asteroidsBlock:4' + - 'GalacticraftMars:tile.asteroidsBlock:5' + - 'NetherOres:tile.netherores.ore.0:*' + - 'NetherOres:tile.netherores.ore.1:*'