GetIsRace

おかしな挙動を確認したのでメモ。

if GetIsRace Dremora == 0

OBSEのユーザー定義関数の中でこの記述をしたら、ここで処理が中断されるという動作を確認しました。
処理が中断され、きちんとユーザー定義関数を抜けるのですが、2回目はこのユーザー定義関数を呼び出せなくなりました。
意味が分かりません。

let is_dremora := GetIsRace Dremora

戻り値を変数に格納することで、正常に動くようになりました。
条件式での関数呼び出しは避けた方が良いようです。
以前にも同じような記事を書いた気がする…。
 

スクリプトで自動的に待機させる

スクリプトで待機メニューを表示して、勝手に待機時間を決定して、勝手に待機ボタンを押す方法。
もっとエレガントな方法がありそうなんですが、この方法しか思いつきませんでした。
また、色々と変な暗黙の了解というか罠があり、試行錯誤の末にできたものです。

クエストスクリプトとして作成。

ScriptName なんかてきとーなの

float fQuestDelayTime
short state
float timer

begin GameMode
  let fQuestDelayTime := 0.1
  let state := 0
  if OnKeyDown 78 ;テンキーの+
    let state := 1
    let timer := 6.0 ;待機するゲーム内時間
    TapControl 16
  endif
end

begin MenuMode 1012
  if state == 2 || state == 3
    float parent_w
    float marker_w
    float min
    float range
    float marker_x
    let parent_w := GetMenuFloatValue "sleep_background\sleep_scroll\width" 1012
    let marker_w := GetMenuFloatValue "sleep_background\sleep_scroll\horizontal_scroll_marker\width" 1012
    let min := GetMenuFloatValue "sleep_background\sleep_scroll\user1" 1012
    let range := GetMenuFloatValue "sleep_background\sleep_scroll\_scrollable_range" 1012
    let marker_x := (parent_w - marker_w) * (timer - min) / range
    SetMenuFloatValue "sleep_background\sleep_scroll\horizontal_scroll_marker\x" 1012 marker_x
    SetMenuFloatValue "sleep_background\sleep_scroll\user7" 1012 timer
    SetMenuStringValue "sleep_background\rest_time_text\string|%.0f" timer 1012
  endif
  if state == 1
    ClickMenuButton "sleep_background\sleep_button_sleep_wait" 1012
    let state := 2
  elseif state == 2 && IsTimePassing
    let timer -= GetSecondsPassed
    if timer < 0
      let timer := 0
    endif
  endif
end

テンキーの+キーを押したら、スクリプトで待機メニューを表示(TapControl 16)して、指定した待機時間が勝手に設定され、勝手に待機ボタンが押され(ClickMenuButton)ます。

SetMenuFloatButtonで待機メニューにあるスクロールバーの情報を色々設定するんですが、機能として重要なのは sleep_background\sleep_scroll\user7 のみで、後は見た目に影響するだけです。
スクロールバーボタンの位置がおかしくても、待機時間の表示テキストがおかしくても、前述の設定さえしておけば、きっちり設定した時間分の待機はしてくれます。

fQuestDelayTimeに設定した時間間隔で待機メニューのスクロールバーボタンが動きます。
サンプルだと0.1秒毎に残りの待機時間を調べて、スクロールバーボタンの位置を適切な位置に変更しています。
見た目に関しては、ひとつ改良の余地があります。
待機メニューにはゲーム内の時刻が表示されていますが、この時刻が変わるタイミングと、残り待機時間が更新されるタイミングがちょっとズレています。
長い時間待機するほど、このズレが大きくなります。
気になるなら、このタイミングの調整が必要です。

通常では、待機時間を決定して待機ボタンを押すと、残りの待機時間を表すスクロールバーボタンが1秒ごとに←に動いて行きます。
ゲーム起動後の初回の待機メニューだと、何故かスクロールバーボタンが動いてくれません。
二回目以降は問題なく動きます。
仕方がないので、スクリプトで強制的に位置を変えています。

敵が近くにいると待機できないのと、敵が近づくと待機状態が解除されます。
そこは通常の待機メニューと同じです。

気になっている点がふたつあります。

1つは、Vanillaのものではなく、独自のHUD(Darkなんとかかんとか等)を使っている場合にきちんと動くか?という点。
もう1つは、待機時間を設定するスクロールバーの1メモリあたりの変化量を調整するようなMODを使っている場合にきちんと動くか?という点です。
例えば、Vanillaだと最大でも24時間しか待機できませんが、一度に1000時間まで待機できて、5時間ずつしか調整できないというようなケース。
 

他のMODのアイテムをAddItemする(OBSE)

