{
  NPC Furry Patch Builder
  Created by Bad Dog based on code by matortheeternal
  
  Creates a NPC furry patch for a load order.
  
	To furrify a mod:
  REWRITE THIS
	- Open YiffyAgeConsolidated and the mod you want to patch in TES5Edit (YiffyAge after the other mod)
	- Select all NPCs added by the other mod
	- Right-click, "Apply Script and select this script
	- Select "yes" to process assets in the other mod and YA. "No" to everything else.
	- Tell it to create a new mod.
	- When it's done check the new mod (your patch) for errors.
	- If the mod you are patching changes any vanilla NPC's, go through those records. Copy the appearance changes from YA (head parts, 
	  skin tints, QNAM) and the rest from the mod.
	- Quit & Save, fire up Creation Kit with your patch as the active mod
	- Run facegen on all NPCs in the mod. Select them and hit CTRL-F4. Check one or two to see that they're furry.
	
	If there are race edits, you're on your own. In general, take all appearance edits from YA and all other edits from the mod you're patching.

  What the mod does:
  
  Eyes are reassigned to match the race. Hyenas get Kygarra eyes; Vaalsark get their eyes. Day-hunting
  cats get Lykaios eyes because they're the best match.
  
  NPCs with tattoos should keep the same tattoo, possibly with a different color, and the
  tattoo may have been modified on the race. NPCs with facial characteristics will keep 
  corresponding characteristics.

  affectedRaces defines the races to change
  
	Hotkey: Ctrl+Alt+F
}

// ====== NEED SPECIAL LOOK FOR SERANA
// Clamp NoseType4 to 0.5 for everything but Khajiit
// Need to set nose color esp tiger fox

unit BD_Furry_Patch_Builder;

interface

implementation

uses BDScriptTools, BDAssetLoader, xEditAPI, Classes, SysUtils, StrUtils, Windows, mteFunctions;

const
    vs = '6';
	LOGLEVEL = 5;
	PROCESSLOGLEVEL = 5;
    LIMIT = HighInteger; // For debugging, limit NCPs fixed; HighInteger for all
    TARGETFILE = ''; // If empty, ask the user
    TARGETNPC = ''; // comma-separated; empty string for all

    DO_OVERRIDES = true;
    DO_NEW_NPCS = true;
    DO_RACES = true;
	DO_FEMALE = true;
	DO_MALE = true;
	DO_SCARS = true;
	DO_HAIR = true;
	DO_CHARGEN = true;
	DO_BRIARHEART = true;
	DO_YA_ONLY = true;
	
    // Hairstyles
    HAIR_FANCY = 1;
    HAIR_LONG = 2;
    HAIR_MESSY = 4;
    HAIR_UNMILITARY = 8;
    HAIR_MANE = 16;
    HAIR_YOUNG = 32;
    HAIR_LEADER = 64;
    HAIR_EARRING = 128;
    HAIR_ORC = 256;
    HAIR_LONGEAR = 1024;

    LYKAIOSBLACKHAIR = $000A0435;

    bethesdaFiles = 'Skyrim.esm/Update.esm/Dawnguard.esm/HearthFires.esm/Dragonborn.esm';
    yaFileName = 'YiffyAgeConsolidated.esp';

var
	affectedRaces: TStringList;
	lykaiosBeardMap: TStringList;
	skinLayerNames: TStringList;
	slMasters: TStringList;
    hairstyles: TStringList;
    myHeadparts: TStringList;
    slBethFiles: TStringList;
    slBlindNPCs: TStringList;
    slNPCs: TStringList;
    slNordRaces: TStringList;
    targetNPCs: TStringList;
	
	briarheartNaked: IwbMainRecord;
	btnOk, btnCancel: TButton;
	cancel: boolean;
	cb1: TCheckBox;
	ed01, ed02: TEdit;
	frm: TForm;
	lbl1, lbl2: TLabel;
    bretonRace: IwbMainRecord;
    briarheartOutfit: IwbMainRecord;
    darkElfRace: IwbMainRecord;
    femaleEyesDremora: IwbMainRecord;
    imperialRace: IwbMainRecord;
    imperialRaceChild: IwbMainRecord;
    logIndent: integer;
    nordRace: IwbMainRecord;
    npcfile: IwbFile;
    reachmanRace: IwbMainRecord;
    skaalRace: IwbMainRecord;
    skaalRaceChild: IwbMainRecord;

	yaIndex: integer;  // File index of YA in load order
	yaFile: IwbFile;
    skyrimFile: IwbFile;

  // Global variables set when working on an NPC
	theNPC: IwbMainRecord;
	theNPCClass: string;
	theNPCClassType: IInterface;
	theNPCEditorID: string;
	theNPCHashSeed: string;
	theNPCRace: IwbMainRecord;
	theNPCRaceName: string;
	theNPCSex: integer;
	theNPCVoice: string;
	theNPCVoiceType: IwbMainRecord;
	tintLayerNextPosition: integer;
    theNPCAccentPreset: IwbElement;
    theNPCFurPreset: IwbElement;
    theNPCFurColor: string;
    theNPCIsTiger: boolean;
    theNPCIsFox: boolean;
    theNPCIsFennec: boolean;
    theNPCIsOld: boolean;
    theNPCRaceIndex: integer;

Procedure SetAffectedRaces();
begin
  // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  // RACES TO AFFECT
  // Comment out races to skip them
  //
  affectedRaces := TStringList.Create;
  affectedRaces.Add('BretonRace');
  affectedRaces.Add('BretonRaceChild');
  affectedRaces.Add('BretonRaceChildVampire');
  affectedRaces.Add('BretonRaceVampire');
  affectedRaces.Add('DA13AfflictedRace');
  affectedRaces.Add('DarkElfRace');
  affectedRaces.Add('DarkElfRaceVampire');
  affectedRaces.Add('DLC1NordRace');
  affectedRaces.Add('DLC2DremoraRace');
  affectedRaces.Add('DremoraRace');
  affectedRaces.Add('ElderRace');
  affectedRaces.Add('ElderRaceVampire');
  affectedRaces.Add('HighElfRace');
  affectedRaces.Add('HighElfRaceVampire');
  affectedRaces.Add('ImperialRace');
  affectedRaces.Add('ImperialRaceVampire');
  affectedRaces.Add('ImperialRaceChild');
  affectedRaces.Add('NordRace');
  affectedRaces.Add('NordRaceAstrid');
  affectedRaces.Add('NordRaceVampire');
  affectedRaces.Add('NordRaceChild');
  affectedRaces.Add('OrcRace');
  affectedRaces.Add('OrcRaceVampire');
  affectedRaces.Add('RedguardRace');
  affectedRaces.Add('RedguardRaceChild');
  affectedRaces.Add('RedguardRaceVampire');
  affectedRaces.Add('SnowElfRace');
  affectedRaces.Add('SnowElfRaceVampire');
  affectedRaces.Add('WoodElfRace');
  affectedRaces.Add('WoodElfRaceVampire');
  affectedRaces.Add('AAATeenRace');
  affectedRaces.Add('BSKWildElfRace');
  affectedRaces.Add('BSKSeaElfRace');
  affectedRaces.Add('BSKSnowElfRace');
  affectedRaces.Add('BSKRedguardElderRace');
  affectedRaces.Add('BSKBretonElderRace');
  affectedRaces.Add('AAEMNordViljaRace');
  affectedRaces.Add('BSKImperialElderRace');
  affectedRaces.Add('BSKReachmanElderRace');
  affectedRaces.Add('BretonRaceChildArnima');
  affectedRaces.Add('ReachmanRace');
  //
  // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
end;

// //==================================================
// // LogMessage
// // Importance: 0 = Minimal, 1 = Most useful, 2 = All
// Procedure LogMessage(importance: integer; txt: string);
// var
//     i: integer;
//     s: string;
// begin
//     s := '';
// 	if importance <= LOGLEVEL then begin
//         for i := 1 to logIndent do s := s + '.   ';
//         AddMessage(s + txt);
//     end;
// end;

//==================================================
Function NPCisTargetRace(theNPC: IInterface): boolean;
	// Determine whether the NPC is one of the desired target races
var race: string;
	i: integer;
begin
	race := EditorID(LinksTo(ElementByPath(theNPC, 'RNAM')));
	i := affectedRaces.IndexOf(race);
	result := (i >= 0);
end;

//==================================================
Function NPCisTargetSex(theNPC: IInterface): boolean;
var isFemale: boolean;
begin
    isFemale := SameText(GetElementEditValues(theNPC, 'ACBS\Flags\female'), '1');
	result := (isFemale and DO_FEMALE) or ((not isFemale) and DO_MALE);
end;

//===================================================
Function RealFormID(e: IInterface): Cardinal;
begin
	//result := FileFormIDtoLoadOrderFormID(GetFile(e), FixedFormID(e));
    Result := GetLoadOrderFormID(e)
end;

//===================================================
Function StrFormID(e: IwbMainRecord): string;
var
    targID: Cardinal;
begin
    targID := GetLoadOrderFormID(e);
    Result := IntToHex(targID shr 24, 2) + IntToHex((targID and $FFFFFF),6);
end;

//=================================================================
// Find the named record of the given type in the YA file and return
// its ID as a string.
//
// This is monumentally stupid, but when there are ESLs screwing up the load
// order and when there are enough mods that the high bit ($80000000) is
// set, usual methods of handling form IDs don't work. So we construct
// the form ID as a string and set it as an Edit value.
//
function GetYAAssetFormID(recordType: string; recordName: string): string;
var
    targ: IwbMainRecord;
begin
    targ := FindAsset(yaFile, recordType, recordName);
    Result := StrFormID(targ)
end;

function IsInList(theList: IwbContainer; target: string): boolean;
var
	found: boolean;
	i: integer;
	n: string;
begin
	found := false;
	if theList <> nil then begin
		for i := 0 to ElementCount(theList)-1 do begin
			n := EditorID(LinksTo(ElementByIndex(theList, i)));
			found := SameText(target, n);
			if found then break;
		end;
	end;
	result := found;
end;

//============================================================
// Determine if NPC is in faction
Function IsInFaction(theFaction: string): boolean;
var
	factionList: IInterface;
	found: boolean;
	i: integer;
	factionName: string;
begin
    found := ElementListContains(theNPC, 'Factions', theFaction);
	factionList := ElementByPath(theNPC, 'Factions');
	found := false;
	if factionList <> nil then begin
		for i := 0 to ElementCount(factionList)-1 do begin
			factionName := EditorID(LinksTo(ElementByPath(
                    ElementByIndex(factionList, i), 'Faction')));
			found := SameText(theFaction, factionName);
			if found then break;
		end;
	end;
	Log(6, Name(theNPC) + 'is in faction ' + theFaction + ': ' + BoolToStr(found));
	result := found;
end;

//======================================================
// Determine whether theNPC should be treated as a reachman
Function isNamedReachman(): boolean;
Begin
    result := Pos(theNPCEditorID,
        'Ainethach Belchimac Bothela Braig Cedran Cosnach Donnel Duach Eltrys EltrysDead Enmon Ennoc Eola dunNchuandErj Hathrasil Imedhnain Madanach Odvan Omluag Perth Rhiada Rondach dunNchuandStromm Tynan Uaile Uraccen Voada MS01Weylin Willem')
        > 0;
End;

// Determine whether the NPC is a forsworn
Function isForsworn(): boolean;
Begin
    Log(6, '<isForsworn');
    Result := false;
    if ContainsText(theNPCEditorID, 'Forsworn') then Result := true
    else if ContainsText(theNPCEditorID, 'Witchman') then Result := true
    else if IsInFaction('ForswornFaction') then Result := true
    else if IsInFaction('ArnimaWitchmen') then Result := true
    else if IsInFaction('ArnimawitchmandialogueFaction') then Result := true; // Beyond Reach
    Log(6, '>isForsworn: ' + BoolToStr(result));
End;

//======================================================
// Determine whether theNPC should be treated as a Skaal
Function isNamedSkaal(): boolean;
Begin
    result := false;
End;

