| /* 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_TICKS, |
| ENTITYSTAT_COLUM_MAX_HEALTH, |
| ENTITYSTAT_COLUM_HEALTH, |
| ENTITYSTAT_COLUM_PERCENTAGE_HEALTH, |
| ENTITYSTAT_COLUM_IDLE_TIME, |
| ENTITYSTAT_COLUM_ATTACK_TARGET_ID, |
| ENTITYSTAT_COLUM_GROWING_AGE, |
| ENTITYSTAT_COLUM_ITEM_AGE, |
| ENTITYSTAT_COLUM_ITEM_PICKUP_DELAY, |
| ENTITYSTAT_COLUM_OWNER, |
| ENTITYSTAT_COLUM_THROWER, |
| ENTITYSTAT_COLUM_ITEM_DAMAGE, |
| ENTITYSTAT_COLUM_ITEM_LIFESPAN, |
| ENTITYSTAT_COLUM_ITEM_COUNT, |
| }; |
| |
| 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_TICKS, "ticks", "TICKS" }, |
| { 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" }, |
| { ENTITYSTAT_COLUM_GROWING_AGE, "growingage", "GROWING-AGE" }, |
| { ENTITYSTAT_COLUM_ITEM_AGE, "itemage", "ITEM-AGE" }, |
| { ENTITYSTAT_COLUM_ITEM_PICKUP_DELAY, "itempickupdelay", "PICKUP-DELAY" }, |
| { ENTITYSTAT_COLUM_OWNER, "owner", "OWNER" }, |
| { ENTITYSTAT_COLUM_THROWER, "thrower", "THROWER" }, |
| { ENTITYSTAT_COLUM_ITEM_DAMAGE, "itemdamage", "ITEM-DAMAGE" }, |
| { ENTITYSTAT_COLUM_ITEM_LIFESPAN, "itemlifespan", "ITEM-LIFESPAN" }, |
| { ENTITYSTAT_COLUM_ITEM_COUNT, "itemcount", "ITEM-COUNT" }, |
| { -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 struct sort_key { |
| enum colum colum; |
| int direction; |
| } *sort_configuration; |
| static unsigned int sort_key_count; |
| |
| static void add_sort_key(struct sort_key key) { |
| sort_configuration = realloc(sort_configuration, sizeof(struct sort_key) * (sort_key_count + 1)); |
| if(!sort_configuration) { |
| fputs("Out of memory\n", stderr); |
| exit(1); |
| } |
| sort_configuration[sort_key_count++] = key; |
| } |
| |
| int entitystat_set_sort(const char *spec) { |
| if(!*spec) { |
| fputs("sort specification cannot be empty\n", stderr); |
| return -1; |
| } |
| char *colum_name; |
| enum colum colum; |
| int direction = 1; |
| const char *p = spec; |
| while(1) switch(*p) { |
| case '+': |
| case '-': |
| if(p != spec) { |
| fprintf(stderr, "Invalid sign '%c' in middle of a sort key\n", *p); |
| return -1; |
| } |
| direction = *p == '+' ? 1 : -1; |
| spec++; |
| p++; |
| break; |
| case 0: |
| case ',': |
| colum_name = malloc(p - spec + 1); |
| if(!colum_name) { |
| fputs("Out of memory\n", stderr); |
| exit(1); |
| } |
| memcpy(colum_name, spec, p - spec); |
| colum_name[p - spec] = 0; |
| colum = get_colum(colum_name); |
| free(colum_name); |
| if(colum == (enum colum)-1) { |
| fprintf(stderr, "Unknown colum '%s'\n", colum_name); |
| return -1; |
| } |
| add_sort_key((struct sort_key){ colum, direction }); |
| direction = 1; |
| if(*p != ',') return 0; |
| spec = 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--; |
| } |
| } |
| |
| static int compare_entities(const void *v1, const void *v2) { |
| const struct entity *entity1 = *(const struct entity **)v1; |
| const struct entity *entity2 = *(const struct entity **)v2; |
| unsigned int i = 0; |
| do { |
| int diff; |
| switch(sort_configuration[i].colum) { |
| case ENTITYSTAT_COLUM_WORLD: |
| diff = strcmp((const char *)entity1->data + entity1->world_name_offset, |
| (const char *)entity2->data + entity2->world_name_offset); |
| break; |
| case ENTITYSTAT_COLUM_ID: |
| diff = entity1->entity_id - entity2->entity_id; |
| break; |
| case ENTITYSTAT_COLUM_TYPE: |
| diff = strcmp((const char *)entity1->data + entity1->entity_type_name_offset, |
| (const char *)entity2->data + entity2->entity_type_name_offset); |
| break; |
| case ENTITYSTAT_COLUM_NAME: |
| diff = strcmp((const char *)entity1->data + entity1->display_name_offset, |
| (const char *)entity2->data + entity2->display_name_offset); |
| break; |
| case ENTITYSTAT_COLUM_LOCATION: |
| /* |
| diff = (entity1->location_x - entity2->location_x) * 10 + |
| (entity1->location_y - entity2->location_y) * 10 + |
| (entity1->location_z - entity2->location_z) * 10; |
| */ |
| /* |
| diff = (entity1->location_x < entity2->location_x ? -100 : 100) + |
| (entity1->location_y < entity2->location_y ? -10 : 10) + |
| (entity1->location_z < entity2->location_z ? -1 : 1); |
| */ |
| if(entity1->location_x < entity2->location_x) diff = -1; |
| else if(entity1->location_x > entity2->location_x) diff = 1; |
| else if(entity1->location_y < entity2->location_y) diff = -1; |
| else if(entity1->location_y > entity2->location_y) diff = 1; |
| else if(entity1->location_z < entity2->location_z) diff = -1; |
| else if(entity1->location_z > entity2->location_z) diff = 1; |
| else diff = 0; |
| break; |
| case ENTITYSTAT_COLUM_ROTATION: |
| if(entity1->rotation_yaw < entity2->rotation_yaw) diff = -1; |
| else if(entity1->rotation_yaw > entity2->rotation_yaw) diff = 1; |
| else if(entity1->rotation_pitch < entity2->rotation_pitch) diff = -1; |
| else if(entity1->rotation_pitch > entity2->rotation_pitch) diff = 1; |
| else diff = 0; |
| break; |
| case ENTITYSTAT_COLUM_STATE: |
| diff = (entity1->is_dead - entity2->is_dead) * 10 + (entity1->is_on_ground - entity2->is_on_ground); |
| break; |
| case ENTITYSTAT_COLUM_WIDTH: |
| if(entity1->width < entity2->width) diff = -1; |
| else if(entity1->width > entity2->width) diff = 1; |
| else diff = 0; |
| break; |
| case ENTITYSTAT_COLUM_HEIGHT: |
| if(entity1->height < entity2->height) diff = -1; |
| else if(entity1->height > entity2->height) diff = 1; |
| else diff = 0; |
| break; |
| case ENTITYSTAT_COLUM_CHUNK: |
| diff = (entity1->chunk_x - entity2->chunk_x) + (entity1->chunk_y - entity1->chunk_y) + |
| (entity1->chunk_z - entity2->chunk_z); |
| break; |
| case ENTITYSTAT_COLUM_UUID: |
| diff = memcmp(&entity1->uuid_most_sig, &entity2->uuid_most_sig, 16); |
| break; |
| case ENTITYSTAT_COLUM_TICKS: |
| diff = entity1->ticks - entity2->ticks; |
| break; |
| case ENTITYSTAT_COLUM_MAX_HEALTH: |
| if(entity1->is_living_entity && entity2->is_living_entity) { |
| if(entity1->living.max_health < entity2->living.max_health) diff = -1; |
| else if(entity1->living.max_health > entity2->living.max_health) diff = 1; |
| else diff = 0; |
| } else diff = entity1->is_living_entity - entity2->is_living_entity; |
| break; |
| case ENTITYSTAT_COLUM_HEALTH: |
| if(entity1->is_living_entity && entity2->is_living_entity) { |
| if(entity1->living.health < entity2->living.health) diff = -1; |
| else if(entity1->living.health > entity2->living.health) diff = 1; |
| else diff = 0; |
| } else if(entity1->is_item && entity2->is_item) { |
| /* |
| if(entity1->item.health < entity2->item.health) diff = -1; |
| else if(entity1->item.health > entity2->item.health) diff = 1; |
| else diff = 0; |
| */ |
| diff = entity1->item.health - entity2->item.health; |
| } else { |
| diff = (entity1->is_living_entity - entity2->is_living_entity) + |
| (entity1->is_item - entity2->is_item); |
| } |
| break; |
| case ENTITYSTAT_COLUM_PERCENTAGE_HEALTH: |
| if(entity1->is_living_entity && entity2->is_living_entity) { |
| double p1 = (double)entity1->living.health / entity1->living.max_health; |
| double p2 = (double)entity1->living.health / entity1->living.max_health; |
| if(p1 < p2) diff = -1; |
| else if(p1 > p2) diff = 1; |
| else diff = 0; |
| } else diff = entity1->is_living_entity - entity2->is_living_entity; |
| break; |
| case ENTITYSTAT_COLUM_IDLE_TIME: |
| diff = entity1->is_living_entity && entity2->is_living_entity ? |
| entity1->living.idle_time - entity2->living.idle_time : |
| entity1->is_living_entity - entity2->is_living_entity; |
| break; |
| case ENTITYSTAT_COLUM_ATTACK_TARGET_ID: |
| diff = entity1->is_living_entity && entity2->is_living_entity ? |
| entity1->living.attack_target_id - entity2->living.attack_target_id : |
| entity1->is_living_entity - entity2->is_living_entity; |
| break; |
| case ENTITYSTAT_COLUM_GROWING_AGE: |
| diff = entity1->is_living_entity && entity2->is_living_entity ? |
| entity1->living.growing_age - entity2->living.growing_age : |
| entity1->is_living_entity - entity2->is_living_entity; |
| break; |
| case ENTITYSTAT_COLUM_ITEM_AGE: |
| diff = entity1->is_item && entity2->is_item ? |
| entity1->item.age - entity2->item.age : |
| entity1->is_item - entity2->is_item; |
| break; |
| case ENTITYSTAT_COLUM_ITEM_PICKUP_DELAY: |
| diff = entity1->is_item && entity2->is_item ? |
| entity1->item.pickup_delay - entity2->item.pickup_delay : |
| entity1->is_item - entity2->is_item; |
| break; |
| case ENTITYSTAT_COLUM_OWNER: |
| diff = entity1->is_item && entity2->is_item ? |
| (entity1->item.owner_offset >= 0 && entity1->item.owner_offset >= 0 ? |
| strcmp((const char *)entity1->data + entity1->item.owner_offset, |
| (const char *)entity2->data + entity2->item.owner_offset) : |
| entity1->item.owner_offset - entity2->item.owner_offset) : |
| entity1->is_item - entity2->is_item; |
| break; |
| case ENTITYSTAT_COLUM_THROWER: |
| diff = entity1->is_item && entity2->is_item ? |
| (entity1->item.thrower_offset >= 0 && entity2->item.thrower_offset >= 0 ? |
| strcmp((const char *)entity1->data + entity1->item.thrower_offset, |
| (const char *)entity2->data + entity2->item.thrower_offset) : |
| entity1->item.thrower_offset - entity2->item.thrower_offset) : |
| entity1->is_item - entity2->is_item; |
| break; |
| case ENTITYSTAT_COLUM_ITEM_DAMAGE: |
| diff = entity1->is_item && entity2->is_item ? |
| entity1->item.item_damage - entity2->item.item_damage : |
| entity1->is_item - entity2->is_item; |
| break; |
| case ENTITYSTAT_COLUM_ITEM_LIFESPAN: |
| diff = entity1->is_item && entity2->is_item ? |
| entity1->item.lifespan - entity2->item.lifespan : |
| entity1->is_item - entity2->is_item; |
| break; |
| case ENTITYSTAT_COLUM_ITEM_COUNT: |
| diff = entity1->is_item && entity2->is_item ? |
| entity1->item.item_count - entity2->item.item_count : |
| entity1->is_item - entity2->is_item; |
| break; |
| default: |
| diff = 0; |
| break; |
| } |
| if(diff) return diff * sort_configuration[i].direction; |
| } while(++i < sort_key_count); |
| return 0; |
| } |
| |
| 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; |
| } |
| |
| if(sort_key_count) qsort(entities, count, sizeof(struct entity *), compare_entities); |
| |
| 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_TICKS: |
| output[row_i][colum_i] = xasprintf("%u", (unsigned int)entity->ticks); |
| break; |
| case ENTITYSTAT_COLUM_MAX_HEALTH: |
| output[row_i][colum_i] = entity->is_living_entity ? |
| xasprintf("%g", (double)entity->living.max_health) : "-"; |
| break; |
| case ENTITYSTAT_COLUM_HEALTH: |
| output[row_i][colum_i] = entity->is_living_entity ? |
| xasprintf("%g", (double)entity->living.health) : |
| (entity->is_item ? xasprintf("%d", (int)entity->item.health) : "-"); |
| break; |
| case ENTITYSTAT_COLUM_PERCENTAGE_HEALTH: |
| output[row_i][colum_i] = entity->is_living_entity ? |
| xasprintf("%g%%", (double)entity->living.health / entity->living.max_health * 100) : "-"; |
| break; |
| case ENTITYSTAT_COLUM_IDLE_TIME: |
| output[row_i][colum_i] = entity->is_living_entity ? |
| xasprintf("%u", (unsigned int)entity->living.idle_time) : "-"; |
| break; |
| case ENTITYSTAT_COLUM_ATTACK_TARGET_ID: |
| output[row_i][colum_i] = entity->is_living_entity && entity->living.attack_target_id != (uint32_t)-1 ? |
| xasprintf("%u", (unsigned int)entity->living.attack_target_id) : "-"; |
| break; |
| case ENTITYSTAT_COLUM_GROWING_AGE: |
| output[row_i][colum_i] = entity->is_living_entity ? |
| xasprintf("%d", (int)entity->living.growing_age) : "-"; |
| break; |
| case ENTITYSTAT_COLUM_ITEM_AGE: |
| output[row_i][colum_i] = entity->is_item ? |
| xasprintf("%d", (int)entity->item.age) : "-"; |
| break; |
| case ENTITYSTAT_COLUM_ITEM_PICKUP_DELAY: |
| output[row_i][colum_i] = entity->is_item ? |
| xasprintf("%d", (int)entity->item.pickup_delay) : "-"; |
| break; |
| case ENTITYSTAT_COLUM_OWNER: |
| output[row_i][colum_i] = entity->is_item && entity->item.owner_offset >= 0 ? |
| (const char *)(entity->data + entity->item.owner_offset) : "-"; |
| break; |
| case ENTITYSTAT_COLUM_THROWER: |
| output[row_i][colum_i] = entity->is_item && entity->item.thrower_offset >= 0 ? |
| (const char *)(entity->data + entity->item.thrower_offset) : "-"; |
| break; |
| case ENTITYSTAT_COLUM_ITEM_DAMAGE: |
| output[row_i][colum_i] = entity->is_item ? |
| xasprintf("%d", (int)entity->item.item_damage) : "-"; |
| break; |
| case ENTITYSTAT_COLUM_ITEM_LIFESPAN: |
| output[row_i][colum_i] = entity->is_item ? |
| xasprintf("%d", (int)entity->item.lifespan) : "-"; |
| break; |
| case ENTITYSTAT_COLUM_ITEM_COUNT: |
| output[row_i][colum_i] = entity->is_item ? |
| xasprintf("%hhu", entity->item.item_count) : "-"; |
| 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'); |
| } |
| } |