防御力を正規化する

環境構築

Modで追加された防具はパラメータが不適切なものが多いです。

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を決定します。

それから、服は無視します。

軽装は最終的に防御力がカンストするので重装の価値が下がると言われています。そこで、軽装の防御力を下方修正して弱体化すれば、重装との差別化が可能です。

タイトルとURLをコピーしました