blob: d75aa60ffd90fe93c15a0fd4af2b7bbe507c0443 [file] [log] [blame] [raw]
/* Minecraft Interprocess Management Client
* Copyright 2015-2020 Rivoreo
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
#include <unistd.h>
#include <termios.h>
#include <string.h>
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include "packet.h"
enum colum {
ENTITYSTAT_COLUM_WORLD,
ENTITYSTAT_COLUM_ID,
ENTITYSTAT_COLUM_TYPE,
ENTITYSTAT_COLUM_NAME,
ENTITYSTAT_COLUM_LOCATION,
ENTITYSTAT_COLUM_ROTATION,
ENTITYSTAT_COLUM_STATE,
ENTITYSTAT_COLUM_WIDTH,
ENTITYSTAT_COLUM_HEIGHT,
ENTITYSTAT_COLUM_CHUNK,
ENTITYSTAT_COLUM_UUID,
ENTITYSTAT_COLUM_MAX_HEALTH,
ENTITYSTAT_COLUM_HEALTH,
ENTITYSTAT_COLUM_PERCENTAGE_HEALTH,
ENTITYSTAT_COLUM_IDLE_TIME,
ENTITYSTAT_COLUM_ATTACK_TARGET_ID,
};
static const struct colum_name_header_map_entry {
enum colum id;
const char *name;
const char *default_header_name;
} colum_name_header_map[] = {
{ ENTITYSTAT_COLUM_WORLD, "world", "WORLD" },
{ ENTITYSTAT_COLUM_ID, "id", "ENTITY-ID" },
{ ENTITYSTAT_COLUM_TYPE, "type", "TYPE" },
{ ENTITYSTAT_COLUM_NAME, "name", "NAME" },
{ ENTITYSTAT_COLUM_LOCATION, "location", "LOCATION" },
{ ENTITYSTAT_COLUM_ROTATION, "rotation", "ROTATION" },
{ ENTITYSTAT_COLUM_STATE, "state", "STATE" },
{ ENTITYSTAT_COLUM_WIDTH, "width", "WIDTH" },
{ ENTITYSTAT_COLUM_HEIGHT, "height", "HEIGHT" },
{ ENTITYSTAT_COLUM_CHUNK, "chunk", "CHUNK" },
{ ENTITYSTAT_COLUM_UUID, "uuid", "UUID" },
{ ENTITYSTAT_COLUM_MAX_HEALTH, "maxhealth", "MAX-HEALTH" },
{ ENTITYSTAT_COLUM_HEALTH, "health", "HEALTH" },
{ ENTITYSTAT_COLUM_PERCENTAGE_HEALTH, "phealth", "%HEALTH" },
{ ENTITYSTAT_COLUM_IDLE_TIME, "idletime", "IDLE-TIME" },
{ ENTITYSTAT_COLUM_ATTACK_TARGET_ID, "attacktarget", "ATTACK-TARGET-ID" },
{ -1, NULL, NULL }
};
struct format_configure_node {
enum colum colum;
const char *header_name;
};
static const struct format_configure_node default_format_configuration[] = {
{ ENTITYSTAT_COLUM_WORLD },
{ ENTITYSTAT_COLUM_ID },
{ ENTITYSTAT_COLUM_LOCATION },
{ ENTITYSTAT_COLUM_TYPE },
{ ENTITYSTAT_COLUM_NAME },
{ -1 }
};
#define DEFAULT_COLUM_COUNT 5
static struct format_configure_node *format_configuration;
static unsigned int custom_colum_count;
static void add_colum(struct format_configure_node config) {
format_configuration = realloc(format_configuration, sizeof(struct format_configure_node) * (custom_colum_count + 2));
if(!format_configuration) {
fputs("Out of memory\n", stderr);
exit(1);
}
format_configuration[custom_colum_count++] = config;
format_configuration[custom_colum_count] = (struct format_configure_node){ -1, NULL };
}
static enum colum get_colum(const char *name) {
const struct colum_name_header_map_entry *e = colum_name_header_map;
while(e->id != (enum colum)-1) {
if(strcmp(e->name, name) == 0) return e->id;
e++;
}
return -1;
}
int entitystat_set_output_format(const char *format) {
if(!*format) {
fputs("output format specification cannot be empty\n", stderr);
return -1;
}
char *colum_name;
enum colum colum;
const char *p = format;
while(1) switch(*p) {
case 0:
case '=':
case ',':
colum_name = malloc(p - format + 1);
if(!colum_name) {
fputs("Out of memory\n", stderr);
exit(1);
}
memcpy(colum_name, format, p - format);
colum_name[p - format] = 0;
colum = get_colum(colum_name);
if(colum == (enum colum)-1) {
fprintf(stderr, "Unknown colum '%s'\n", colum_name);
return -1;
}
add_colum((struct format_configure_node){ colum, *p == '=' ? p + 1 : NULL });
if(*p != ',') return 0;
format = p + 1;
// Fallthrough
default:
p++;
break;
}
}
static char *xasprintf(const char *format, ...) {
char *r;
va_list va;
va_start(va, format);
if(vasprintf(&r, format, va) < 0) {
perror("vasprintf");
exit(1);
}
va_end(va);
return r;
}
static char *uuid_to_string(const unsigned char *p) {
return xasprintf("%02hhx%02hhx%02hhx%02hhx-%02hhx%02hhx-%02hhx%02hhx-%02hhx%02hhx-%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx",
p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], p[8], p[9], p[10], p[11], p[12], p[13], p[14], p[15]);
}
static void print_spaces(size_t count) {
while(count > 0) {
putchar(' ');
count--;
}
}
void entitystat_print(struct entity **entities, unsigned int count, int wide) {
unsigned int colum_i, row_i, e_i;
int should_print_header = 0;
const struct format_configure_node *config;
unsigned int colum_count;
if(format_configuration) {
config = format_configuration;
colum_count = custom_colum_count;
for(colum_i = 0; colum_i < colum_count; colum_i++) {
const char *name = config[colum_i].header_name;
if(!name || *name) {
should_print_header = 1;
break;
}
}
} else {
config = default_format_configuration;
colum_count = DEFAULT_COLUM_COUNT;
should_print_header = 1;
}
const char *output[should_print_header + count][colum_count];
if(should_print_header) {
for(colum_i = 0; colum_i < colum_count; colum_i++) {
const char *name = config[colum_i].header_name;
if(!name) {
const struct colum_name_header_map_entry *e = colum_name_header_map;
while(e->id != (enum colum)-1) {
if(e->id == config[colum_i].colum) {
name = e->default_header_name;
break;
}
e++;
}
//assert(name != NULL);
}
output[0][colum_i] = name;
}
row_i = 1;
} else {
row_i = 0;
}
for(e_i = 0; e_i < count; e_i++) {
const struct entity *entity = entities[e_i];
for(colum_i = 0; colum_i < colum_count; colum_i++) {
switch(config[colum_i].colum) {
case ENTITYSTAT_COLUM_WORLD:
output[row_i][colum_i] = (const char *)(entity->data + entity->world_name_offset);
break;
case ENTITYSTAT_COLUM_ID:
output[row_i][colum_i] = xasprintf("%u", (unsigned int)entity->entity_id);
break;
case ENTITYSTAT_COLUM_TYPE:
output[row_i][colum_i] = (const char *)(entity->data + entity->entity_type_name_offset);
break;
case ENTITYSTAT_COLUM_NAME:
output[row_i][colum_i] = (const char *)(entity->data + entity->display_name_offset);
break;
case ENTITYSTAT_COLUM_LOCATION:
output[row_i][colum_i] = xasprintf("(%g,%g,%g)", entity->location_x, entity->location_y, entity->location_z);
break;
case ENTITYSTAT_COLUM_ROTATION:
output[row_i][colum_i] = xasprintf("(%g,%g)", (double)entity->rotation_yaw, (double)entity->rotation_pitch);
break;
case ENTITYSTAT_COLUM_STATE:
output[row_i][colum_i] = xasprintf("%c%c", entity->is_on_ground ? 'G' : '-', entity->is_dead ? 'D' : '-');
break;
case ENTITYSTAT_COLUM_WIDTH:
output[row_i][colum_i] = xasprintf("%g", (double)entity->width);
break;
case ENTITYSTAT_COLUM_HEIGHT:
output[row_i][colum_i] = xasprintf("%g", (double)entity->height);
break;
case ENTITYSTAT_COLUM_CHUNK:
output[row_i][colum_i] = xasprintf("(%d,%d,%d)", (int)entity->chunk_x, (int)entity->chunk_y, (int)entity->chunk_z);
break;
case ENTITYSTAT_COLUM_UUID:
output[row_i][colum_i] = uuid_to_string((const unsigned char *)&entity->uuid_most_sig);
break;
case ENTITYSTAT_COLUM_MAX_HEALTH:
output[row_i][colum_i] = entity->is_living_entity ?
xasprintf("%g", (double)entity->max_health) : "-";
break;
case ENTITYSTAT_COLUM_HEALTH:
output[row_i][colum_i] = entity->is_living_entity ?
xasprintf("%g", (double)entity->health) : "-";
break;
case ENTITYSTAT_COLUM_PERCENTAGE_HEALTH:
output[row_i][colum_i] = entity->is_living_entity ?
xasprintf("%g%%", (double)entity->health / entity->max_health * 100) : "-";
break;
case ENTITYSTAT_COLUM_IDLE_TIME:
output[row_i][colum_i] = entity->is_living_entity ?
xasprintf("%u", (unsigned int)entity->idle_time) : "-";
break;
case ENTITYSTAT_COLUM_ATTACK_TARGET_ID:
output[row_i][colum_i] = entity->is_living_entity && entity->attack_target_id != (uint32_t)-1 ?
xasprintf("%u", (unsigned int)entity->attack_target_id) : "-";
break;
}
}
row_i++;
}
unsigned int colum_widths[colum_count];
unsigned int colum_width_except_last = 0;
for(colum_i = 0; colum_i < colum_count; colum_i++) {
unsigned int *width = colum_widths + colum_i;
*width = 0;
for(row_i = 0; row_i < should_print_header + count; row_i++) {
size_t len = strlen(output[row_i][colum_i]) + 1;
if(len > *width) *width = len;
}
if(colum_i + 1 < colum_count) colum_width_except_last += *width;
}
unsigned int max_width = 0;
if(!wide && config[colum_count - 1].colum == ENTITYSTAT_COLUM_NAME && isatty(STDOUT_FILENO)) {
struct winsize ws;
if(ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) != -1) {
max_width = ws.ws_col;
}
}
for(row_i = 0; row_i < should_print_header + count; row_i++) {
for(colum_i = 0; colum_i < colum_count; colum_i++) {
int is_last = colum_i + 1 == colum_count;
size_t len = strlen(output[row_i][colum_i]);
if(is_last && max_width && len > 6 && colum_width_except_last + len > max_width) {
if(max_width < colum_width_except_last || max_width - colum_width_except_last < 6) len = 6;
else len = max_width - colum_width_except_last;
}
fwrite(output[row_i][colum_i], len, 1, stdout);
if(!is_last) print_spaces(colum_widths[colum_i] - len);
}
putchar('\n');
}
}