他のMODにあるアイテムを自分のMODでAddItemしたい場合、よくある手順は、CSで編集する時に自分のMODと、そのMODの両方にチェックをつけ、自分のMODに Set as Active File を指定する…と考えがちですが、これだとうまく行きません。


string_var mod_name
string_var form_id6
ref form_id

let mod_name := "test.esp"

;AddItemしたいmod_nameにあるアイテムのFormID
;先頭の2桁を除去し、残り6桁を指定する

let form_id6 := "123456"

let form_id := GetFormFromMod $mod_name $form_id6
if form_id
  player.AddItem form_id 1
endif
sv_destruct mod_name form_id6

mod_name と form_id6 は文字列リテラルで指定しても問題ありません。
GetFormFromMod "test.esp" "123456"
肝は GetFormFromMod で、引数に string_var を渡す時は、先頭に $ を付けないと動作しません。

サンプルコードでは、playerにAddItemしていますが、自分のMODのコンパニオンや、自分のMODで配置しているチェストにも使えます。

この方法なら、CSで編集する際に他のMODを読み込む必要がありません。
 

死体を解体する 5

・周りに善良な人がいる時に死体を解体すると人殺しと間違われる

どうにも良い方法が思い浮かばなかったため、Dan's Necromancyを参考にさせていただきました。

該当するスクリプトを抜粋

ScriptName DJCDetectionSpellScript

ref Target

Begin ScriptEffectStart
  Set Target to GetSelf
  If Target != Player
    If GetDetectionLevel Player > 2 && GetActorValue Responsibility >= 30 && GetDead == 0
      If DJCNecroGeneralQuest.Reported == 0
        Player.ModCrimeGold 500
        Set DJCNecroGeneralQuest.Reported to 1
      EndIf
      Say Murder
    EndIf
  EndIf
End



範囲魔法なんですね。
魔法の効果範囲内にいるNPCがプレイヤーに気づいてて、責任感が30以上で生きてるなら、プレイヤーに500ゴールドの懸賞金をかけ、NPCが人殺し!というセリフを言う。

自分の力不足を思い知らされます。

高額の懸賞金をかければガードが逮捕しに来ます。
「人殺しだー!」というセリフは無理やり言わせれば良いと。

範囲魔法にしてしまえば、GetFirstRefを使う必要もなし。
恐らく、GetFirstRefで周囲のNPC全てに処理するより負荷は低いでしょう。

やり方は分かりました。
次は実際にどういう形で組み込むかですね。

1同じように範囲魔法で作る→誰にキャストさせるか?
2GetFirstRefで近くにいる全てのNPCに処理をする
 

死体を解体する 4

不具合はひと通り解消して、最後の機能の組み込みまで来ました。

・周りに善良な人がいる時に死体を解体すると人殺しと間違われる

人殺しアラームを出す関数があると思ったんですが、TES4 Scriptにはありませんでした。
OBSEのドキュメントを見ると SetPCAMurderer という関数がありました。

SetPCAMurderer 1 でプレイヤーが人殺しになるようなんですが、詳しい説明が一切ありません。
関数を実行したフレームだけ人殺し扱いなのか、SetPCAMurderer 0 を実行するまでずっと人殺しなのか、何かのタイミングでリセットされるのか、友好度や名声は影響するのか…何も分かりません。
CS Wikiにも説明はありませんでした。

とりあえず、解体時にこの関数を実行するようにスクリプトを変更。
宿屋のおやっさんの目の前で死体を解体したのですが、おやっさんの対応は何も変わらず。
友好度も変化していませんでした。

グーグル先生に聞くと、OBSEのソースコードが出てきました。
処理を見ると、人殺しフラグをセットしてるだけみたいです。

このフラグの値を見て、別の場所で何かアラームを出すような処理をしている可能性もあります。

TES4 Scriptのヘルプで IsPCAMurderer を調べると、プレイヤーがNPCを殺したことがあるかどうか?を調べる関数と対応しているっぽいことが分かります。
どうも、SetPCAMurderer は、単にNPC殺したことある?フラグを操作するだけで、周囲に犯罪アラームを出すということはないようです。
闇兄弟のクエスト開始条件として使うんでしょう。

もうひとつ、NPC.kill player とすることで、playerがNPCを殺したことにできるようなのですが、NPCが既に死んでいる場合、効果はあるんでしょうか?

試してみましたが、同じく変化なし。

試しに、SendTresspassAlarm player を使用。
不法侵入の警報を出す関数です。
どうも、この関数より下にある処理が実行されなくなるようなので、処理の一番最後に移動。

変化なし。

・・・

死霊術MODで、死体解体時に周囲に犯罪アラームを出すものがあった気がします。
参考になるかも知れません。
 
×

この広告は1年以上新しい記事の投稿がないブログに表示されております。