Jump to content
模组网
icedream

破衣框架BUFramework_v1.01解析

Recommended Posts

本文转自《破衣框架BUFramework_v1.01解析》,原作者为newalbert。
字符串,数组,进而是用户自定义函数,obse团队为脚本打造了一个完整的支持平台,按理说脚本类MOD现在才真正到了发力的时候。

但现实往往就是这么让人嗟叹,当我们把所有的工具都准备好时,回首一望,却人去楼空,只留下前辈们夺目的艺术成果,和一个个离去的脚印。。。

基本没有更新的wiki,豪华璀璨但后继乏力的T网,论坛中已经神隐或即将神隐的高人。。。
这些残酷的现状都在告诉俺们,上古卷轴4的路已经快走完了,辉煌的顶点已经离我们远去,一切都将成为过眼烟云。。。


俺真的很扼腕,如此晚才接触这样的神作,错过了如此多的高人,错过了如此开放共享的团队。。。

不过俺仍然一直在努力,既是为下一次机会未雨绸缪,也是为有兴趣加入游戏modder行列的新人们降低一点点门槛。。。

俺知道能看完这样一大篇纯文字帖子的人不多,俺只是对从各位高人手里伸手拿东西的一点点小回报。。。


好了,废话说完,进入正题!!!


为何拿破衣框架来说事?并不是因为这个带点邪恶性质的mod能吸引眼球(说实话,俺觉得还是有点)。。。

而是这个小小的mod使用到了obse新的数据结构string,array和强大的用户自定义函数function,对于脚本进阶的童鞋有重要的参考和学习意义。。。


作者将框架代码和使用代码作为txt开放出来,对阅读和分析应该没有做限制,俺使用1.01版为大家做个介绍
首先对作者表示由衷的感谢,BUFramework是作为一个esm存在的,也就是说任何esp都可以很方便的使用其中定义的内容和函数,就像它们本来就存在与老滚中一样。。。



破衣框架使用原文:
 
; 個々の装備に貼り付けるオブジェクトスクリプト
scn BUDWDScript

array_var param
int ret

ref rEquip
array_var aEquip
array_var aMesP
array_var aMesA

Begin gamemode
let ret := Call BUCmnFnc param aEquip aMesP aMesA
if (ret == 0)
        return
endif
if (ret == 2)
        let param := aEquip := aMesP := aMesA := ar_Null
        return
endif

; 初期化処理
let aEquip := ar_Construct Array
let aMesP := ar_Construct Array
let aMesA := ar_Construct Array

; 個別に変更が必要なのはここから↓
        ; 対象装備のEditorIDを指定(EditorIDが数字で始まる場合は""でくくる)
        set rEquip to BUDWD

        ; 各破壊段階でのnifファイルを指定。段階数は任意。
        ; 初期状態
        let aEquip[ar_Size aEquip] := "BreakUndiesDWDShortDressE1.nif"
        let aEquip[ar_Size aEquip] := "BreakUndiesDWDShortDressE2.nif"
        let aEquip[ar_Size aEquip] := "BreakUndiesDWDShortDressE3.nif"
        let aEquip[ar_Size aEquip] := "BreakUndiesDWDShortDressE4.nif"
        ; 耐久度0の状態

        ; 上の段階数-1個分のメッセージを設定(プレイヤー側)
        let aMesP[ar_Size aMesP] := "激しい戦闘で服が破けた…!(1回目)"
        let aMesP[ar_Size aMesP] := "激しい戦闘で服が破けた…!(2回目)"
        let aMesP[ar_Size aMesP] := "激しい戦闘で服が破けた…!(3回目)"
        ; メッセージ設定(敵側)
        let aMesA[ar_Size aMesA] := "激しい戦闘で敵の服が破けた…!(1回目)"
        let aMesA[ar_Size aMesA] := "激しい戦闘で敵の服が破けた…!(2回目)"
        let aMesA[ar_Size aMesA] := "激しい戦闘で敵の服が破けた…!(3回目)"
; ↑ここまで

let param := ar_List 0, 0, 0, (ar_Size aEquip) - 1, rEquip
; 初期化処理ここまで
End

首先来看看怎么使用,使用破衣框架很简单:
1:在你的装备上做一个Object script

2:将上面的原文贴进去,把第一行的scn BUDWDScript换成你自己取的名字
        scn XXX

3:在aEquip数组中填入装备各阶段的nif路径

4:将rEquip赋值为自己装备的Editor ID
        set rEquip to XXX


好了,搞定了,就这么简单,你的装备现在具有了破衣属性,阶段数就是你填入aEquip数组的元素个数,其它一切都不需要你操心,破衣框架会自动帮你完成。

当然,你也可以把破衣时输出的提示换成其它文字,中文英文日文德文任君所好。。。