// Determine whether the NPC is a Skaal
Function isSkaalFaction(): boolean;
Var
    outfit: string;
Begin
    Log(5, '<isSkaalFaction');
    result := IsInFaction('DLC2SkaalVillageCitizenFaction')
        or IsInFaction('DLC2ThirskNordFaction');
    if not result then begin
        outfit := EditorID(LinksTo(ElementByPath(theNPC, 'DOFT')));
        if Pos('Skaal', outfit) > 0 then result := true;
    end;
    Log(5, '>isSkaalFaction: ' + BoolToStr(result));
End;

//======================================================================
// Set the global variables to reflect the current NPC
// NPC record is already copied to override file
// Uses theNPC, theNPCEditorID, theNPCSex
//
Procedure SetupNPC();
var
  sexName: string;
  //raceIndex: integer;
  npcRace, npcVoice, npcFace, npcHair, rn: string;
  targRace: IwbMainRecord;
  targRaceID: Cardinal;
  newRace: IwbMainRecord;
Begin
	Log(3, '<SetupNPC: ' + Name(theNPC));

	theNPCRace := WinningOverride(LinksTo(ElementByPath(theNPC, 'RNAM')));
    theNPCRaceName := EditorID(theNPCRace);
    theNPCRaceIndex := masterRaceList.IndexOf(theNPCRaceName);

    // Remove skin tints before changing race
    RemoveSkinTints;

    newRace := nil;

    // Check whether this is an old person
    theNPCIsOld := false;
    npcRace := EditorID(LinksTo(ElementByPath(theNPC, 'RNAM')));
    npcVoice := GetElementEditValues(theNPC, 'VTCK');
    npcFace := GetElementEditValues(theNPC, 'FTST');
    npcHair := GetElementEditValues(theNPC, 'HCLF');
    Log(4, 'Race is: ' + npcRace);

    if Pos('Elder', npcRace) > 0 then begin
        Log(4, 'NPC is old, race: ' + npcRace);
        theNPCIsOld := true;
    end
    else if Pos('Old', npcVoice) > 0 then begin
        Log(4, 'NPC is old, voice is: ' + npcVoice);
        theNPCIsOld := true;
    end
    else if (Pos('Old', npcFace) > 0) or (Pos('50', npcFace) > 0) then begin
        Log(4, 'NPC is old, face is: ' + npcFace);
        theNPCIsOld := true;
    end
    else if (Pos('Grey', npcHair) > 0) or (Pos('White', npcHair) > 0) then begin
        Log(4, 'NPC is old, hair is: ' + npcHair);
        theNPCIsOld := true;
    end
    else if SameText(theNPCEditorID, 'KodlakWhitemane') then theNPCIsOld := true
    else if ContainsText(theNPCEditorID, 'Tullius') then theNPCIsOld := true;

	// Set the hash seed to the editor ID or something consistent if there's more than one
	// record for the same NPC
	if ContainsText(theNPCEditorID, 'Nazir') then
		theNPCHashSeed := 'Nazir'
	else if ContainsText(theNPCEditorID, 'Kodlak') then
		theNPCHashSeed := 'KodlakWhitemane'
	else if ContainsText(theNPCEditorID, 'Ulfric') then
		theNPCHashSeed := 'Ulfric'
	else if ContainsText(theNPCEditorID, 'Tullius') then
		theNPCHashSeed := 'GeneralTullius'
	else if ContainsText(theNPCEditorID, 'Astrid') then
		theNPCHashSeed := 'Astrid'
	else if ContainsText(theNPCEditorID, 'Cicero') then
		theNPCHashSeed := 'Cicero'
	else if ContainsText(theNPCEditorID, 'Festus') then
		theNPCHashSeed := 'FestusKrex'
	else if ContainsText(theNPCEditorID, 'Curwe') then
		theNPCHashSeed := 'Curwe'
	else if ContainsText(theNPCEditorID, 'Malkus') then
		theNPCHashSeed := 'DLC1Malkus'
	else if ContainsText(theNPCEditorID, 'Eltrys') then
		theNPCHashSeed := 'Eltrys'
	else if ContainsText(theNPCEditorID, 'Gabriella') then
		theNPCHashSeed := 'Gabriella'
	else if ContainsText(theNPCEditorID, 'Susanna') then
		theNPCHashSeed := 'Susanna'
	else if ContainsText(theNPCEditorID, 'Tova') then
		theNPCHashSeed := 'Tova'
	else if ContainsText(theNPCEditorID, 'VantusLoreius') then
		theNPCHashSeed := 'VantusLoreius'
	else if ContainsText(theNPCEditorID, 'Veezara') then
		theNPCHashSeed := 'Veezara'
	else if ContainsText(theNPCEditorID, 'TitusMede') then
		theNPCHashSeed := 'TitusMede'
	else if ContainsText(theNPCEditorID, 'Sogrlaf') then
		theNPCHashSeed := 'Sogrlaf'
	else if ContainsText(theNPCEditorID, 'Gabania_Daughter3DNPC') then
		theNPCHashSeed := 'Gabania_Daughter'
	else if ContainsText(theNPCEditorID, 'VigilantTolan') then
		theNPCHashSeed := 'DLC1VigilantTolan'
	else if ContainsText(theNPCEditorID, 'Arniel') then
		theNPCHashSeed := 'ArnielGane'
    else if ContainsText(theNPCEditorID, 'Pelagius') then
        theNPCHashSeed := 'Pelagius'

	else
		theNPCHashSeed := theNPCEditorID;

	// Set the race if it needs to be changed
	if ContainsText(theNPCEditorID, 'Nocturnal') then newRace := darkElfRace
	else if ContainsText(theNPCEditorID, 'MinetteVinius') then newRace := imperialRaceChild
	else if ContainsText(theNPCEditorID, 'JouaneManette') or
			ContainsText(theNPCEditorID, 'CYRGalarynn') then newRace := bretonRace
    else if SameText(theNPCEditorID, 'AstridEnd') then newRace := nordRace
	else if ContainsText(theNPCEditorID, 'FestusKrex') or
            ContainsText(theNPCEditorID, 'CresciusCaerellius') or
			ContainsText(theNPCEditorID, 'Rogatus') or
			ContainsText(theNPCEditorID, 'CYRErlusRisula') or
			ContainsText(theNPCEditorID, 'CYRGergusMalumea') or
			ContainsText(theNPCEditorID, 'CYRCindaiaMalumea') or
			ContainsText(theNPCEditorID, 'CYRAstorAstentius') then newRace := imperialRace
    else if ContainsText(npcRace, 'BretonRace')
            and (isNamedReachman() or IsForsworn()) then
        newRace := reachmanRace
    else if slNordRaces.IndexOf(npcRace) >= 0 then
        if isNamedSkaal() or isSkaalFaction() then begin
            if ContainsText(npcRace, 'Child') then
                newRace := skaalRaceChild
            else
                newRace := skaalRace;
        end;

    if Assigned(newRace) then begin
        Log(4, 'Converting ' + npcRace + ' to ' + Name(newRace));
        if not SameText(theNPCEditorID, 'AstridEnd') then
            // AstridEnd keeps her race; everyone else is actually changed
            SetElementRef(ElementByPath(theNPC, 'RNAM'), newRace);
        theNPCRace := newRace;
        theNPCRaceName := EditorID(theNPCRace);
        theNPCRaceIndex := masterRaceList.IndexOf(theNPCRaceName);
        Log(4, 'Set race for ' + Name(theNPC) + ' to ' + theNPCRaceName);
    end;


    // Set npc gender & voice
    if GetElementNativeValues(theNPC, 'ACBS\Flags\female') then theNPCSex := 1
    else theNPCSex := 0;
	theNPCVoiceType := WinningOverride(LinksTo(ElementByPath(theNPC, 'VTCK')));
	theNPCVoice := EditorID(theNPCVoiceType);
	theNPCClassType := WinningOverride(LinksTo(ElementByPath(theNPC, 'CNAM')));
	theNPCClass := EditorID(theNPCClassType);

    theNPCIsFox := (StartsText('Breton', theNPCRaceName)
        or (theNPCRaceName = 'DA13AfflictedRace'));
    theNPCisFennec := false;
    theNPCIsTiger := StartsText('WoodElf', theNPCRaceName);

	Log(3, '>SetupNPC');
End;

//=========================================================================
// CopyTintLayer: Copies values from a source tint layer to another
function CopyTintLayer(source: IInterface; e: IInterface; index: integer): integer;
begin
  senv(e, 'TINI', index);
  Add(e, 'TINC', True);
  Add(e, 'TINV', True);
  senv(e, 'TINV', genv(source, 'TINV'));
  senv(e, 'TINC\[0]', genv(source, 'TINC\[0]'));
  senv(e, 'TINC\[1]', genv(source, 'TINC\[1]'));
  senv(e, 'TINC\[2]', genv(source, 'TINC\[2]'));
end;

//=========================================================================
// Calc QNAM from tint layer
function TintToLighting(val: integer; influence: double): double;
begin
	result := Max(((val-128.0)*influence + 127.0), 0)
end;

//===================================================
// Sets the color of the given tint layer
//
Procedure SetColor(theLayer: IInterface; theColor: IInterface; alpha: float; isSkinTone: boolean);
var
	r, g, b: double;
begin
	Log(5, 'SetColor ' + geev(theLayer, 'TINI') + ' ' + BoolToStr(isSkinTone));

	Add(theLayer, 'TINC', true);

	r := genv(theColor, 'CNAM\Red');
	g := genv(theColor, 'CNAM\Green');
	b := genv(theColor, 'CNAM\Blue');

	senv(theLayer, 'TINC\[0]', r);
	senv(theLayer, 'TINC\[1]', g);
	senv(theLayer, 'TINC\[2]', b);

	if isSkinTone then begin
		Add(theNPC, 'QNAM', True);
		seev(theNPC, 'QNAM\Red', FloatToStr(TintToLighting(r, alpha)));
		seev(theNPC, 'QNAM\Green', FloatToStr(TintToLighting(g, alpha)));
		seev(theNPC, 'QNAM\Blue', FloatToStr(TintToLighting(b, alpha)));
	end;
end;

//===================================================
// Applies the given preset to the given NPC skin layer
// Returns the color form associated with the preset
// skinLayer: tint layer in the NPC record to set
// tintMask: tint layer in the race record
// preset: preset associated with the tint mask layer to set
// isSkinTone: Set to indicate this is the base skin color, so QNAM should
//      be set also
// Result: The color that was set, as a color record
Function ApplySkinTintPreset(skinLayer: IwbElement; 
							 tintMask: IwbElement; 
							 preset: IwbElement; 
							 isSkinTone: boolean): IwbMainRecord;
var
	tintLayers: IInterface;
	theColor: IInterface;
	TINV: double;
	TIRS: integer;
	r, g, b: double;
Begin	
	theColor := WinningOverride(LinksTo(ElementByPath(preset, 'TINC')));
	TINV := genv(preset, 'TINV');
	TIRS := genv(preset, 'TIRS');

	senv(skinLayer, 'TINI', genv(tintMask, 'Tint Layer\Texture\TINI'));
	Add(skinLayer, 'TINV', true);
	seev(skinLayer, 'TINV', geev(preset, 'TINV'));
	Add(skinLayer, 'TIAS', true);
	senv(skinLayer, 'TIAS', genv(preset, 'TIRS'));

	SetColor(skinLayer, theColor, TINV, isSkinTone);
	
	Result := theColor;
end;

//=========================================================================
// Given a tint mask preset on the Skin Tone layer, return the preset.
// Returns a random preset if not found
//
Function FindRaceSkinPresetByTIRS(theTIRS: integer): IInterface;
var
	raceBaseLayer, raceBasePresets: IInterface;
	i: integer;
