422 lines
14 KiB
Svelte
422 lines
14 KiB
Svelte
<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 { PokemonData } from "../model/PokemonStatus";
|
||
|
||
export let player1Data: PokemonData | undefined;
|
||
export let player2Data: PokemonData | undefined;
|
||
export let attackId: number | undefined;
|
||
let attackData;
|
||
$: {
|
||
if (myValue > 0 && myValue !== currentValue) {
|
||
invoke("search_move", { index: myValue }).then((r) => {
|
||
currentValue = myValue;
|
||
attackData = r;
|
||
console.log(attackData);
|
||
});
|
||
}
|
||
if (!!attackId) {
|
||
invoke("search_move", { index: attackId }).then((r) => {
|
||
currentValue = myValue = attackId;
|
||
attackData = r;
|
||
console.log(attackData);
|
||
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;
|
||
let defender;
|
||
let atk_value;
|
||
let def_value;
|
||
if (
|
||
!player1Data ||
|
||
!player2Data ||
|
||
!attackData ||
|
||
(!!attackData && ![1, 2].includes(attackData.category))
|
||
) {
|
||
// display some message somewhere
|
||
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.atk;
|
||
def_value = defender.def;
|
||
} else {
|
||
atk_value = attacker.spatk;
|
||
def_value = defender.spdef;
|
||
}
|
||
|
||
// 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: JSON.parse(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 myValue;
|
||
let currentValue = 0;
|
||
</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={myValue}
|
||
/>
|
||
</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>
|