如果你是个装备modder,只想使用破衣框架来制作破损盔甲,那么按上面这样使用,就一切都ok了。






如果你是个script modder,想研究学习破衣框架的实现细节,并对obse中较新的几种数据结构和强大的用户自定义函数感兴趣,那么请往下看

下文需要一些编程基础知识,虽然老滚本身的脚本很直白,但obse的扩展,不借助一些术语确实不好解释,抱歉。。。


一:obse新数据结构

1,字符串

定义方式:
string_var s

赋值:
字符串可以显式赋值,如
string_var string1
set string1 to sv_Construct "First string"

或者通过隐式赋值,如破衣框架中的
let s := aEquip[0]

set语法是老滚脚本默认支持的赋值语句。
let,eval等语法是obse的扩展语句,当使用字符串,数组等obse扩展数据类型时,最好使用obse扩展赋值语句。

说明:
字符串是OBSE v0016新定义的数据类型,在v0017中完善,使用字符串可以很灵活的处理文本和信息。

注意:
* 释放
字符串使用完后,需要释放掉,否则导致内存泄漏。
当没有参考指向一个字符串的内存时,obse会自行释放,不过作为一个好的习惯,当一个字符串不需要时,最好主动释放:
sv_Destruct s

* 拷贝和赋值
字符串变量间的直接赋值使用的是引用拷贝,而非内存拷贝,例如

string_var string1
string_var string1
set string1 to sv_Construct "First string"
set string2 to string1

string2和string1都会指向数据"First string",改变其中一个同样也会改变另外一个

set string2 to sv_Construct "First string"

这时,虽然string1和string2都是内容"First string",但指向了不同的内存地址,改变一个不会影响另一个


tips:
当遇到需要使用字符串作为参数的老滚函数时,可以使用“$”来将一个字符串变量变成一个立即数,框架中同样也用到了


2,数组

定义方式:
array_var a

赋值:
数组可以显式赋值,如
array_var a
let a := ar_Construct Array
let a := ar_Construct Map
let a := ar_Construct StringMap

或者通过隐式赋值,某些obse的函数会返回一个数组类型
let a := 某个返回数组的函数

说明:
数组是OBSE v0017新定义的数据类型,在v0018中完善,obse的数组是一个包含传统array和关联array的强大数据结构
array_val类型有三种内部实现,Array(普通数组,以正整数为key),Map(以负数或浮点数作为key的map表),StringMap(以string作为key的map表)

注意:
* 释放
数组使用完后,需要释放掉,否则导致内存泄漏。
当没有参考指向一个数组的内存时,obse会自行释放,同样,好的习惯是当一个数组不需要时,主动释放掉:
let a := ar_Null

* 拷贝和赋值
跟字符串一样,数组间的直接赋值使用的是引用拷贝,而非内存拷贝



二:用户自定义函数
感谢obse伟大的努力!
在obse v0018中,实现了长久以来的script modder的一个怨念:自定义函数!!!


熟悉编程的朋友都知道,函数库是代码共享和复用的根本,老滚现在终于有了可复用的自定义函数结构,使用函数的好处俺就不多废话了,直接看语法

1,定义
函数脚本必须是Object script,且只能有一个块:Begin Function,参数使用{}确定,参数的类型使用预声明的本地变量来指定

scn BUCmnFnc

array_var param
array_var aEquip
array_var aMesP
array_var aMesA

Begin Function { param aEquip aMesP aMesA }
        SetFunctionValue 0
        return
End

这就是一个完整的函数定义

2,参数
所有使用的参数必须都预先声明出类型,参数个数最多10个

3,返回值
SetFunctionValue语句设置函数的返回值
任何obse支持的类型都可以作为返回值,包括string和array
如果没有指定任何返回值,函数默认返回数字0

4,tips:
自定义函数支持递归,但不要多于10层
自定义函数因为是Object类的脚本,所以无法使用fquestdelaytime,执行频率取决了外部调用

5,调用
使用Call语句来调用自定义函数,例如:

let ret := Call BUCmnFnc param aEquip aMesP aMesA

ret保存为函数的返回值

Call后的第一个参数BUCmnFnc是函数名,也就是函数脚本第一行的scn BUCmnFnc

函数名后的参数是真正传递给函数的参数,务必与函数定义中{}中的参数个数和类型相符,虽然函数定义中的参数以“,”分隔,但调用的时候使用空格来分开各个参数

破衣框架原文:

; Break Undies Common Function
;  各装備には状態保持用の配列と初期化処理、最低限のコードだけを貼り付け
;  大半の処理はここで行う。
scn BUCmnFnc

array_var param
array_var aEquip
array_var aMesP
array_var aMesA
ref who

