配布の設定は要確認
Actorにスペルを配布するためにSPIDを使用しています。SPIDの設定ファイルはMaximumCarnage_DISTR.iniになります。この設定は修正した方がいいかもしれません。
NPCの設定ですが、私の環境ではLeveled NPCの一部に配布されていませんでした。そこでFox Raceを加えることで強引に配布させたところ、概ね大丈夫のようでした。
;MC_Gore Human Spell = 0x0012CBF6 - MaximumCarnage.esp | ActorTypeNPC , Charmed Vigilant , Spellsword | NONE | NONE | NONE | NONE | 100 Spell = 0x0012CBF6 - MaximumCarnage.esp | NONE | 0x00109C7C ;MC_Gore Human Magic Spell = 0x001FF321 - MaximumCarnage.esp | ActorTypeNPC , Charmed Vigilant , Spellsword | NONE | NONE | NONE | NONE | 100 Spell = 0x001FF321 - MaximumCarnage.esp | NONE | 0x00109C7C
ドラウグルはなぜか名前指定になっています。これだと日本語化環境では配布されません。Faction指定にしたら大丈夫そうでした。
;MC_Gore Draugr ;Spell = 0x0012CBFB - MaximumCarnage.esp | Draugr , Restless Draugr , Draugr Wight , Draugr Scourge , Draugr Deathlord , Draugr Overlord , Draugr Wight Lord , Draugr Scourge Lord , Draugr Death Overlord , Draugr Thrall , Red Eagle , Mikrul Gauldurson , Sigdis Gauldurson , Jyrik Gauldurson , Guardian Torsten , Guardian Saerek | NONE | NONE | NONE | NONE | 100 Spell = 0x0012CBFB - MaximumCarnage.esp | NONE | 0x0002430D , NOT 0x00109C7C ;MC_Gore Draugr Magic ;Spell = 0x00209190 - MaximumCarnage.esp | Draugr , Restless Draugr , Draugr Wight , Draugr Scourge , Draugr Deathlord , Draugr Overlord , Draugr Wight Lord , Draugr Scourge Lord , Draugr Death Overlord , Draugr Thrall , Red Eagle , Mikrul Gauldurson , Sigdis Gauldurson , Jyrik Gauldurson , Guardian Torsten , Guardian Saerek | NONE | NONE | NONE | NONE | 100 Spell = 0x00209190 - MaximumCarnage.esp | NONE | 0x0002430D , NOT 0x00109C7C
スクリプトの負荷に注意
スペルが物理攻撃版と魔法攻撃版でわかれています。
以降、NPCの場合で解説します。
物理攻撃版
物理攻撃版はOnHitイベントを捕捉していますが、ほぼNo Waitなので、状況次第では高負荷になります。
わざわざBusyステートに移行させているにもかかわらず、条件に一致しない場合はステートを即戻しているので、あまり負荷対策になっていません。1秒ほど待機時間を入れると、かなり負荷が下がると思います。ただし、処理を間引くことで負荷を下げているため、攻撃が重なると無視されやすくなる点には注意します。応答性と負荷のトレードオフなので仕方がないです。
mc_deatheffectsscript.psc (papyrus)
; 普段はReadyステートに遷移させておく
Event OnEffectStart(Actor Target, Actor Caster)
; 中略
GoToState("Ready")
EndEvent
; ステート遷移用関数を定義する
Function GoToReady()
EndFunction
Function GoToKillmove()
EndFunction
; 普段のステート
State Ready
Event OnAnimationEvent(ObjectReference akSource, string asEventName)
; 略
EndEvent
; 攻撃を受けたらBusyステートに遷移して、しばらくしたらReadyステートに戻る
Event OnHit(ObjectReference akAggressor, Form akSource, Projectile akProjectile, bool abPowerAttack, bool abSneakAttack, bool abBashAttack, bool abHitBlocked)
GotoState("Busy")
; 中略
Utility.Wait(1.0)
GoToReady()
EndEvent
EndState
; 攻撃を受けてしばらくOnHitを無視しているときのステート
State Busy
; BusyステートからReadyステートに戻る
Function GoToReady()
GotoState("Ready")
EndFunction
EndState
; キルムーブの発動を検知している間のステート
State Killmove
; 自分が攻撃側であることが判明したらReadyステートに戻る
; 自分が被害者側であることが判明したらBusyステートに遷移する
Event OnAnimationEvent(ObjectReference akSource, string asEventName)
if asEventName == "Decapitate"
GotoState("Busy")
elseif asEventName == "weaponSwing"
GoToReady()
killmove = false
UnregisterForAnimationEvent(akSource, "weaponSwing")
endif
EndEvent
Event OnHit(ObjectReference akAggressor, Form akSource, Projectile akProjectile, bool abPowerAttack, bool abSneakAttack, bool abBashAttack, bool abHitBlocked)
GotoState("Busy")
if !(akSource as Weapon)
; 武器でないなら無視
Utility.Wait(1.0)
GoToKillmove()
elseif !TargetActor.IsDead()
; 死んでないなら無視
Utility.Wait(1.0)
GoToKillmove()
else
; 武器によって殺されたので処理
position = (TargetRef.GetHeadingAngle(akAggressor) as Int)
weap = (akSource as Weapon)
GotoState("kState" + weap.GetWeaponType())
endif
EndEvent
Function GoToReady()
GotoState("Ready")
EndFunction
Function GoToKillmove()
GotoState("Killmove")
EndFunction
EndState
魔法攻撃版
魔法版はOnMagicEffectApplyイベントを捕捉しています。これは非常に悪手だと思います。特にAbility型のCloakスペルとの相性が最悪で、あっという間に高負荷になるのでOnHitを使うように書き換えた方がいいでしょう。
mc_deatheffectsmagicscript.psc (papyrus)
Int lastEffectType = 0
; 普段はReadyステートに遷移しておく
Event OnEffectStart(Actor Target, Actor Caster)
; 中略
;RegisterForAnimationEvent(TargetRef, "KillMoveStart")
GoToState("Ready")
EndEvent
; OnCellDetachは全ステートで受け取る
Event OnCellDetach()
if TargetActor.IsDead()
if torched
TargetActor.AttachAshPile(DefaultAshPile1)
TargetActor.SetCriticalStage(4)
elseif burned
TargetActor.AttachAshPile(MC_AshPile1)
TargetActor.SetCriticalStage(4)
elseif iced
TargetActor.AttachAshPile(DefaultAshPileIce)
TargetActor.SetCriticalStage(4)
else
RemoveGore()
endif
endif
EndEvent
; 死んだらゴア発動
Event OnDeath(Actor akKiller)
UnregisterForUpdate()
;UnregisterForAnimationEvent(TargetRef, "weaponSwing")
;UnregisterForAnimationEvent(TargetRef, "KillMoveStart")
GotoState("Dead")
if lastEffectType >= 1
ElementalType(lastEffectType)
endif
EndEvent
; 魔法の効果切れ
Event OnUpdate()
lastEffectType = 0
EndEvent
; ステート遷移用
Function GoToReady()
EndFunction
; エンチャントからMagic Effectを得る
Function ParseEnchantmentMagicEffect(Enchantment akEnchantment)
Int iNumEffect = akEnchantment.GetNumEffects()
Int i = 0
while i < iNumEffect
MagicEffect kEffect = akEnchantment.GetNthEffectMagicEffect(i)
if kEffect.GetAssociatedSkill() == "Destruction"
Int iType = 0
if kEffect.HasKeyword(MagicDamageFire)
iType = 1
elseif kEffect.HasKeyword(MagicDamageFrost)
iType = 2
elseif kEffect.HasKeyword(MagicDamageShock)
iType = 3
endif
if iType >= 1
UnregisterForUpdate()
RegisterForSingleUpdate(5.0)
dt = kEffect.GetDeliveryType()
lastEffectType = iType
;Debug.Trace(TargetActor + " lastEffectType=" + lastEffectType)
return
endif
endif
i += 1
endwhile
EndFunction
; スペルからMagic Effectを得る
Function ParseSpellMagicEffect(Spell akSpell)
Int iNumEffect = akSpell.GetNumEffects()
Int i = 0
while i < iNumEffect
MagicEffect kEffect = akSpell.GetNthEffectMagicEffect(i)
if kEffect.GetAssociatedSkill() == "Destruction"
Int iType = 0
if kEffect.HasKeyword(MagicDamageFire)
iType = 1
elseif kEffect.HasKeyword(MagicDamageFrost)
iType = 2
elseif kEffect.HasKeyword(MagicDamageShock)
iType = 3
endif
if iType >= 1
UnregisterForUpdate()
RegisterForSingleUpdate(5.0)
dt = kEffect.GetDeliveryType()
lastEffectType = iType
;Debug.Trace(TargetActor + " lastEffectType=" + lastEffectType)
return
endif
endif
i += 1
endwhile
EndFunction
State Ready
Event OnHit(ObjectReference akAggressor, Form akSource, Projectile akProjectile, bool abPowerAttack, bool abSneakAttack, bool abBashAttack, bool abHitBlocked)
GotoState("Busy")
if akSource
if akSource as Spell
mage = akAggressor
ParseSpellMagicEffect(akSource as Spell)
elseif akSource as Enchantment
mage = akAggressor
ParseEnchantmentMagicEffect(akSource as Enchantment)
endif
endif
Utility.Wait(1.0)
GoToReady()
EndEvent
EndState
State Busy
Function GoToReady()
GoToState("Ready")
EndFunction
EndState
Function ElementalType(Int type)
if !iced
if type == 1
if !burned
ElementalDeath(1)
endif
elseif type == 2
ElementalDeath(2)
elseif type == 3
if !torched
ElementalDeath(3)
endif
endif
endif
EndFunction
まず、 OnMagicEffectApplyとOnAnimationEventは使わないのですべて削除します。Killmove関連も使いませんので削除です。
OnHitで攻撃を受けたら、スペルまたはエンチャントである場合に属性を記憶します。リセットは5秒としました。属性攻撃を受けてから5秒以内に死んだらゴアを発動です。
エンチャントは物理攻撃、エンチャント攻撃の順にOnHitが発火した場合はエンチャント側が無視されてしまいます。これをエンチャント攻撃だけを受け取る方法が私にはわかりませんでした。ですが、雷撃付きクロスボウでテストした結果、概ね雷撃ゴアが発生したので大丈夫みたいです。
ElementalType関数はMagic Effectを受け取る処理から属性番号を受け取る処理に置き換えます。
あるいは、スペルに一定時間継続する効果を追加して、その効果の有無で判定すればOnHitすらなくせます。これなら判定漏れはありません。ただし競合しやすいのと、すべての破壊魔法に手を入れる必要があるのが難点です。
他の種族について
それぞれ物理と魔法でわかれていますが、基本的な考え方は同じです。
専用の処理が追加されていて微妙に異なっている点には注意します。
ウ○チについて
初期のバージョンではたくさん出たらしいです。いまのバージョンはMCMで無効にできて、有効にしても1回しか出ないようです。
手を加えてたくさん出るようにしてみました。
処理は魔法版のTorch関数になります。種族によってはないので注意します。
Torch関数 (papyrus)
Function Torch()
torched = true
; なんかのエフェクトを再生させている
(MC_EffectShaders.GetAt(0) as EffectShader).Play(TargetRef, 1.0)
(MC_EffectShaders.GetAt(2) as EffectShader).Play(TargetRef, 2.0)
; 死体に物理的な力を加えることで痙攣を表現している
TargetRef.ApplyHavokImpulse(0.000000, 0.000000, 1.00000, 50.0)
; 適宜間隔をあける
Utility.Wait(0.5)
TargetRef.ApplyHavokImpulse(0.000000, 0.000000, 1.00000, 100.0)
Utility.Wait(0.5)
TargetRef.ApplyHavokImpulse(0.000000, 1.000000, 1.00000, 100.0)
Utility.Wait(1.0)
(MC_EffectShaders.GetAt(2) as EffectShader).Play(TargetRef, 10.0)
TargetRef.ApplyHavokImpulse(0.000000, 0.000000, 1.00000, 50.0)
Utility.Wait(Utility.RandomFloat(0.2, 1.0))
TargetRef.ApplyHavokImpulse(1.000000, 0.000000, 1.00000, 50.0)
Utility.Wait(Utility.RandomFloat(0.2, 1.0))
TargetRef.ApplyHavokImpulse(0.000000, 0.000000, 1.00000, 50.0)
Utility.Wait(Utility.RandomFloat(0.2, 1.0))
TargetRef.ApplyHavokImpulse(1.000000, 0.000000, 1.00000, 50.0)
Utility.Wait(Utility.RandomFloat(0.2, 1.0))
TargetRef.ApplyHavokImpulse(0.000000, 1.000000, 1.00000, 50.0)
Utility.Wait(Utility.RandomFloat(0.2, 1.0))
TargetRef.ApplyHavokImpulse(0.000000, 0.000000, 1.00000, 50.0)
Utility.Wait(Utility.RandomFloat(0.2, 1.0))
if MC_Poop.GetValue() == 1
; これはNoneになるから動いていない
;(MC_BloodSpellsHumanoids.GetAt(10) as Spell).Cast(TargetRef, TargetRef)
; esp側で6番目を足しておく
(MC_BloodSpellsHumanoids.GetAt(6) as Spell).Cast(TargetRef, TargetRef)
; explosionを発生させる。これがウ○チになる
explod1 = TargetRef.placeatme(MC_Explosions.GetAt(4) as Explosion, 1, false, false)
TargetRef.ApplyHavokImpulse(0.000000, 0.000000, 1.00000, 150.0)
Utility.Wait(1.0)
endif
(MC_EffectShaders.GetAt(4) as EffectShader).Play(TargetRef, -1.0)
if MC_Poop.GetValue() == 1 && Utility.RandomInt(0, 9) == 0
; たくさん出すモード(10%で発生)
explod2 = TargetRef.placeatme(MC_Explosions.GetAt(4) as Explosion, 1, false, false)
TargetRef.ApplyHavokImpulse(0.000000, 0.000000, 1.00000, 150.0)
Utility.Wait(0.2)
explod3 = TargetRef.placeatme(MC_Explosions.GetAt(4) as Explosion, 1, false, false)
Utility.Wait(0.2)
explod4 = TargetRef.placeatme(MC_Explosions.GetAt(4) as Explosion, 1, false, false)
Utility.Wait(0.2)
Int i = Utility.RandomInt(0, 2)
while i > 0
i -= 1
explod2.Delete()
explod2 = TargetRef.placeatme(MC_Explosions.GetAt(4) as Explosion, 1, false, false)
TargetRef.ApplyHavokImpulse(0.000000, 0.000000, 1.00000, 150.0)
Utility.Wait(0.2)
explod3.Delete()
explod3 = TargetRef.placeatme(MC_Explosions.GetAt(4) as Explosion, 1, false, false)
Utility.Wait(0.2)
explod4.Delete()
explod4 = TargetRef.placeatme(MC_Explosions.GetAt(4) as Explosion, 1, false, false)
Utility.Wait(0.2)
explod2.Delete()
explod2 = TargetRef.placeatme(MC_Explosions.GetAt(4) as Explosion, 1, false, false)
TargetRef.ApplyHavokImpulse(0.000000, 0.000000, 1.00000, 150.0)
Utility.Wait(0.2)
explod3.Delete()
explod3 = TargetRef.placeatme(MC_Explosions.GetAt(4) as Explosion, 1, false, false)
Utility.Wait(0.2)
explod4.Delete()
explod4 = TargetRef.placeatme(MC_Explosions.GetAt(4) as Explosion, 1, false, false)
Utility.Wait(0.2)
endwhile
else
; 少し出すモード(それぞれ50%で発生、最大で3回分追加)
if MC_Poop.GetValue() == 1 && Utility.RandomInt(0, 1) == 0
explod2 = TargetRef.placeatme(MC_Explosions.GetAt(4) as Explosion, 1, false, false)
TargetRef.ApplyHavokImpulse(0.000000, 0.000000, 1.00000, 150.0)
else
TargetRef.ApplyHavokImpulse(0.000000, 1.000000, 1.00000, 50.0)
endif
Utility.Wait(Utility.RandomFloat(0.2, 1.0))
if MC_Poop.GetValue() == 1 && Utility.RandomInt(0, 1) == 0
explod3 = TargetRef.placeatme(MC_Explosions.GetAt(4) as Explosion, 1, false, false)
TargetRef.ApplyHavokImpulse(0.000000, 0.000000, 1.00000, 150.0)
else
TargetRef.ApplyHavokImpulse(0.000000, 1.000000, 1.00000, 50.0)
endif
Utility.Wait(Utility.RandomFloat(0.2, 1.0))
if MC_Poop.GetValue() == 1 && Utility.RandomInt(0, 1) == 0
explod4 = TargetRef.placeatme(MC_Explosions.GetAt(4) as Explosion, 1, false, false)
TargetRef.ApplyHavokImpulse(0.000000, 0.000000, 1.00000, 150.0)
else
TargetRef.ApplyHavokImpulse(0.000000, 1.000000, 1.00000, 50.0)
endif
endif
; 発生させたexplosionは必ず消すこと
if MC_Poop.GetValue() == 1
Utility.Wait(1.0)
if explod1
explod1.Delete()
explod1 = none
endif
if explod2
explod2.Delete()
explod2 = none
endif
if explod3
explod3.Delete()
explod3 = none
endif
if explod4
explod4.Delete()
explod4 = none
endif
endif
EndFunction
互換性について
キルムーブの誤認識
キルムーブ発生時にゴアを適用しているため、特殊な状況下ではゴアが誤適用されてしまいます。
具体的にはzxlice BackStab and Parry SSE - Script Freeのバックスタブなどが該当します。
まず、不死属性 (IsEssentialがTrueを返すActor) が付いているNPCはゴアの対象外なので問題ありません。
不死属性が付いていないNPCがバックスタブを受けると、キルムーブを受けたと判定されてゴアが適用されてしまします。
フォロワーはEssentialが付いていないことがほとんどです。かわりにProtectedが付いています。なのでゴアが誤適用されうるのです。
キルムーブの処理で対象が死んでいることを確認するとよいでしょう。
刀のパワーアタックを認識しない
Immersive Weaponsで追加される刀ですが、パワーアタックを出したにもかかわらず、OnHitイベントのabPowerAttackが常にFalseを渡すせいで、刀ではゴアが発動しません。
Immersive Weaponsの刀にキーワードを付与してアニメーションを変更する例をDynamic Animation Replacerで解説しています。この環境での話になります。アニメーションを変更しなければ問題ないのかもしれません。
刀でパワーアタックと認識されない問題の修正 (papyrus)
Event OnHit(ObjectReference akAggressor, Form akSource, Projectile akProjectile, bool abPowerAttack, bool abSneakAttack, bool abBashAttack, bool abHitBlocked)
GotoState("Busy")
if TargetActor.IsDead()
if abPowerAttack
; ここにゴア適用の処理
elseif (akSource as Weapon) && akAggressor
if akSource.HasKeywordString("WeapTypeKatana")
; ここにゴア適用の処理
; 上と全く同じでよい
elseif akAggressor.HasKeyword(ActorTypeCreature)
; ここにクリーチャー用のゴア適用の処理
; 上とは少し違うので注意
endif
endif
; 死んでいるならステートを戻さずにこのまま終わってよい
return
endif
Utility.Wait(1.0)
GoToReady()
EndEvent
刀でとどめを刺した場合は常にゴアを発動させるようにしました。理想はパワーアタックを認識できることですが、abPowerAttack引数が使えないとなるとかなり回りくどい方法を取らないと無理なので、これで妥協です。
何が何でもパワーアタックを判定させるなら、イベントでパワーアタックの開始と終了を見てフラグを立てる、パワーアタック中のみ発動するスペルを用意してスペルの発動を見る、といった方法になると思います。