From 00cacbbadc1bff2ca28da24dadd6b2df63a9fdb0 Mon Sep 17 00:00:00 2001 From: Boblet Date: Mon, 3 Mar 2025 16:05:28 +0100 Subject: [PATCH] damage fix, mas36 anim error, server config command --- changelog | 33 ++--- .../java/com/hbm/blocks/bomb/Landmine.java | 29 ++++- .../com/hbm/commands/CommandReloadClient.java | 123 ++++-------------- .../com/hbm/commands/CommandReloadConfig.java | 102 +++++++++++++++ .../com/hbm/commands/CommandReloadServer.java | 45 +++++++ .../java/com/hbm/config/ClientConfig.java | 96 +------------- .../java/com/hbm/config/RunningConfig.java | 110 ++++++++++++++++ .../java/com/hbm/config/ServerConfig.java | 58 +++++++++ .../com/hbm/explosion/ExplosionNukeSmall.java | 2 +- src/main/java/com/hbm/items/ModItems.java | 3 +- .../weapon/sedna/factory/XFactory762mm.java | 2 +- src/main/java/com/hbm/main/MainRegistry.java | 3 + .../java/com/hbm/main/ModEventHandler.java | 13 ++ .../hbm/render/anim/BusAnimationSequence.java | 1 + .../java/com/hbm/util/EntityDamageUtil.java | 57 +++++++- .../hbm/textures/blocks/glass_polarized.png | Bin 0 -> 205 bytes .../textures/blocks/glass_polarized_ct.png | Bin 0 -> 260 bytes .../hbm/textures/items/ingot_metal_sheet.png | Bin 6159 -> 6157 bytes 18 files changed, 446 insertions(+), 231 deletions(-) create mode 100644 src/main/java/com/hbm/commands/CommandReloadConfig.java create mode 100644 src/main/java/com/hbm/commands/CommandReloadServer.java create mode 100644 src/main/java/com/hbm/config/RunningConfig.java create mode 100644 src/main/java/com/hbm/config/ServerConfig.java create mode 100644 src/main/resources/assets/hbm/textures/blocks/glass_polarized.png create mode 100644 src/main/resources/assets/hbm/textures/blocks/glass_polarized_ct.png diff --git a/changelog b/changelog index c410ab62a..96cce6458 100644 --- a/changelog +++ b/changelog @@ -1,29 +1,14 @@ ## Added -* A new legendary weapon +* `/ntmserver` + * Functions like `/ntmclient` but for common settings + * Can toggle `DAMAGE_COMPATIBILITY_MODE`, off by default, enables a more compatible (but slightly jankier) version of the bullet damage code + * `MINE__DAMAGE` can be used to adjust landmine damage ## Changed -* Updated russian localization -* Large deposits (hematite, malachite, bauxite) and caves (sulfur, asbestos) can now be toggled in the config -* Removed recipes for most old particle accelerator parts -* Dense coils no longer have recipes either for the most part, all coils with no recipes can be recycled back into dense wires -* Natural gas can now be processed in a pyrolysis oven, 12k of gas yields 8k hydrogen and one graphite ingot -* Saturnite now has an alternate recipe, adding one pile of borax for doubled output -* All mass storage units (except wood) are now substantially cheaper -* Reduced base spread for all 12 and 10 gauge buckshot shells from 0.05 to 0.035 -* Reduced legendary 12 lever action's spread multiplier from x1.35 to x1.15 -* Bullet casings now spawn with randomized angular velocity -* Bullet casings now correctly bounce off walls, and change angles when bouncing -* Two previously unobtainable legendaries are now in the red room loot pool (about 10x less common than most other items) +* Fat mines now use the standardized mini nuke code + * Fat mines now have a base damage of exactly 100, being identical to demolition mini nukes + * Fat mines now gib affected entities +* IV bags now use `setHealth` operations instead of dealing damage, preventing health duplication by just avoiding the damage ## Fixed -* Fixed an issue where `/ntmreload` would load fluids after recipes, meaning that recipes using newly added fluids would not work correctly, as the fluids don't exist by the time the recipe is loaded -* Fixed bedrock coltan being way too common, drowning out almost all other bedrock ores -* Fixed rotary furnace not saving its output stack -* Fixed strand caster water check being incorrect, creating negative water by allowing operations with insufficient cooling -* Fixed radar not using the small remaining amount of power, causing the animation getting stuck -* Fixed the new system structures being way too common -* Fixed RBMKs losing all their flux when reloading the world -* Fixed issue where DODD fuel item stats would only update when the GUI was open -* Fixed muzzle flashes not being fullbright -* Fixed guns having their name permanently visible over the toolbar -* Fixed hangman being absolutely gigantic when dropped \ No newline at end of file +* Fixed animation error on the MAS-36 \ No newline at end of file diff --git a/src/main/java/com/hbm/blocks/bomb/Landmine.java b/src/main/java/com/hbm/blocks/bomb/Landmine.java index 03a505e55..ec75235b6 100644 --- a/src/main/java/com/hbm/blocks/bomb/Landmine.java +++ b/src/main/java/com/hbm/blocks/bomb/Landmine.java @@ -3,8 +3,8 @@ package com.hbm.blocks.bomb; import java.util.Random; import com.hbm.blocks.ModBlocks; +import com.hbm.config.ServerConfig; import com.hbm.explosion.ExplosionLarge; -import com.hbm.explosion.ExplosionNukeSmall; import com.hbm.explosion.vanillant.ExplosionVNT; import com.hbm.explosion.vanillant.standard.BlockAllocatorStandard; import com.hbm.explosion.vanillant.standard.BlockProcessorStandard; @@ -13,8 +13,13 @@ import com.hbm.explosion.vanillant.standard.ExplosionEffectWeapon; import com.hbm.explosion.vanillant.standard.PlayerProcessorStandard; import com.hbm.interfaces.IBomb; import com.hbm.items.ModItems; +import com.hbm.items.weapon.sedna.factory.XFactoryCatapult; +import com.hbm.main.MainRegistry; +import com.hbm.packet.PacketDispatcher; +import com.hbm.packet.toclient.AuxParticlePacketNT; import com.hbm.tileentity.bomb.TileEntityLandmine; +import cpw.mods.fml.common.network.NetworkRegistry.TargetPoint; import net.minecraft.block.Block; import net.minecraft.block.BlockContainer; import net.minecraft.block.BlockFence; @@ -23,6 +28,7 @@ import net.minecraft.entity.item.EntityItem; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; import net.minecraft.tileentity.TileEntity; import net.minecraft.util.AxisAlignedBB; import net.minecraft.world.IBlockAccess; @@ -138,7 +144,7 @@ public class Landmine extends BlockContainer implements IBomb { if(this == ModBlocks.mine_ap) { ExplosionVNT vnt = new ExplosionVNT(world, x + 0.5, y + 0.5, z + 0.5, 3F); - vnt.setEntityProcessor(new EntityProcessorCrossSmooth(0.5, 10F).setupPiercing(5F, 0.2F)); + vnt.setEntityProcessor(new EntityProcessorCrossSmooth(0.5, ServerConfig.MINE_AP_DAMAGE.get()).setupPiercing(5F, 0.2F)); vnt.setPlayerProcessor(new PlayerProcessorStandard()); vnt.setSFX(new ExplosionEffectWeapon(5, 1F, 0.5F)); vnt.explode(); @@ -146,13 +152,13 @@ public class Landmine extends BlockContainer implements IBomb { ExplosionVNT vnt = new ExplosionVNT(world, x + 0.5, y + 0.5, z + 0.5, 4F); vnt.setBlockAllocator(new BlockAllocatorStandard()); vnt.setBlockProcessor(new BlockProcessorStandard()); - vnt.setEntityProcessor(new EntityProcessorCrossSmooth(1, 35).setupPiercing(15F, 0.2F)); + vnt.setEntityProcessor(new EntityProcessorCrossSmooth(1, ServerConfig.MINE_HE_DAMAGE.get()).setupPiercing(15F, 0.2F)); vnt.setPlayerProcessor(new PlayerProcessorStandard()); vnt.setSFX(new ExplosionEffectWeapon(15, 3.5F, 1.25F)); vnt.explode(); } else if(this == ModBlocks.mine_shrap) { ExplosionVNT vnt = new ExplosionVNT(world, x + 0.5, y + 0.5, z + 0.5, 3F); - vnt.setEntityProcessor(new EntityProcessorCrossSmooth(0.5, 7.5F)); + vnt.setEntityProcessor(new EntityProcessorCrossSmooth(0.5, ServerConfig.MINE_SHRAP_DAMAGE.get())); vnt.setPlayerProcessor(new PlayerProcessorStandard()); vnt.setSFX(new ExplosionEffectWeapon(5, 1F, 0.5F)); vnt.explode(); @@ -160,7 +166,20 @@ public class Landmine extends BlockContainer implements IBomb { ExplosionLarge.spawnShrapnelShower(world, x + 0.5, y + 0.5, z + 0.5, 0, 1D, 0, 45, 0.2D); ExplosionLarge.spawnShrapnels(world, x + 0.5, y + 0.5, z + 0.5, 5); } else if(this == ModBlocks.mine_fat) { - ExplosionNukeSmall.explode(world, x + 0.5, y + 0.5, z + 0.5, ExplosionNukeSmall.PARAMS_MEDIUM); + + ExplosionVNT vnt = new ExplosionVNT(world, x + 0.5, y + 0.5, z + 0.5, 10); + vnt.setBlockAllocator(new BlockAllocatorStandard(64)); + vnt.setBlockProcessor(new BlockProcessorStandard()); + vnt.setEntityProcessor(new EntityProcessorCrossSmooth(2, ServerConfig.MINE_NUKE_DAMAGE.get()).withRangeMod(1.5F)); + vnt.setPlayerProcessor(new PlayerProcessorStandard()); + vnt.explode(); + + XFactoryCatapult.incrementRad(world, x, y, z, 1.5F); + NBTTagCompound data = new NBTTagCompound(); + data.setString("type", "muke"); + data.setBoolean("balefire", MainRegistry.polaroidID == 11 || world.rand.nextInt(100) == 0); + PacketDispatcher.wrapper.sendToAllAround(new AuxParticlePacketNT(data, x + 0.5, y + 0.5, z + 0.5), new TargetPoint(world.provider.dimensionId, x + 0.5, y + 0.5, z + 0.5, 250)); + } } diff --git a/src/main/java/com/hbm/commands/CommandReloadClient.java b/src/main/java/com/hbm/commands/CommandReloadClient.java index 6f0c9672a..84f21988b 100644 --- a/src/main/java/com/hbm/commands/CommandReloadClient.java +++ b/src/main/java/com/hbm/commands/CommandReloadClient.java @@ -1,24 +1,18 @@ package com.hbm.commands; -import java.util.Collections; -import java.util.List; -import java.util.Map.Entry; -import java.util.stream.Collectors; +import java.util.HashMap; import com.hbm.config.ClientConfig; -import com.hbm.config.ClientConfig.ConfigWrapper; +import com.hbm.config.RunningConfig.ConfigWrapper; import cpw.mods.fml.relauncher.FMLLaunchHandler; import cpw.mods.fml.relauncher.Side; -import net.minecraft.command.CommandBase; -import net.minecraft.command.CommandException; import net.minecraft.command.ICommandSender; -import net.minecraft.entity.player.EntityPlayer; import net.minecraft.util.ChatComponentText; import net.minecraft.util.EnumChatFormatting; import net.minecraftforge.client.ClientCommandHandler; -public class CommandReloadClient extends CommandBase { +public class CommandReloadClient extends CommandReloadConfig { public static void register() { if(FMLLaunchHandler.side() != Side.CLIENT) return; @@ -34,95 +28,26 @@ public class CommandReloadClient extends CommandBase { public String getCommandUsage(ICommandSender sender) { return "/ntmclient help"; } - - @Override - public boolean canCommandSenderUseCommand(ICommandSender sender) { - return sender instanceof EntityPlayer; - } - - @Override - public void processCommand(ICommandSender sender, String[] args) { - - if(args.length < 1) throw new CommandException(getCommandUsage(sender)); - - String operator = args[0]; - - if("help".equals(operator)) { - - if(args.length >= 2) { - String command = args[1]; - if("help".equals(command)) sender.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW + "Shows usage for /ntmclient subcommands.")); - if("list".equals(command)) sender.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW + "Shows all client variable names and values.")); - if("reload".equals(command)) sender.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW + "Reads client variables from the config file.")); - if("get".equals(command)) sender.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW + "Shows value for the specified variable name.")); - if("set".equals(command)) sender.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW + "Sets a variable's value and saves it to the config file.")); - } else { - sender.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW + "/ntmclient " + EnumChatFormatting.GOLD + "help " + EnumChatFormatting.RED + "")); - sender.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW + "/ntmclient " + EnumChatFormatting.GOLD + "list")); - sender.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW + "/ntmclient " + EnumChatFormatting.GOLD + "reload")); - sender.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW + "/ntmclient " + EnumChatFormatting.GOLD + "get " + EnumChatFormatting.RED + "")); - sender.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW + "/ntmclient " + EnumChatFormatting.GOLD + "set " + EnumChatFormatting.RED + " ")); - } - return; - } - - if("list".equals(operator)) { - sender.addChatMessage(new ChatComponentText(EnumChatFormatting.RED + "CLIENT VARIABLES:")); - for(Entry line : ClientConfig.configMap.entrySet()) { - sender.addChatMessage(new ChatComponentText(" " + EnumChatFormatting.GOLD + line.getKey() + ": " + EnumChatFormatting.YELLOW + line.getValue().value)); - } - return; - } - - if("reload".equals(operator)) { - ClientConfig.reload(); - sender.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW + "Variables loaded from config file.")); - return; - } - - if(args.length < 2) throw new CommandException(getCommandUsage(sender)); - - String key = args[1]; - - if("get".equals(operator)) { - ConfigWrapper wrapper = ClientConfig.configMap.get(key); - if(wrapper == null) throw new CommandException("Key does not exist."); - sender.addChatMessage(new ChatComponentText(EnumChatFormatting.GOLD + key + ": " + EnumChatFormatting.YELLOW + wrapper.value)); - return; - } - - if(args.length < 3) throw new CommandException(getCommandUsage(sender)); - - String value = args[2]; - - if("set".equals(operator)) { - ConfigWrapper wrapper = ClientConfig.configMap.get(key); - if(wrapper == null) throw new CommandException("Key does not exist."); - - try { - wrapper.update(value); - ClientConfig.refresh(); - sender.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW + "Value updated.")); - } catch(Exception ex) { - throw new CommandException("Error parsing type for " + wrapper.value.getClass().getSimpleName() + ": " + ex.getLocalizedMessage()); - } - - return; - } - - throw new CommandException(getCommandUsage(sender)); - } - - @SuppressWarnings("rawtypes") - @Override - public List addTabCompletionOptions(ICommandSender sender, String[] args) { - if(!(sender instanceof EntityPlayer)) return Collections.emptyList(); - if(args.length < 1) return Collections.emptyList(); - if(args.length == 1) return getListOfStringsMatchingLastWord(args, "list", "reload", "get", "set"); - String operator = args[0]; - if(args.length == 2 && ("get".equals(operator) || "set".equals(operator))) { - return getListOfStringsFromIterableMatchingLastWord(args, ClientConfig.configMap.keySet().stream().map(String::valueOf).collect(Collectors.toList())); - } - return Collections.emptyList(); + + @Override public void help(ICommandSender sender, String[] args) { + if(args.length >= 2) { + String command = args[1]; + if("help".equals(command)) sender.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW + "Shows usage for /ntmclient subcommands.")); + if("list".equals(command)) sender.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW + "Shows all client variable names and values.")); + if("reload".equals(command)) sender.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW + "Reads client variables from the config file.")); + if("get".equals(command)) sender.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW + "Shows value for the specified variable name.")); + if("set".equals(command)) sender.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW + "Sets a variable's value and saves it to the config file.")); + } else { + sender.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW + "/ntmclient " + EnumChatFormatting.GOLD + "help " + EnumChatFormatting.RED + "")); + sender.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW + "/ntmclient " + EnumChatFormatting.GOLD + "list")); + sender.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW + "/ntmclient " + EnumChatFormatting.GOLD + "reload")); + sender.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW + "/ntmclient " + EnumChatFormatting.GOLD + "get " + EnumChatFormatting.RED + "")); + sender.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW + "/ntmclient " + EnumChatFormatting.GOLD + "set " + EnumChatFormatting.RED + " ")); + } } + + @Override public HashMap getConfigMap() { return ClientConfig.configMap; } + @Override public void refresh() { ClientConfig.refresh(); } + @Override public void reload() { ClientConfig.reload(); } + @Override public String getTitle() { return "CLIENT VARIABLES:"; } } diff --git a/src/main/java/com/hbm/commands/CommandReloadConfig.java b/src/main/java/com/hbm/commands/CommandReloadConfig.java new file mode 100644 index 000000000..fbe35111a --- /dev/null +++ b/src/main/java/com/hbm/commands/CommandReloadConfig.java @@ -0,0 +1,102 @@ +package com.hbm.commands; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map.Entry; +import java.util.stream.Collectors; + +import com.hbm.config.RunningConfig.ConfigWrapper; + +import net.minecraft.command.CommandBase; +import net.minecraft.command.CommandException; +import net.minecraft.command.ICommandSender; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.util.ChatComponentText; +import net.minecraft.util.EnumChatFormatting; + +public abstract class CommandReloadConfig extends CommandBase { + + @Override + public boolean canCommandSenderUseCommand(ICommandSender sender) { + return sender instanceof EntityPlayer; + } + + public abstract void help(ICommandSender sender, String[] args); + public abstract HashMap getConfigMap(); + public abstract void refresh(); + public abstract void reload(); + public abstract String getTitle(); + + @Override + public void processCommand(ICommandSender sender, String[] args) { + + if(args.length < 1) throw new CommandException(getCommandUsage(sender)); + + String operator = args[0]; + + if("help".equals(operator)) { + help(sender, args); + return; + } + + if("list".equals(operator)) { + sender.addChatMessage(new ChatComponentText(EnumChatFormatting.RED + getTitle())); + for(Entry line : getConfigMap().entrySet()) { + sender.addChatMessage(new ChatComponentText(" " + EnumChatFormatting.GOLD + line.getKey() + ": " + EnumChatFormatting.YELLOW + line.getValue().value)); + } + return; + } + + if("reload".equals(operator)) { + reload(); + sender.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW + "Variables loaded from config file.")); + return; + } + + if(args.length < 2) throw new CommandException(getCommandUsage(sender)); + + String key = args[1]; + + if("get".equals(operator)) { + ConfigWrapper wrapper = getConfigMap().get(key); + if(wrapper == null) throw new CommandException("Key does not exist."); + sender.addChatMessage(new ChatComponentText(EnumChatFormatting.GOLD + key + ": " + EnumChatFormatting.YELLOW + wrapper.value)); + return; + } + + if(args.length < 3) throw new CommandException(getCommandUsage(sender)); + + String value = args[2]; + + if("set".equals(operator)) { + ConfigWrapper wrapper = getConfigMap().get(key); + if(wrapper == null) throw new CommandException("Key does not exist."); + + try { + wrapper.update(value); + refresh(); + sender.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW + "Value updated.")); + } catch(Exception ex) { + throw new CommandException("Error parsing type for " + wrapper.value.getClass().getSimpleName() + ": " + ex.getLocalizedMessage()); + } + + return; + } + + throw new CommandException(getCommandUsage(sender)); + } + + @SuppressWarnings("rawtypes") + @Override + public List addTabCompletionOptions(ICommandSender sender, String[] args) { + if(!(sender instanceof EntityPlayer)) return Collections.emptyList(); + if(args.length < 1) return Collections.emptyList(); + if(args.length == 1) return getListOfStringsMatchingLastWord(args, "list", "reload", "get", "set"); + String operator = args[0]; + if(args.length == 2 && ("get".equals(operator) || "set".equals(operator))) { + return getListOfStringsFromIterableMatchingLastWord(args, getConfigMap().keySet().stream().map(String::valueOf).collect(Collectors.toList())); + } + return Collections.emptyList(); + } +} diff --git a/src/main/java/com/hbm/commands/CommandReloadServer.java b/src/main/java/com/hbm/commands/CommandReloadServer.java new file mode 100644 index 000000000..33c572141 --- /dev/null +++ b/src/main/java/com/hbm/commands/CommandReloadServer.java @@ -0,0 +1,45 @@ +package com.hbm.commands; + +import java.util.HashMap; + +import com.hbm.config.RunningConfig.ConfigWrapper; +import com.hbm.config.ServerConfig; + +import net.minecraft.command.ICommandSender; +import net.minecraft.util.ChatComponentText; +import net.minecraft.util.EnumChatFormatting; + +public class CommandReloadServer extends CommandReloadConfig { + + @Override + public String getCommandName() { + return "ntmserver"; + } + + @Override + public String getCommandUsage(ICommandSender sender) { + return "/ntmserver help"; + } + + @Override public void help(ICommandSender sender, String[] args) { + if(args.length >= 2) { + String command = args[1]; + if("help".equals(command)) sender.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW + "Shows usage for /ntmserver subcommands.")); + if("list".equals(command)) sender.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW + "Shows all server variable names and values.")); + if("reload".equals(command)) sender.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW + "Reads server variables from the config file.")); + if("get".equals(command)) sender.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW + "Shows value for the specified variable name.")); + if("set".equals(command)) sender.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW + "Sets a variable's value and saves it to the config file.")); + } else { + sender.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW + "/ntmserver " + EnumChatFormatting.GOLD + "help " + EnumChatFormatting.RED + "")); + sender.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW + "/ntmserver " + EnumChatFormatting.GOLD + "list")); + sender.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW + "/ntmserver " + EnumChatFormatting.GOLD + "reload")); + sender.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW + "/ntmserver " + EnumChatFormatting.GOLD + "get " + EnumChatFormatting.RED + "")); + sender.addChatMessage(new ChatComponentText(EnumChatFormatting.YELLOW + "/ntmserver " + EnumChatFormatting.GOLD + "set " + EnumChatFormatting.RED + " ")); + } + } + + @Override public HashMap getConfigMap() { return ServerConfig.configMap; } + @Override public void refresh() { ServerConfig.refresh(); } + @Override public void reload() { ServerConfig.reload(); } + @Override public String getTitle() { return "SERVER VARIABLES:"; } +} diff --git a/src/main/java/com/hbm/config/ClientConfig.java b/src/main/java/com/hbm/config/ClientConfig.java index 4d171cad7..2f5cec902 100644 --- a/src/main/java/com/hbm/config/ClientConfig.java +++ b/src/main/java/com/hbm/config/ClientConfig.java @@ -1,21 +1,12 @@ package com.hbm.config; import com.google.gson.Gson; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.stream.JsonWriter; +import com.hbm.config.RunningConfig.ConfigWrapper; import com.hbm.main.MainRegistry; import com.hbm.util.Compat; import java.io.File; -import java.io.FileReader; -import java.io.FileWriter; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; -import java.util.List; -import java.util.Map.Entry; // https://youtube.com/shorts/XTHZWqZt_AI public class ClientConfig { @@ -86,91 +77,10 @@ public class ClientConfig { } private static void readConfig(File config) { - - try { - JsonObject json = gson.fromJson(new FileReader(config), JsonObject.class); - - for(Entry line : configMap.entrySet()) { - - if(json.has(line.getKey())) { - JsonElement value = json.get(line.getKey()); - - try { - - //world's shittiest dynamic type parser - if(configMap.containsKey(line.getKey())) { - if(line.getValue().value instanceof String) configMap.get(line.getKey()).set(value.getAsString()); - if(line.getValue().value instanceof Float) configMap.get(line.getKey()).set(value.getAsFloat()); - if(line.getValue().value instanceof Double) configMap.get(line.getKey()).set(value.getAsDouble()); - if(line.getValue().value instanceof Integer) configMap.get(line.getKey()).set(value.getAsInt()); - if(line.getValue().value instanceof Boolean) configMap.get(line.getKey()).set(value.getAsBoolean()); - } - - //gson doesn't give me the option to read the raw value of a JsonPrimitive so we have to this shit effectively twice - //once to make sure that the parsed data matches with what's determined by the default, - //and a second time in the ConfigWrapper to add ease of reading the data without needing manual casts - - } catch(Exception ex) { - ex.printStackTrace(); - } - } - } - - } catch(Exception ex) { - ex.printStackTrace(); - } + RunningConfig.readConfig(config, configMap); } private static void writeConfig(File config) { - - try { - JsonWriter writer = new JsonWriter(new FileWriter(config)); - writer.setIndent(" "); - writer.beginObject(); - - writer.name("info").value("This file can be edited ingame using the /ntmclient command."); - - List keys = new ArrayList(); - keys.addAll(configMap.keySet()); - Collections.sort(keys); //readability is cool - - for(String key : keys) { - - ConfigWrapper wrapper = configMap.get(key); - Object value = wrapper.value; - //this sucks and i am too stupid to come up with something better - if(value instanceof String) writer.name(key).value((String) value); - if(value instanceof Float) writer.name(key).value((Float) value); - if(value instanceof Double) writer.name(key).value((Double) value); - if(value instanceof Integer) writer.name(key).value((Integer) value); - if(value instanceof Boolean) writer.name(key).value((Boolean) value); - } - - writer.endObject(); - writer.close(); - } catch(IOException e) { - e.printStackTrace(); - } - } - - public static class ConfigWrapper { - public T value; - - public ConfigWrapper(T o) { - this.value = o; - } - - public T get() { return value; } - public void set(T value) { this.value = value; } - - public void update(String param) { - Object stupidBufferObject = null; // wahh wahh can't cast Float to T wahh wahh shut the fuck up - if(value instanceof String) stupidBufferObject = param; - if(value instanceof Float) stupidBufferObject = Float.parseFloat(param); - if(value instanceof Double) stupidBufferObject = Double.parseDouble(param); - if(value instanceof Integer) stupidBufferObject = Integer.parseInt(param); - if(value instanceof Boolean) stupidBufferObject = Boolean.parseBoolean(param); - if(stupidBufferObject != null) this.value = (T) stupidBufferObject; - } + RunningConfig.writeConfig(config, configMap, "This file can be edited ingame using the /ntmclient command."); } } diff --git a/src/main/java/com/hbm/config/RunningConfig.java b/src/main/java/com/hbm/config/RunningConfig.java new file mode 100644 index 000000000..66ebe9193 --- /dev/null +++ b/src/main/java/com/hbm/config/RunningConfig.java @@ -0,0 +1,110 @@ +package com.hbm.config; + +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map.Entry; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.stream.JsonWriter; + +public class RunningConfig { + + public static final Gson gson = new Gson(); + + public static void readConfig(File config, HashMap configMap) { + + try { + JsonObject json = gson.fromJson(new FileReader(config), JsonObject.class); + + for(Entry line : configMap.entrySet()) { + + if(json.has(line.getKey())) { + JsonElement value = json.get(line.getKey()); + + try { + + //world's shittiest dynamic type parser + if(configMap.containsKey(line.getKey())) { + if(line.getValue().value instanceof String) configMap.get(line.getKey()).set(value.getAsString()); + if(line.getValue().value instanceof Float) configMap.get(line.getKey()).set(value.getAsFloat()); + if(line.getValue().value instanceof Double) configMap.get(line.getKey()).set(value.getAsDouble()); + if(line.getValue().value instanceof Integer) configMap.get(line.getKey()).set(value.getAsInt()); + if(line.getValue().value instanceof Boolean) configMap.get(line.getKey()).set(value.getAsBoolean()); + } + + //gson doesn't give me the option to read the raw value of a JsonPrimitive so we have to this shit effectively twice + //once to make sure that the parsed data matches with what's determined by the default, + //and a second time in the ConfigWrapper to add ease of reading the data without needing manual casts + + } catch(Exception ex) { + ex.printStackTrace(); + } + } + } + + } catch(Exception ex) { + ex.printStackTrace(); + } + } + + public static void writeConfig(File config, HashMap configMap, String info) { + + try { + JsonWriter writer = new JsonWriter(new FileWriter(config)); + writer.setIndent(" "); + writer.beginObject(); + + writer.name("info").value(info); + + List keys = new ArrayList(); + keys.addAll(configMap.keySet()); + Collections.sort(keys); //readability is cool + + for(String key : keys) { + + ConfigWrapper wrapper = configMap.get(key); + Object value = wrapper.value; + //this sucks and i am too stupid to come up with something better + if(value instanceof String) writer.name(key).value((String) value); + if(value instanceof Float) writer.name(key).value((Float) value); + if(value instanceof Double) writer.name(key).value((Double) value); + if(value instanceof Integer) writer.name(key).value((Integer) value); + if(value instanceof Boolean) writer.name(key).value((Boolean) value); + } + + writer.endObject(); + writer.close(); + } catch(IOException e) { + e.printStackTrace(); + } + } + + public static class ConfigWrapper { + public T value; + + public ConfigWrapper(T o) { + this.value = o; + } + + public T get() { return value; } + public void set(T value) { this.value = value; } + + public void update(String param) { + Object stupidBufferObject = null; // wahh wahh can't cast Float to T wahh wahh shut the fuck up + if(value instanceof String) stupidBufferObject = param; + if(value instanceof Float) stupidBufferObject = Float.parseFloat(param); + if(value instanceof Double) stupidBufferObject = Double.parseDouble(param); + if(value instanceof Integer) stupidBufferObject = Integer.parseInt(param); + if(value instanceof Boolean) stupidBufferObject = Boolean.parseBoolean(param); + if(stupidBufferObject != null) this.value = (T) stupidBufferObject; + } + } +} diff --git a/src/main/java/com/hbm/config/ServerConfig.java b/src/main/java/com/hbm/config/ServerConfig.java new file mode 100644 index 000000000..fd9586113 --- /dev/null +++ b/src/main/java/com/hbm/config/ServerConfig.java @@ -0,0 +1,58 @@ +package com.hbm.config; + +import java.io.File; +import java.util.HashMap; + +import com.google.gson.Gson; +import com.hbm.main.MainRegistry; + +public class ServerConfig extends RunningConfig { + + public static final Gson gson = new Gson(); + public static HashMap configMap = new HashMap(); + + public static ConfigWrapper DAMAGE_COMPATIBILITY_MODE = new ConfigWrapper(false); + public static ConfigWrapper MINE_AP_DAMAGE = new ConfigWrapper(10F); + public static ConfigWrapper MINE_HE_DAMAGE = new ConfigWrapper(35F); + public static ConfigWrapper MINE_SHRAP_DAMAGE = new ConfigWrapper(7.5F); + public static ConfigWrapper MINE_NUKE_DAMAGE = new ConfigWrapper(100F); + + private static void initDefaults() { + configMap.put("DAMAGE_COMPATIBILITY_MODE", DAMAGE_COMPATIBILITY_MODE); + configMap.put("MINE_AP_DAMAGE", MINE_AP_DAMAGE); + configMap.put("MINE_HE_DAMAGE", MINE_HE_DAMAGE); + configMap.put("MINE_SHRAP_DAMAGE", MINE_SHRAP_DAMAGE); + configMap.put("MINE_NUKE_DAMAGE", MINE_NUKE_DAMAGE); + } + + /** Initializes defaults, then reads the config file if it exists, then writes the config file. */ + public static void initConfig() { + initDefaults(); + File folder = MainRegistry.configHbmDir; + File config = new File(folder.getAbsolutePath() + File.separatorChar + "hbmServer.json"); + if(config.exists()) readConfig(config); + refresh(); + } + + /** Writes over the config file using the running config. */ + public static void refresh() { + File folder = MainRegistry.configHbmDir; + File config = new File(folder.getAbsolutePath() + File.separatorChar + "hbmServer.json"); + writeConfig(config); + } + + /** Writes over the running config using the config file. */ + public static void reload() { + File folder = MainRegistry.configHbmDir; + File config = new File(folder.getAbsolutePath() + File.separatorChar + "hbmServer.json"); + if(config.exists()) readConfig(config); + } + + private static void readConfig(File config) { + RunningConfig.readConfig(config, configMap); + } + + private static void writeConfig(File config) { + RunningConfig.writeConfig(config, configMap, "This file can be edited ingame using the /ntmserver command."); + } +} diff --git a/src/main/java/com/hbm/explosion/ExplosionNukeSmall.java b/src/main/java/com/hbm/explosion/ExplosionNukeSmall.java index cca47893b..47067d8b2 100644 --- a/src/main/java/com/hbm/explosion/ExplosionNukeSmall.java +++ b/src/main/java/com/hbm/explosion/ExplosionNukeSmall.java @@ -12,7 +12,7 @@ import cpw.mods.fml.common.network.NetworkRegistry.TargetPoint; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.world.World; -public class ExplosionNukeSmall { +@Deprecated public class ExplosionNukeSmall { public static void explode(World world, double posX, double posY, double posZ, MukeParams params) { diff --git a/src/main/java/com/hbm/items/ModItems.java b/src/main/java/com/hbm/items/ModItems.java index 598003562..3b2feed15 100644 --- a/src/main/java/com/hbm/items/ModItems.java +++ b/src/main/java/com/hbm/items/ModItems.java @@ -3022,7 +3022,8 @@ public class ModItems { iv_empty = new ItemSimpleConsumable().setUseActionServer((stack, user) -> { if(user.hurtResistantTime <= 0) { ItemSimpleConsumable.giveSoundAndDecrement(stack, user, "hbm:item.syringe", new ItemStack(ModItems.iv_blood)); - user.attackEntityFrom(DamageSource.magic, 5F); + user.setHealth(Math.max(user.getHealth() - 5F, 0F)); + if(user.getHealth() <= 0) user.onDeath(DamageSource.magic); } }).setUnlocalizedName("iv_empty").setCreativeTab(MainRegistry.consumableTab).setTextureName(RefStrings.MODID + ":iv_empty"); diff --git a/src/main/java/com/hbm/items/weapon/sedna/factory/XFactory762mm.java b/src/main/java/com/hbm/items/weapon/sedna/factory/XFactory762mm.java index b17c15d78..f63275b9a 100644 --- a/src/main/java/com/hbm/items/weapon/sedna/factory/XFactory762mm.java +++ b/src/main/java/com/hbm/items/weapon/sedna/factory/XFactory762mm.java @@ -191,7 +191,7 @@ public class XFactory762mm { .addBus("LIFT", new BusAnimationSequence().hold(200).addPos(30, 0, 0, 500, IType.SIN_FULL).holdUntil(1200).addPos(0, 0, 0, 500, IType.SIN_FULL)) .addBus("SHOW_CLIP", new BusAnimationSequence().setPos(1, 1, 1)) .addBus("CLIP", new BusAnimationSequence().setPos(2, -3, 0).hold(250).addPos(0.5, 1, 0, 500, IType.SIN_DOWN).addPos(0, 0, 0, 250, IType.SIN_FULL).hold(400).addPos(-0.5, 0.5, 0, 150).addPos(-3, -3, 0, 250, IType.SIN_UP)) - .addBus("BULLETS", new BusAnimationSequence().setPos(2, -4, 0).hold(250).addPos(0.5, 1, 0, 500, IType.SIN_DOWN).addPos(0, 0, 0, 250, IType.SIN_FULL).hold(150).addPos(0, -1.5, 0, 250, IType.SIN_DOWN)); + .addBus("BULLETS", new BusAnimationSequence().setPos(2, -3, 0).hold(250).addPos(0.5, 1, 0, 500, IType.SIN_DOWN).addPos(0, 0, 0, 250, IType.SIN_FULL).hold(150).addPos(0, -1.5, 0, 250, IType.SIN_DOWN)); case JAMMED: return new BusAnimation() .addBus("LIFT", new BusAnimationSequence().hold(250).addPos(-15, 0, 0, 500, IType.SIN_FULL).holdUntil(1650).addPos(0, 0, 0, 500, IType.SIN_FULL)) .addBus("BOLT_TURN", new BusAnimationSequence().hold(250).addPos(0, 0, turn, 150).holdUntil(1250).addPos(0, 0, 0, 150)) diff --git a/src/main/java/com/hbm/main/MainRegistry.java b/src/main/java/com/hbm/main/MainRegistry.java index 94e2e5afa..bb9616014 100644 --- a/src/main/java/com/hbm/main/MainRegistry.java +++ b/src/main/java/com/hbm/main/MainRegistry.java @@ -861,7 +861,9 @@ public class MainRegistry { FalloutConfigJSON.initialize(); ItemPoolConfigJSON.initialize(); + ClientConfig.initConfig(); + ServerConfig.initConfig(); TileEntityNukeCustom.registerBombItems(); ArmorUtil.register(); @@ -948,6 +950,7 @@ public class MainRegistry { event.registerServerCommand(new CommandSatellites()); event.registerServerCommand(new CommandRadiation()); event.registerServerCommand(new CommandPacketInfo()); + event.registerServerCommand(new CommandReloadServer()); } @EventHandler diff --git a/src/main/java/com/hbm/main/ModEventHandler.java b/src/main/java/com/hbm/main/ModEventHandler.java index 292c2a435..bdba621ca 100644 --- a/src/main/java/com/hbm/main/ModEventHandler.java +++ b/src/main/java/com/hbm/main/ModEventHandler.java @@ -114,6 +114,7 @@ import net.minecraftforge.event.entity.player.PlayerInteractEvent; import net.minecraftforge.event.entity.player.PlayerInteractEvent.Action; import net.minecraftforge.event.entity.player.PlayerUseItemEvent; import net.minecraftforge.event.world.BlockEvent.BreakEvent; +import net.minecraftforge.event.world.ChunkEvent; import net.minecraftforge.event.world.WorldEvent; import org.apache.commons.lang3.math.NumberUtils; import org.apache.logging.log4j.Level; @@ -1231,6 +1232,18 @@ public class ModEventHandler { }*/ } + @SubscribeEvent + public void onChunkLoad(ChunkEvent.Load event) { + + //test for automatic in-world block replacement + + /*for(int x = 0; x < 16; x++) for(int y = 0; y < 255; y++) for(int z = 0; z < 16; z++) { + if(event.getChunk().getBlock(x, y, z) instanceof MachineArcFurnace) { + event.getChunk().func_150807_a(x, y, z, Blocks.air, 0); + } + }*/ + } + @SubscribeEvent public void onPlayerClone(net.minecraftforge.event.entity.player.PlayerEvent.Clone event) { diff --git a/src/main/java/com/hbm/render/anim/BusAnimationSequence.java b/src/main/java/com/hbm/render/anim/BusAnimationSequence.java index 640c22318..bd1630630 100644 --- a/src/main/java/com/hbm/render/anim/BusAnimationSequence.java +++ b/src/main/java/com/hbm/render/anim/BusAnimationSequence.java @@ -82,6 +82,7 @@ public class BusAnimationSequence { /** Repeats the previous keyframe for a duration depending on the previous keyframes. Useful for getting different buses to sync up. */ public BusAnimationSequence holdUntil(int end) { int duration = end - getTotalTime(); + //FIXME: holdUntil breaks as soon as the animation speed is not 1 return hold(duration); } diff --git a/src/main/java/com/hbm/util/EntityDamageUtil.java b/src/main/java/com/hbm/util/EntityDamageUtil.java index 03c2363d2..6ba1791ce 100644 --- a/src/main/java/com/hbm/util/EntityDamageUtil.java +++ b/src/main/java/com/hbm/util/EntityDamageUtil.java @@ -3,6 +3,8 @@ package com.hbm.util; import java.lang.reflect.Method; import java.util.List; +import com.hbm.config.ServerConfig; + import cpw.mods.fml.relauncher.ReflectionHelper; import net.minecraft.enchantment.EnchantmentHelper; import net.minecraft.entity.Entity; @@ -21,7 +23,8 @@ import net.minecraftforge.common.ForgeHooks; public class EntityDamageUtil { - public static boolean attackEntityFromIgnoreIFrame(Entity victim, DamageSource src, float damage) { + /** Shitty hack, if the first attack fails, it retries with damage + previous damage, allowing damage to penetrate */ + @Deprecated public static boolean attackEntityFromIgnoreIFrame(Entity victim, DamageSource src, float damage) { if(!victim.attackEntityFrom(src, damage)) { @@ -38,6 +41,7 @@ public class EntityDamageUtil { } } + /** New and improved entity damage calc - only use this one */ public static boolean attackEntityFromNT(EntityLivingBase living, DamageSource source, float amount, boolean ignoreIFrame, boolean allowSpecialCancel, double knockbackMultiplier, float pierceDT, float pierce) { if(living instanceof EntityPlayerMP && source.getEntity() instanceof EntityPlayer) { EntityPlayerMP playerMP = (EntityPlayerMP) living; @@ -45,14 +49,55 @@ public class EntityDamageUtil { if(!playerMP.canAttackPlayer(attacker)) return false; //handles wack-ass no PVP rule as well as scoreboard friendly fire } DamageResistanceHandler.setup(pierceDT, pierce); - living.attackEntityFrom(source, 0F); boolean ret = attackEntityFromNTInternal(living, source, amount, ignoreIFrame, allowSpecialCancel, knockbackMultiplier); - //boolean ret = living.attackEntityFrom(source, amount); DamageResistanceHandler.reset(); return ret; } - + private static boolean attackEntityFromNTInternal(EntityLivingBase living, DamageSource source, float amount, boolean ignoreIFrame, boolean allowSpecialCancel, double knockbackMultiplier) { + boolean superCompatibility = ServerConfig.DAMAGE_COMPATIBILITY_MODE.get(); + return superCompatibility + ? attackEntitySuperCompatibility(living, source, amount, ignoreIFrame, allowSpecialCancel, knockbackMultiplier) + : attackEntitySEDNAPatch(living, source, amount, ignoreIFrame, allowSpecialCancel, knockbackMultiplier); + } + + /** + * MK2 SEDNA damage system, currently untested. An even hackier, yet more compatible solution using the vanilla damage calc directly but tweaking certain apsects. + * Limitation: Does not apply DR piercing to vanilla armor + */ + private static boolean attackEntitySuperCompatibility(EntityLivingBase living, DamageSource source, float amount, boolean ignoreIFrame, boolean allowSpecialCancel, double knockbackMultiplier) { + //disable iframes + if(ignoreIFrame) { living.lastDamage = 0F; living.hurtResistantTime = 0; } + //cache last velocity + double motionX = living.motionX; + double motionY = living.motionX; + double motionZ = living.motionX; + //bam! + boolean ret = living.attackEntityFrom(source, amount); + //restore last velocity + living.motionX = motionX; + living.motionY = motionY; + living.motionZ = motionZ; + //apply own knockback + Entity entity = source.getEntity(); + if(entity != null) { + double deltaX = entity.posX - living.posX; + double deltaZ; + + for(deltaZ = entity.posZ - living.posZ; deltaX * deltaX + deltaZ * deltaZ < 1.0E-4D; deltaZ = (Math.random() - Math.random()) * 0.01D) { + deltaX = (Math.random() - Math.random()) * 0.01D; + } + + living.attackedAtYaw = (float) (Math.atan2(deltaZ, deltaX) * 180.0D / Math.PI) - living.rotationYaw; + if(knockbackMultiplier > 0) knockBack(living, entity, amount, deltaX, deltaZ, knockbackMultiplier); + } + return ret; + } + + /** MK1 SEDNA damage system, basically re-implements the vanilla code (only from Entity, child class code is effectively ignored) with some adjustments */ + private static boolean attackEntitySEDNAPatch(EntityLivingBase living, DamageSource source, float amount, boolean ignoreIFrame, boolean allowSpecialCancel, double knockbackMultiplier) { + living.attackEntityFrom(source, 0F); + if(ignoreIFrame) living.lastDamage = 0F; if(ForgeHooks.onLivingAttack(living, source, amount) && allowSpecialCancel) return false; if(living.isEntityInvulnerable()) return false; if(living.worldObj.isRemote) return false; @@ -183,9 +228,7 @@ public class EntityDamageUtil { return amount; } - public static void damageArmorNT(EntityLivingBase living, float amount) { - - } + public static void damageArmorNT(EntityLivingBase living, float amount) { } /** Currently just a copy of the vanilla damage code */ @Deprecated public static boolean attackEntityFromNT(EntityLivingBase living, DamageSource source, float amount) { diff --git a/src/main/resources/assets/hbm/textures/blocks/glass_polarized.png b/src/main/resources/assets/hbm/textures/blocks/glass_polarized.png new file mode 100644 index 0000000000000000000000000000000000000000..29a0ce9d64fce4fcdfa7454d74e2f6c1ca7d0861 GIT binary patch literal 205 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`EX7WqAsj$Z!;#Vfh9FE!eBv1c7cgy#~DwY zDf-S7QEIIHtmUfa%*eE`TC;f@>|RW%ciND9+qlW_SU=~tW%lYF97e^R4`#ey)zz^@ z`=qt_M6>gfIEF;D zz75~V<*dkaC8(h2^Z)v@{2#XIq|2K`hsg&`unyr8E46} zUek7Psn$-&V>l3Ho-?~=*TZ1%H;yiP$1H4^8J@i6b6+fGe((9*jK>VCjh;{;OXk;vd$@? F2>@s{YS{n) literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/hbm/textures/items/ingot_metal_sheet.png b/src/main/resources/assets/hbm/textures/items/ingot_metal_sheet.png index 456950b8bb27993d7715bc2f2541a8fff5eec97e..27e251a795ad303b1740fae6e20eec7677a5ec48 100644 GIT binary patch literal 6157 zcmV+o81me^gXgw&y<=3K0shM1EKxhTjH}#%LQvs77h0V_q+n31W5!E z$^ulRtfIt%qWBGG{wVJCR#7Mg*oo2aTCDq{?%wy@v-jD1pMCbdNa6(q6Oev`#5Ax8 zq0s#Ch1(<#=>w-fhBPgjsW3D&BwQ{RF)=aI=HqZUL`+PK06k(``7>jqDS-Me>--Me?sn7_eb5b^QxQ_^qRv`K8* zv?(C|d-m)Rd-m+PXU{xWa5x+yG&D3I3lJI_Ds^>r-PvTR1^)Qsj{{(}S~+?0ouy$Wqu7=JodOGI`qOLaP40^v^yHf-1+s;jFd zS-}1dCz-Tmz-rimS+-^6oyz+|DzE@s( z1%OvyeU-g)O8z+>^++uJAc zjMnL>z3?f~IvrVQsbr<4PEv$uosRa&w-?pb)x7@t>tffgT>ugwgb=*``s?i4wTlfK zHZa%1moXa)paJOZ?WL@&jMCCl8X6kduwjF8v0CUd{0Z>l#f!*37eKGqV=|e@%*j@e<*Zq=2Jk4`R7xP5e6`SJMUdkGU#JBQ zikbI1cdvvGGoJ;twY7OHKvYx|aNBJA6FPW=DnmB8GGyVmw6rMK8jVKfccam$1d;xD z%TUJ^zDl6J&Q~GI%gf2g$N)SshP>}}S zdlj<}V6)jI@c3Z$_25x}nwpwP7NEDc7k|^WJ3*cu$iCZXGzxWmppF4#!JExy<=AXC z^W~RcvTWHh;PzBdtpMPO0~`(q$?G=)P_XJT0GchWbUU52*L(zU$63H%8ncN3+S=NL zroFvgN=ix+aN82dohh4Ze?s@g%9Sg0cGp zQ_kJHcMEyO;xC%N6H-!A6v6rm-ydFj=_LVQU%`WCGy^lX0ABfMIh}f^l&Tuy1;lLf z+0&&r7z{!l@#_7*)cx-z{Y+_P%w{b>2?Ax@ixlr%Kn@1wfH2UJ|4oL=<(lyqr9(C~Cf{KDmO5g8ddYx+5> z2dJp12xv)ms`c=SiV9&g8il+Kl-?g?7oh%A(;JOO#YN1v{ejT?`rtse4u@k_^y+P( zkm-~d;N_QJRu~zLMln-AYcv`~Lqh{uSy{;2+3*98@QaI!QLi+bOeP+i*x$W-w~(iN z#l^(|FCzJ(Wcx3~^^PlE=MtAZLgm3<`sJ(ksex>po12x9I1~CneZLwqy#yZNo6Tl2 zGBU8)Z1nc_DhU-C`}gmcBwV)oPto&rq`c7eW^P_80$#6ICZzvdEJOy$o!B z@ozZzkH43#f>67I*<|9rYA7*)xg>aq@d8B*`KcP zi;IiBO#I1{CzapSZs=|Sr0fDN18iOI8Tr7O7CR@ZP5^MdqkR(HH_`$G0<{Hz$-duY zGD-5tm+Sr1!t{;y10DH$_wJRRfBt#)?%gZ-o1$*pwr!GI@`XGDxZcsu`U#(Zrp2xZ zfBn|=T<>W2I}^B{EkF>!z`%gl$)uztv|8=7MnB*sVD+C`&?b{fl3l`7{y#c8T5%C_ zWzlrazRhNnrdq`;FE971X7Y#1gTIvIsoueV{5=32H{EpH^gO>j1E?uKG$|n@kLbI# zai-VbOK8B;d0}B;UJa66z;q!OY1GuzP*hYzO-;?Dgv_0ye}BT4+cus1$H&JD-)m2v zJSq7*KNaMefGp^A)=Zpl_eun*;c~f{NfJn|$(t_Cy@Ip}A^fdk`Mw6GRtULL=#Qz# zc=9)5#xFRE7Z6YWM&LGl2RV}$i11~4`TkQ0d#dxk^fIQK3KlOQhX2

P+Btw+@Fx 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 literal 6159 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