Begin
	Log(5, '<FindRaceSkinPresetByTIRS ' + IntToStr(theTIRS));

	raceBaseLayer := raceInfo[theNPCRaceIndex].tints[theNPCSex, TL_SKIN_TONE];
	raceBasePresets := ElementByPath(raceBaseLayer, 'Presets');

	for i := 0 to ElementCount(raceBasePresets)-1 do begin
		// LogMessage(3, 'comparing ' + IntToStr(theTIRS) + ' to '
				  // + geev(ElementByIndex(raceBasePresets, i), 'TIRS'));
		if theTIRS = genv(ElementByIndex(raceBasePresets, i), 'TIRS') then begin
			result := ElementByIndex(raceBasePresets, i);
			exit;
		end;
	end;
	
	Err(1, 'FindRaceSkinPresetByTIRS did not find ' + IntToStr(theTIRS));
	result := ElementByIndex(raceBasePresets, 
        Hash(theNPCHashSeed, 21, ElementCount(raceBasePresets)));
	Log(5, '>FindRaceSkinPresetByTIRS');
End;

//===================================================
// Add a tint layer to the given NPC.
// tintIndex = TINI index of tint layer mask on the race
// color = color record to use on this layer
// tintLayerNextPosition determines where the new layer is inserted
// increments tintLayerNextPosition
//
Procedure AddTintLayer(tintIndex: integer; color: IwbMainRecord; 
    alpha: float; presetIndex: integer);
var
	tintLayers, layer: IInterface;
begin
	Log(5, '<AddTintLayer ' + theNPCEditorID + ' ' + IntToStr(tintIndex) + ' (' +
            EditorID(color) +
		    ') at ' + IntToStr(tintLayerNextPosition));

    if alpha > 0 then begin
        if not ElementExists(theNPC, 'Tint Layers') then begin
            Add(theNPC, 'Tint Layers', true);
            tintLayers := ElementByPath(theNPC, 'Tint Layers');
            Log(5, 'Tint layers did not exist, created new, count=' + IntToStr(ElementCount(tintLayers)));
            layer := ElementByIndex(tintLayers, 0);
        end
        else begin
            tintLayers := ElementByPath(theNPC, 'Tint Layers');
            Log(5, 'Tint layers exist, count=' + IntToStr(ElementCount(tintLayers))
                + ', next pos=' + IntToStr(tintLayerNextPosition));
            layer := ElementAssign(tintLayers, tintLayerNextPosition, nil, false);
        end;

        Add(layer, 'TINI', true);
        SetElementNativeValues(layer, 'TINI', tintIndex);
        Add(layer, 'TINV', true);
        SetElementEditValues(layer, 'TINV', FloatToStr(alpha));
        Add(layer, 'TIAS', true);
        SetElementNativeValues(layer, 'TIAS', presetIndex);

        Log(4, 'adding TINC for ' + Name(color));
        //Log(4, 'calling SetColor, tintLayerNextPosition=' + IntToStr(tintLayerNextPosition));
        SetColor(layer, color, alpha, (tintLayerNextPosition=0));
        inc(tintLayerNextPosition);
    end;
    Log(5, '>AddTintLayer');
end;

//==============================================================
// Add a tint layer to the NPC by given a preset from the race
Procedure AddTintLayerPreset(tintIndex: integer; thePreset: IwbElement);
begin
    if Assigned(thePreset) then 
        AddTintLayer(tintIndex, 
                    LinksTo(ElementByPath(thePreset, 'TINC')),
                    GetElementNativeValues(thePreset, 'TINV'),
                    GetElementNativeValues(thePreset, 'TIRS'))
    else
        Err('AddTintLayerPreset: Preset not valid. NPC = ' + Name(theNPC)
            + ', tint index = ' + IntToStr(tintIndex) +
            ', preset = ' + PathName(thePreset));
end; 

//==============================================================
// Add a tint layer to the NPC by the filename of the mask
Procedure AddTintLayerByName(filename: string; colorname: string);
var
    targetTintIndex: integer;
    targetPreset: IwbElement;
begin
    Log(5, '<AddTintLayerByName: ' + filename + ', ' + colorname);
    targetTintIndex := FindTintLayerByFilename(theNPCRaceIndex, theNPCSex, filename);
    if targetTintIndex >= 0 then begin
        targetPreset := ChoosePresetByColor(theNPCRaceIndex, theNPCSex, colorname, targetTintIndex);
        if Assigned(targetPreset) then 
            AddTintLayerPreset(GetTINIByTintIndex(theNPCRaceIndex, theNPCSex, targetTintIndex), 
                               targetPreset)
        else
            Err('Preset not found for color');
    end
    else
        Err('Tint layer not found for file');
    Log(5, '>AddTintLayerByName');
end; 

// Add tint layer of pseudo-random color. 
// If this is the base color, record it for later. 
// If it's an accent layer, use the accent color if any; otherwise choose one
// and set it as the accent color.
Procedure AddRandomTint(hashVal, tintLayer: integer);
var
    chosenPreset: IwbElement;
begin
    Log(4, '<AddRandomTint ' + tintLayersList[tintLayer] + '/' + IntToStr(tintLayer));
    if tintLayer = TL_SKIN_TONE then begin
        chosenPreset := PickRandomTintPreset(
                theNPCHashSeed, hashVal,
                theNPCRaceIndex, theNPCSex, tintLayer, 0);
        theNPCFurPreset := chosenPreset;
        theNPCFurColor := EditorID(LinksTo(ElementByPath(chosenPreset, 'TINC')));
    end
    else begin // Doing an accent layer 
        {if Assigned(theNPCAccentPreset) then begin
            // Use existing accent layer
            chosenPreset := theNPCAccentPreset;
            Log(4, 'Using existing accent color ' + PathName(chosenPreset));
        end
        else} begin
            // Choose an accent layer for the NPC
            chosenPreset := PickRandomTintPreset(
                    theNPCHashSeed, hashVal,
                    theNPCRaceIndex, theNPCSex, tintLayer, 1);
            theNPCAccentPreset := chosenPreset;
            Log(4, 'Choosing new accent color ' + PathName(chosenPreset));
        end;
    end;

    if Assigned(chosenPreset) then 
        AddTintLayer(GetRaceTintTINI(theNPCRaceIndex, theNPCSex, tintLayer),
                    LinksTo(ElementByPath(chosenPreset, 'TINC')),
                    GetElementNativeValues(chosenPreset, 'TINV'),
                    GetElementNativevalues(chosenPreset, 'TIRS'));
    Log(4, '>AddRandomTint');
end;

// Add mask of random color. The color is set as the accent color.
Procedure AddRandomMask(hashVal: integer; strength: float);
var
    chosenPreset: IwbElement;
    m: integer;
begin
    m := TL_MASK + HashInt(theNPCHashSeed, hashVal,
        0, GetRaceMaskCount(theNPCRaceIndex, theNPCSex));
    chosenPreset := PickRandomTintPreset(
            theNPCHashSeed, hashVal+10, theNPCRaceIndex, theNPCSex, m, 1);
    AddTintLayer(
        GetRaceTintTINI(theNPCRaceIndex, theNPCSex, m),
        LinksTo(ElementByPath(chosenPreset, 'TINC')),
        GetElementNativeValues(chosenPreset, 'TINV') * strength,
        GetElementNativevalues(chosenPreset, 'TIRS'));
end;

// Add muzzle accent of random color. The color is set as accent color.
Procedure AddRandomMuzzle(hashVal: integer);
var
    chosenPreset: IwbElement;
    m: integer;
begin
    m := TL_MUZZLE + HashInt(theNPCHashSeed, hashVal,
        0, GetRaceMuzzleCount(theNPCRaceIndex, theNPCSex));
    chosenPreset := PickRandomTintPreset(
            theNPCHashSeed, hashVal+10, theNPCRaceIndex, theNPCSex, m, 1);
    AddTintLayer(
        GetRaceTintTINI(theNPCRaceIndex, theNPCSex, m),
        LinksTo(ElementByPath(chosenPreset, 'TINC')),
        GetElementNativeValues(chosenPreset, 'TINV'),
        GetElementNativevalues(chosenPreset, 'TIRS'));
end;

//===================================================
// SetNPCSkinLayers: Sets skin tint layers, considering race-specific tint
// variants and also special NPCs.
// "theNPC" set to the NPC to work on
//
Procedure SetNPCSkinLayers();
var
    p: IwbElement;
    m: intenger;
    mp: IwbElement;
    alpha: float;
    hasMask, hasMuzzle: boolean;
    hasChin: integer;
    earChance: integer;
    maskStrength: float;
begin
	Log(5, '<SetNPCSkinLayers ' + theNPCEditorID);

    maskStrength := 1.0;

	//RemoveSkinTints;

    AddRandomTint(1042, TL_SKIN_TONE);

    if theNPCIsFox then begin
        hasMask := true;
        hasMuzzle := true;
        earChance := 80;
        theNPCIsFennec := EndsText('Tan', theNPCFurColor);
    end
    else if StartsText('Reachman', theNPCRaceName)
        or StartsText('Redguard', theNPCRaceName) then begin
        hasMask := true;
        hasMuzzle := HashInt(theNPCHashSeed, 1092, 0, 2) = 0;
    end
    else if (slNordRaces.IndexOf(theNPCRaceName) >= 0) 
        or StartsText('Imperial', theNPCRaceName) then begin
        hasMask := HashInt(theNPCHashSeed, 1094, 0, 8) = 0;
        hasMuzzle := HashInt(theNPCHashSeed, 1095, 0, 3) = 0;
        earChance := 40;
    end
    else if theNPCIsTiger then begin
        hasMask := true;
    end
    else begin
        hasMask := false;
        hasMuzzle := false;
        earChance := 0;
    end;

    if hasMuzzle and (not hasMask) then 
        hasChin := HashInt(theNPCHashSeed, 1104, 0, 8) > 0
    else
        hasChin := 0;

    if hasMask then AddRandomMask(1102, maskStrength);
    if hasMuzzle then AddRandomMuzzle(1104);

    // Some chance of chin, small or large
    case (hasChin) of
        0: AddRandomTint(1068, TL_CHIN);
        1: AddRandomTint(1069, TL_NECK);
    end;

    // Some chance of darker ears
    if HashInt(theNPCHashSeed, 1004, 0, 100) < earChance then
        AddRandomTint(1051, TL_EAR);

    // Some chance of brows
    m := HashInt(theNPCHashSeed, 1054, 0, 6);
    if m = 0 then AddRandomTint(1055, TL_EYESOCKET_UPPER)
    else if m = 1 then AddRandomTint(1056, TL_EYELINER);

    if theNPCIsOld then AddRandomTint(1059, TL_OLD);

    // Noses override fur so they go last
    AddRandomTint(1062, TL_NOSE);

	Log(5, '>SetNPCSkinLayers ' + theNPCEditorID);
end;

//===================================================
// Remove all skin tints (but not warpaint or base layer) from the given NPCs.
// Furry tint layers are too different from human for the human layers to be
// at all useful.
// Uses theNPC, theNPCEditorID, theNPCRaceIndex, theNPCSex
// Sets theNPCFurPreset, theNPCAccentPreset, tintLayerNextPosition
//
Procedure RemoveSkinTints();
var
	tintLayers: IwbElement;
	thisLayer: IwbElement;
	i: integer;
	layerTINI: integer;
	layerTINV: float;
	layerMaskIndex: integer;
    foundLayer: integer;
    foundType: string;
