Task 296: .HUM File Format

Task 296: .HUM File Format

.HUM File Format Specifications

The .HUM file format is used in OMSI The Bus Simulator for configuring AI humans (pedestrians and passengers). It is a plain text file with a keyword-based structure, where each keyword is enclosed in square brackets and followed by one or more lines of parameter values. Keywords define various attributes of the human, such as physical dimensions, animations, and behaviors. The format does not support sounds or scripts; OMSI ignores unrelated keywords. The file is typically located in subfolders within the game's "Humans" directory and references a corresponding .cfg file for model data.

List of all the properties of this file format intrinsic to its file system:

  • Model: Relative path to the .cfg model configuration file (string).
  • Seat height: Seating height in meters (decimal number; lower values make the human sit higher).
  • Human geom: Foot distance in meters (decimal) and absolute height in meters (decimal).
  • Body part positions (under [left], but values apply to the right side due to a labeling quirk): Multiple lines of position data for body parts, each line containing 1-3 decimal numbers (x, y, z coordinates from tools like Blender; x for forward/back, y for left/right, z for height). The exact number of body parts and lines is fixed per human type but derived from the model (typically matching bone definitions in the .cfg file).
  • Voice: Folder name for the voice audio files in the map's ticket pack (string).
  • Walk param: Step size in meters (decimal), upper arm angle in degrees (decimal), hands animation intensity (decimal), hips animation intensity (decimal), waist animation intensity (decimal).
  • Mass: Mass in tonnes (decimal).
  • Age: Age as a whole number (integer), used for ticket determination.

Two direct download links for files of format .HUM:

I was unable to locate publicly available direct download links to individual .HUM files, as they are typically bundled in game mods or add-ons (often in ZIP archives) and not hosted as standalone files. However, here are two free mod downloads that contain .HUM files within their archives:

Ghost blog embedded HTML JavaScript for drag and drop .HUM file dump:

This is an embedded HTML snippet with JavaScript for a Ghost blog post. It creates a drop zone where users can drag and drop a .HUM file. The script reads the file as text, parses the keywords, extracts the properties, and displays them on the screen.

Drag and drop a .HUM file here

Python class for .HUM handling:

import sys

class HumFile:
    def __init__(self, filepath):
        self.filepath = filepath
        self.properties = {}

    def read(self):
        with open(self.filepath, 'r') as f:
            content = f.read()
        lines = [line.strip() for line in content.split('\n')]
        current_key = None
        body_positions = []
        for line in lines:
            if line.startswith('[') and line.endswith(']'):
                current_key = line[1:-1].lower()
                if current_key == 'left':
                    body_positions = []
                else:
                    self.properties[current_key] = []
            elif current_key:
                if current_key == 'left':
                    body_positions.append(line)
                else:
                    self.properties[current_key].append(line)
        if body_positions:
            self.properties['body part positions'] = body_positions

    def print_properties(self):
        for key, values in self.properties.items():
            print(f"{key.capitalize()}: {', '.join(values)}")

    def write(self, new_filepath=None):
        filepath = new_filepath or self.filepath
        with open(filepath, 'w') as f:
            for key, values in self.properties.items():
                if key == 'body part positions':
                    f.write('[left]\n')
                    for val in values:
                        f.write(f"{val}\n")
                else:
                    f.write(f"[{key}]\n")
                    for val in values:
                        f.write(f"{val}\n")

# Example usage
if __name__ == "__main__":
    if len(sys.argv) < 2:
        print("Usage: python hum.py <filepath>")
        sys.exit(1)
    hum = HumFile(sys.argv[1])
    hum.read()
    hum.print_properties()
    # To write: hum.write('output.hum')

Java class for .HUM handling:

import java.io.*;
import java.util.*;

public class HumFile {
    private String filepath;
    private Map<String, List<String>> properties = new HashMap<>();

    public HumFile(String filepath) {
        this.filepath = filepath;
    }

    public void read() throws IOException {
        try (BufferedReader reader = new BufferedReader(new FileReader(filepath))) {
            String line;
            String currentKey = null;
            List<String> bodyPositions = new ArrayList<>();
            while ((line = reader.readLine()) != null) {
                line = line.trim();
                if (line.startsWith("[") && line.endsWith("]")) {
                    currentKey = line.substring(1, line.length() - 1).toLowerCase();
                    if ("left".equals(currentKey)) {
                        bodyPositions = new ArrayList<>();
                    } else {
                        properties.put(currentKey, new ArrayList<>());
                    }
                } else if (currentKey != null) {
                    if ("left".equals(currentKey)) {
                        bodyPositions.add(line);
                    } else {
                        properties.get(currentKey).add(line);
                    }
                }
            }
            if (!bodyPositions.isEmpty()) {
                properties.put("body part positions", bodyPositions);
            }
        }
    }

    public void printProperties() {
        for (Map.Entry<String, List<String>> entry : properties.entrySet()) {
            System.out.println(capitalize(entry.getKey()) + ": " + String.join(", ", entry.getValue()));
        }
    }

    private String capitalize(String str) {
        return str.substring(0, 1).toUpperCase() + str.substring(1);
    }