int nEquip
ref rEquip
int break
int state
string_var s

Begin Function { param aEquip aMesP aMesA }
set who to getcontainer
if (who.IsActor == 0 || who.GetDead)
        ; アクターが所持していないか死んでる場合
        if (param == ar_Null)
                SetFunctionValue 0
                return
        endif

        if eval(param[2] != 0)        ; break?
                ; 念のため装備のNif指定を初期に戻す。
                let rEquip := param[4]
                let s := aEquip[0]
                ; 装備し直さないのでその場で表示は変わらない。
                SetFemaleBipedPath $s rEquip
                sv_Destruct s
                let param[2] := 0
        endif
        SetFunctionValue 2                ; 解放
        return
endif

if (param == ar_Null)
        SetFunctionValue 1                ; 初期化が必要
        return
endif

SetFunctionValue 0

if eval(param[0] > 0)                        ; need wait?
        let param[0] := param[0] - 1
        return
endif

; 再装備の必要があるか?
if eval(param[1] & 1)                                        ; change?
        let rEquip := param[4]
        who.equipItemNS rEquip
        let param[0] := 1                                ; wait
        let param[1] := param[1] & 2147483646                ; flags &= 0x7FFFFFFE
        return
endif

if eval((param[1] & 2) == 0)
        let param[1] := param[1] | 2                                ; flags |= 2
        let s := aEquip[0]
        let rEquip := param[4]
        SetFemaleBipedPath $s rEquip
        sv_Destruct s
        if (who.GetEquipped rEquip)
                ; 表示を反映させるために脱着。
                who.UnequipItemNS rEquip
                let param[0] := 1                                                        ; wait
                let param[1] := param[1] | 1                        ; flags |= 1
                return
        endif
endif

; 現在の装備の状態
let nEquip := param[3]
set state to nEquip * (1.0 - (GetCurrentHealth / GetObjectHealth))
; 上限下限チェック
if (state < 0)
        set state to 0
elseif (state > nEquip)
        set state to nEquip
endif

let break := param[2]
if (GetGameLoaded == 1)
        let break := 0
endif
if (break != state)        ; 表示と状態が異なる場合
        if (break < state)                ; 破壊が進行
                if (who.IsInCombat)
                        if (who == player)                ;-- 戦闘中で、プレイヤーが着ていたら以下を実行
                                let s := aMesP[break]
                        else                                        ;-- Actorであり戦闘中ならメッセージを流す
                                let s := aMesA[break]
                        endif
                        message $s
                        ; effectの発生 effectBreakArmor を0.3秒発生させる
                        who.pms effectBreakArmor 0.3
                endif
                let break += 1
        else                ; 修復した場合
                let break -= 1
        endif

        ; 後で装備した時に表示が一致するように
        ; 装備しているかどうかに関わらずパスを変えておく。
        let rEquip := param[4]
        let s := aEquip[break]
        SetFemaleBipedPath $s rEquip
        sv_Destruct s

        if (who.GetEquipped rEquip && break == state)
                ; 表示を反映させるために脱着。
                who.UnequipItemNS rEquip
                let param[0] := 1                                                        ; wait
                let param[1] := param[1] | 1                        ; flags |= 1
        endif
endif
let param[2] := break
End

有了string,array和function的基础知识,上面这段代码自然也就比较容易读懂了,需要注意的几个地方如下:

1,位操作
obse支持位操作,逻辑与逻辑或什么的,但不支持16进制立即数,所以,要将最后一位置0,只能这样干

let param[1] := param[1] & 2147483646

后面的注释写得很明确,这行的意思跟let param[1] := param[1] & 0x7FFFFFFE一样

2,SetFemaleBipedPath
这是设定一件装备的nif文件路径的函数,当装甲需要破损时,使用这个函数将装备的nif文件设置成下一阶段
需要先卸下再装备,效果才能出现

3,字符串变量转为立即数

SetFemaleBipedPath $s rEquip

SetFemaleBipedPath函数的第一个参数需要指定一个字符串立即数,也就是用""包含的文本,obse字符串的“$”操作符可以将string变量转换为立即数

4,效果渲染
who.pms effectBreakArmor 0.3

pms是PlayMagicShaderVisuals函数的简称,意思是播放一段可视化效果

效果名为第一个参数,这里是effectBreakArmor,这是破衣框架自己实现的一个效果:碎片四散
播放时间是第二个参数,这里是0.3秒,俺个人认为改成1秒更好

5,注意

IsInCombat

这个函数是用来判断一个actor是否处于战斗状态,使用时一定注意用actorRef.IsInCombat的语法,不要用IsInCombat actorRef
虽然cs不会报错,但你的所有脚本将会失效,这是个bug !

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×
×
  • Create New...