begin
	Log(5, '<RemoveSkinTints ' + theNPCEditorID);

	theNPCFurPreset := Nil;
    theNPCAccentPreset := nil;

	tintLayerNextPosition := 0;

    // Walk the NPC's tint layers (reverse order because we remove them as we go)
	tintLayers := ElementByPath(theNPC, 'Tint Layers');
	for i := ElementCount(tintlayers)-1 downto 0 do begin
		thisLayer := ElementByIndex(tintLayers, i);
		layerTINI := GetElementNativeValues(thisLayer, 'TINI');
        layerTINV := GetElementNativeValues(thisLayer, 'TINV');
		if layerTINI = 0 then begin
			Log(5, 'Removing invalid layer with TINI=0 at ' + IntToStr(i));
			RemoveByIndex(tintLayers, i, true) 
		end
        else if layerTINV = 0 then begin
			Log(5, 'Removing invisible layer with TINV=0 at ' + IntToStr(i));
			RemoveByIndex(tintLayers, i, true) 
        end
		else begin
			foundLayer := FindRaceTintLayerByTINI(
                theNPCRaceIndex, theNPCSex, layerTINI);
			if (i > 0) and (foundLayer < 0) then begin
                // Not found as a skin layer, so keep it. Might be paint or tatto.
                // Except index 0 is always skin tone, so get rid of that
				Log(5, 'Keeping unknown NPC tint layer ' + IntToStr(layerTINI) + ' at ' + IntToStr(i));
				// RemoveByIndex(tintLayers, i, true);
			end
			else if foundLayer > 0 then begin
                // Paint, dirt, tattoos are kept; all other tints removed.
                foundType := GetRaceTintMaskType(theNPCRaceIndex, theNPCSex, foundLayer);
				Log(5, 'Found layer ' + IntToStr(layerTINI) + ': ' + foundType);
                if (not SameText(foundType, 'Paint')) then
                if (not SameText(foundType, 'Dirt')) then
                if (length(foundType) > 0 {boethiah/blackblood have no mask type}) then
				begin
					Log(5, 'Removing layer ' + foundType);
					RemoveByIndex(tintLayers, i, true);
				end;
			end;
		end;
	end;

	Log(5, '>RemoveSkinTints ' + theNPCEditorID);
end;

//================================================
// If this is one of the predefined NPCs, set the values
//
Function SetPredefinedNPC(): boolean;
var
	predefinedIndex: integer;
	simpleName: string;
	isPredefined: boolean;
Begin
	isPredefined := false;

	if SameText(theNPCEditorID, 'Siddgeir') then begin 
        Log(5, 'Special preset for ' + theNPCEditorID);
        AddTintLayerPreset( 
            GetRaceTintTINI(theNPCRaceIndex, theNPCSex, TL_SKIN_TONE),
            ChoosePresetByColor(theNPCRaceIndex, theNPCSex, '00LykaiosBlack', TL_SKIN_TONE));
        AddTintLayerByName('StripeLargeMuzzle', '00LykaiosWhite');
        AddTintLayerPreset( 
            GetRaceTintTINI(theNPCRaceIndex, theNPCSex, TL_NECK),
            ChoosePresetByColor(theNPCRaceIndex, theNPCSex, '00LykaiosWhite', TL_NECK));
        AddTintLayerPreset( 
            GetRaceTintTINI(theNPCRaceIndex, theNPCSex, TL_EYELINER),
            ChoosePresetByColor(theNPCRaceIndex, theNPCSex, '00LykaiosTan', TL_EYELINER));
        AddTintLayerPreset( 
            GetRaceTintTINI(theNPCRaceIndex, theNPCSex, TL_CHEEK_COLOR_LOWER),
            ChoosePresetByColor(theNPCRaceIndex, theNPCSex, '00LykaiosTan', TL_CHEEK_COLOR_LOWER));
		isPredefined := true;
	end
	else if ContainsText(theNPCEditorID, 'Kodlak') then begin
        Log(5, 'Special preset for ' + theNPCEditorID);
        AddTintLayerPreset( 
            GetRaceTintTINI(theNPCRaceIndex, theNPCSex, TL_SKIN_TONE),
            ChoosePresetByColor(theNPCRaceIndex, theNPCSex, '00LykaiosWhite', TL_SKIN_TONE));
        AddTintLayerPreset( 
            GetRaceTintTINI(theNPCRaceIndex, theNPCSex, TL_CHEEK_COLOR_LOWER),
            ChoosePresetByColor(theNPCRaceIndex, theNPCSex, '00LykaiosBlack', TL_CHEEK_COLOR_LOWER));
		isPredefined := true;
	end
	else if SameText(theNPCEditorID, 'FalkFirebeard') then begin 
        Log(5, 'Special preset for ' + theNPCEditorID);
        AddTintLayerPreset( 
            GetRaceTintTINI(theNPCRaceIndex, theNPCSex, TL_SKIN_TONE),
            ChoosePresetByColor(theNPCRaceIndex, theNPCSex, '00LykaiosRedDark', TL_SKIN_TONE));
        AddTintLayerByName('NoseStripeMuzzle', '00LykaiosBlack');
        AddTintLayerPreset( 
            GetRaceTintTINI(theNPCRaceIndex, theNPCSex, TL_CHIN),
            ChoosePresetByColor(theNPCRaceIndex, theNPCSex, '00LykaiosBlack', TL_CHIN));
        AddTintLayerPreset( 
            GetRaceTintTINI(theNPCRaceIndex, theNPCSex, TL_EYELINER),
            ChoosePresetByColor(theNPCRaceIndex, theNPCSex, '00LykaiosBlack', TL_EYELINER));
		isPredefined := true;
	end
	else if SameText(theNPCEditorID, 'BalgruuftheGreater') then begin 
        Log(5, 'Special preset for ' + theNPCEditorID);
        AddTintLayerPreset( 
            GetRaceTintTINI(theNPCRaceIndex, theNPCSex, TL_SKIN_TONE),
            ChoosePresetByColor(theNPCRaceIndex, theNPCSex, '00LykaiosTan', TL_SKIN_TONE));
        AddTintLayerPreset( 
            GetRaceTintTINI(theNPCRaceIndex, theNPCSex, TL_NOSE),
            ChoosePresetByColor(theNPCRaceIndex, theNPCSex, 'black', TL_NOSE));
		isPredefined := true;
	end
	else if ContainsText(theNPCEditorID, 'Ulfric') then begin 
        Log(5, 'Special preset for ' + theNPCEditorID);
        AddTintLayerPreset( 
            GetRaceTintTINI(theNPCRaceIndex, theNPCSex, TL_SKIN_TONE),
            ChoosePresetByColor(theNPCRaceIndex, theNPCSex, '00LykaiosBlack', TL_SKIN_TONE));
        AddTintLayerByName('FullMask', '00LykaiosWhite');
        AddTintLayerPreset( 
            GetRaceTintTINI(theNPCRaceIndex, theNPCSex, TL_EYELINER),
            ChoosePresetByColor(theNPCRaceIndex, theNPCSex, '00LykaiosWhite', TL_EYELINER));
        AddTintLayerPreset( 
            GetRaceTintTINI(theNPCRaceIndex, theNPCSex, TL_NOSE),
            ChoosePresetByColor(theNPCRaceIndex, theNPCSex, 'black', TL_NOSE));
		isPredefined := true;
	end
	else if ContainsText(theNPCEditorID, 'Aela') then begin 
        Log(5, 'Special preset for ' + theNPCEditorID);
        AddTintLayerPreset( 
            GetRaceTintTINI(theNPCRaceIndex, theNPCSex, TL_SKIN_TONE),
            ChoosePresetByColor(theNPCRaceIndex, theNPCSex, '00LykaiosFoxRed', TL_SKIN_TONE));
        AddTintLayerByName('FemFullCheeksMask', '00LykaiosWhite');
        AddTintLayerPreset( 
            GetRaceTintTINI(theNPCRaceIndex, theNPCSex, TL_NOSE),
            ChoosePresetByColor(theNPCRaceIndex, theNPCSex, 'black', TL_NOSE));
		isPredefined := true;
	end
	else if Pos('Nocturnal', theNPCEditorID) > 0 then begin 
        Log(5, 'Special preset for ' + theNPCEditorID);
        AddTintLayerPreset( 
            GetRaceTintTINI(theNPCRaceIndex, theNPCSex, TL_SKIN_TONE),
            ChoosePresetByColor(theNPCRaceIndex, theNPCSex, 'DarkElfSkin02', TL_SKIN_TONE));
        SetHeadPart('Eyes', 'FemaleEyesDremora');
	end;
		
	Result := isPredefined;
End;

//===================================================
// ReshapeHead: set NPC face morphs to reduce extreme facial deformation.
// Fox jaws and cheeks keep the full deformation because those set the
// ruff size.
Procedure ReshapeHead();
var
	j: integer;
	r: double;
	original: IwbMainRecord;
	//thePath: string;
    faceMorphs, targFaceMorphs: IwbContainer;
    m: IwbElement;
begin
    Log(2, '<ReshapeHead');
	original := MasterOrSelf(theNPC);
	
	Log(2, 'ReshapeHead Base record ' + Name(original));
	// set theNPC face morphs to half the original to reduce extreme facial deformation
    faceMorphs := ElementByPath(original, 'NAM9');
    targFaceMorphs := ElementByPath(theNPC, 'NAM9');

	for j := 0 to ElementCount(faceMorphs) - 2 do begin
        // Only change the morph if it's not a fox or tiger jaw
        if not ((theNPCIsFox or theNPCIsTiger) and j >= 2 and j <= 6) then begin
            if theNPCIsFennec and (j = 9) then
                // Fennec get big ears
                r := HashVal(theNPCHashSeed, 998, 0.7, 1.0);
            else
                // All other morph are halved
                r := GetNativeValue(ElementByIndex(faceMorphs, j)) * 0.5;

            m := ElementByIndex(faceMorphs, j);
            //Log(2, 'Fixing morph at ' + PathName(m) + ': ' + FloatToStr(r));
            //LogMessage(2, 'New value: ' + FloatToStr(r));
            //RemoveElement(ElementByPath(theNPC, 'NAM9'), j);
            SetNativeValue(ElementByIndex(targFaceMorphs, j), r);
        end;
	end;
    Log(2, '>ReshapeHead');
end;

//===================================================
// Fix scars to match the head mesh
//
Procedure FixScars;
var
	npcHeadParts: IwbElement;
    theHP: IwbMainRecord;
    theHPtype: string;
    newScarIndex: integer;
    newScar: IwbMainRecord;
    npcHPIndex: integer;
begin
    Log(3, '<Fixing scars: ' + IntToStr(theNPCRaceIndex) + 
        ' sex=' + IntToStr(theNPCSex) +
        ' HP=' + IntToStr(HEADPART_SCAR));
    npcHeadParts := ElementByPath(theNPC, 'Head Parts');
    if Assigned(npcHeadParts)
        and (GetRaceHeadpartCount(theNPCRaceIndex, theNPCSex, HEADPART_SCAR) > 0) 
    then begin
        // NPC has scars and we have race-specific scars
        for npcHPIndex := 0 to ElementCount(npcHeadParts)-1 do begin
            theHP := LinksTo(ElementByIndex(npcHeadParts, npcHPIndex));
            theHPtype := GetElementEditValues(theHP, 'PNAM');
            if theHPType = 'Scar' then begin
                if not HeadpartValidForRace(theHP, theNPCRaceIndex, theNPCSex, HEADPART_SCAR) then begin
                    Log(3, 'NPC scar invalid for race, substituting furry scar ' +
                        EditorID(theHP));
                    newScarIndex := Hash(EditorID(theHP),
                        1978,
                        GetRaceHeadpartCount(theNPCRaceIndex, theNPCSex, HEADPART_SCAR)-1);
                    newScar := GetRaceHeadpart(theNPCRaceIndex, theNPCSex, 
                        HEADPART_SCAR, newScarIndex);
                    AssignElementRef(ElementByIndex(npcHeadParts, npcHPIndex), newScar);
                    Log(4, 'Assigned scar ' + Name(newScar));
                end;
            end;
        end;
    end;

    Log(3, '>Fixing scars');
End;
//=========================================================================
// OkButtonControl: Disables the OK button if invalid values entered
procedure OkButtonControl;
var
  enable: boolean;
begin
    enable := true;
    try
    if (StrToInt(StringReplace(ed01.Text, '%', '', [rfReplaceAll])) > 100) then enable := false;
    if (StrToInt(StringReplace(ed02.Text, '%', '', [rfReplaceAll])) > 100) then enable := false;
    except on Exception do begin
        enable := false;
        btnOk.Enabled := false;
        end;
    end;

  btnOk.Enabled := enable;
end;

//=========================================================================
// OptionsForm: The main options form
procedure OptionsForm;
var
    i: integer;
    s: string;