    public void write(String newFilepath) throws IOException {
        String outPath = (newFilepath != null) ? newFilepath : filepath;
        try (BufferedWriter writer = new BufferedWriter(new FileWriter(outPath))) {
            for (Map.Entry<String, List<String>> entry : properties.entrySet()) {
                String key = entry.getKey();
                if ("body part positions".equals(key)) {
                    writer.write("[left]\n");
                } else {
                    writer.write("[" + key + "]\n");
                }
                for (String val : entry.getValue()) {
                    writer.write(val + "\n");
                }
            }
        }
    }

    public static void main(String[] args) throws IOException {
        if (args.length < 1) {
            System.out.println("Usage: java HumFile <filepath>");
            System.exit(1);
        }
        HumFile hum = new HumFile(args[0]);
        hum.read();
        hum.printProperties();
        // To write: hum.write("output.hum");
    }
}

JavaScript class for .HUM handling (Node.js):

const fs = require('fs');

class HumFile {
    constructor(filepath) {
        this.filepath = filepath;
        this.properties = {};
    }

    read() {
        const content = fs.readFileSync(this.filepath, 'utf8');
        const lines = content.split('\n').map(line => line.trim());
        let currentKey = null;
        let bodyPositions = [];
        for (let line of lines) {
            if (line.startsWith('[') && line.endsWith(']')) {
                currentKey = line.slice(1, -1).toLowerCase();
                if (currentKey === 'left') {
                    bodyPositions = [];
                } else {
                    this.properties[currentKey] = [];
                }
            } else if (currentKey) {
                if (currentKey === 'left') {
                    bodyPositions.push(line);
                } else {
                    this.properties[currentKey].push(line);
                }
            }
        }
        if (bodyPositions.length > 0) {
            this.properties['body part positions'] = bodyPositions;
        }
    }

    printProperties() {
        for (let key in this.properties) {
            console.log(`${key.charAt(0).toUpperCase() + key.slice(1)}: ${this.properties[key].join(', ')}`);
        }
    }

    write(newFilepath = null) {
        const outPath = newFilepath || this.filepath;
        let output = '';
        for (let key in this.properties) {
            if (key === 'body part positions') {
                output += '[left]\n';
            } else {
                output += `[${key}]\n`;
            }
            output += this.properties[key].join('\n') + '\n';
        }
        fs.writeFileSync(outPath, output);
    }
}

// Example usage
if (process.argv.length < 3) {
    console.log('Usage: node hum.js <filepath>');
    process.exit(1);
}
const hum = new HumFile(process.argv[2]);
hum.read();
hum.printProperties();
// To write: hum.write('output.hum');

C++ class for .HUM handling (using std::string, std::vector, std::map):

#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <map>
#include <algorithm>

class HumFile {
private:
    std::string filepath;
    std::map<std::string, std::vector<std::string>> properties;

public:
    HumFile(const std::string& fp) : filepath(fp) {}

    void read() {
        std::ifstream file(filepath);
        if (!file.is_open()) {
            std::cerr << "Error opening file: " << filepath << std::endl;
            return;
        }
        std::string line;
        std::string currentKey;
        std::vector<std::string> bodyPositions;
        while (std::getline(file, line)) {
            line.erase(0, line.find_first_not_of(" \t"));
            line.erase(line.find_last_not_of(" \t") + 1);
            if (line.empty()) continue;
            if (line.front() == '[' && line.back() == ']') {
                currentKey = line.substr(1, line.size() - 2);
                std::transform(currentKey.begin(), currentKey.end(), currentKey.begin(), ::tolower);
                if (currentKey == "left") {
                    bodyPositions.clear();
                } else {
                    properties[currentKey] = {};
                }
            } else if (!currentKey.empty()) {
                if (currentKey == "left") {
                    bodyPositions.push_back(line);
                } else {
                    properties[currentKey].push_back(line);
                }
            }
        }
        if (!bodyPositions.empty()) {
            properties["body part positions"] = bodyPositions;
        }
        file.close();
    }

    void printProperties() const {
        for (const auto& pair : properties) {
            std::string key = pair.first;
            key[0] = std::toupper(key[0]);
            std::cout << key << ": ";
            for (size_t i = 0; i < pair.second.size(); ++i) {
                std::cout << pair.second[i];
                if (i < pair.second.size() - 1) std::cout << ", ";
            }
            std::cout << std::endl;
        }
    }

    void write(const std::string& newFilepath = "") const {
        std::string outPath = newFilepath.empty() ? filepath : newFilepath;
        std::ofstream file(outPath);
        if (!file.is_open()) {
            std::cerr << "Error writing file: " << outPath << std::endl;
            return;
        }
        for (const auto& pair : properties) {
            std::string key = pair.first;
            if (key == "body part positions") {
                file << "[left]\n";
            } else {
                file << "[" << key << "]\n";
            }
            for (const auto& val : pair.second) {
                file << val << "\n";
            }
        }
        file.close();
    }
};

int main(int argc, char* argv[]) {
    if (argc < 2) {
        std::cerr << "Usage: " << argv[0] << " <filepath>" << std::endl;
        return 1;
    }
    HumFile hum(argv[1]);
    hum.read();
    hum.printProperties();
    // To write: hum.write("output.hum");
    return 0;
}