ffdammit.SkyProcMain.java Source code

Java tutorial

Introduction

Here is the source code for ffdammit.SkyProcMain.java

Source

/*
 * Copyright (c) 2016. Created by Bystander from Nexus. License - the same as the one of SkyProc, whatever it is.
 *  I don't really care. Would be nice to be mentioned if you create some derived work
 */

package ffdammit;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import ffdammit.NBWSaveFile.Settings;
import ffdammit.dto.RaceData;
import lev.gui.LSaveFile;
import skyproc.*;
import skyproc.genenums.Gender;
import skyproc.gui.SPMainMenuPanel;
import skyproc.gui.SUM;
import skyproc.gui.SUMGUI;

import javax.swing.*;
import java.awt.*;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @author Bystander
 */
public class SkyProcMain implements SUM {

    /*
     * The important functions to change are:
     * - getStandardMenu(), where you set up the GUI
     * - runChangesToPatch(), where you put all the processing code and add records to the output patch.
     */

    public static String myPatchName = "NPCProc";
    public static String authorName = "Bystander";
    public static String version = "1.5";
    public static String welcomeText = "Removes opposite gender animations flag from all NPCs, optionally fixes races models, heights";
    public static String descriptionToShowInSUM = welcomeText;
    public static Color headerColor = new Color(66, 181, 184); // Teal
    public static Color settingsColor = new Color(72, 179, 58); // Green
    public static Font settingsFont = new Font("Serif", Font.BOLD, 15);
    public static SkyProcSave save = new NBWSaveFile();
    public static String racesDataPath = "races.json";
    /*
     * The types of records you want your patcher to import. Change this to
     * customize the import to what you need.
     */
    GRUP_TYPE[] importRequests = new GRUP_TYPE[] { GRUP_TYPE.NPC_, GRUP_TYPE.RACE };

    // Do not write the bulk of your program here
    // Instead, write your patch changes in the "runChangesToPatch" function
    // at the bottom
    public static void main(String[] args) {
        try {
            SPGlobal.createGlobalLog();
            SUMGUI.open(new SkyProcMain(), args);
        } catch (Exception e) {
            // If a major error happens, print it everywhere and display a message box.
            System.err.println(e.toString());
            SPGlobal.logException(e);
            JOptionPane.showMessageDialog(null, "There was an exception thrown during program execution: '" + e
                    + "'  Check the debug logs or contact the author.");
            SPGlobal.closeDebug();
        }
    }

    @Override
    public String getName() {
        return "NoButchWalk";
    }

    // This function labels any record types that you "multiply".
    // For example, if you took all the armors in a mod list and made 3 copies,
    // you would put ARMO here.
    // This is to help monitor/prevent issues where multiple SkyProc patchers
    // multiply the same record type to yeild a huge number of records.
    @Override
    public GRUP_TYPE[] dangerousRecordReport() {
        // None
        return new GRUP_TYPE[0];
    }

    @Override
    public GRUP_TYPE[] importRequests() {
        return importRequests;
    }

    @Override
    public boolean importAtStart() {
        return false;
    }

    @Override
    public boolean hasStandardMenu() {
        return true;
    }

    // This is where you add panels to the main menu.
    // First create custom panel classes (as shown by YourFirstSettingsPanel),
    // Then add them here.
    @Override
    public SPMainMenuPanel getStandardMenu() {
        SPMainMenuPanel settingsMenu = new SPMainMenuPanel(getHeaderColor());

        settingsMenu.setWelcomePanel(new WelcomePanel(settingsMenu));
        settingsMenu.addMenu(new GeneralSettingsPanel(settingsMenu), false, save, Settings.GENERAL_SETTINGS);
        settingsMenu.addMenu(new VisualSettingsPanel(settingsMenu), false, save, Settings.VISUAL_SETTINGS);
        settingsMenu.addMenu(new CombatSettingsPanel(settingsMenu), false, save, Settings.COMBAT_SETTINGS);

        return settingsMenu;
    }

    // Usually false unless you want to make your own GUI
    @Override
    public boolean hasCustomMenu() {
        return false;
    }

