OSDN Git Service

Refactor likert() to avoid sprintf(). Does part of the work of resolving https:...
[hengbandforosx/hengbandosx.git] / src / view / status-first-page.cpp
1 /*!
2  * @file status-first-page.c
3  * @brief キャラ基本情報及び技能値の表示
4  * @date 2020/02/23
5  * @author Hourier
6  */
7
8 #include "view/status-first-page.h"
9 #include "artifact/fixed-art-types.h"
10 #include "combat/attack-power-table.h"
11 #include "combat/shoot.h"
12 #include "game-option/text-display-options.h"
13 #include "inventory/inventory-slot-types.h"
14 #include "mutation/mutation-flag-types.h"
15 #include "object-enchant/special-object-flags.h"
16 #include "object-enchant/tr-types.h"
17 #include "object/object-flags.h"
18 #include "object/tval-types.h"
19 #include "perception/object-perception.h"
20 #include "player-base/player-class.h"
21 #include "player-info/equipment-info.h"
22 #include "player-info/monk-data-type.h"
23 #include "player-status/player-hand-types.h"
24 #include "player/player-status-flags.h"
25 #include "player/special-defense-types.h"
26 #include "sv-definition/sv-weapon-types.h"
27 #include "system/item-entity.h"
28 #include "system/player-type-definition.h"
29 #include "term/term-color-types.h"
30 #include "term/z-form.h"
31 #include "util/bit-flags-calculator.h"
32 #include "view/display-util.h"
33
34 /*!
35  * @brief
36  * @param player_ptr プレイヤーへの参照ポインタ
37  * @param o_ptr 装備中の弓への参照ポインタ
38  * @param shots 射撃回数
39  * @param shot_frac 射撃速度
40  */
41 static void calc_shot_params(PlayerType *player_ptr, ItemEntity *o_ptr, int *shots, int *shot_frac)
42 {
43     if (o_ptr->bi_id == 0) {
44         return;
45     }
46
47     const auto energy_fire = o_ptr->get_bow_energy();
48     *shots = player_ptr->num_fire * 100;
49     *shot_frac = ((*shots) * 100 / energy_fire) % 100;
50     *shots = (*shots) / energy_fire;
51     if (!o_ptr->is_specific_artifact(FixedArtifactId::CRIMSON)) {
52         return;
53     }
54
55     *shots = 1;
56     *shot_frac = 0;
57     if (!PlayerClass(player_ptr).equals(PlayerClassType::ARCHER)) {
58         return;
59     }
60
61     if (player_ptr->lev >= 10) {
62         (*shots)++;
63     }
64     if (player_ptr->lev >= 30) {
65         (*shots)++;
66     }
67     if (player_ptr->lev >= 45) {
68         (*shots)++;
69     }
70 }
71
72 /*!
73  * @brief 武器装備に制限のあるクラスで、直接攻撃のダメージを計算する
74  * @param player_ptr プレイヤーへの参照ポインタ
75  * @param hand 手 (利き手が0、反対の手が1…のはず)
76  * @param damage 直接攻撃のダメージ
77  * @param basedam 素手における直接攻撃のダメージ
78  * @param o_ptr 装備中の武器への参照ポインタ
79  * @return 利き手ならTRUE、反対の手ならFALSE
80  */
81 static bool calc_weapon_damage_limit(PlayerType *player_ptr, int hand, int *damage, int *basedam, ItemEntity *o_ptr)
82 {
83     PLAYER_LEVEL level = player_ptr->lev;
84     if (hand > 0) {
85         damage[hand] = 0;
86         return false;
87     }
88
89     PlayerClass pc(player_ptr);
90     if (pc.equals(PlayerClassType::FORCETRAINER)) {
91         level = std::max<short>(1, level - 3);
92     }
93
94     if (pc.monk_stance_is(MonkStanceType::BYAKKO)) {
95         *basedam = monk_ave_damage[level][1];
96     } else if (pc.monk_stance_is(MonkStanceType::GENBU) || pc.monk_stance_is(MonkStanceType::SUZAKU)) {
97         *basedam = monk_ave_damage[level][2];
98     } else {
99         *basedam = monk_ave_damage[level][0];
100     }
101
102     damage[hand] += *basedam;
103     if (o_ptr->bi_key == BaseitemKey(ItemKindType::SWORD, SV_POISON_NEEDLE)) {
104         damage[hand] = 1;
105     }
106     if (damage[hand] < 0) {
107         damage[hand] = 0;
108     }
109
110     return true;
111 }
112
113 /*!
114  * @brief 片手あたりのダメージ量を計算する
115  * @param o_ptr 装備中の武器への参照ポインタ
116  * @param hand 手
117  * @param damage 直接攻撃のダメージ
118  * @param basedam 素手における直接攻撃のダメージ
119  * @return 素手ならFALSE、武器を持っていればTRUE
120  */
121 static bool calc_weapon_one_hand(ItemEntity *o_ptr, int hand, int *damage, int *basedam)
122 {
123     if (o_ptr->bi_id == 0) {
124         return false;
125     }
126
127     *basedam = 0;
128     damage[hand] += *basedam;
129     if (o_ptr->bi_key == BaseitemKey(ItemKindType::SWORD, SV_POISON_NEEDLE)) {
130         damage[hand] = 1;
131     }
132
133     if (damage[hand] < 0) {
134         damage[hand] = 0;
135     }
136
137     return true;
138 }
139
140 /*!
141  * @brief ヴォーパル武器等によるダメージ強化
142  * @param player_ptr プレイヤーへの参照ポインタ
143  * @param o_ptr 装備中の武器への参照ポインタ
144  * @param basedam 素手における直接攻撃のダメージ
145  * @param flgs オブジェクトフラグ群
146  * @return 強化後の素手ダメージ
147  * @todo ヴォーパル計算処理が building-craft-weapon::compare_weapon_aux() と多重実装.
148  */
149 static int strengthen_basedam(PlayerType *player_ptr, ItemEntity *o_ptr, int basedam, const TrFlags &flgs)
150 {
151     const auto is_vorpal_blade = o_ptr->is_specific_artifact(FixedArtifactId::VORPAL_BLADE);
152     const auto is_chainsword = o_ptr->is_specific_artifact(FixedArtifactId::CHAINSWORD);
153     if (o_ptr->is_fully_known() && (is_vorpal_blade || is_chainsword)) {
154         /* vorpal blade */
155         basedam *= 5;
156         basedam /= 3;
157     } else if (flgs.has(TR_VORPAL)) {
158         /* vorpal flag only */
159         basedam *= 11;
160         basedam /= 9;
161     }
162
163     // 理力
164     bool is_force = !PlayerClass(player_ptr).equals(PlayerClassType::SAMURAI);
165     is_force &= flgs.has(TR_FORCE_WEAPON);
166     is_force &= player_ptr->csp > (o_ptr->dd * o_ptr->ds / 5);
167     if (is_force) {
168         basedam = basedam * 7 / 2;
169     }
170
171     return basedam;
172 }
173
174 /*!
175  * @brief 技能ランクの表示基準を定める
176  * Returns a "rating" of x depending on y
177  * @param x 技能値
178  * @param y 技能値に対するランク基準比
179  * @return スキル レベルのテキスト説明とその説明のカラー インデックスのペア
180  */
181 static std::pair<std::string, TERM_COLOR> likert(int x, int y)
182 {
183     std::string desc;
184
185     if (show_actual_value) {
186         desc = format("%3d-", x);
187     }
188
189     if (x < 0) {
190         return make_pair(desc.append(_("最低", "Very Bad")), TERM_L_DARK);
191     }
192
193     if (y <= 0) {
194         y = 1;
195     }
196
197     switch ((x / y)) {
198     case 0:
199     case 1: {
200         return make_pair(desc.append(_("悪い", "Bad")), TERM_RED);
201     }
202     case 2: {
203         return make_pair(desc.append(_("劣る", "Poor")), TERM_L_RED);
204     }
205     case 3:
206     case 4: {
207         return make_pair(desc.append(_("普通", "Fair")), TERM_ORANGE);
208     }
209     case 5: {
210         return make_pair(desc.append(_("良い", "Good")), TERM_YELLOW);
211     }
212     case 6: {
213         return make_pair(desc.append(_("大変良い", "Very Good")), TERM_YELLOW);
214     }
215     case 7:
216     case 8: {
217         return make_pair(desc.append(_("卓越", "Excellent")), TERM_L_GREEN);
218     }
219     case 9:
220     case 10:
221     case 11:
222     case 12:
223     case 13: {
224         return make_pair(desc.append(_("超越", "Superb")), TERM_GREEN);
225     }
226     case 14:
227     case 15:
228     case 16:
229     case 17: {
230         return make_pair(desc.append(_("英雄的", "Heroic")), TERM_BLUE);
231     }
232     default: {
233         desc.append(format(_("伝説的[%d]", "Legendary[%d]"), (int)((((x / y) - 17) * 5) / 2)));
234         return make_pair(desc, TERM_VIOLET);
235     }
236     }
237 }
238
239 /*!
240  * @brief 弓+両手の武器それぞれについてダメージを計算する
241  * @param player_ptr プレイヤーへの参照ポインタ
242  * @param damage 直接攻撃のダメージ
243  * @param to_h 命中補正
244  */
245 static void calc_two_hands(PlayerType *player_ptr, int *damage, int *to_h)
246 {
247     ItemEntity *o_ptr;
248     o_ptr = &player_ptr->inventory_list[INVEN_BOW];
249
250     for (int i = 0; i < 2; i++) {
251         int basedam;
252         damage[i] = player_ptr->dis_to_d[i] * 100;
253         PlayerClass pc(player_ptr);
254         if (pc.is_martial_arts_pro() && (empty_hands(player_ptr, true) & EMPTY_HAND_MAIN)) {
255             if (!calc_weapon_damage_limit(player_ptr, i, damage, &basedam, o_ptr)) {
256                 break;
257             }
258
259             continue;
260         }
261
262         o_ptr = &player_ptr->inventory_list[INVEN_MAIN_HAND + i];
263         if (!calc_weapon_one_hand(o_ptr, i, damage, &basedam)) {
264             continue;
265         }
266
267         to_h[i] = 0;
268         auto poison_needle = false;
269         if (o_ptr->bi_key == BaseitemKey(ItemKindType::SWORD, SV_POISON_NEEDLE)) {
270             poison_needle = true;
271         }
272
273         if (o_ptr->is_known()) {
274             damage[i] += o_ptr->to_d * 100;
275             to_h[i] += o_ptr->to_h;
276         }
277
278         basedam = ((o_ptr->dd + player_ptr->to_dd[i]) * (o_ptr->ds + player_ptr->to_ds[i] + 1)) * 50;
279         auto flgs = object_flags_known(o_ptr);
280
281         bool impact = player_ptr->impact != 0;
282         basedam = calc_expect_crit(player_ptr, o_ptr->weight, to_h[i], basedam, player_ptr->dis_to_h[i], poison_needle, impact);
283         basedam = strengthen_basedam(player_ptr, o_ptr, basedam, flgs);
284         damage[i] += basedam;
285         if (o_ptr->bi_key == BaseitemKey(ItemKindType::SWORD, SV_POISON_NEEDLE)) {
286             damage[i] = 1;
287         }
288         if (damage[i] < 0) {
289             damage[i] = 0;
290         }
291     }
292 }
293
294 /*!
295  * @brief キャラ基本情報及び技能値をメインウィンドウに表示する
296  * @param player_ptr プレイヤーへの参照ポインタ
297  * @param xthb 武器等を含めた最終命中率
298  * @param damage 打撃修正
299  * @param shots 射撃回数
300  * @param shot_frac 射撃速度
301  * @param display_player_one_line 1行表示用のコールバック関数
302  */
303 static void display_first_page(PlayerType *player_ptr, int xthb, int *damage, int shots, int shot_frac)
304 {
305     int xthn = player_ptr->skill_thn + (player_ptr->to_h_m * BTH_PLUS_ADJ);
306
307     int muta_att = 0;
308     if (player_ptr->muta.has(PlayerMutationType::HORNS)) {
309         muta_att++;
310     }
311     if (player_ptr->muta.has(PlayerMutationType::SCOR_TAIL)) {
312         muta_att++;
313     }
314     if (player_ptr->muta.has(PlayerMutationType::BEAK)) {
315         muta_att++;
316     }
317     if (player_ptr->muta.has(PlayerMutationType::TRUNK)) {
318         muta_att++;
319     }
320     if (player_ptr->muta.has(PlayerMutationType::TENTACLES)) {
321         muta_att++;
322     }
323
324     int blows1 = can_attack_with_main_hand(player_ptr) ? player_ptr->num_blow[0] : 0;
325     int blows2 = can_attack_with_sub_hand(player_ptr) ? player_ptr->num_blow[1] : 0;
326     int xdis = player_ptr->skill_dis;
327     int xdev = player_ptr->skill_dev;
328     int xsav = player_ptr->skill_sav;
329     int xstl = player_ptr->skill_stl;
330     int xsrh = player_ptr->skill_srh;
331     int xfos = player_ptr->skill_fos;
332     int xdig = player_ptr->skill_dig;
333
334     auto sd = likert(xthn, 12);
335     display_player_one_line(ENTRY_SKILL_FIGHT, sd.first.data(), sd.second);
336
337     sd = likert(xthb, 12);
338     display_player_one_line(ENTRY_SKILL_SHOOT, sd.first.data(), sd.second);
339
340     sd = likert(xsav, 7);
341     display_player_one_line(ENTRY_SKILL_SAVING, sd.first.data(), sd.second);
342
343     sd = likert((xstl > 0) ? xstl : -1, 1);
344     display_player_one_line(ENTRY_SKILL_STEALTH, sd.first.data(), sd.second);
345
346     sd = likert(xfos, 6);
347     display_player_one_line(ENTRY_SKILL_PERCEP, sd.first.data(), sd.second);
348
349     sd = likert(xsrh, 6);
350     display_player_one_line(ENTRY_SKILL_SEARCH, sd.first.data(), sd.second);
351
352     sd = likert(xdis, 8);
353     display_player_one_line(ENTRY_SKILL_DISARM, sd.first.data(), sd.second);
354
355     sd = likert(xdev, 6);
356     display_player_one_line(ENTRY_SKILL_DEVICE, sd.first.data(), sd.second);
357
358     sd = likert(xdig, 4);
359     display_player_one_line(ENTRY_SKILL_DIG, sd.first.data(), sd.second);
360
361     if (!muta_att) {
362         display_player_one_line(ENTRY_BLOWS, format("%d+%d", blows1, blows2), TERM_L_BLUE);
363     } else {
364         display_player_one_line(ENTRY_BLOWS, format("%d+%d+%d", blows1, blows2, muta_att), TERM_L_BLUE);
365     }
366
367     display_player_one_line(ENTRY_SHOTS, format("%d.%02d", shots, shot_frac), TERM_L_BLUE);
368
369     concptr desc;
370     if ((damage[0] + damage[1]) == 0) {
371         desc = "nil!";
372     } else {
373         desc = format("%d+%d", blows1 * damage[0] / 100, blows2 * damage[1] / 100);
374     }
375
376     display_player_one_line(ENTRY_AVG_DMG, desc, TERM_L_BLUE);
377     display_player_one_line(ENTRY_INFRA, format("%d feet", player_ptr->see_infra * 10), TERM_WHITE);
378 }
379
380 /*!
381  * @brief プレイヤーステータスの1ページ目各種詳細をまとめて表示する
382  * Prints ratings on certain abilities
383  * @param player_ptr プレイヤーへの参照ポインタ
384  * @param display_player_one_line 1行表示用のコールバック関数
385  * @details
386  * This code is "imitated" elsewhere to "dump" a character sheet.
387  */
388 void display_player_various(PlayerType *player_ptr)
389 {
390     ItemEntity *o_ptr;
391     o_ptr = &player_ptr->inventory_list[INVEN_BOW];
392     int tmp = player_ptr->to_h_b + o_ptr->to_h;
393     int xthb = player_ptr->skill_thb + (tmp * BTH_PLUS_ADJ);
394     int shots = 0;
395     int shot_frac = 0;
396     calc_shot_params(player_ptr, o_ptr, &shots, &shot_frac);
397
398     int damage[2];
399     int to_h[2];
400     calc_two_hands(player_ptr, damage, to_h);
401     display_first_page(player_ptr, xthb, damage, shots, shot_frac);
402 }