begin
	cancel := false;

    if not cancel then begin
        s := TARGETFILE;
        if length(s) > 0 then begin
            for i := 1 to FileCount-1 do begin
                if SameText(GetFileName(FileByIndex(i)), s) then begin
                    npcfile := FileByIndex(i);
                    break;
                end;
            end;
            if not Assigned(npcfile) then begin
                Log(0, 'Creating file ' + s);
                npcfile := AddNewFileName(s);
            end;
        end;


        if not Assigned(npcfile) then begin
            npcfile := FileSelect('Choose the file you want to use for NPC overrides');
            if not Assigned(npcfile) then begin
                LogMessage(0, 'User cancelled.');
                exit;
            end;
        end;
    end;

    if not cancel then Log(0, 'Override NPCs will be stored in the file: '+GetFileName(npcfile));
end;

procedure CatagorizeHair(theHair: IwbMainRecord);
var
    hairID: string;
    hairType: integer;
begin
    hairType := 0;
    hairID := EditorID(theHair);

	// Note "young" means only young NPCs should have it
	if (Pos('LykaiosMaleDreads003', hairID) > 0) then 
        hairType := HAIR_FANCY + HAIR_LONG + HAIR_UNMILITARY
	else if (Pos('LykaiosMaleDreads004', hairID) > 0) then 
        hairType := HAIR_FANCY + HAIR_LONG + HAIR_UNMILITARY
	else if (Pos('LykaiosMaleDreadsFringe', hairID) > 0) then 
        hairType := HAIR_FANCY + HAIR_LONG + HAIR_UNMILITARY
	else if (Pos('LykaiosMaleDreadsHeadband', hairID) > 0) then 
        hairType := HAIR_FANCY + HAIR_LONG + HAIR_UNMILITARY
	else if (Pos('LykaiosFemaleDreads002', hairID) > 0) then 
        hairType := HAIR_FANCY + HAIR_LONG + HAIR_UNMILITARY
	else if (Pos('LykaiosFemaleDreads004', hairID) > 0) then 
        hairType := HAIR_FANCY + HAIR_LONG + HAIR_UNMILITARY
	else if Pos('Dread', hairID) > 0 then 
        hairType := HAIR_MESSY + HAIR_LONG + HAIR_UNMILITARY
	else if Pos('Flip', hairID) > 0 then begin 
		if HeadpartSexIs(theHair, MALE) then 
            hairType := HAIR_YOUNG + HAIR_UNMILITARY
	end
	else if Pos('Mane', hairID) > 0 then 
        hairType := HAIR_MANE + HAIR_LEADER + HAIR_LONG
	else if Pos('LongBraid', hairID) > 0 then
        hairType := HAIR_FANCY + HAIR_LONG + HAIR_UNMILITARY
	else if (Pos('Lykaios', hairID) > 0) and (Pos('Mohawk', hairID) > 0) then
        hairType := HAIR_FANCY
	else if (Pos('Shaggy', hairID) > 0) then
        hairType := HAIR_MESSY + HAIR_LONG + HAIR_UNMILITARY
	else if (Pos('HairKhajiitMale08', hairID) > 0) then
        hairType := HAIR_MESSY + HAIR_LONG + HAIR_UNMILITARY
	else if (Pos('ApachiiHairMKhajiit01', hairID) > 0) then
        hairType := HAIR_MESSY + HAIR_LONG + HAIR_UNMILITARY
	else if (Pos('ApachiiHairMKhajiit02', hairID) > 0) then
        hairType := HAIR_MESSY + HAIR_LONG + HAIR_UNMILITARY
	else if (Pos('TiedStyle', hairID) > 0) then 
        hairType := HAIR_FANCY + HAIR_LONG + HAIR_UNMILITARY
	else if (Pos('VanillaBraid', hairID) > 0) then 
        hairType := HAIR_FANCY + HAIR_LONG + HAIR_UNMILITARY
	else if (Pos('LykaiosFemaleBraid', hairID) > 0) then 
        hairType := HAIR_FANCY + HAIR_LONG + HAIR_UNMILITARY
	else if (Pos('HairKhajiitMale03', hairID) > 0) then 
        hairType := HAIR_MESSY + HAIR_LONG + HAIR_UNMILITARY
	else if (Pos('ApachiiKhajiitHairF04', hairID) > 0) then 
        hairType := HAIR_MESSY + HAIR_LONG + HAIR_UNMILITARY
	else if (Pos('ApachiiKhajiitHairF05', hairID) > 0) then 
        hairType := HAIR_MESSY + HAIR_LONG + HAIR_UNMILITARY
	else if (Pos('HairKhajiitMale07', hairID) > 0) then 
        hairType := HAIR_MESSY + HAIR_UNMILITARY + HAIR_EARRING
	else if (Pos('HairKhajiitMale06', hairID) > 0) then
        hairType := HAIR_EARRING
    else if (Pos('HairKhajiitMale09', hairID) > 0) then
        hairType := HAIR_EARRING
	else if (Pos('HairKhajiitFemale07', hairID) > 0) then
        hairType := HAIR_EARRING
	else if (Pos('HairKhajiitFemale10', hairID) > 0) then
        hairType := HAIR_EARRING
	else if (Pos('HairKhajiitFemale05', hairID) > 0) then 
        hairType := HAIR_UNMILITARY + HAIR_EARRING
	else if (Pos('HairKhajiitFemale06', hairID) > 0) then 
        hairType := HAIR_UNMILITARY + HAIR_EARRING
	else if (Pos('HairKhajiitFemale08', hairID) > 0) then 
        hairType := HAIR_UNMILITARY + HAIR_EARRING
	else if (Pos('HairKhajiitFemale09', hairID) > 0) then 
        hairType := HAIR_UNMILITARY + HAIR_EARRING
	else if (Pos('HairKhajiitFemale10', hairID) > 0) then 
        hairType := HAIR_UNMILITARY + HAIR_EARRING
	else if (Pos('EarTalon', hairID) > 0) then 
        hairType := HAIR_UNMILITARY + HAIR_EARRING
	else if (Pos('FemaleShortCrop003', hairID) > 0) then 
        hairType := HAIR_UNMILITARY
	else if (Pos('ApachiiKhajiitHairF01', hairID) > 0) then 
        hairType := HAIR_LONG + HAIR_UNMILITARY
	else if (Pos('Orc', hairID) > 0) then 
        hairType := HAIR_ORC;

    if GetElementEditValues(theHair, 'RNAM') = 'HeadPartsLongEar' then
        hairType = hairType + HAIR_LONGEAR;

    hairstyles.Add(hairID + '=' + IntToStr(hairType));
end;

procedure CollectAllHair;
var
    i: integer;
    g: IwbContainer;
    hp: IwbMainRecord;
    hpname: string;
begin
    Log(11, '<Collecting hair');
    hairstyles := TStringList.Create;
    hairstyles.Duplicates := dupIgnore;
    hairstyles.Sorted := true;

    lykaiosBeardMap := TStringList.Create;
    lykaiosBeardMap.Duplicates := dupIgnore;
    lykaiosBeardMap.Sorted := true;

    g := GroupBySignature(yaFile, 'HDPT');
    for i := 0 to Pred(ElementCount(g)) do begin
        hp := WinningOverride(ElementByIndex(g, i));
        hpname := EditorID(hp);

        if GetElementEditValues(hp, 'PNAM') = 'Hair' then 
            CatagorizeHair(hp)
        // Only use some of the beards. Catch those as they go by for later reference.
        else if hpname = 'LykaiosBeardALLbraid_' then
            lykaiosBeardMap.AddObject('HumanBeard36', hp) // Roggi
        else if hpname = 'LykaiosBeardALL_' then
            lykaiosBeardMap.AddObject('HumanBeard41', hp) // Falk
        else if hpname = 'LykaiosbeardChin_' then
            lykaiosBeardMap.AddObject('HumanBeard43', hp) // Ulfberth
        else if hpname = 'LykaiosBeardChinBraid_' then
            lykaiosBeardMap.AddObject('HumanBeard35', hp); // Balgruuf
    end;

    Log(11, '>Collecting hair: ' + IntToStr(hairstyles.count));
end;

function HairIsStyle(hp: IwbMainRecord; style: integer): boolean;
begin
    i := hairstyles.IndexOfName(EditorID(hp));
    if i >= 0 then begin
        v := StrToInt(hairstyles.ValueFromIndex[i]);
        if v and style then Result := true
        else Result := false
    end
    else Result := false;
end;

function GetHairStyleFlags(hp: IwbMainRecord): integer;
var
    i: integer;
begin
    i := hairstyles.IndexOfName(EditorID(hp));
    if i >= 0 then 
        Result := StrToInt(hairstyles.ValueFromIndex[i])
    else 
        Result := 0;
end;

//=======================================================================
// Get the last override (or base) *before* the given file
function GetPriorOverride(theElement: IwbMainRecord; fileIndex: integer): IwbMainRecord;
var
  i, n: Integer;
  base, e, thisOverride: IwbMainRecord;
begin
    // LogMessage(5, 'GetPriorOverride: ' + EditorID(theElement) + ' in ' + GetFileName(GetFile(theElement)));
    base := MasterOrSelf(theElement);
    n := OverrideCount(base);
    thisOverride := base;

    for i := n-1 downto 0 do begin
        e := OverrideByIndex(base, i);
//        LogMessage(5, Name(base) + ' in ' + GetFileName(GetFile(base))
//            + ' has override ' + IntToStr(i)
//            + ' in ' + IntToStr(GetLoadOrder(GetFile(e)))
//            + '/' + GetFileName(GetFile(e))
//            + ' ?= ' + IntToStr(fileIndex));
        if GetLoadOrder(GetFile(e)) < fileIndex then begin
            thisOverride := e;
            break;
        end;
    end;

    Result := thisOverride;
end;

//=========================================================================
// Determine whether this element needs patching.
// Of interest if element is overridden by a mod loaded before YA and then
// overridden by YA (so we want to merge those other changes with YA's)
// or else, for npcs, the latest override is not in YA (so needs furrifying)
// RETURNS 0=not of interest; 1=needs merge; 2=needs furrifying
function ElementIsOfInterest(e: IwbMainRecord): integer;
var
    prior: IwbMainRecord;
    priorFileIndex: integer;
begin
    Result := 0;
    prior := GetPriorOverride(e, yaIndex);
    priorFileIndex := GetLoadOrder(GetFile(prior));
    Log(9, '<ElementIsOfInterest testing ' + Name(e)
       + ' file=' + IntToStr(GetLoadOrder(GetFile(e))) + '/' + GetFileName(GetFile(e))
       + ' prior=' + IntToStr(GetLoadOrder(GetFile(prior))) + '/' + GetFileName(GetFile(prior)));

    if (GetLoadOrder(GetFile(e)) = yaIndex)  // winning override in YA
        and (slBethFiles.IndexOf(GetFileName(GetFile(prior))) < 0)  // prior winner not from Beth
        and (not priorFileIndex < yaIndex)  // prior winner not YA
    then
        Result := 1
    else if SameText(Signature(e), 'NPC_')
        and (GetLoadOrder(GetFile(e)) <> yaIndex) // NPC was not defined in YA
    then
        Result := 2;

    Log(9, '>ElementIsOfInterest ' + Name(e) + ': file = ' + GetFileName(GetFile(e))
           + ', prior override = ' + GetFileName(GetFile(prior))
           + ' ==> ' + IntToStr(Result));
end;


//=========================================================================
// Collect NPCs to process on Finalize.
procedure CollectNPCs();
var
    f: IwbFile;
    g: IwbContainer;
    i, j: integer;
    basenpc, winnpc, npc: IwbMainRecord;
    npcname: string;
    slAllNPCs: TStringList;
