Files
pokemon-data-displayer/src/lib/DamageCalculator.svelte
2023-02-06 02:59:27 +09:00

424 lines
15 KiB
Svelte
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script lang="ts">
import { invoke } from "@tauri-apps/api/tauri";
import AutoComplete from "simple-svelte-autocomplete";
import attack_types from "../const/attack_types.json";
import type { PokemonStatus } from "../model/PokemonStatus";
export let player1Data: PokemonStatus | undefined;
export let player2Data: PokemonStatus | undefined;
export let attackId: number | undefined;
let attackData;
$: {
if (moveValue > 0) {
invoke("search_move", { index: moveValue }).then((r) => {
attackData = r;
moveValue = undefined;
});
}
if (!!attackId) {
console.log(attackId);
invoke("search_move", { index: attackId }).then((r) => {
attackData = r;
attackId = undefined;
});
}
}
let attack_direction = "p1p2";
let total_max_damage = 0;
let total_min_damage = 0;
let total_max_damage_percentage = 0;
let total_min_damage_percentage = 0;
let item_magnification = 1.0;
function calculate_damage() {
let attacker: PokemonStatus;
let defender: PokemonStatus;
let atk_value;
let def_value;
if (
!player1Data ||
!player2Data ||
!attackData ||
(!!attackData && ![1, 2].includes(attackData.category))
) {
// display some message somewhere
total_max_damage = 0;
total_min_damage = 0;
total_max_damage_percentage = 0;
total_min_damage_percentage = 0;
console.log("skip");
return;
}
if (attack_direction === "p1p2") {
attacker = player1Data;
defender = player2Data;
} else {
attacker = player2Data;
defender = player1Data;
}
// 物理or特殊の判定
if (attackData.category === 1) {
atk_value = attacker.attack;
def_value = defender.defense;
} else {
atk_value = attacker.special_attack;
def_value = defender.special_defense;
}
// debug
// ステータスアップアイテム
const A = '{"attack":1.5}';
const B = '{"defense":1.5}';
const C = '{"special_attack":1.5}';
const D = '{"special_defense":1.5}';
const S = '{"speed":1.5}';
const BE = '{"attack":1.3,"defense":1.3,"special_attack":1.3,"special_defense":1.3,"speed":1.5,"condition":"highest_status"}';
// 技威力up
const E = '{"attack_power":1.3,"condition":"type","type":0}';
// 計算で使用する値をひとまとめにしてみる
let data = {
level: 50, // 対戦時は基本的にレベル50なのでconstでも良さそう
attack_power: attackData.power, // 技威力
atk_value: atk_value,
def_value: def_value,
// 最大ダメージと最小ダメージ
maxDamage: 0,
minDamage: 0,
// 攻撃側タイプ
// TODO: try catchでエラーハンドリング
atk_type: attacker.types,
atk_terastype: attacker.terastype,
// 防御側タイプ
def_type:
defender.terastype[0] > 0
? defender.terastype
: defender.types,
// 持ち物情報
// 来週直したらコメントアウト外す
// atk_item_effect: JSON.parse(A), // データの形決まったら調整
// def_item_effect: JSON.parse(A), // データの形決まったら調整
// 攻撃側の技が物理(1)or特殊の判定
attack_data_category: attackData.category,
// 技タイプ
move_type: attackData.types,
// 範囲の計算で使用する
};
// 計算
/*
memo:計算用テストデータ
- level: 50,
- power: 100(じしん)
- attac: 182ガブリアスA252
- defence: 189キョジオーンBがVで努力値176
- 結果
- タイプ一致なし:37-44
- タイプ一致あり:55-66
- タイプ一致あり2倍:110~132
- タイプ一致あり4倍:220~264(セキタンザン)
- タイプ一致あり無効: 0~0
- タイプ一致あり等倍(半減と抜群): 76~91(これはモロバレルで計算)
- タイプ一致こだわりハチマキ: 164~194
- タイプ一致テラスタルこだわりハチマキ: 220~260
- ハチマキが特殊技に乗らないことを確認
- ガブリアスC100(実数値)火炎放射(ハチマキ)=>: キョジオーンD110 => 16~19
- メガネが特殊技に乗ることを確認
- ガブリアスC100(実数値)火炎放射(メガネ)=>: キョジオーンD110 => 23~28
- チョッキの効果が特殊技に乗ることを確認
- ガブリアスC100(実数値)火炎放射=>: キョジオーンD110 => 23~28
- チョッキの効果が物理に乗らないことを確認
- ガブリアスA182(実数値)地震=>: キョジオーンB189 => 110~132
*/
let d = damage(data);
total_max_damage = d.maxDamage;
total_min_damage = d.minDamage;
total_max_damage_percentage = (d.maxDamage * 100) / defender.hp;
total_min_damage_percentage = (d.minDamage * 100) / defender.hp;
}
function damage(data) {
// TODO: 来週直す
// 命の珠の計算がズレる
// アイテムの情報を受け取れるようにする
/*
// 攻撃側のアイテム補正
if (data.atk_item_effect.attack_power !== undefined){
// アイテムで技の威力が上昇する場合の判定
data.attack_power *= aktPowerIncreasingItems(data)
}else {
// アイテムでステータスが上昇する場合の判定
data.atk_value *=atkStatusIncreasingItems(data)
}
// 防御側のアイテム補正
data.def_value *=defStatusIncreasingItems(data)
*/
// 基本の計算
var step1 = Math.trunc((data.level * 2) / 5 + 2);
var step2 = Math.trunc(
(step1 * data.attack_power * data.atk_value) / data.def_value
);
data.maxDamage = Math.trunc(step2 / 50 + 2); // この時点の計算結果は最大ダメージ
// 各種補正かけた結果を返す
return modify(data);
}
// アイテムで技の威力補正を計算する
function aktPowerIncreasingItems(data){
if (data.atk_item_effect.attack_power !== undefined){
// const E = '{"attack_power":1.3,"condition":"type","type":}';
// タイプ一致アイテム
if (data.atk_item_effect.type !== undefined){
console.log("タイプ一致", data.atk_item_effect.type, data.atk_type)
if (data.atk_item_effect.type === data.atk_type){
return data.atk_item_effect.attack_power
}
}
// 対応予定
// 物知りメガネとか => 物理特殊一致させる必要がある
// 達人の帯 => 抜群のときだけ1.2倍
// 命の珠は無条件で1.3倍
console.log("いのちのたま: ", data.atk_item_effect.attack_power)
return data.atk_item_effect.attack_power
}
return 1
}
// アイテムでのステータス補正を計算する
function atkStatusIncreasingItems(data){
// アイテムで技の威力が上がる場合は、上の関数で計算する
// Note: ハチマキの効果が特攻に乗らないようにする必要がある
// TODO: ブーストエナジーの計算は、挙動がおかしかったら直す(たぶん考慮たりない)
if (data.atk_item_effect.condition !== undefined){
// ブーストエナジー
// 技が物理or特殊で分岐
if (data.attack_data_category === 1){
return data.atk_item_effect.attack
}else {
return data.atk_item_effect.special_attack
}
} else if (data.atk_item_effect.attack !== undefined && data.attack_data_category === 1){
// 物理
return data.atk_item_effect.attack
} else if (data.atk_item_effect.special_attack !== undefined){
// 特殊
return data.atk_item_effect.special_attack
}
return 1
}
function defStatusIncreasingItems(data){
if (data.def_item_effect.condition !== undefined){
// ブーストエナジー
// 技が物理or特殊で分岐
if (data.attack_data_category === 1){
return data.def_item_effect.defense
}else {
return data.def_item_effect.special_defense
}
}else if (data.def_item_effect.defense !== undefined && data.def_item_effect.special_defense !== undefined){
// 進化の輝石
// 防御と特防の両方が上がるのでどちらを返してもOK
return data.def_item_effect.defense
} else if (data.def_item_effect.defense === undefined && data.def_item_effect.special_defense !== undefined && data.attack_data_category !==1){
// チョッキ
// 物理の計算でチョッキの効果を出さないようにするため、防御の計算値がないことと、攻撃が物理ではないことを比較する
return data.def_item_effect.special_defense
}
return 1
}
// 全部一気にやると大変なので、一旦基本計算から
function modify(data) {
// 範囲補正
// 親子愛補正
// ※svでおやこあいの特性はないのでスキップする
// 天気補正
// 急所補正
// 乱数補正
data.minDamage = getMinDamage(data);
// タイプ一致などの倍率
let magnification = 1; // タイプ相性倍率
let magnification_terastype = 1; // タイプ一致補正 テラスタル含む
// ポケモンのタイプとテラスタルのタイプ一致の判定
let is_terastype = data.atk_terastype !== 0;
let terastype_match = false;
if (is_terastype) {
terastype_match = data.atk_type.includes(data.atk_terastype[0]);
}
// ポケモンのタイプと技のタイプの判定
// 1. テラスタルしてるなら、テラスタルのタイプで判定
// 2. テラスタルしていないなら、元のタイプで判定
let atk_type;
if (is_terastype) {
atk_type = data.atk_terastype;
} else {
atk_type = data.atk_type;
}
let type_match =
data.atk_type.findIndex((type) => type === data.move_type[0]) > -1;
// Note:
// フライングプレスだけ格闘と飛行の2属性があるが、タイプ一致補正は格闘にだけ補正が掛かる
// data.move_type[0] = 7で格闘が取れるのでOK
// 技のタイプとテラスタルのタイプの一致判定
let movetype_match = false;
if (is_terastype) {
movetype_match = data.move_type.includes(data.atk_terastype);
}
// 技のタイプ一致倍率(技のタイプが一致するかどうか)
// 本来のタイプ テラスタルタイプ 倍率
if (type_match && is_terastype && terastype_match) {
// 1. 一致する 一致する => x2
magnification_terastype = 2;
} else if (
(type_match && is_terastype && !terastype_match) ||
(type_match && !is_terastype) ||
(!type_match && is_terastype && movetype_match)
) {
// 2. 一致する 一致しない => x1.5
// 3. 一致する テラスタルなし => x1.5
// 4. 一致しない 一致する => x1.5 NG
magnification_terastype = 1.5;
}
// 5. 一致しない 一致しない 1
// 6. 一致しない テラスタルなし 1
// そのままなので特に処理なし
// 相性補正
// 相性補正の倍率を計算
magnification = getCompatibility(data);
// やけど補正
// 壁補正
// ブレインフォース補正
// スナイパー補正
// いろめがね補正
// もふもふ(ほのお)補正
// ダメージ半減特性の補正(M half)
// 効果抜群を軽減する特性の補正(M filter)
// フレンドガード補正
// 達人の帯補正
// メトロノーム補正
// 命の珠補正
// 半減木の実補正
// その他ダメージ補正(M twice)
// ダイマックス技などの軽減(M protect)
// 最後に倍率かける
data.maxDamage = Math.trunc(data.maxDamage * magnification_terastype);
data.minDamage = Math.trunc(data.minDamage * magnification_terastype);
data.maxDamage = Math.trunc(data.maxDamage * magnification);
data.minDamage = Math.trunc(data.minDamage * magnification);
data.maxDamage = Math.trunc(data.maxDamage * item_magnification);
data.minDamage = Math.trunc(data.minDamage * item_magnification);
return data;
}
// 乱数補正
// ダメージは、乱数で0.85~1.00をかけた値の範囲で変動する
// 1.00はそのままの値なので、
// 0.85をかけた最小ダメージだけを計算する
// 少数切り捨て
function getMinDamage(data) {
return Math.trunc((data.maxDamage * 85) / 100);
}
// 相性計算
// 倍率だけ返す
function getCompatibility(data) {
let type_magnification = 1;
// 技のタイプの相性倍率とりだす
// attack_types の json の index と move_type の数字が1つずれてる
let compatibility = attack_types[data.move_type - 1];
// 相手のタイプ毎に倍率計算
for (let i = 0; i < data.def_type.length; i++) {
// 倍率
type_magnification *= compatibility[data.def_type[i] - 1]; // ここもindexずれる
}
return type_magnification;
}
// 技名検索
async function getItems(keyword) {
try {
let result = await invoke("autosearch_move", { keyword });
// console.log(result);
return result;
} catch (e) {
console.log(e);
return [];
}
}
let moveValue;
</script>
<div class="calculator">
<table>
<tr>
<td>
<input
type="radio"
name="direction"
value="p1p2"
bind:group={attack_direction}
/> P1 ->> P2
</td>
<td>
<input
type="radio"
name="direction"
value="p2p1"
bind:group={attack_direction}
/> P2 ->> P1
</td>
<td>
<input
type="text"
bind:value={item_magnification}
/>
</td>
</tr>
<tr>
<td>
<AutoComplete
showClear={true}
searchFunction={getItems}
delay="200"
localFiltering={false}
labelFieldName="name"
valueFieldName="id"
bind:value={moveValue}
/>
</td>
<td>{ attackData ? attackData.name : "" }</td>
<td>
<input type="button" value="Calculate" on:click={calculate_damage} />
</td>
<td>Total Damage: {total_min_damage} ~ {total_max_damage}</td>
<td>
Percentage: {Math.round(total_min_damage_percentage * 100)/100}% ~ {Math.round(total_max_damage_percentage * 100)/100}%
</td>
</tr>
</table>
<div class="debug-data">
{JSON.stringify(player1Data)} -
{JSON.stringify(player2Data)}
</div>
</div>
<style>
.debug-data {
font-size: 10px;
}
.calculator {
height: 100px;
width: 100%;
border-top: 2px solid white;
}
</style>