Modで追加された防具はパラメータが不適切なものが多いです。基本的にはSSEEditで調整していくしかありません。
手で直していくのは大変だったので、SSEEdit用スクリプトを用意して機械的に処理させてみました。
キーワードを適切なものにする
多くのModが防具のキーワードを見て動作しています。不適切なキーワードはバランス崩壊の原因になるので、適切なものにします。
特に材質は重要です。YASHもKYEも材質のキーワードでペナルティを決定しています。
TAWOBAは部位のキーワードをたくさん付けているので、KYEの強化と弱点が多重に適用されてしまいます。以下のスクリプトを使うと余分なキーワードを取り除きます。
Armor - Fix Armor Keyword.pas (pascal)
{
Remove incorrect armor keyword.
}
unit userscript;
//uses mteFunctions;
{
mteFunctions
https://github.com/matortheeternal/mteFunctions/blob/master/LICENSE
}
{
ElementByIP:
Element by Indexed Path
This is a function to help with getting at elements that are inside
lists. It allows you to use an "indexed path" to get at these elements
that would otherwise be inaccessible without multiple lines of code.
Example usage:
element0 := ElementByIP(e, 'Conditions\[0]\CTDA - \Function');
element1 := ElementByIP(e, 'Conditions\[1]\CTDA - \Function');
}
function ElementByIP(e: IInterface; ip: string): IInterface;
var
i, index: integer;
path: TStringList;
begin
// replace forward slashes with backslashes
ip := StringReplace(ip, '/', '\', [rfReplaceAll]);
// prepare path stringlist delimited by backslashes
path := TStringList.Create;
path.Delimiter := '\';
path.StrictDelimiter := true;
path.DelimitedText := ip;
// traverse path
for i := 0 to Pred(path.count) do begin
if Pos('[', path[i]) > 0 then begin
index := StrToInt(GetTextIn(path[i], '[', ']'));
e := ElementByIndex(e, index);
end
else
e := ElementByPath(e, path[i]);
end;
// set result
Result := e;
end;
{
GetTextIn:
Returns a substring of @str between characters @open and @close.
Example usage:
s := 'Hello [test] world';
AddMessage(GetTextIn(s, '[', ']')); // prints 'test'
}
function GetTextIn(str: string; open, close: char): string;
var
i, openIndex: integer;
bOpen: boolean;
begin
Result := '';
bOpen := false;
for i := 1 to Length(str) do begin
if not bOpen and (GetChar(str, i) = open) then begin
openIndex := i;
bOpen := true;
end;
if bOpen and (GetChar(str, i) = close) then begin
Result := CopyFromTo(str, openIndex + 1, i - 1);
break;
end;
end;
end;
{
GetChar:
Gets a character in a string and returns it.
Example usage:
s := '1234';
AddMessage(GetChar(s, 3)); //'3'
}
function GetChar(const s: string; n: integer): char;
begin
Result := Copy(s, n, 1);
end;
{
CopyFromTo:
A copy function that allows you to copy from one position to another.
This function is a better copy function, in my opinion.
Example usage:
s := 'Hi. I'm a cool guy.';
s := CopyFromTo(s, Pos('a', s), Pos('g', s));
AddMessage(s); //'a cool g'
}
function CopyFromTo(s: string; p1: integer; p2: integer): string;
begin
Result := '';
if p1 > p2 then exit;
Result := Copy(s, p1, p2 - p1 + 1);
end;
function IsValidArmorKeyword(e: IInterface; slotmask: integer): boolean;
var
i: integer;
s: string;
begin
Result := True;
s := IntToHex(GetNativeValue(e), 8);
if s = '0006C0EC' then begin // ArmorCuirass
if slotmask and $4 = 0 then begin
Result := False;
end;
end;
if s = '0006C0ED' then begin // ArmorBoots
if slotmask and $80 = 0 then begin
Result := False;
end;
end;
if s = '0006C0EE' then begin // ArmorHelm
if slotmask and ($1 + $1000) = 0 then begin
Result := False;
end;
end;
if s = '0006C0EF' then begin // ArmorGauntlets
if slotmask and $8 = 0 then begin
Result := False;
end;
end;
if s = '000965B2' then begin // ArmorShield
if slotmask and $200 = 0 then begin
Result := False;
end;
end;
end;
function Process(e: IInterface): integer;
var
slotmask, i, j, p: integer;
kwd, kwda, keyword: IInterface;
ev, id: string;
slKeywords: TStringList;
begin
// abort if this element is not an armor
if Signature(e) <> 'ARMO' then Exit;
// abort if this element is clothing
if GetElementNativeValues(e, 'BOD2\Armor Type') = 2 then Exit;
// get keywords
kwda := ElementBySignature(e, 'KWDA');
if not Assigned(kwda) then Exit;
// get slotmaks
slotmask := GetElementNativeValues(e, 'BOD2\First Person Flags');
// remove all incorrect keywords
// (does not work)
{
for i := Pred( ElementCount(kwda) ) downto 0 do begin
if not IsValidArmorKeyword( ElementByIndex(kwda, i), slotmask) then begin
AddMessage( 'remove ' + GetEditValue( ElementByIndex(kwda, i) ) + ' from ' + GetElementEditValues(e, 'FULL') );
RemoveByIndex(kwda, i, True);
end;
end;
}
// get valid keywords
slKeywords := TStringList.Create;
j := 0;
for i := Pred( ElementCount(kwda) ) downto 0 do begin
kwd := ElementByIndex(kwda, i);
if IsValidArmorKeyword(kwd, slotmask) then begin
ev := GetEditValue(kwd);
p := Pos(':', ev);
id := Copy(ev, p + 1, 8);
//AddMessage(id);
slKeywords.Add(id);
end else begin
AddMessage( 'remove ' + GetEditValue(kwd) + ' from ' + GetElementEditValues(e, 'FULL') );
end;
end;
// remove keywords
RemoveElement(e, 'KWDA');
// rebuild keywords
if slKeywords.Count > 0 then begin
// first one
Add(e, 'KWDA', True);
SetEditValue(ElementByIP(e, 'KWDA\[0]'), slKeywords[0]);
// second one
kwda := ElementBySignature(e, 'KWDA');
for i := 1 to slKeywords.Count - 1 do begin
keyword := ElementAssign(kwda, HighInteger, nil, False);
SetEditValue(keyword, slKeywords[i]);
end;
// update keyword count
SetElementNativeValues(e, 'KSIZ', slKeywords.Count);
end else begin
// remove keyword count
RemoveElement(e, 'KSIZ');
end;
slKeywords.Free;
end;
end.
TheAmazingWorldOfBikiniArmor.espに直接適用するのではなく、パッチを作成して適用することをオススメします。
防御力を一括で変換する
こちらはヴァニラ準拠の頭、胴、手、足、盾の5パーツ用です。材質と部位から防御力を決定します。
Armor - Fix Armor Rate.pas (pascal)
{
Fix armor rate for vanilla armor.
}
unit userscript;
function GetArmorMultiByKeyword(e: IInterface): double;
var
i: integer;
s: string;
begin
Result := 1.0;
for i := 0 to ElementCount(e) - 1 do begin
s := IntToHex(GetNativeValue( ElementByIndex(e, i) ), 8);
if s = '0006C0EC' then begin // ArmorCuirass
Result := 2.5;
break;
end;
if s = '0006C0ED' then begin // ArmorBoots
Result := 1.0;
break;
end;
if s = '0006C0EE' then begin // ArmorHelm
Result := 1.5;
break;
end;
if s = '0006C0EF' then begin // ArmorGauntlets
Result := 1.0;
break;
end;
if s = '000965B2' then begin // ArmorShield
Result := 2.0;
break;
end;
end;
end;
function GetBaseArmorRateByMaterial(e: IInterface): double;
var
i: integer;
s: string;
begin
Result := 10;
for i := 0 to ElementCount(e) - 1 do begin
s := IntToHex(GetNativeValue( ElementByIndex(e, i) ), 8);
if s = '0006BBD4' then begin // ArmorMaterialDaedric
Result := 18;
break;
end;
if s = '0006BBD5' then begin // ArmorMaterialDragonplate
Result := 17;
break;
end;
if s = '0006BBD6' then begin // ArmorMaterialDragonscale
Result := 12;
break;
end;
if s = '0006BBD7' then begin // ArmorMaterialDwarven
Result := 13;
break;
end;
if s = '0006BBD8' then begin // ArmorMaterialEbony
Result := 16;
break;
end;
if s = '0006BBD9' then begin // ArmorMaterialElven
Result := 8;
break;
end;
if s = '0006BBDA' then begin // ArmorMaterialElvenGilded
Result := 8;
break;
end;
if s = '0006BBDB' then begin // ArmorMaterialLeather
Result := 7;
break;
end;
if s = '0006BBDC' then begin // ArmorMaterialGrass
Result := 11;
break;
end;
if s = '0006BBDD' then begin // ArmorMaterialHide
Result := 5;
break;
end;
if s = '0006BBDE' then begin // ArmorMaterialScaled
Result := 9;
break;
end;
if s = '0006BBDF' then begin // ArmorMaterialStudded
Result := 10;
break;
end;
if s = '0006BBE0' then begin // ArmorMaterialImperialLight
Result := 6;
break;
end;
if s = '0006BBE1' then begin // ArmorMaterialImperialStudded
Result := 8;
break;
end;
if s = '0006BBE2' then begin // ArmorMaterialImperialHeavy
Result := 10;
break;
end;
if s = '0006BBE3' then begin // ArmorMaterialIron
Result := 10;
break;
end;
if s = '0006BBE4' then begin // ArmorMaterialIronBanded
Result := 11.2;
break;
end;
if s = '0006BBE5' then begin // ArmorMaterialOrcish
Result := 15;
break;
end;
if s = '0006BBE6' then begin // ArmorMaterialSteel
Result := 12;
break;
end;
if s = '0006BBE7' then begin // ArmorMaterialSteelPlate
Result := 14;
break;
end;
if s = '010009BD' then begin // ArmorMaterialFalmer
Result := 12;
break;
end;
if s = '010009C0' then begin // ArmorMaterialBlades
Result := 13;
break;
end;
if s = '04024100' then begin // DLC2ArmorMaterialBonemoldLight
Result := 7.2;
break;
end;
if s = '04024101' then begin // DLC2ArmorMaterialBonemoldHeavy
Result := 12;
break;
end;
if s = '04024102' then begin // DLC2ArmorMaterialChitinLight
Result := 8.5;
break;
end;
if s = '04024103' then begin // DLC2ArmorMaterialChitinHeavy
Result := 14;
break;
end;
if s = '04024104' then begin // DLC2ArmorMaterialNordicLight
Result := 9;
break;
end;
if s = '04024105' then begin // DLC2ArmorMaterialNordicHeavy
Result := 15;
break;
end;
if s = '04024106' then begin // DLC2ArmorMaterialStalhrinHeavy
Result := 17;
break;
end;
if s = '04024107' then begin // DLC2ArmorMaterialStalhrinLight
Result := 11.5;
break;
end;
end;
end;
function Process(e: IInterface): Integer;
var
ar, mult: double;
kwda: IInterface;
begin
// abort if this element is not an armor
if Signature(e) <> 'ARMO' then Exit;
// abort if this element is clothing
if GetElementNativeValues(e, 'BOD2\Armor Type') = 2 then Exit;
// get keywords
kwda := ElementBySignature(e, 'KWDA');
if not Assigned(kwda) then Exit;
// fix AR
ar := GetBaseArmorRateByMaterial(kwda);
mult := GetArmorMultiByKeyword(kwda);
ar := ar * mult * 100;
SetElementNativeValues(e, 'DNAM', ar);
end;
end.
こちらは追加パーツが用意されているMod用です。TAWOBAやLADXはこちらになります。頭、手、足はヴァニラと同じ防御力、胴と追加パーツについては低めにします。
Armor - Fix Armor Rate Bikini.pas (pascal)
{
Fix armor rate for TAWOBA.
}
unit userscript;
function GetArmorMultiByKeyword(e: IInterface): double;
var
i: integer;
s: string;
begin
Result := 0.5;
for i := 0 to ElementCount(e) - 1 do begin
s := IntToHex(GetNativeValue( ElementByIndex(e, i) ), 8);
if s = '0006C0EC' then begin // ArmorCuirass
Result := 0.5;
break;
end;
if s = '0006C0ED' then begin // ArmorBoots
Result := 1.0;
break;
end;
if s = '0006C0EE' then begin // ArmorHelm
Result := 1.5;
break;
end;
if s = '0006C0EF' then begin // ArmorGauntlets
Result := 1.0;
break;
end;
if s = '000965B2' then begin // ArmorShield
Result := 2.0;
break;
end;
end;
end;
function GetBaseArmorRateByMaterial(e: IInterface): double;
var
i: integer;
s: string;
begin
Result := 10;
for i := 0 to ElementCount(e) - 1 do begin
s := IntToHex(GetNativeValue( ElementByIndex(e, i) ), 8);
if s = '0006BBD4' then begin // ArmorMaterialDaedric
Result := 18;
break;
end;
if s = '0006BBD5' then begin // ArmorMaterialDragonplate
Result := 17;
break;
end;
if s = '0006BBD6' then begin // ArmorMaterialDragonscale
Result := 12;
break;
end;
if s = '0006BBD7' then begin // ArmorMaterialDwarven
Result := 13;
break;
end;
if s = '0006BBD8' then begin // ArmorMaterialEbony
Result := 16;
break;
end;
if s = '0006BBD9' then begin // ArmorMaterialElven
Result := 8;
break;
end;
if s = '0006BBDA' then begin // ArmorMaterialElvenGilded
Result := 8;
break;
end;
if s = '0006BBDB' then begin // ArmorMaterialLeather
Result := 7;
break;
end;
if s = '0006BBDC' then begin // ArmorMaterialGrass
Result := 11;
break;
end;
if s = '0006BBDD' then begin // ArmorMaterialHide
Result := 5;
break;
end;
if s = '0006BBDE' then begin // ArmorMaterialScaled
Result := 9;
break;
end;
if s = '0006BBDF' then begin // ArmorMaterialStudded
Result := 10;
break;
end;
if s = '0006BBE0' then begin // ArmorMaterialImperialLight
Result := 6;
break;
end;
if s = '0006BBE1' then begin // ArmorMaterialImperialStudded
Result := 8;
break;
end;
if s = '0006BBE2' then begin // ArmorMaterialImperialHeavy
Result := 10;
break;
end;
if s = '0006BBE3' then begin // ArmorMaterialIron
Result := 10;
break;
end;
if s = '0006BBE4' then begin // ArmorMaterialIronBanded
Result := 11.2;
break;
end;
if s = '0006BBE5' then begin // ArmorMaterialOrcish
Result := 15;
break;
end;
if s = '0006BBE6' then begin // ArmorMaterialSteel
Result := 12;
break;
end;
if s = '0006BBE7' then begin // ArmorMaterialSteelPlate
Result := 14;
break;
end;
if s = '010009BD' then begin // ArmorMaterialFalmer
Result := 12;
break;
end;
if s = '010009C0' then begin // ArmorMaterialBlades
Result := 13;
break;
end;
if s = '04024100' then begin // DLC2ArmorMaterialBonemoldLight
Result := 7.2;
break;
end;
if s = '04024101' then begin // DLC2ArmorMaterialBonemoldHeavy
Result := 12;
break;
end;
if s = '04024102' then begin // DLC2ArmorMaterialChitinLight
Result := 8.5;
break;
end;
if s = '04024103' then begin // DLC2ArmorMaterialChitinHeavy
Result := 14;
break;
end;
if s = '04024104' then begin // DLC2ArmorMaterialNordicLight
Result := 9;
break;
end;
if s = '04024105' then begin // DLC2ArmorMaterialNordicHeavy
Result := 15;
break;
end;
if s = '04024106' then begin // DLC2ArmorMaterialStalhrinHeavy
Result := 17;
break;
end;
if s = '04024107' then begin // DLC2ArmorMaterialStalhrinLight
Result := 11.5;
break;
end;
end;
end;
function Process(e: IInterface): Integer;
var
ar, mult: double;
kwda: IInterface;
begin
// abort if this element is not an armor
if Signature(e) <> 'ARMO' then Exit;
// abort if this element is clothing
if GetElementNativeValues(e, 'BOD2\Armor Type') = 2 then Exit;
// get keywords
kwda := ElementBySignature(e, 'KWDA');
if not Assigned(kwda) then Exit;
// fix AR
ar := GetBaseArmorRateByMaterial(kwda);
mult := GetArmorMultiByKeyword(kwda);
ar := ar * mult * 100;
SetElementNativeValues(e, 'DNAM', ar);
end;
end.
仕様について
ヴァニラのAR一覧を眺めてみたところ、手と足のARを基本ARとすると、盾は2倍、頭は1.5倍、胴は2.5倍になっていました。ただし、一部は微妙に調整されているようです。
そこで、まず材質から基本ARを決定します。そして、キーワードを元に部位を確定し、ARを決定します。
それから、服は無視します。
軽装は最終的に防御力がカンストするので重装の価値が下がると言われています。そこで、軽装の防御力を下方修正して弱体化すれば、重装との差別化が可能です。