begin
    slAllNPCs := TStringList.Create;
    slAllNPCs.duplicates := dupIgnore;
    slAllNPCs.sorted := true;
    
    // Walk through all NPC records
    for i := 0 to Pred(FileCount()) do begin
        f := FileByLoadOrder(i);
        if SameText(GetFileName(f), yaFileName) then break; // Stop when we hit YA

        Log(2, 'Collecting NPCs in ' + GetFileName(f));

        g := GroupBySignature(f, 'NPC_');
        for j := 0 to Pred(ElementCount(g)) do begin
            npc := ElementByIndex(g, j);
            winnpc := WinningOverride(npc);
            npcname := EditorID(npc);

            // decide whether this is a record we should process
            Log(5, 'Testing NPC ' + Name(npc) + ': '
                + IntToStr(slNPCs.Count) + '/' +
                + IntToStr(slNPCs.IndexOf(npcname)));
            if targetNPCs.IndexOf(npcname) >= 0 then
                slNPCs.AddObject(npcname, winnpc)
            else
                if targetNPCs.Count = 0 then
                if (slAllNPCs.IndexOf(npcname) < 0) then // Haven't rejected already
                if NPCisTargetRace(winnpc) then
                if NPCisTargetSex(winnpc) then
                if (GetElementEditValues(winnpc, 'ACBS - Configuration\Template Flags\Use Traits')
                    <> '1') then {NPC not based on template}
                if ElementIsOfInterest(winnpc) > 0
                then
                    slNPCs.AddObject(npcname, winnpc);
            
            slAllNPCs.Add(npcname);

            if slNPCs.Count > LIMIT then break;
        end;
    end;
    slAllNPCs.Free;
end;

//====================================================================
// Set up the string lists
procedure InitializeStringLists();
var
  sl: TStringList;
  i: integer;

begin
    Log(3, '<InitializeStringLists');
    slBethFiles := TStringList.Create;
    slBethFiles.Sorted := True;
    slBethFiles.Delimiter := '/';
    slBethFiles.DelimitedText := bethesdaFiles;

    slNPCs := TStringList.Create;
    slNPCs.Sorted := True;
    slNPCs.Duplicates := dupIgnore;

    slMasters := TStringList.Create;
    slMasters.Sorted := True;
    slMasters.Duplicates := dupIgnore;

    skinLayerNames := TStringList.Create;
    skinLayerNames.Add('Skin Tone');
    skinLayerNames.Add('EyeSocket Upper');
    skinLayerNames.Add('EyeSocket Lower');
    skinLayerNames.Add('Cheek Color');
    skinLayerNames.Add('Cheek Color Lower');
    skinLayerNames.Add('Forehead');
    skinLayerNames.Add('Laugh Lines');
    skinLayerNames.Add('Lip Color');
    skinLayerNames.Add('Chin');
    skinLayerNames.Add('Eyeliner');
    skinLayerNames.Add('Nose');
    skinLayerNames.Add('Neck');
    skinLayerNames.Add('Dirt');

    //Races using the lykaios head
    slNordRaces := TStringList.Create;
    slNordRaces.Add('NordRace');
    slNordRaces.Add('NordRaceVampire');
    slNordRaces.Add('ElderRace');
    slNordRaces.Add('ElderRaceVampire');
    slNordRaces.Add('NordRaceChild');
    slNordRaces.Add('NordRaceAstrid');
    slNordRaces.Add('DLC1NordRace');

    // Doesn't actually work. The only blind NPC without blind eyes is
    // Ulfr, and that's the way it is in vanilla.
    slBlindNPCs := TStringList.Create;
    slBlindNPCs.Add('MG07LabyrinthianThrall01');
    slBlindNPCs.Add('MG07LabyrinthianThrall02');
    slBlindNPCs.Add('DLC2GeneralCarius');
    slBlindNPCs.Add('DA15Sheogorath');
    slBlindNPCs.Add('dunWhiteRiverWatch_BanditWatchman');

    targetNPCs := TStringList.Create;
    targetNPCs.Duplicates := dupIgnore;
    targetNPCs.Sorted := True;
    targetNPCs.Delimiter := ',';
    if length(TARGETNPC) > 0 then
        targetNPCs.DelimitedText := TARGETNPC;

    myHeadparts := TStringList.Create;
    myHeadparts.Duplicates := dupIgnore;
    myHeadparts.Sorted := true;
    myHeadparts.Add('00HairLykaiosFemaleMane003');
    myHeadparts.Add('00HairLykaiosFemaleMohawk002_');
    myHeadparts.Add('00HairLykaiosFemaleTiedStyle001_');
    myHeadparts.Add('00HairLykaiosFemaleVanillaBraid001_');
    myHeadparts.Add('00HairLykaiosMaleDreads003_');
    myHeadparts.Add('00HairLykaiosMaleLionMane001');
    myHeadparts.Add('00HairLykaiosMaleLionMane002');
    myHeadparts.Add('00HairLykaiosMaleLionManeHeadband');
    myHeadparts.Add('00HairLykaiosMaleLionManebraids');
    myHeadparts.Add('00HairLykaiosMaleLongBraidleft_');
    myHeadparts.Add('HairMaleImperial1');
    myHeadparts.Add('FemaleEyesDremora');

    Log(3, '>InitializeStringLists');
end;

//=================================================================
// Load the furry assets we'll need later
procedure LoadFurryAssets();
var
  i, j, k: integer;
  e, f, group: IInterface;
  filename: string;
  r: real;
  thisID: string;
  colorForms: IInterface;
  hpRaces: string;
  isBethFile: boolean;
