OSDN Git Service

[Refactor] #2647 monster_is_valid() をオブジェクトメソッド「monster_type::is_valid()」にした
[hengbandforosx/hengbandosx.git] / src / spell-kind / spells-teleport.cpp
1 /*!
2  * @brief テレポート魔法全般
3  * @date 2020/06/04
4  * @author Hourier
5  */
6
7 #include "spell-kind/spells-teleport.h"
8 #include "avatar/avatar.h"
9 #include "core/asking-player.h"
10 #include "core/player-update-types.h"
11 #include "core/speed-table.h"
12 #include "effect/attribute-types.h"
13 #include "effect/effect-characteristics.h"
14 #include "floor/cave.h"
15 #include "floor/geometry.h"
16 #include "floor/line-of-sight.h"
17 #include "grid/grid.h"
18 #include "inventory/inventory-slot-types.h"
19 #include "main/sound-definitions-table.h"
20 #include "main/sound-of-music.h"
21 #include "monster-floor/monster-move.h"
22 #include "monster-race/monster-race.h"
23 #include "monster-race/race-flags-resistance.h"
24 #include "monster-race/race-flags7.h"
25 #include "monster/monster-info.h"
26 #include "monster/monster-status-setter.h"
27 #include "monster/monster-status.h"
28 #include "monster/monster-update.h"
29 #include "mutation/mutation-flag-types.h"
30 #include "object-enchant/tr-types.h"
31 #include "object/object-flags.h"
32 #include "player-base/player-class.h"
33 #include "player/player-move.h"
34 #include "player/player-status.h"
35 #include "spell-kind/spells-launcher.h"
36 #include "system/floor-type-definition.h"
37 #include "system/grid-type-definition.h"
38 #include "system/monster-race-definition.h"
39 #include "system/monster-type-definition.h"
40 #include "system/object-type-definition.h"
41 #include "system/player-type-definition.h"
42 #include "target/grid-selector.h"
43 #include "target/target-checker.h"
44 #include "util/bit-flags-calculator.h"
45 #include "view/display-messages.h"
46 #include "world/world.h"
47
48 /*!
49  * @brief モンスターとの位置交換処理 / Switch position with a monster.
50  * @param player_ptr プレイヤーへの参照ポインタ
51  * @param dir 方向(5ならばグローバル変数 target_col/target_row の座標を目標にする)
52  * @return 作用が実際にあった場合TRUEを返す
53  */
54 bool teleport_swap(PlayerType *player_ptr, DIRECTION dir)
55 {
56     POSITION tx, ty;
57     if ((dir == 5) && target_okay(player_ptr)) {
58         tx = target_col;
59         ty = target_row;
60     } else {
61         tx = player_ptr->x + ddx[dir];
62         ty = player_ptr->y + ddy[dir];
63     }
64
65     if (player_ptr->anti_tele) {
66         msg_print(_("不思議な力がテレポートを防いだ!", "A mysterious force prevents you from teleporting!"));
67         return false;
68     }
69
70     grid_type *g_ptr;
71     g_ptr = &player_ptr->current_floor_ptr->grid_array[ty][tx];
72     if (!g_ptr->m_idx || (g_ptr->m_idx == player_ptr->riding)) {
73         msg_print(_("それとは場所を交換できません。", "You can't trade places with that!"));
74         return false;
75     }
76
77     if ((g_ptr->is_icky()) || (distance(ty, tx, player_ptr->y, player_ptr->x) > player_ptr->lev * 3 / 2 + 10)) {
78         msg_print(_("失敗した。", "Failed to swap."));
79         return false;
80     }
81
82     monster_type *m_ptr;
83     monster_race *r_ptr;
84     m_ptr = &player_ptr->current_floor_ptr->m_list[g_ptr->m_idx];
85     r_ptr = &r_info[m_ptr->r_idx];
86
87     (void)set_monster_csleep(player_ptr, g_ptr->m_idx, 0);
88
89     if (r_ptr->resistance_flags.has(MonsterResistanceType::RESIST_TELEPORT)) {
90         msg_print(_("テレポートを邪魔された!", "Your teleportation is blocked!"));
91         if (is_original_ap_and_seen(player_ptr, m_ptr)) {
92             r_ptr->r_resistance_flags.set(MonsterResistanceType::RESIST_TELEPORT);
93         }
94         return false;
95     }
96
97     sound(SOUND_TELEPORT);
98     (void)move_player_effect(player_ptr, ty, tx, MPE_FORGET_FLOW | MPE_HANDLE_STUFF | MPE_DONT_PICKUP);
99     return true;
100 }
101
102 /*!
103  * @brief モンスター用テレポート処理
104  * @param player_ptr プレイヤーへの参照ポインタ
105  * @param dir 方向(5ならばグローバル変数 target_col/target_row の座標を目標にする)
106  * @param distance 移動距離
107  * @return 作用が実際にあった場合TRUEを返す
108  */
109 bool teleport_monster(PlayerType *player_ptr, DIRECTION dir, int distance)
110 {
111     BIT_FLAGS flg = PROJECT_BEAM | PROJECT_KILL;
112     return project_hook(player_ptr, AttributeType::AWAY_ALL, dir, distance, flg);
113 }
114
115 /*!
116  * @brief モンスターのテレポートアウェイ処理 /
117  * Teleport a monster, normally up to "dis" grids away.
118  * @param player_ptr プレイヤーへの参照ポインタ
119  * @param m_idx モンスターID
120  * @param dis テレポート距離
121  * @param mode オプション
122  * @return テレポートが実際に行われたらtrue
123  * @details
124  * Attempt to move the monster at least "dis/2" grids away.
125  * But allow variation to prevent infinite loops.
126  */
127 bool teleport_away(PlayerType *player_ptr, MONSTER_IDX m_idx, POSITION dis, teleport_flags mode)
128 {
129     auto *m_ptr = &player_ptr->current_floor_ptr->m_list[m_idx];
130     if (!m_ptr->is_valid()) {
131         return false;
132     }
133
134     if ((mode & TELEPORT_DEC_VALOUR) && (((player_ptr->chp * 10) / player_ptr->mhp) > 5) && (4 + randint1(5) < ((player_ptr->chp * 10) / player_ptr->mhp))) {
135         chg_virtue(player_ptr, V_VALOUR, -1);
136     }
137
138     POSITION oy = m_ptr->fy;
139     POSITION ox = m_ptr->fx;
140     POSITION min = dis / 2;
141     int tries = 0;
142     POSITION ny = 0, nx = 0;
143     bool look = true;
144     while (look) {
145         tries++;
146         if (dis > 200) {
147             dis = 200;
148         }
149
150         for (int i = 0; i < 500; i++) {
151             while (true) {
152                 ny = rand_spread(oy, dis);
153                 nx = rand_spread(ox, dis);
154                 POSITION d = distance(oy, ox, ny, nx);
155                 if ((d >= min) && (d <= dis)) {
156                     break;
157                 }
158             }
159
160             if (!in_bounds(player_ptr->current_floor_ptr, ny, nx)) {
161                 continue;
162             }
163             if (!cave_monster_teleportable_bold(player_ptr, m_idx, ny, nx, mode)) {
164                 continue;
165             }
166             if (!(inside_quest(player_ptr->current_floor_ptr->quest_number) || player_ptr->current_floor_ptr->inside_arena)) {
167                 if (player_ptr->current_floor_ptr->grid_array[ny][nx].is_icky()) {
168                     continue;
169                 }
170             }
171
172             look = false;
173             break;
174         }
175
176         dis = dis * 2;
177         min = min / 2;
178         const int MAX_TELEPORT_TRIES = 100;
179         if (tries > MAX_TELEPORT_TRIES) {
180             return false;
181         }
182     }
183
184     sound(SOUND_TPOTHER);
185     player_ptr->current_floor_ptr->grid_array[oy][ox].m_idx = 0;
186     player_ptr->current_floor_ptr->grid_array[ny][nx].m_idx = m_idx;
187
188     m_ptr->fy = ny;
189     m_ptr->fx = nx;
190
191     reset_target(m_ptr);
192     update_monster(player_ptr, m_idx, true);
193     lite_spot(player_ptr, oy, ox);
194     lite_spot(player_ptr, ny, nx);
195
196     if (r_info[m_ptr->r_idx].flags7 & (RF7_LITE_MASK | RF7_DARK_MASK)) {
197         player_ptr->update |= (PU_MON_LITE);
198     }
199
200     return true;
201 }
202
203 /*!
204  * @brief モンスターを指定された座標付近にテレポートする /
205  * Teleport monster next to a grid near the given location
206  * @param player_ptr プレイヤーへの参照ポインタ
207  * @param m_idx モンスターID
208  * @param ty 目安Y座標
209  * @param tx 目安X座標
210  * @param power テレポート成功確率
211  * @param mode オプション
212  */
213 void teleport_monster_to(PlayerType *player_ptr, MONSTER_IDX m_idx, POSITION ty, POSITION tx, int power, teleport_flags mode)
214 {
215     auto *m_ptr = &player_ptr->current_floor_ptr->m_list[m_idx];
216     if (!MonsterRace(m_ptr->r_idx).is_valid()) {
217         return;
218     }
219     if (randint1(100) > power) {
220         return;
221     }
222
223     POSITION ny = m_ptr->fy;
224     POSITION nx = m_ptr->fx;
225     POSITION oy = m_ptr->fy;
226     POSITION ox = m_ptr->fx;
227
228     POSITION dis = 2;
229     int min = dis / 2;
230     int attempts = 500;
231     bool look = true;
232     while (look && --attempts) {
233         if (dis > 200) {
234             dis = 200;
235         }
236
237         for (int i = 0; i < 500; i++) {
238             while (true) {
239                 ny = rand_spread(ty, dis);
240                 nx = rand_spread(tx, dis);
241                 int d = distance(ty, tx, ny, nx);
242                 if ((d >= min) && (d <= dis)) {
243                     break;
244                 }
245             }
246
247             if (!in_bounds(player_ptr->current_floor_ptr, ny, nx)) {
248                 continue;
249             }
250             if (!cave_monster_teleportable_bold(player_ptr, m_idx, ny, nx, mode)) {
251                 continue;
252             }
253
254             look = false;
255             break;
256         }
257
258         dis = dis * 2;
259         min = min / 2;
260     }
261
262     if (attempts < 1) {
263         return;
264     }
265
266     sound(SOUND_TPOTHER);
267     player_ptr->current_floor_ptr->grid_array[oy][ox].m_idx = 0;
268     player_ptr->current_floor_ptr->grid_array[ny][nx].m_idx = m_idx;
269
270     m_ptr->fy = ny;
271     m_ptr->fx = nx;
272
273     update_monster(player_ptr, m_idx, true);
274     lite_spot(player_ptr, oy, ox);
275     lite_spot(player_ptr, ny, nx);
276
277     if (r_info[m_ptr->r_idx].flags7 & (RF7_LITE_MASK | RF7_DARK_MASK)) {
278         player_ptr->update |= (PU_MON_LITE);
279     }
280 }
281
282 /*!
283  * @brief プレイヤーのテレポート先選定と移動処理 /
284  * Teleport the player to a location up to "dis" grids away.
285  * @param player_ptr プレイヤーへの参照ポインタ
286  * @param dis 基本移動距離
287  * @param is_quantum_effect 量子的効果 (反テレポ無効)によるテレポートアウェイならばTRUE
288  * @param mode オプション
289  * @return 実際にテレポート処理が行われたらtrue
290  * @details
291  * <pre>
292  * If no such spaces are readily available, the distance may increase.
293  * Try very hard to move the player at least a quarter that distance.
294  *
295  * There was a nasty tendency for a long time; which was causing the
296  * player to "bounce" between two or three different spots because
297  * these are the only spots that are "far enough" way to satisfy the
298  * algorithm.
299  *
300  * But this tendency is now removed; in the new algorithm, a list of
301  * candidates is selected first, which includes at least 50% of all
302  * floor grids within the distance, and any single grid in this list
303  * of candidates has equal possibility to be choosen as a destination.
304  * </pre>
305  */
306 bool teleport_player_aux(PlayerType *player_ptr, POSITION dis, bool is_quantum_effect, teleport_flags mode)
307 {
308     if (player_ptr->wild_mode) {
309         return false;
310     }
311     if (!is_quantum_effect && player_ptr->anti_tele && !(mode & TELEPORT_NONMAGICAL)) {
312         msg_print(_("不思議な力がテレポートを防いだ!", "A mysterious force prevents you from teleporting!"));
313         return false;
314     }
315
316     int candidates_at[MAX_TELEPORT_DISTANCE + 1];
317     for (int i = 0; i <= MAX_TELEPORT_DISTANCE; i++) {
318         candidates_at[i] = 0;
319     }
320
321     if (dis > MAX_TELEPORT_DISTANCE) {
322         dis = MAX_TELEPORT_DISTANCE;
323     }
324
325     int left = std::max(1, player_ptr->x - dis);
326     int right = std::min(player_ptr->current_floor_ptr->width - 2, player_ptr->x + dis);
327     int top = std::max(1, player_ptr->y - dis);
328     int bottom = std::min(player_ptr->current_floor_ptr->height - 2, player_ptr->y + dis);
329     int total_candidates = 0;
330     for (POSITION y = top; y <= bottom; y++) {
331         for (POSITION x = left; x <= right; x++) {
332             if (!cave_player_teleportable_bold(player_ptr, y, x, mode)) {
333                 continue;
334             }
335
336             int d = distance(player_ptr->y, player_ptr->x, y, x);
337             if (d > dis) {
338                 continue;
339             }
340
341             total_candidates++;
342             candidates_at[d]++;
343         }
344     }
345
346     if (0 == total_candidates) {
347         return false;
348     }
349
350     int cur_candidates;
351     int min = dis;
352     for (cur_candidates = 0; min >= 0; min--) {
353         cur_candidates += candidates_at[min];
354         if (cur_candidates && (cur_candidates >= total_candidates / 2)) {
355             break;
356         }
357     }
358
359     int pick = randint1(cur_candidates);
360
361     /* Search again the choosen location */
362     POSITION yy, xx = 0;
363     for (yy = top; yy <= bottom; yy++) {
364         for (xx = left; xx <= right; xx++) {
365             if (!cave_player_teleportable_bold(player_ptr, yy, xx, mode)) {
366                 continue;
367             }
368
369             int d = distance(player_ptr->y, player_ptr->x, yy, xx);
370             if (d > dis) {
371                 continue;
372             }
373             if (d < min) {
374                 continue;
375             }
376
377             pick--;
378             if (!pick) {
379                 break;
380             }
381         }
382
383         if (!pick) {
384             break;
385         }
386     }
387
388     if (player_bold(player_ptr, yy, xx)) {
389         return false;
390     }
391
392     sound(SOUND_TELEPORT);
393 #ifdef JP
394     if (is_echizen(player_ptr)) {
395         msg_format("『こっちだぁ、%s』", player_ptr->name);
396     }
397 #endif
398     (void)move_player_effect(player_ptr, yy, xx, MPE_FORGET_FLOW | MPE_HANDLE_STUFF | MPE_DONT_PICKUP);
399     return true;
400 }
401
402 /*!
403  * @brief プレイヤーのテレポート処理メインルーチン
404  * @param player_ptr プレイヤーへの参照ポインタ
405  * @param dis 基本移動距離
406  * @param mode オプション
407  */
408 void teleport_player(PlayerType *player_ptr, POSITION dis, BIT_FLAGS mode)
409 {
410     const POSITION oy = player_ptr->y;
411     const POSITION ox = player_ptr->x;
412
413     if (!teleport_player_aux(player_ptr, dis, false, i2enum<teleport_flags>(mode))) {
414         return;
415     }
416
417     /* Monsters with teleport ability may follow the player */
418     for (POSITION xx = -1; xx < 2; xx++) {
419         for (POSITION yy = -1; yy < 2; yy++) {
420             MONSTER_IDX tmp_m_idx = player_ptr->current_floor_ptr->grid_array[oy + yy][ox + xx].m_idx;
421             if (tmp_m_idx && (player_ptr->riding != tmp_m_idx)) {
422                 auto *m_ptr = &player_ptr->current_floor_ptr->m_list[tmp_m_idx];
423                 auto *r_ptr = &r_info[m_ptr->r_idx];
424
425                 bool can_follow = r_ptr->ability_flags.has(MonsterAbilityType::TPORT);
426                 can_follow &= r_ptr->resistance_flags.has_not(MonsterResistanceType::RESIST_TELEPORT);
427                 can_follow &= monster_csleep_remaining(m_ptr) == 0;
428                 if (can_follow) {
429                     teleport_monster_to(player_ptr, tmp_m_idx, player_ptr->y, player_ptr->x, r_ptr->level, TELEPORT_SPONTANEOUS);
430                 }
431             }
432         }
433     }
434 }
435
436 /*!
437  * @brief プレイヤーのテレポートアウェイ処理 /
438  * @param m_idx アウェイを試みたモンスターID
439  * @param player_ptr プレイヤーへの参照ポインタ
440  * @param dis テレポート距離
441  * @param is_quantum_effect 量子的効果によるテレポートアウェイならばTRUE
442  */
443 void teleport_player_away(MONSTER_IDX m_idx, PlayerType *player_ptr, POSITION dis, bool is_quantum_effect)
444 {
445     if (player_ptr->phase_out) {
446         return;
447     }
448
449     const POSITION oy = player_ptr->y;
450     const POSITION ox = player_ptr->x;
451
452     if (!teleport_player_aux(player_ptr, dis, is_quantum_effect, TELEPORT_PASSIVE)) {
453         return;
454     }
455
456     /* Monsters with teleport ability may follow the player */
457     for (POSITION xx = -1; xx < 2; xx++) {
458         for (POSITION yy = -1; yy < 2; yy++) {
459             MONSTER_IDX tmp_m_idx = player_ptr->current_floor_ptr->grid_array[oy + yy][ox + xx].m_idx;
460             bool is_teleportable = tmp_m_idx > 0;
461             is_teleportable &= player_ptr->riding != tmp_m_idx;
462             is_teleportable &= m_idx != tmp_m_idx;
463             if (!is_teleportable) {
464                 continue;
465             }
466
467             auto *m_ptr = &player_ptr->current_floor_ptr->m_list[tmp_m_idx];
468             auto *r_ptr = &r_info[m_ptr->r_idx];
469
470             bool can_follow = r_ptr->ability_flags.has(MonsterAbilityType::TPORT);
471             can_follow &= r_ptr->resistance_flags.has_not(MonsterResistanceType::RESIST_TELEPORT);
472             can_follow &= monster_csleep_remaining(m_ptr) == 0;
473             if (can_follow) {
474                 teleport_monster_to(player_ptr, tmp_m_idx, player_ptr->y, player_ptr->x, r_ptr->level, TELEPORT_SPONTANEOUS);
475             }
476         }
477     }
478 }
479
480 /*!
481  * @brief プレイヤーを指定位置近辺にテレポートさせる
482  * Teleport player to a grid near the given location
483  * @param player_ptr プレイヤーへの参照ポインタ
484  * @param ny 目標Y座標
485  * @param nx 目標X座標
486  * @param mode オプションフラグ
487  * @details
488  * <pre>
489  * This function is slightly obsessive about correctness.
490  * This function allows teleporting into vaults (!)
491  * </pre>
492  */
493 void teleport_player_to(PlayerType *player_ptr, POSITION ny, POSITION nx, teleport_flags mode)
494 {
495     if (player_ptr->anti_tele && !(mode & TELEPORT_NONMAGICAL)) {
496         msg_print(_("不思議な力がテレポートを防いだ!", "A mysterious force prevents you from teleporting!"));
497         return;
498     }
499
500     /* Find a usable location */
501     POSITION y, x;
502     POSITION dis = 0, ctr = 0;
503     while (true) {
504         while (true) {
505             y = (POSITION)rand_spread(ny, dis);
506             x = (POSITION)rand_spread(nx, dis);
507             if (in_bounds(player_ptr->current_floor_ptr, y, x)) {
508                 break;
509             }
510         }
511
512         bool is_anywhere = w_ptr->wizard;
513         is_anywhere &= (mode & TELEPORT_PASSIVE) == 0;
514         is_anywhere &= (player_ptr->current_floor_ptr->grid_array[y][x].m_idx > 0) || player_ptr->current_floor_ptr->grid_array[y][x].m_idx == player_ptr->riding;
515         if (is_anywhere) {
516             break;
517         }
518
519         if (cave_player_teleportable_bold(player_ptr, y, x, mode)) {
520             break;
521         }
522
523         if (++ctr > (4 * dis * dis + 4 * dis + 1)) {
524             ctr = 0;
525             dis++;
526         }
527     }
528
529     sound(SOUND_TELEPORT);
530     (void)move_player_effect(player_ptr, y, x, MPE_FORGET_FLOW | MPE_HANDLE_STUFF | MPE_DONT_PICKUP);
531 }
532
533 void teleport_away_followable(PlayerType *player_ptr, MONSTER_IDX m_idx)
534 {
535     auto *m_ptr = &player_ptr->current_floor_ptr->m_list[m_idx];
536     POSITION oldfy = m_ptr->fy;
537     POSITION oldfx = m_ptr->fx;
538     bool old_ml = m_ptr->ml;
539     POSITION old_cdis = m_ptr->cdis;
540
541     teleport_away(player_ptr, m_idx, MAX_SIGHT * 2 + 5, TELEPORT_SPONTANEOUS);
542
543     bool is_followable = old_ml;
544     is_followable &= old_cdis <= MAX_SIGHT;
545     is_followable &= w_ptr->timewalk_m_idx == 0;
546     is_followable &= !player_ptr->phase_out;
547     is_followable &= los(player_ptr, player_ptr->y, player_ptr->x, oldfy, oldfx);
548     if (!is_followable) {
549         return;
550     }
551
552     bool follow = false;
553     if (player_ptr->muta.has(PlayerMutationType::VTELEPORT) || PlayerClass(player_ptr).equals(PlayerClassType::IMITATOR)) {
554         follow = true;
555     } else {
556         ObjectType *o_ptr;
557         INVENTORY_IDX i;
558
559         for (i = INVEN_MAIN_HAND; i < INVEN_TOTAL; i++) {
560             o_ptr = &player_ptr->inventory_list[i];
561             if (o_ptr->k_idx && !o_ptr->is_cursed()) {
562                 auto flgs = object_flags(o_ptr);
563                 if (flgs.has(TR_TELEPORT)) {
564                     follow = true;
565                     break;
566                 }
567             }
568         }
569     }
570
571     if (!follow) {
572         return;
573     }
574     if (!get_check_strict(player_ptr, _("ついていきますか?", "Do you follow it? "), CHECK_OKAY_CANCEL)) {
575         return;
576     }
577
578     if (one_in_(3)) {
579         teleport_player(player_ptr, 200, TELEPORT_PASSIVE);
580         msg_print(_("失敗!", "Failed!"));
581     } else {
582         teleport_player_to(player_ptr, m_ptr->fy, m_ptr->fx, TELEPORT_SPONTANEOUS);
583     }
584
585     player_ptr->energy_need += ENERGY_NEED();
586 }
587
588 /*!
589  * @brief 次元の扉処理 /
590  * Dimension Door
591  * @param player_ptr プレイヤーへの参照ポインタ
592  * @param x テレポート先のX座標
593  * @param y テレポート先のY座標
594  * @return 目標に指定通りテレポートできたならばTRUEを返す
595  */
596 bool exe_dimension_door(PlayerType *player_ptr, POSITION x, POSITION y)
597 {
598     PLAYER_LEVEL plev = player_ptr->lev;
599
600     player_ptr->energy_need += (int16_t)((int32_t)(60 - plev) * ENERGY_NEED() / 100L);
601
602     if (!cave_player_teleportable_bold(player_ptr, y, x, TELEPORT_SPONTANEOUS) || (distance(y, x, player_ptr->y, player_ptr->x) > plev / 2 + 10) || (!randint0(plev / 10 + 10))) {
603         player_ptr->energy_need += (int16_t)((int32_t)(60 - plev) * ENERGY_NEED() / 100L);
604         teleport_player(player_ptr, (plev + 2) * 2, TELEPORT_PASSIVE);
605         return false;
606     }
607
608     teleport_player_to(player_ptr, y, x, TELEPORT_SPONTANEOUS);
609     return true;
610 }
611
612 /*!
613  * @brief 次元の扉処理のメインルーチン /
614  * @param player_ptr プレイヤーへの参照ポインタ
615  * Dimension Door
616  * @return ターンを消費した場合TRUEを返す
617  */
618 bool dimension_door(PlayerType *player_ptr)
619 {
620     DEPTH x = 0, y = 0;
621     if (!tgt_pt(player_ptr, &x, &y)) {
622         return false;
623     }
624
625     if (exe_dimension_door(player_ptr, x, y)) {
626         return true;
627     }
628
629     msg_print(_("精霊界から物質界に戻る時うまくいかなかった!", "You fail to exit the astral plane correctly!"));
630     return true;
631 }