    @Override
    public JFrame openCustomMenu() {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public boolean hasLogo() {
        return false;
    }

    @Override
    public URL getLogo() {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public boolean hasSave() {
        return true;
    }

    @Override
    public LSaveFile getSave() {
        return save;
    }

    @Override
    public String getVersion() {
        return version;
    }

    @Override
    public ModListing getListing() {
        return new ModListing(getName(), false);
    }

    @Override
    public Mod getExportPatch() {
        Mod out = new Mod(getListing());
        out.setAuthor(authorName);
        return out;
    }

    @Override
    public Color getHeaderColor() {
        return headerColor;
    }

    // Add any custom checks to determine if a patch is needed.
    // On Automatic Variants, this function would check if any new packages were
    // added or removed.
    @Override
    public boolean needsPatching() {
        return false;
    }

    // This function runs when the program opens to "set things up"
    // It runs right after the save file is loaded, and before the GUI is displayed
    @Override
    public void onStart() throws Exception {
    }

    // This function runs right as the program is about to close.
    @Override
    public void onExit(boolean patchWasGenerated) throws Exception {
    }

    // Add any mods that you REQUIRE to be present in order to patch.
    @Override
    public ArrayList<ModListing> requiredMods() {
        return new ArrayList<>(0);
    }

    @Override
    public String description() {
        return descriptionToShowInSUM;
    }

    public List<String> loadTextArray(String fileName) {
        try {
            return Files.readAllLines(new File(fileName).toPath(), Charset.defaultCharset()).stream()
                    .map(String::trim).distinct().collect(Collectors.toList());
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }

    public void copyFaceData(final NPC_ src, NPC_ dest) {
        dest.setRace(src.getRace());
        dest.setSkin(src.getSkin());
        dest.setVoiceType(src.getVoiceType());
        dest.setAttackDataRace(src.getAttackDataRace());
        dest.setEyePreset(src.getEyePreset());
        dest.setFaceTint(RGB.Red, dest.getFaceTint(RGB.Red));
        dest.setFaceTint(RGB.Green, dest.getFaceTint(RGB.Green));
        dest.setFaceTint(RGB.Blue, dest.getFaceTint(RGB.Blue));
        for (NPC_.FacePart fp : NPC_.FacePart.values()) {
            float faceValue = src.getFaceValue(fp);
            if (faceValue != 0.0f) {
                dest.setFaceValue(fp, faceValue);
            }
        }
        dest.setHairColor(src.getHairColor());
        dest.setHeight(src.getHeight());
        dest.setMouthPreset(src.getMouthPreset());
        dest.setNosePreset(src.getNosePreset());
        dest.setWeight(src.getWeight());
    }

    // This is where you should write the bulk of your code.
    // Write the changes you would like to make to the patch,
    // but DO NOT export it.  Exporting is handled internally.
    @Override
    public void runChangesToPatch() throws Exception {

        ObjectMapper mapper = new ObjectMapper();
        final Map<String, RaceData> raceData = save.getBool(Settings.PROCESS_RACE_HEIGHTS)
                ? mapper.readValue(new File(racesDataPath), new TypeReference<HashMap<String, RaceData>>() {
                })
                : null;

        final List<String> faceModList = save.getBool(Settings.PROCESS_FACE_VISUALS) ? loadTextArray("faces.txt")
                : null;

        final List<String> essentialList = save.getInt(Settings.PRESERVE_PROTECTION_OPTIONS) > 0
                ? loadTextArray("essential.txt")
                : null;

        final List<String> protectedList = save.getInt(Settings.PRESERVE_PROTECTION_OPTIONS) > 0
                ? loadTextArray("protected.txt")
                : null;

        final List<String> noProtectionList = save.getInt(Settings.PRESERVE_PROTECTION_OPTIONS) > 0
                ? loadTextArray("noprotection.txt")
                : null;

        Mod patch = SPGlobal.getGlobalPatch();

        Mod merger = new Mod(getName() + "Merger", false);
        merger.addAsOverrides(SPGlobal.getDB());

        merger.getNPCs().forEach(n -> {

            boolean isNPCRecordChanged = false;

            if (faceModList != null) {
                ArrayList<MajorRecord> hist = n.getRecordHistory();

                if (hist.size() > 1) { // processing face data
                    int idx = 999999;
                    MajorRecord mrr = null;
                    for (MajorRecord mr : hist) {
                        ModListing ml = mr.getModImportedFrom();
                        Mod mod = SPDatabase.getMod(ml);
                        String modName = mod.getName();
                        int i = faceModList.indexOf(modName);
                        if (i != -1 && idx > i) {
                            // the mod matching first listed in faces.txt wins
                            mrr = mr;
                            idx = i;
                        }
                    }
                    if (mrr != null && mrr instanceof NPC_) {
                        NPC_ from = (NPC_) mrr;
                        copyFaceData(from, n);
                        isNPCRecordChanged = true;
                    }
                }
            }

            if (save.getInt(Settings.PRESERVE_PROTECTION_OPTIONS) > 0) {
                boolean isProtected = false;
                boolean isEssential = false;
                boolean isProtectionProcessed = false;
                if (essentialList != null && essentialList.contains(n.getName())) {
                    n.set(NPC_.NPCFlag.Essential, true);
                    n.set(NPC_.NPCFlag.Protected, false);
                } else if (protectedList != null && protectedList.contains(n.getName())) {
                    n.set(NPC_.NPCFlag.Essential, false);
                    n.set(NPC_.NPCFlag.Protected, true);
                } else if (noProtectionList != null && noProtectionList.contains(n.getName())) {
                    n.set(NPC_.NPCFlag.Essential, false);
                    n.set(NPC_.NPCFlag.Protected, false);
                } else if (save.getInt(Settings.PRESERVE_PROTECTION_OPTIONS) > 1) { // don't process if set to 'files only'
                    // try checking record's history
                    ArrayList<MajorRecord> hist = n.getRecordHistory();
                    for (MajorRecord mr : hist) {
                        if (mr instanceof NPC_) {
                            NPC_ nh = (NPC_) mr;
                            if (nh.get(NPC_.NPCFlag.Essential)) {
                                isEssential = true;
                                isProtectionProcessed = true;
                            }
                            if (nh.get(NPC_.NPCFlag.Protected)) {
                                isProtected = true;
                                isProtectionProcessed = true;
                            }
                        }
                    }
                    if (isProtectionProcessed) {
                        switch (save.getInt(Settings.PRESERVE_PROTECTION_OPTIONS)) {
                        case 2: // Protected/Essential -> Essential
                            isEssential = isEssential | isProtected;
                            if (isEssential) {
                                isNPCRecordChanged = true;
                                n.set(NPC_.NPCFlag.Protected, false);
                                n.set(NPC_.NPCFlag.Essential, true);
                            }
                            break;
                        case 3: // Protected/Essential -> Protected
                            isProtected = isProtected | isEssential;
                            if (isProtected) {
                                isNPCRecordChanged = true;
                                n.set(NPC_.NPCFlag.Protected, true);
                                n.set(NPC_.NPCFlag.Essential, false);
                            }
                            break;
                        case 4: // Essential -> Protected
                            isProtected = isProtected | isEssential | n.get(NPC_.NPCFlag.Essential)
                                    | n.get(NPC_.NPCFlag.Protected);
                            if (isProtected) {
                                isNPCRecordChanged = true;
                                n.set(NPC_.NPCFlag.Protected, true);
                                n.set(NPC_.NPCFlag.Essential, false);
                            }
                            break;
                        case 5: // Protected -> Essential
                            isEssential = isProtected | isEssential | n.get(NPC_.NPCFlag.Essential)
                                    | n.get(NPC_.NPCFlag.Protected);
                            if (isEssential) {
                                isNPCRecordChanged = true;
                                n.set(NPC_.NPCFlag.Protected, false);
                                n.set(NPC_.NPCFlag.Essential, true);
                            }
                            break;
                        }
                    }
                }
            }

            if (save.getBool(Settings.PROCESS_OPPOSITE_GENDER_ANIMS)) {
                if (n.get(NPC_.NPCFlag.Female) && n.get(NPC_.NPCFlag.OppositeGenderAnims)) {
                    n.set(NPC_.NPCFlag.OppositeGenderAnims, false);
                    isNPCRecordChanged = true;
                }
            }

            if (isNPCRecordChanged) {
                patch.addRecord(n);
            }
        });

        merger.getRaces().forEach(r -> r.getKeywordSet().getKeywordRefs().forEach(kw -> {
            boolean isRaceRecordChanged = false;
            String title = kw.getTitle();
            if (save.getBool(Settings.COMBAT_IN_WATER) && r.get(RACE.RACEFlags.NoCombatInWater)) {
                r.set(RACE.RACEFlags.NoCombatInWater, false);
                isRaceRecordChanged = true;
            }
            if (title != null && title.equals("013794Skyrim.esm")) { // NPC race
                if (save.getBool(Settings.PROCESS_RACE_MODELS)) {
                    try {
                        Model model = r.getPhysicsModel(Gender.FEMALE); // throws NPE sometimes
                        if (model != null && model.getFileName().equals("Actors\\Character\\DefaultMale.hkx")) {
                            model.setFileName("Actors\\Character\\DefaultFemale.hkx");
                            isRaceRecordChanged = true;
                        }
                    } catch (NullPointerException e) {
                        e.printStackTrace();
                    }
                }
                if (save.getBool(Settings.PROCESS_RACE_HEIGHTS) && !r.get(RACE.RACEFlags.Child)) {
                    String name = r.getName();
                    if (raceData != null && raceData.containsKey(name)) {
                        RaceData rd = raceData.get(name);
                        r.setHeight(Gender.MALE, rd.heightMale);
                        r.setHeight(Gender.FEMALE, rd.heightFemale);
                        isRaceRecordChanged = true;
                    }
                }
            }
            if (isRaceRecordChanged) {
                patch.addRecord(r);
            }
        }));
    }
}