d2a0a7a93b
Аутентификация переделана по схеме пользователя: лаунчер — единственный
источник токена, сервер проверяет его сам и лаунчера не спрашивает.
- rsx-auth/GameToken: токен это base64url(payload) + "." + base64url(HMAC-SHA256),
payload {uuid, nick, aud, exp, jti, typ}, typ = session или reload.
- Сессия живёт час, reload-токен полтора часа и только внутри игры. Пакет
SessionReload продлевает сессию и выдаёт новый reload-токен.
- Сервер проверяет подпись, аудиенс, срок и совпадение uuid. HTTP-запрос к
лаунчеру из сервера убран.
- Клиент: окно пароля убрано совсем, ник берётся из токена, клиент сам
продлевает сессию за 5 минут до конца.
- Для локальной разработки сервер с --dev-token пишет свой токен в
world/dev-token.txt, клиент берёт токен оттуда.
- Права админа: свой список world/admins.txt, перечитывается раз в 5 секунд,
поэтому право снимается на лету. Пакет AdminState.
- Ванильные блоки мира ломает только админ, режим игры прав не даёт.
- F3+B только для админов: без прав показываем отказ, с правом включаются и
ванильные хитбоксы, и наш отладочный рендер. Права сняли посреди просмотра —
гаснут сразу.
- run-game.ps1 умеет -Token.
Проверено на живом сервере: own session ok, player in, rights of Player given.
Тесты: протокол 34, сервер 88, форк собирается чисто.
277 lines
9.8 KiB
Java
277 lines
9.8 KiB
Java
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:
|
|
*
|
|
* <pre>
|
|
* /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
|
|
* </pre>
|
|
*/
|
|
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<String, Packets.HitboxModel> MODELS =
|
|
new LinkedHashMap<String, Packets.HitboxModel>();
|
|
private static final List<Packets.HitboxInstance> INSTANCES =
|
|
new ArrayList<Packets.HitboxInstance>();
|
|
|
|
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<Entity> 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};
|
|
}
|
|
}
|