package net.rustnotmc.client; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import net.minecraft.client.Minecraft; import net.minecraft.client.entity.EntityPlayerSP; import net.minecraft.client.renderer.GlStateManager; import net.minecraft.client.renderer.RenderGlobal; import net.minecraft.client.renderer.debug.DebugRenderer; import net.minecraft.entity.Entity; import net.rustnotmc.protocol.Hitbox; import net.rustnotmc.protocol.Packets; /** * Draws the boxes of the models. * * The boxes are the editor: the server reads them from a file and sends them * here, and they are drawn around the things they belong to. A model that * does not fit is changed in the file, the file is read again, and the new * numbers are on the screen. * * The command is: * *
 *   /rsx hitboxes on        show the boxes
 *   /rsx hitboxes off       hide them
 *   /rsx hitboxes reload    ask the server to read its file again
 *   /rsx hitboxes list      what the server knows
 *   /rsx hitboxes show NAME put one model in front of the player
 * 
*/ public final class RsxHitboxDebug { /** The model whose boxes describe a player. */ private static final String PLAYER_MODEL = "player"; /** The height the player model is written for. */ private static final float PLAYER_MODEL_HEIGHT = 1.8F; private static final Map MODELS = new LinkedHashMap(); private static final List INSTANCES = new ArrayList(); private static boolean visible; private static String preview; private RsxHitboxDebug() { } /** Takes the boxes of the models from the server. */ public static void accept(Packets.Hitboxes boxes) { MODELS.clear(); for (int i = 0; i < boxes.models.length; i++) { MODELS.put(boxes.models[i].name, boxes.models[i]); } } /** Takes the places where the models stand. */ public static void accept(Packets.HitboxInstances instances) { INSTANCES.clear(); for (int i = 0; i < instances.instances.length; i++) { INSTANCES.add(instances.instances[i]); } } /** True when the boxes are being drawn. */ public static boolean visible() { return visible; } /** * Shows or hides the boxes. * * The right to look at them belongs to the administrators, so when the * right is taken away the boxes go at once. */ public static void setVisible(boolean value) { visible = value; if (!value) { preview = null; } } /** How many models the server has sent. */ public static int modelCount() { return MODELS.size(); } /** * Carries out one of our commands. * * Returns true when the line was ours, so that the mods do not see it. */ public static boolean command(String line) { if (line == null || !line.startsWith("/")) { return false; } String[] parts = line.substring(1).trim().split("\\s+"); if (parts.length < 2 || !"rsx".equalsIgnoreCase(parts[0]) || !"hitboxes".equalsIgnoreCase(parts[1])) { return false; } String what = parts.length > 2 ? parts[2].toLowerCase() : ""; if ("on".equals(what)) { visible = true; say("hitboxes on (" + MODELS.size() + " models)"); return true; } if ("off".equals(what)) { visible = false; say("hitboxes off"); return true; } if ("reload".equals(what)) { net.rustnotmc.client.net.RsxNetworkManager.sendHitboxesReload(); say("the server was asked to read its file again"); return true; } if ("list".equals(what)) { StringBuilder names = new StringBuilder(); for (String name : MODELS.keySet()) { if (names.length() > 0) { names.append(", "); } names.append(name); } say("models: " + (names.length() == 0 ? "none" : names.toString())); return true; } if ("show".equals(what)) { if (parts.length < 4) { say("usage: /rsx hitboxes show NAME"); return true; } preview = parts[3]; visible = true; say("showing " + preview); return true; } say("usage: /rsx hitboxes on|off|reload|list|show NAME"); return true; } private static void say(String message) { net.rustnotmc.loader.Log.info("hitboxes: " + message); Minecraft mc = Minecraft.getMinecraft(); if (mc.ingameGUI != null) { mc.ingameGUI.addChatMessage(net.minecraft.util.text.ChatType.CHAT, new net.minecraft.util.text.TextComponentString(message)); } } /** * Draws the boxes. * * Called from the frame hook, so this is after the world and after the * interface: the lines are drawn over both, which is what a debug view * should do. */ public static void draw(Minecraft mc) { if (!visible || mc.player == null || mc.world == null) { return; } Entity view = mc.getRenderViewEntity(); if (view == null) { return; } float partial = mc.getRenderPartialTicks(); double camX = view.lastTickPosX + (view.posX - view.lastTickPosX) * partial; double camY = view.lastTickPosY + (view.posY - view.lastTickPosY) * partial; double camZ = view.lastTickPosZ + (view.posZ - view.lastTickPosZ) * partial; GlStateManager.enableBlend(); GlStateManager.tryBlendFuncSeparate(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA, GlStateManager.SourceFactor.ONE, GlStateManager.DestFactor.ZERO); GlStateManager.glLineWidth(2.0F); GlStateManager.disableTexture2D(); GlStateManager.depthMask(false); for (int i = 0; i < INSTANCES.size(); i++) { Packets.HitboxInstance instance = INSTANCES.get(i); Packets.HitboxModel model = MODELS.get(instance.model); if (model == null) { continue; } drawModel(model, instance.x, instance.y, instance.z, instance.yaw, camX, camY, camZ, 1.0F); } if (preview != null) { Packets.HitboxModel model = MODELS.get(preview); if (model != null) { EntityPlayerSP player = mc.player; drawModel(model, player.posX + 1.0D, player.posY, player.posZ, player.rotationYaw, camX, camY, camZ, 1.0F); } } // and the players, with the zones the damage is counted by Packets.HitboxModel player = MODELS.get(PLAYER_MODEL); if (player != null) { List players = mc.world.getLoadedEntityList(); for (int i = 0; i < players.size(); i++) { Entity entity = players.get(i); if (!(entity instanceof EntityPlayerSP)) { continue; } float scale = entity.height / PLAYER_MODEL_HEIGHT; drawModel(player, entity.posX, entity.posY, entity.posZ, entity.rotationYaw, camX, camY, camZ, scale); } } GlStateManager.depthMask(true); GlStateManager.enableTexture2D(); GlStateManager.disableBlend(); } /** * Draws one model. * * The boxes of the model are turned by the yaw of the thing and moved to * where it stands, and then drawn relative to the camera, because that is * how the world is drawn too. */ private static void drawModel(Packets.HitboxModel model, double x, double y, double z, float yaw, double camX, double camY, double camZ, float scale) { double cos = Math.cos(Math.toRadians(yaw)); double sin = Math.sin(Math.toRadians(yaw)); for (int i = 0; i < model.boxes.length; i++) { Hitbox box = model.boxes[i]; float[] colour = colourOf(box.part); double minX = turn(box.minX * scale, box.minZ * scale, cos, sin); double minZ = turn(box.minZ * scale, -box.minX * scale, cos, sin); double maxX = turn(box.maxX * scale, box.maxZ * scale, cos, sin); double maxZ = turn(box.maxZ * scale, -box.maxX * scale, cos, sin); double minY = box.minY * scale; double maxY = box.maxY * scale; RenderGlobal.drawBoundingBox( x + minX - camX, y + minY - camY, z + minZ - camZ, x + maxX - camX, y + maxY - camY, z + maxZ - camZ, colour[0], colour[1], colour[2], 0.9F); if (!box.name.isEmpty()) { DebugRenderer.renderDebugText(box.name, x + minX, y + maxY, z + minZ, 0.02F, 0xFFFFFFFF); } } } /** Turns a point around the vertical axis. */ private static double turn(double along, double across, double cos, double sin) { return along * cos + across * sin; } /** The colour of a box: the head is red, the body yellow, the legs blue. */ private static float[] colourOf(String part) { if (Hitbox.PART_HEAD.equals(part)) { return new float[]{1.0F, 0.25F, 0.25F}; } if (Hitbox.PART_BODY.equals(part)) { return new float[]{1.0F, 0.9F, 0.2F}; } if (Hitbox.PART_LEGS.equals(part)) { return new float[]{0.3F, 0.55F, 1.0F}; } return new float[]{0.3F, 1.0F, 0.4F}; } }