begin
    Log(10, '<LoadFurryAssets');
    CollectAllHair;

    // load stringlist data from available files
    Log(12, 'Loading data from master files.'+#13#10);

    for i := 0 to FileCount - 1 do begin
        f := FileByIndex(i);
        filename := GetFileName(f);
        //isBethFile := (slBethFiles.IndexOf(filename) >= 0);

        if (Pos('.esm', filename) > 0) then
            slMasters.Add(filename) // TODO ONLY ADD MASTER WHEN NEEDED
        else begin
            if SameText(yaFileName, filename) then begin
                slMasters.Add(filename); // Always process assets in YA, we need them
                YAIndex := GetLoadOrder(f);
                YAFile := f;
            end
            else if not DO_YA_ONLY then
                if MessageDlg('Process assets in: '+filename+' ?', mtConfirmation, [mbYes, mbNo], 0) = mrYes then
                    slMasters.Add(filename);
        end;
    end;

    briarheartNaked := FindAsset(yaFile, 'ARMO', 'SkinNakedBriarHeart');
    if not Assigned(briarheartNaked) then Err('Could not find briarheartNaked');
    briarheartOutfit := FindAsset(FileByIndex(0), 'OTFT', 'SkinBriarHeart');
    if not Assigned(briarheartOutfit) then Err('Could not find briarheartOutfit');

    bretonRace := MainRecordByEditorID(GroupBySignature(yaFile, 'RACE'), 'BretonRace');
    reachmanRace := MainRecordByEditorID(GroupBySignature(yaFile, 'RACE'), 'ReachmanRace');
    darkElfRace := MainRecordByEditorID(GroupBySignature(yaFile, 'RACE'), 'DarkElfRace');
    imperialRace := MainRecordByEditorID(GroupBySignature(yaFile, 'RACE'), 'ImperialRace');
    imperialRaceChild := MainRecordByEditorID(GroupBySignature(yaFile, 'RACE'), 'ImperialRaceChild');
    skaalRace := MainRecordByEditorID(GroupBySignature(yaFile, 'RACE'), 'SkaalRace');
    skaalRaceChild := MainRecordByEditorID(GroupBySignature(yaFile, 'RACE'), 'SkaalRaceChild');

    Log(10, '>LoadFurryAssets');
end;

//=========================================================================
// Initialize: Set up all the string lists, get user options, collect NPCs.
//=========================================================================
function Initialize: integer;
var
    i: integer;
    f: IwbFile;
begin
    // welcome messages
    AddMessage(#13#10+#13#10+
               '---------------------------------------------------------------');
    AddMessage('NPC Furrifier '+vs+': Makes NPCs furry.');
    AddMessage('---------------------------------------------------------------');

    SetAffectedRaces();

    InitializeLogging;

    // Get options from user
    cancel := true;
    OptionsForm;
    if cancel then exit;

    InitializeAssetLoader;
    InitializeStringLists;
    LoadRaceAssets(myHeadparts);

    Log(5, '00HairLykaiosMaleLionMane001: ' +
        IntToStr(specialHeadparts.IndexOf('00HairLykaiosMaleLionMane001')) + ', ' + 
        EditorID(ObjectToElement(specialHeadparts.Objects[
            specialHeadparts.IndexOf('00HairLykaiosMaleLionMane001')])));

    randomize();

    skyrimFile := FileByIndex(0);
    for i := 0 to FileCount()-1 do begin
        f := FileByLoadOrder(i);
        if GetFileName(f) = yaFileName then begin
            yaIndex := GetLoadOrder(f);
            yaFile := f;
        end;
    end;
    
    Log(2, 'YA file load order = ' + IntToHex(GetLoadOrder(yaFile), 8));

    LoadFurryAssets();

    LOGLEVEL := PROCESSLOGLEVEL;

    if DO_OVERRIDES or DO_NEW_NPCS then CollectNPCs();
end;

//=========================================================================
// process selected NPCs.
// Just put them on the slNPCs list; actual processing happens in Finalize.
//function Process(e: IInterface): integer;
//begin
//	if cancel then exit;
//
//	// decide whether this is a record we should process
//	if SameText(Signature(e), 'NPC_') then
//		if NPCisTargetRace(e)
//			and NPCisTargetSex(e)
//			and (geev(e, 'ACBS - Configuration\Template Flags\Use Traits') <> '1') {NPC not based on template}
//			// and (Pos('Preset', geev(e, 'EDID')) = 0) {not a preset}
//		then
//			slNPCs.AddObject(Name(e), TObject(e));
//end;

//=========================================================================
Procedure FreeLists();
Begin
    hairstyles.Free;
    slBethFiles.Free;
    myHeadparts.Free;
    slMasters.Free;
    slNordRaces.Free;
    slBlindNPCs.Free;
    skinLayerNames.Free;
    affectedRaces.Free;
    targetNPCs.Free;
    slNPCs.Free;
    lykaiosBeardMap.Free;
End;

//===============================================
// Set the given head part on the NPC
//
Procedure SetHeadPartRecord(headPartType: string; theHP: IwbMainRecord);
var
	i, hpIdx: integer;
    hp: IwbMainRecord;
	headparts, slot: IwbElement;
	done: boolean;
Begin
	Log(2, '<SetHeadPartRecord for ' + theNPCEditorID + ': ' + Name(theHP));

    if Assigned(theHP) then begin
        headparts := ElementByPath(theNPC, 'Head Parts');
        if headparts <> nil then begin
            for hpIdx := ElementCount(headparts)-1 downto 0 do begin
                slot := ElementByIndex(headparts, hpIdx);
                hp := WinningOverride(LinksTo(slot));
                if SameText(GetElementEditValues(hp, 'PNAM'), headPartType) then begin
                    Log(2, 'Setting ' + headPartType + ' to ' + EditorID(theHP));
                    AssignElementRef(slot, theHP);

                    done := True;
                    Break;
                end;
            end;
        end;

        if not done then begin
            Log(2, 'Adding ' + headPartType + ': [' + EditorID(theHP) +']');
            slot := ElementAssign(headparts, 100, nil, false);
            AssignElementRef(slot, theHP);
        end;
    end;

	Log(2, '>SetHeadPartRecord');
End;

//===============================================
// Set head part by name
//
Procedure SetHeadPart(headPartType: string; headPartName: string);
var
	i, hpIdx: integer;
	id: string;
	done: boolean;
Begin
	Log(4, '<SetHeadPart for ' + theNPCEditorID + ': ' + headPartType + ', ' + headPartName);

    i := specialHeadparts.IndexOf(headPartName);
    if i >= 0 then begin
        SetHeadPartRecord(headPartType, ObjectToElement(specialHeadparts.Objects[i]));
    end
    else
        Err('Special headpart not in list: ' + headPartName);

	Log(4, '>SetHeadPart');
end;

//==========================================================
Procedure AssignEyes();
var
	hpIdx: integer;
	hp: IwbElement;
    theHP, eyeTrial: IwbMainRecord;
    eyeIsBlind, npcIsBlind: integer;
    eyeCount, eyeIndex, ei: integer;
    trial: integer;
Begin
	Log(4, '<AssignEyes to ' + theNPCEditorID + ' race ' + theNPCRaceName);
	hp := ElementByPath(theNPC, 'Head Parts');
	for hpIdx := 0 to ElementCount(hp) do begin
        theHP := WinningOverride(LinksTo(ElementByIndex(hp, hpIdx)));
        if GetElementEditValues(theHP, 'PNAM') ='Eyes' then begin
            npcIsBlind := 0;
            if ContainsText(EditorID(theHP), 'BlindLeft') then npcIsBlind := 1
            else if ContainsText(EditorID(theHP), 'BlindRight') then npcIsBlind := 2
            else if ContainsText(EditorID(theHP), 'Blind') then npcIsBlind := 3
            else if slBlindNPCs.IndexOf(theNPCEditorID) >= 0 then npcIsBlind := 3;
            Log(4, 'NPC is blind: ' + IntToStr(npcIsBlind));

            eyeCount := GetRaceHeadpartCount(theNPCRaceIndex, theNPCSex, HEADPART_EYES);
            eyeIndex := HashInt(theNPCHashSeed, 3616, 0, eyeCount);

            for trial := 0 to eyeCount do begin
                if trial = eyeCount then 
                    Err('Could not find valid eyes for ' + Name(theNPC))
                else begin
                    ei := (eyeIndex + trial) mod eyeCount;
                    eyeTrial := GetRaceHeadpart(theNPCRaceIndex, theNPCSex, HEADPART_EYES, ei);
                    eyeIsBlind := 0;
                    if ContainsText(EditorID(eyeTrial), 'BlindLeft') then eyeIsBlind := 1
                    else if ContainsText(EditorID(eyeTrial), 'BlindRight') then eyeIsBlind := 2
                    else if ContainsText(EditorID(eyeTrial), 'Blind') then eyeIsBlind := 3;

                    Log(5, 'Trying eyes ' + EditorId(eyeTrial) + ': ' +
                        IntToStr(npcIsBlind) + '<>' + IntToStr(eyeIsBlind));
                    if npcIsBlind = eyeIsBlind then begin
                        AssignElementRef(ElementByIndex(hp, hpIdx), eyeTrial);
                        break;
                    end;
                end;
            end;
            Break;
        end;
	end;
	Log(4, '>AssignEyes');
End;

//==========================================================
Procedure AssignNordBeards();
var
	hpIdx, beardIdx: integer;
	hp, theHP, beard: IInterface;
Begin
	Log(3, '<AssignNordBeards to ' + theNPCEditorID + ' race ' + theNPCRaceName);

	hp := ElementByPath(theNPC, 'Head Parts');

    // Look to see if the NPC is bearded
	for hpIdx := 0 to ElementCount(hp) - 1 do begin
		theHP := WinningOverride(LinksTo(ElementByIndex(hp, hpIdx)));
		Log(4, IntToStr(hpIdx) + ': part name is ' + geev(theHP, 'PNAM'));
		if SameText(geev(theHP, 'PNAM'), 'Facial Hair') then begin
			beardIdx := lykaiosBeardMap.IndexOf(EditorID(theHP));
			if beardIdx >= 0 then begin
				Log(4, 'New beard replacing ' + EditorID(theHP));
                AssignElementRef(ElementByIndex(hp, hpIdx),
                                 ObjectToElement(lykaiosBeardMap.Objects[beardIdx]));
			end;
			Break;
		end;
	end;
	Log(3, '>AssignNordBeards');
End;

//================================================================
// Choose earrings for the NPC_
//
Procedure ChooseEarrings();
var
	r, n, hpi: integer;
	theEarring: IwbMainRecord;
    theEarringHP: string;
begin
	Log(4, '<ChooseEarrings for ' + theNPCEditorID);

    n := GetRaceHeadpartCount(theNPCRaceIndex, theNPCSex, HEADPART_EYEBROWS);
	r := HashInt(theNPCHashSeed, 84, 0, 4); // 1 in 4 chance of earrings
	if (n > 0) and (r = 0) then begin
        hpi := HashInt(theNPCHashSeed, 1770, 0, n);
        theEarring := GetRaceHeadpart(theNPCRaceIndex, theNPCSex, HEADPART_EYEBROWS, hpi);
		Log(4, 'chose ' + EditorID(theEarring) + ' at ' + IntToStr(hpi));
        SetHeadPartRecord('Eyebrows', theEarring);

	end;
	Log(4, '>ChooseEarrings');
end;

//================================================================
// Choose a hair style appropriate to the NPC
// Return the EDID of the hair
//
Function ChooseHair(): String;
var
	trial: integer;
	i, r, h: integer;
	hairID: string;
	hairKW: string;
	disallowed: boolean;
    hairCount: integer;
    hairIndex, hi: integer;
    theHair: IwbMainElement;
    s: integer;

begin
	Log(3, '<ChooseHair for ' + theNPCEditorID + '--' + theNPCVoice + '--' + theNPCClass);
    // if logLevel >= 5 then begin
    //     Log(5, 'Available hair:');
    //     inc(logIndent);
    //     hairCount := raceInfo[theNPCRaceIndex]
    //         .headparts[theNPCSex, HEADPART_HAIR].Count;
    //     for i := 0 to hairCount-1 do begin
    //         Log(5, EditorID(ObjectToElement(raceInfo[theNPCRaceIndex]
    //             .headparts[theNPCSex, HEADPART_HAIR].Objects[i])));
    //     end;
    //     dec(logIndent);
    // end;

	if ContainsText(theNPCEditorID, 'Nocturnal') then 
        begin end
    else if (Pos('Yamarz', theNPCEditorID) > 0) or
	   (Pos('Mauhulakh', theNPCEditorID) > 0) or
	   (Pos('Larak', theNPCEditorID) > 0) or
	   (Pos('Burguk', theNPCEditorID) > 0) then

		SetHeadPart('Hair', '00HairLykaiosMaleLionManeHeadband')

	else if (Pos('Ulfric', theNPCEditorID) > 0) then
	begin

		Log(3, 'Ulfric: ' + theNPCEditorID + ' pos ' + IntToStr(Pos('Ulfric', theNPCEditorID)));
		SetHeadPart('Hair', '00HairLykaiosMaleLionManebraids');
		senv(theNPC, 'HCLF', LYKAIOSBLACKHAIR);

	end
	else if (Pos('Torygg', theNPCEditorID) > 0) or
			(Pos('Sibbi', theNPCEditorID) > 0) or
			(Pos('Thonar', theNPCEditorID) > 0) or
			(Pos('Urag', theNPCEditorID) > 0) then

		SetHeadPart('Hair', '00HairLykaiosMaleLionMane002')

	else if (Pos('Kodlak', theNPCEditorID) > 0) or
		    (Pos('Dengeir', theNPCEditorID) > 0) or
		    (Pos('Eorlund', theNPCEditorID) > 0) or
		    (Pos('Igmund', theNPCEditorID) > 0) then

		SetHeadPart('Hair', '00HairLykaiosMaleLionMane001')

	else if Pos('Mjoll', theNPCEditorID) > 0 then

		SetHeadPart('Hair', '00HairLykaiosFemaleMane003')

	else if Pos('Aela', theNPCEditorID) > 0 then

		SetHeadPart('Hair', '00HairLykaiosFemaleMohawk002_')

	else if Pos('Tullius', theNPCEditorID) > 0 then

		SetHeadPart('Hair', 'HairMaleImperial1')

	else if Pos('Amaund', theNPCEditorID) > 0 then

		SetHeadPart('Hair', '00HairLykaiosMaleLongBraidleft_')

	else if Pos('Elisif', theNPCEditorID) > 0 then

		SetHeadPart('Hair', '00HairLykaiosFemaleTiedStyle001_')

	else if Pos('Delphine', theNPCEditorID) > 0 then

		SetHeadPart('Hair', '00HairLykaiosFemaleVanillaBraid001_')

	else if Pos('Ahtar', theNPCEditorID) > 0 then

		SetHeadPart('Hair', '00HairLykaiosMaleDreads003_')

	else begin
		// Pick a random hair based on npc characteristics
        hairCount := GetRaceHeadpartCount(theNPCRaceIndex, theNPCSex, HEADPART_HAIR);
        hairIndex := Hash(theNPCHashSeed, 3884, hairCount);
		
        for trial := 0 to hairCount-1 do begin 
            hi := (hairIndex + trial) mod hairCount;
            theHair := GetRaceHeadpart(theNPCRaceIndex, theNPCSex, HEADPART_HAIR, hi);
            Log(4, 'Trying hair ' + EditorID(theHair));

            s := GetHairStyleFlags(theHair);

			disallowed := false;

			// No manes except on lykaios and lions
			if (Pos('Nord', theNPCRaceName) = 0) and
			   (Pos('Orc', theNPCRaceName) = 0) and
			   (((s and HAIR_MANE) <> 0) <> 0)
			then disallowed := true

			// Tribal orcs have to have orc hair (except chiefs, covered above)
			else if
			   SameText('OrcRace', theNPCRaceName) then begin
				if IsInFaction('CrimeFactionOrcs' {tribal orc}) and
				   ((s and HAIR_ORC) = 0)
				then disallowed := true;
			end

			// Rough trades means no manes, no fancy
			else if
			    ((Pos('Miner', theNPCClass) > 0) or
				(Pos('Lumberjack', theNPCClass) > 0) or
				(Pos('Beggar', theNPCClass) > 0) or
				(Pos('Farmer', theNPCClass) > 0) or
				(Pos('Carriage', theNPCEditorID) > 0) or
				(Pos('Worker', theNPCEditorID) > 0) or
				(Pos('Hunter', theNPCEditorID) > 0) or
				(Pos('Butcher', theNPCEditorID) > 0) or
				(Pos('Jailor', theNPCClass) > 0))
				    and
				((s and (HAIR_MANE + HAIR_FANCY + HAIR_YOUNG)) <> 0)
			then disallowed := true

			// Messy hair only goes on messy people
			else if
				((s and HAIR_MESSY) <> 0)
				and not (
			    (Pos('Hunter', theNPCEditorID) > 0) or
				(Pos('Beggar', theNPCClass) > 0) or
				(Pos('Brute', theNPCVoice) > 0) or
				(Pos('Bandit', theNPCVoice) > 0) or
				(Pos('Blackblood', theNPCEditorID) > 0) or
				(Pos('Bandit', theNPCEditorID) > 0))
			then disallowed := true

			// Blacksmiths don't have messy, long or fancy hair
			else if 
				(Pos('VendorBlacksmith', theNPCClass) > 0)
				and
                ((s and (HAIR_MANE + HAIR_FANCY + HAIR_LONG + HAIR_MESSY + HAIR_YOUNG)) <> 0)
		    then disallowed := true

			// Bandits aren't fancy or young
			else if 
				((Pos('Brute', theNPCVoice) > 0) or
				(Pos('Bandit', theNPCVoice) > 0) or
				(Pos('Blackblood', theNPCEditorID) > 0) or
				(Pos('Bandit', theNPCEditorID) > 0))
				and
				((s and (HAIR_FANCY + HAIR_YOUNG + HAIR_MANE)) <> 0)
			then disallowed := true

			// Commanders have to be military and not messy and no orc styles
			else if (not disallowed) and
				((Pos('Commander', theNPCVoice) > 0) or
				(Pos('Captain', theNPCEditorID) > 0))
				and
                ((s and (HAIR_UNMILITARY + HAIR_MESSY + HAIR_ORC)) <> 0)
			then disallowed := true

			// Other military has to be military and not messy and no manes, not fancy
			else if
			   ((Pos('Guard', theNPCVoice) > 0) or
				(Pos('Soldier', theNPCVoice) > 0) or
				(Pos('Guard', theNPCEditorID) > 0) or
				(Pos('Soldier', theNPCEditorID) > 0))
				and
                ((s and (HAIR_UNMILITARY + HAIR_MESSY + HAIR_FANCY + HAIR_MANE)) <> 0)
			then disallowed := true

			// Snooty types aren't messy
			else if 
			   ((Pos('Condescending', theNPCVoice) > 0) or
				(Pos('Haughty', theNPCVoice) > 0) or
				(Pos('Prelate', theNPCEditorID) > 0) or
				(Pos('Priest', theNPCEditorID) > 0) or
				(Pos('Emperor', theNPCVoice) > 0))
			   and
               ((s and (HAIR_MESSY + HAIR_ORC + HAIR_YOUNG)) <> 0)
			then disallowed := true

			// Young'uns can't have manes or be fancy
			else if
			   (Pos('Young', theNPCVoice) > 0)
			   and
               ((s and (HAIR_MANE + HAIR_FANCY + HAIR_ORC)) <> 0)
			then disallowed := true

			// Stewards can't be messy
			else if
				IsInFaction('JobStewardFaction')
				and
				((s and HAIR_MESSY) <> 0)
			then disallowed := true

			// Jarls must be fancy
			else if
				IsInFaction('JobJarlFaction')
				and
				((s and HAIR_FANCY) = 0)
			then disallowed := true

			// Generic NPCs don't get young hairstyles or manes
			else if
                (s and (HAIR_YOUNG + HAIR_MANE + HAIR_ORC)) <> 0
			then disallowed := true;

			if not disallowed then break;
		end;

		if not disallowed then 
            SetHeadPartRecord('Hair', theHair)
        else
            Log(1, 'Could not find valid hair for ' + Name(theNPC));

		if ((s and HAIR_EARRING) = 0) then ChooseEarrings();
	end;
    Log(3, '>ChooseHair')
end;

procedure FurrifyNPC;
begin
    Log(1, 'Furrifying ' + SmallName(theNPC) + ' in ' + GetFileName(GetFile(theNPC)));
    inc(logIndent);

    AddRecursiveMaster(npcfile, GetFile(theNPC));

    theNPC := wbCopyElementToFile(theNPC, npcfile, False, True);
    Log(5, 'Override record for ' + Name(theNPC) + ' created in ' + GetFileName(npcfile));

    // Setup global variables for the NPC
    SetupNPC;
    Log(2, Name(theNPC) + ' has race ' + Name(theNPCRace));

    // ---------- set head-specific scars ----------
    if DO_SCARS then FixScars;

    // ---------- give out hair -----------------
    if DO_HAIR then ChooseHair;

    // ---------- set hyena eyes, also anyone on the special 'blind' list ----------
    if DO_CHARGEN then begin
        if (Pos('RedguardRace', theNPCRaceName) > 0) or (Pos('Reachman', theNPCRaceName) > 0)
                or (slBlindNPCs.IndexOf(theNPCEditorID) >= 0) then
            AssignEyes;
        // // also blind eyes
        // if slBlindNPCs.IndexOf(theNPCEditorID) >= 0 then
        //     if theNPCSex = FEMALE then
        //         SetHeadPart('Eyes','FemaleEyesKhajiitBlind')
        //     else
        //         SetHeadPart('Eyes', 'MaleEyesKhajiitBlind');
    end;

    // ---------- set beards -------------
    if DO_HAIR and (slNordRaces.IndexOf(theNPCRaceName) >= 0) and (theNPCSex = MALE) then
        AssignNordBeards;

    // ---------- set skin tints -------------
    if DO_CHARGEN then begin
        if not SetPredefinedNPC then SetNPCSkinLayers;
    end;

    if DO_CHARGEN then ReshapeHead;

    // ----------- Set Briarheart bodies ---------
    if DO_BRIARHEART then
    begin
        if SameText(theNPCRaceName, 'BretonRace') then begin
            if length(GetElementEditValues(theNPC, 'DOFT')) > 0 then
            begin
                Log(2, 'Breton outfit is ' + geev(theNPC, 'DOFT') + 
                    ' (' + IntToStr(genv(theNPC, 'DOFT')) + '/' + 
                        IntToStr(FormID(briarheartOutfit)) + ')');
                if genv(theNPC, 'DOFT') = FormID(briarheartOutfit) then
                    begin
                    Add(theNPC, 'WNAM', true);
                    senv(theNPC, 'WNAM', FormID(briarheartNaked));
                end;
            end;
        end;
    end;

    dec(logIndent);
end;

//=========================================================================
// Merge the furriness in YA with the prior version of the NPC
// Input: theNPC. Guaranteed to have an override before YA.
procedure MergeFurryNPC;
var
    priorNPC, newNPC: IwbMainRecord;
begin
    // Get the last override *before* YA
    Log(3, '<MergeFurryNPC: ' + IntToStr(yaIndex));

    priorNPC := GetPriorOverride(theNPC, yaIndex);

    Log(3, 'Merging NPC ' + EditorID(theNPC) + ' from file ' + GetFileName(GetFile(priorNPC)));

    // New override based on the override before YA
    AddRecursiveMaster(npcfile, GetFile(priorNPC));
    newNPC := wbCopyElementToFile(priorNPC, npcfile, False, True);

    // Now copy into it YA's appearance edits
    ElementAssign(ElementByPath(newNPC, 'RNAM'),
        LowInteger,
        ElementByPath(theNPC, 'RNAM'),
        False);
    ElementAssign(ElementByPath(newNPC, 'QNAM'),
        LowInteger,
        ElementByPath(theNPC, 'QNAM'),
        False);
    if Assigned(ElementByPath(theNPC, 'Head Parts')) then
        ElementAssign(ElementByPath(newNPC, 'Head Parts'),
            LowInteger,
            ElementByPath(theNPC, 'Head Parts'),
            False);
    ElementAssign(ElementByPath(newNPC, 'HCLF'),
        LowInteger,
        ElementByPath(theNPC, 'HCLF'),
        False);
    ElementAssign(ElementByPath(newNPC, 'FTST'),
        LowInteger,
        ElementByPath(theNPC, 'FTST'),
        False);
    ElementAssign(ElementByPath(newNPC, 'NAM9'),
        LowInteger,
        ElementByPath(theNPC, 'NAM9'),
        False);
    ElementAssign(ElementByPath(newNPC, 'NAMA'),
        LowInteger,
        ElementByPath(theNPC, 'NAMA'),
        False);
    ElementAssign(ElementByPath(newNPC, 'Tint Layers'),
        LowInteger,
        ElementByPath(theNPC, 'Tint Layers'),
        False);

    Log(3, '>MergeFurryNPC');
end;

//===================================================================
// Extends the container at trgElement with all the contents of srcElement
procedure ExtendElementList(srcElement, trgElement: IwbContainer; path: string);
var
    i: integer;
    target: IwbMainRecord;
    src, trg, slot: IwbElement;
    targID: Cardinal;
    idStr: string;
begin
    src := ElementByPath(srcElement, path);
    if Assigned(src) then begin
        trg := ElementByPath(trgElement, path);

        for i := 0 to ElementCount(src)-1 do begin
            target := LinksTo(ElementByIndex(src, i));
            if not IsInList(trg, EditorID(target)) then begin
                if not Assigned(trg) then begin
                    trg := Add(trgElement, path, true);
                    slot := ElementByIndex(trg, 0);
                end
                else
                    slot := ElementAssign(trg, HighInteger, nil, False);
                Log(5, 'Adding element ' + EditorID(target) + '/' + IntToHex(GetLoadOrderFormID(target), 8));
                targID := GetLoadOrderFormID(target);
                idStr := IntToHex(targID shr 24, 2) + IntToHex((targID and $FFFFFF),6);
                SetEditValue(slot, idStr);
            end;
        end;
    end;

end;

procedure MergeFurryRace(theRace: IwbMainRecord);
var
    priorRace, newRace: IwbMainRecord;
    ae, kw: IwbContainer;
    aeSource, kwSource: IwbElement;
    aeSlot, kwSlot: IwbElement;
    target: IwbMainRecord;
    i: integer;
begin
    // Get the last override *before* YA
    Log(3, '<MergeFurryRace: ' + IntToStr(yaIndex));

    priorRace := GetPriorOverride(theRace, yaIndex);

    Log(3, 'Merging race ' + EditorID(theRace) + ' from file ' + GetFileName(GetFile(priorRace)));

    // New override based on the override before YA
    AddRecursiveMaster(npcfile, GetFile(priorRace));
    newRace := wbCopyElementToFile(priorRace, npcfile, False, True);

    // Now copy into it YA's appearance edits
    ElementAssign(ElementByPath(newRace, 'WNAM - Skin'),
        LowInteger,
        ElementByPath(theRace, 'WNAM - Skin'),
        False);
    ElementAssign(ElementByPath(newRace, 'ANAM - Male Skeletal Model'),
        LowInteger,
        ElementByPath(theRace, 'ANAM - Male Skeletal Model'),
        False);
    ElementAssign(ElementByPath(newRace, 'ANAM - Female Skeletal Model'),
        LowInteger,
        ElementByPath(theRace, 'ANAM - Female Skeletal Model'),
        False);
    ElementAssign(ElementByPath(newRace, 'Head Data'),
        LowInteger,
        ElementByPath(theRace, 'Head Data'),
        False);
    ElementAssign(ElementByPath(newRace, 'BOD2'),
        LowInteger,
        ElementByPath(theRace, 'BOD2'),
        False);

    // Add any YA abilities & keywords
    ExtendElementList(theRace, newRace, 'Actor Effects');
    ExtendElementList(theRace, newRace, 'KWDA');

    Log(3, '>MergeFurryRace');
end;

procedure MergeFurryRaceOverrides;
var
    i, j: integer;
    f: IwbFile;
    g: IwbContainer;
    race: IwbMainRecord;
    racename: string;
    isGood: boolean;
    slHandled: TStringList;
begin
    Log(1, '<MergeFurryRaceOverrides');

	// Walk through all Race records
    slHandled := TStringList.Create;
    slHandled.Duplicates := dupIgnore;
    slHandled.Sorted := true;

	for i := 0 to Pred(FileCount()) do begin
		f := FileByLoadOrder(i);
        if SameText(GetFileName(f), yaFileName) then break; // Stop when we hit YA

        g := GroupBySignature(f, 'RACE');
        for j := 0 to Pred(ElementCount(g)) do begin
            race := WinningOverride(ElementByIndex(g, j));
            racename := EditorID(race);

            // decide whether this is a record we should process
            if slHandled.IndexOf(racename) < 0 then
                if ElementIsOfInterest(race) > 0 then
                    if not SameText(EditorID(race), 'DremoraRace') then
                        // Keep YA dremoras bc we made them playable
                        MergeFurryRace(race);

            slHandled.Add(racename);
        end;
    end;

    slHandled.Free;
    Log(1, '>MergeFurryRaceOverrides');
end;

//=========================================================================
// finalize
function Finalize: integer;
var
    npcIdx: integer;
    mark, iv: integer;
begin
    if cancel then begin
        FreeLists();
        exit;
    end;

    if not SameText(GetFileName(npcfile), yaFileName) then
        AddRecursiveMaster(npcfile, yaFile);

    Add(npcfile, 'NPC_', true);

    LOGLEVEL := PROCESSLOGLEVEL; // <<<<<<<<<<debugging>>>>>>>>>>

    // transmogrify npcs
    Log(0, #13#10+'Furrifying NPCs...');
    Log(2, '# of NPCs: ' + IntToStr(slNPCs.Count));

    mark := slNPCs.Count/10;
    iv := 0;
    for npcIdx := 0 to slNPCs.Count-1 do begin

        if npcIdx > mark then begin
            inc(iv);
            AddMessage('**** ' + IntToStr(iv*10) + '%');
            mark := slNPCs.Count * (iv + 1) / 10;
        end;

        theNPC := ObjectToElement(slNPCs.Objects[npcIdx]);
        theNPCEditorID := EditorID(theNPC);
        Log(3, 'NPC ' + Name(theNPC) + ' in file ' + GetFileName(GetFile(theNPC)));

        case ElementIsOfInterest(theNPC) of
        1: if DO_OVERRIDES then MergeFurryNPC;
        2: if DO_NEW_NPCS then FurrifyNPC;
        end;
    end;

    if DO_RACES and (targetNPCs.Count = 0) then MergeFurryRaceOverrides;

    SortMasters(npcfile);
    FreeLists();
    ShutdownAssetLoader;
end;

end.
