Know Your Enemy - Trait-based resistances and weaknessesを導入してニューゲームで始めたものの、ラビがArmor Perkを持っていないことをたまたま見つけました。というわけで対策をします。
なぜ持っていないのか
zEditでパッチを作るときに処理から漏れてしまったということですが、どのようにして処理しているのかを調べてみると、種族の一覧があることがわかりました。
場所はzEditのmodules/KnowYourArmorPatcher/misc.jsになります。バニラ種族とドラウグル、ファルメルがあります。ラビは独自の追加種族なので処理から漏れてしまったということになります。
他にも、Templateを使っているActorは条件次第ではPerkを割り当ててくれないみたいです。TemplateにLeveled Characterを指定していてUse Traitsがオンなので種族が不定、かつUse Spellsがオフになっていて独自にSpellやPerkを持たせているActorレコードは、厳密には人型かどうかを判別できない、ということのようです。
それから、バニラではTemplateとTemplate Flagsがあるのに、YASHがTemplateだけを削除していて、Template FlagsのUse Spellsがオンのままのために除外されてしまうケースもありました。
パッチ処理の対象にする
もう一度書きますが、修正するのはzEditのmodules/KnowYourArmorPatcher/misc.jsです。
流儀に従うのであれば、SSEEditで当該Actorの種族を調べて、misc.jsに追加すればいいです。これで独自種族のカスタムフォロワーは大丈夫でした。
そして、Leveled Characterの場合は、Use Traitsがオンなので種族がデフォルトのFoxRaceになっています。そこで、Attack Raceがない場合は人型であるとみなします。本当のFoxならAttack Raceがあるからです。
KYE armor patcherでLeveled Characterを対象にする (js)
if (race_name == 'FoxRace' && !xelib.HasElement(record, 'ATKR')) {
return true;
}
Template FlagsとTemplateが両方あるときだけTemplate Flagsを見るようにします。
Templateのチェック (js)
if (xelib.HasElement(record, 'ACBS\\Template Flags') && xelib.HasElement(record, 'TPLT')) {
let flags = xelib.GetEnabledFlags(record, 'ACBS\\Template Flags');
if (flags.includes("Use Spell List")) return false;
}
ついでにYASHのYASH2_Perk_UAPも持たせるようにしてみました。modules/KnowYourArmorPatcher/index.jsの一部です。
initialize: function() {
locals.armor_rules = require(`${patcherPath}/armor_rules.js`);
locals.misc_rules = require(`${patcherPath}/misc.js`);
let kye = xelib.FileByName("know_your_enemy.esp");
locals.kye_armor_perk = xelib.GetHexFormID(xelib.GetElement(kye, 'kye_perk_armors2'));
let yash = xelib.FileByName("YASH2.esp");
locals.yash_uap_perk = xelib.GetHexFormID(xelib.GetElement(yash, 'YASH2_PerkUAP'));
locals.elemental_destruction_installed = xelib.HasElement(0, 'Elemental Destruction.esp');
locals.know_your_elements_installed = xelib.HasElement(0, 'Know Your Elements.esp');
locals.shadow_spells_installed = xelib.HasElement(0, 'ShadowSpellPackage.esp');
locals.light_and_shadow_installed = xelib.HasElement(0, 'KYE Light and Shadow.esp');
check_for_addons(locals, helpers);
},
process: [{
// ***** PART 1 *****
// Add the armor perk to all relevant NPCs
load: {
signature: 'NPC_',
filter: function(record) {
if (xelib.HasElement(record, 'ACBS\\Template Flags') && xelib.HasElement(record, 'TPLT')) {
let flags = xelib.GetEnabledFlags(record, 'ACBS\\Template Flags');
if (flags.includes("Use Spell List")) return false;
}
if (xelib.HasKeyword(record, 'ActorTypeGhost')) return false;
if (xelib.HasKeyword(record, 'ActorTypeNPC')) return true;
let race_name = edid_of_referenced_record(record, 'RNAM');
if (race_name == 'FoxRace' && !xelib.HasElement(record, 'ATKR')) {
return true;
}
return locals.misc_rules.armor_races.includes(race_name);
}
},
patch: function (record) {
xelib.AddPerk(record, locals.kye_armor_perk, '1');
if (!xelib.HasPerk(record, locals.yash_uap_perk)) {
xelib.AddPerk(record, locals.yash_uap_perk, '1');
}
}
}, {
// ***** PART 2 *****
パッチを作り直す
既存のセーブデータがある場合は、パッチを作り直す前にカスタムフォロワーのModを無効にして、セーブデータのクリーニングをしておいた方がいいです。そうしないとPerkが反映されません。パッチを作り直した後だとマスター指定が追加されてしまうのでKYEパッチを無効にできず、強引に無効にするとすべてのActorのPerkが狂います。
RespawnするActorの場合はセルリセットで大丈夫です。
パッチを作り直してSSEEditで確認したところ、きちんとPerkを持っていました。
ゲーム内でもPerkの所持を確認できました。
Perkを持っていないActorを自動検知する
Perkを持っていないActorを手作業で見つけ出すのは大変なので、自動的に検知する仕組みを作りました。
Cloakスペルを用意してスペルを配布します。Aimedスペルの条件はこんな感じです。
MiscItemからforkあたりをコピーしてaaaDetectedを用意します。Non-Playableにして見えないようにします。
Aimedスペルに紐づけるスクリプトはこうです。
KYEのPerkを持っていないActorを検知する (papyrus)
Scriptname kyeEffectDetectDeliverScript Extends ActiveMagicEffect
MiscObject Property aaaDetected Auto
Event OnEffectStart(Actor akTarget, Actor akCaster)
akTarget.AddItem(aaaDetected, abSilent = true)
Debug.Trace( "KYE Debug: detected actor=" + akTarget + " race=" + akTarget.GetRace() )
Debug.MessageBox( "KYE Debug\nThis actor does not have armor perk!\n" + akTarget.GetDisplayName() )
EndEvent
クエストを作ってプレイヤーのReferenceAliasを用意し、スクリプトを紐づけます。
KYEの検知用スペルをばらまく (papyrus)
Scriptname kyePlayerScript Extends ReferenceAlias
Spell Property aaaSpellDetectCloak Auto
Actor PlayerRef
Event OnInit()
PlayerRef = Game.GetPlayer()
RegisterForSingleUpdate(10.0)
EndEvent
Event OnCellLoad()
UnregisterForUpdate()
RegisterForSingleUpdate(10.0)
EndEvent
Event OnUpdate()
aaaSpellDetectCloak.Cast(PlayerRef)
RegisterForSingleUpdate(10.0)
EndEvent
10秒おきに周囲のActorを検索しています。セル移動直後は何かと重いので、すぐに検索しないようにしてあります。
Perkを持っていないActorを検知すると、このように知らせてくれます。
トレースログに詳細が出力されますので、misc.jsに追加していきます。