de.blizzy.rust.lootconfig.LootConfigDump.java Source code

Java tutorial

Introduction

Here is the source code for de.blizzy.rust.lootconfig.LootConfigDump.java

Source

/*
    
LootConfigDump - Dumps statistics about loot config on Rust servers.
Copyright (C) 2016 Maik Schreiber
    
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.
    
You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
    
*/

package de.blizzy.rust.lootconfig;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.text.Collator;
import java.util.Collection;
import java.util.Collections;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

import org.apache.commons.io.FileUtils;

import com.google.common.collect.HashBasedTable;
import com.google.common.collect.HashMultiset;
import com.google.common.collect.Multiset;
import com.google.common.collect.Table;
import com.google.common.util.concurrent.AtomicDouble;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;

import de.blizzy.rust.lootconfig.model.Category;
import de.blizzy.rust.lootconfig.model.LootConfig;
import de.blizzy.rust.lootconfig.model.LootContainer;
import de.blizzy.rust.lootconfig.model.SubSpawn;

public class LootConfigDump {
    private static final boolean SHOW_DMLOOT = false;

    private File configFile;

    public static void main(String... args) throws IOException {
        File configFile = new File(args[0]);
        new LootConfigDump(configFile).run();
    }

    public LootConfigDump(File configFile) {
        this.configFile = configFile;
    }

    private void run() throws IOException {
        LootConfig config = loadConfig(configFile);

        Table<LootContainer, Category, Multiset<Float>> dropChances = HashBasedTable.create();

        Collection<LootContainer> lootContainers = config.LootContainers.values();
        config.Categories.values().stream().filter(Category::hasItemsOrBlueprints).forEach(category -> {
            lootContainers.forEach(lootContainer -> {
                Multiset<Float> categoryInContainerDropChances = getItemCategoryDropChances(category,
                        lootContainer);
                if (!categoryInContainerDropChances.isEmpty()) {
                    dropChances.put(lootContainer, category, categoryInContainerDropChances);
                }
            });
        });

        dropChances.rowKeySet().stream()
                .filter(lootContainer -> SHOW_DMLOOT || !lootContainer.name.contains("dmloot"))
                .sorted((lootContainer1, lootContainer2) -> Collator.getInstance().compare(lootContainer1.name,
                        lootContainer2.name))
                .forEach(lootContainer -> {
                    System.out.printf("%s (blueprint fragments: %s)", lootContainer,
                            lootContainer.DistributeFragments ? "yes" : "no").println();
                    Map<Category, Multiset<Float>> lootContainerDropChances = dropChances.row(lootContainer);
                    AtomicDouble lootContainerDropChancesSum = new AtomicDouble();
                    AtomicInteger categoriesCount = new AtomicInteger();
                    lootContainerDropChances.entrySet().stream().sorted(this::compareByChances).limit(7)
                            .forEach(categoryDropChancesEntry -> {
                                Category category = categoryDropChancesEntry.getKey();
                                Multiset<Float> categoryDropChances = categoryDropChancesEntry.getValue();
                                float categoryDropChancesSum = sum(categoryDropChances);
                                lootContainerDropChancesSum.addAndGet(categoryDropChancesSum);
                                System.out.printf("  %s %s%s%s", formatPercent(categoryDropChancesSum), category,
                                        (category.Items.size() > 0) ? " (" + formatItems(category) + ")" : "",
                                        (category.Blueprints.size() > 0) ? " [" + formatBlueprints(category) + "]"
                                                : "")
                                        .println();
                                categoriesCount.incrementAndGet();
                            });
                    if (categoriesCount.get() < lootContainerDropChances.size()) {
                        System.out.printf("  %s other (%d)",
                                formatPercent(1f - (float) lootContainerDropChancesSum.get()),
                                lootContainerDropChances.size() - categoriesCount.get()).println();
                    }
                });
    }

    private String formatItems(Category category) {
        return category.Items.stream().sorted((itemSpawn1, itemSpawn2) -> Collator.getInstance()
                .compare(itemSpawn1.item.Shortname, itemSpawn2.item.Shortname)).map(itemSpawn -> {
                    if (itemSpawn.Amount > 1) {
                        return String.format("%s x%d", itemSpawn.item.Shortname, itemSpawn.Amount);
                    } else {
                        return itemSpawn.item.Shortname;
                    }
                }).collect(Collectors.joining(", "));
    }

    private String formatBlueprints(Category category) {
        return category.Blueprints.stream()
                .sorted((blueprintSpawn1, blueprintSpawn2) -> Collator.getInstance()
                        .compare(blueprintSpawn1.item.Shortname, blueprintSpawn2.item.Shortname))
                .peek(blueprintSpawn -> {
                    if (blueprintSpawn.item == null) {
                        throw new IllegalStateException("blueprint spawn without item");
                    }
                }).map(blueprintSpawn -> blueprintSpawn.item.Shortname).collect(Collectors.joining(", "));
    }

    private float sum(Multiset<Float> chances) {
        return (float) (double) chances.stream().collect(Collectors.summingDouble(f -> f));
    }

    private int compareByChances(Map.Entry<Category, Multiset<Float>> entry1,
            Map.Entry<Category, Multiset<Float>> entry2) {
        return -Float.compare(sum(entry1.getValue()), sum(entry2.getValue()));
    }

    private String formatPercent(float chance) {
        return String.format(Locale.US, "%.3f%%", chance * 100f);
    }

    private Multiset<Float> getItemCategoryDropChances(Category itemCategory, LootContainer lootContainer) {
        Multiset<Float> itemCategoryDropChances = HashMultiset.create();

        SubSpawn rootSubSpawn = new SubSpawn();
        rootSubSpawn.Category = lootContainer.LootDefinition;
        rootSubSpawn.Weight = 1;
        fillItemCategoryDropChances(itemCategory, Collections.singleton(rootSubSpawn), 1f, itemCategoryDropChances);

        return itemCategoryDropChances;
    }

    private void fillItemCategoryDropChances(Category itemCategory, Set<SubSpawn> subSpawns, float parentChance,
            Multiset<Float> itemCategoryDropChances) {
        int totalWeight = subSpawns.stream().collect(Collectors.summingInt(subSpawn -> subSpawn.Weight));

        for (SubSpawn subSpawn : subSpawns) {
            float subSpawnChance = parentChance * subSpawn.Weight / totalWeight;
            if (subSpawn.Category == itemCategory) {
                itemCategoryDropChances.add(subSpawnChance);
            } else if (subSpawn.Category.hasSubSpawns()) {
                fillItemCategoryDropChances(itemCategory, subSpawn.Category.SubSpawn, subSpawnChance,
                        itemCategoryDropChances);
            }
        }
    }

    private LootConfig loadConfig(File configFile) throws IOException {
        String json = FileUtils.readFileToString(configFile, StandardCharsets.UTF_8);
        Type type = new TypeToken<LootConfig>() {
        }.getType();
        GsonBuilder gsonBuilder = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().serializeNulls();
        Gson gson = gsonBuilder.create();
        LootConfig config = gson.fromJson(json, type);
        config.initAfterDeserialize();
        return config;
    }
}