装備ボーナスオブジェクトのフォーマットと求値アルゴリズム
艦これの装備ボーナスはmain.jsに記述されておりユーザが調べるまでもなく決定的です。
もちろん、その実際の効果は検証によって明らかにされることではありますが、装備ボーナスはUIの一部として通信を行わずしてクライアント側で観測できる要素です。
したがって「何かの艦娘に何かの装備を載せたところ、これこれというステータス上昇が得られた」などという情報は「柔らかなソーシャル」においてはもっともですがシステムとして本質ではありません。
さて、装備ボーナスは難読化されたうえでmain.jsに記載されています。
われわれ一般提督が扱いやすいように有志提督がJSONに変換したもの、それがgithubなどで公開されています。
本稿では、それらJSONで記述された「装備ボーナスオブジェクト」のフォーマットおよび装備ボーナスの求値アルゴリズムに説明の付与を試みます。
最小構成の例
step1
いきなり装備ボーナスについて説明すると複雑度が大きいため、構成要素を絞ります。
「ある条件を満たすときある値を加算する」これをコードで表現すると以下のように表せます。
なお、コードはプログラミング言語C++をもとに書いています。
細かな言語仕様はともかく、雰囲気は伝わると思っています。
int main() {
int total = 0;
if (cond1) {
total += value1;
}
if (cond2) {
total += value2;
}
if (cond3) {
total += value3;
}
}
step2
step1はとても説明的に表せていて良いように見えます。
しかし、たとえば次の二点において嬉しくないこともあります。
- 条件と値のペアが増えると何行でもコードが肥大化する
- 処理言語が変わると
ifや+=などの文法が異なることもあるため移植性に乏しい
これらの嬉しくない点については容易に解決できます。
たとえば、値と条件のペア、これを配列で表すとともにforを用いることでコードが肥大化せず処理できます。
加えて、この配列をJSONで表現できれば移植が容易になります。
以下の例では、JSONの解析は本稿の範囲外とし、単なる配列として条件と値のペアの配列を与えています。
struct datum_type {
bool cond;
int value;
};
const datum_type data[] = {
datum_type{ .cond = true, .value = 1 },
datum_type{ .cond = false, .value = 2 },
datum_type{ .cond = true, .value = 3 },
};
int main() {
int total = 0;
for (const auto& [cond, value] : data) {
if (cond) {
total += value;
}
}
}
step3
装備ボーナスに進む前にもう少し複雑化してステップを踏んでおきます。
条件がより複雑でそれが型condition_typeで表され、値もより複雑でそれが型value_typeで表されるとします。
struct condition_type {
};
struct value_type {
};
struct datum_type {
condition_type cond;
value_type value;
};
condition_type cond;をifにアダプトするために述語関数(boolを返す関数)を定義します。
bool matches_condition(const condition_type& cond) {
}
値が複雑化した場合は+=をカスタマイズするなどをします。
C++ではオーバーロードによって+=をカスタマイズできます。
ご利用の言語をご確認ください。
auto operator+=(value_type& lhs, const value_type& rhs) -> value_type& {
lhs.rais += rhs.rais;
lhs.tais += rhs.tais;
return lhs;
}
これらを用意すると以下のように書けます。
全体の複雑度は増していますが、主たる処理に大きな変更はありません。
const datum_type data[] = { };
int main() {
auto total = value_type{};
for (const auto& [cond, value] : data) {
if (matches_condition(cond)) {
total += value;
}
}
}
型condition_typeおよび述語関数bool matches_condition(const condition_type& cond);について、
メンバ同士はAND、メンバが配列の場合その各要素はORとして記述できます。
たとえば、"装備IDidが{1, 2, 3}のいずれか" かつ "装備改修値level > 6"の場合は次のように書けます。
具体的な値はJSONによって与えられるためここでは指定しません。
struct equipment_type {
int id;
int level;
};
struct condition_type {
std::vector<int> ids;
int level;
};
bool matches_condition(const equipment_type& equipment, const condition_type& cond) {
return std::ranges::contains(cond.ids, equipment.id)
and equipment.level > cond.level;
}
フォーマット
公開されているフォーマット(JSONデータ)は、各々の開発者が独自に使い良いように定義しているためフォーマットは統一されていません。
代表的なものとして以下があります。
- 74式EN
- KC3改
- 制空権シミュレータ
- (fleethub)
いずれのフォーマットも似通っているため、ここでは代表として74式ENのフォーマットを取り扱います。
FitBonusPerEquipment
- 装備ボーナスを集計する条件その1を表します。艦娘が
idsで指定される装備をN個搭載しているか、 typesで指定される装備をN個搭載している場合にbonusesを処理します(N > 0)。
idsとtypesとが同時に指定されることは無いはずです。どちらも指定されないといったことも無いはずです。つまりidsとtypesの存在性はXORにはるはずです。
{
ids?: number[] // 必要装備ID
types?: number[] // 必要装備カテゴリID
bonuses: FitBonusData[] // ids XOR types で指定される装備をベースとする装備ボーナス、その追加条件と値
}
FitBonusData
- 装備ボーナスを集計する条件その2を表します。この条件を満たす場合に
FitBonusValueを計上します。
idsXORtypesで指定される装備を搭載していて、艦娘に対する条件を満たし、requires*によって指定される装備を搭載し、idsXORtypesで指定される装備のうち改修値がlevel以上の装備をnum個以上搭載している場合に計上します。
{
// 艦娘に対する条件
shipS?: number[] // 未改造ID
shipClass?: number[] // 艦型ID
shipNationality?: number[] // 国籍
shipType?: number[] // 艦種ID
shipX?: number[] // 艦船ID
// いわゆるシナジー条件 その1
requires?: number[] // 装備ID
requiresLevel?: number // 装備IDの最小改修値
requiresNum?: number // 装備IDの必要個数 (未使用?)
// いわゆるシナジー条件 その2
requiresType?: number[] // 装備カテゴリID
requiresNumType?: number // 装備カテゴリIDの必要個数
// ids XOR types で指定される装備に対する条件
level?: number // 以上の条件を全てANDで満たすとともに、(ids XOR types)で指定された装備のうち改修値がlevel以上の装備について、
num?: number // その個数がnum個以上であるならば一限で計上する
bonus?: FitBonusValue // 個数指定が無ければ搭載数ぶんだけ計上する
// 電探条件
bonusAR?: FitBonusValue // numとlevelを除く以上の条件を全てANDで満たすとともに、対空電探を搭載している場合に計上する
bonusSR?: FitBonusValue // numとlevelを除く以上の条件を全てANDで満たすとともに、水上電探を搭載している場合に計上する
bonusAccR?: FitBonusValue // numとlevelを除く以上の条件を全てANDで満たすとともに、命中電探を搭載している場合に計上する
}
FitBonusValue
装備ボーナスの値を表します。この値を集計したものが最終的な装備ボーナス値となります。
{
houg?: number // 火力
tyku?: number // 対空
kaih?: number // 回避
souk?: number // 装甲
houm?: number // 命中
tais?: number // 対潜
raig?: number // 雷撃
saku?: number // 索敵
leng?: number // 射程
baku?: number // 爆装
}
大まかには以下のような雰囲気で処理できます。
ifの条件式を/* コメント */に替えているものは、実装としては単なるnullチェックに相当します。
auto total_equipment_bonus(const ship_t& ship, const std::vector<FitBonusPerEquipment>& fit_bonuses) -> FitBonusValue {
auto total = FitBonusValue{};
for (const auto& [ids, types, bonuses] : fit_bonuses) {
const auto fit_equipments = extract_fit_equipments(ship, ids, types);
if (fit_equipments.size() == 0) continue;
for (const auto& data : bonuses) {
if (not matches_data(ship, data)) continue;
if () {
const int num =
?
: ;
if () {
if (num >= data.num) {
total += data.bonus;
}
} else {
total += data.bonus * num;
}
}
if (has_anti_air_radar and ) {
total += bonusAR;
}
}
}
return total;
}