Crossfire Server, Trunk
food.cpp
Go to the documentation of this file.
1 /*
2  * Crossfire -- cooperative multi-player graphical RPG and adventure game
3  *
4  * Copyright (c) 1999-2014 Mark Wedel and the Crossfire Development Team
5  * Copyright (c) 1992 Frank Tore Johansen
6  *
7  * Crossfire is free software and comes with ABSOLUTELY NO WARRANTY. You are
8  * welcome to redistribute it under certain conditions. For details, please
9  * see COPYING and LICENSE.
10  *
11  * The authors can be reached via e-mail at <crossfire@metalforge.org>.
12  */
13 
19 #include "global.h"
20 
21 #include <math.h>
22 #include <stdlib.h>
23 #include <string.h>
24 
25 #include "ob_methods.h"
26 #include "ob_types.h"
27 #include "sounds.h"
28 #include "sproto.h"
29 
30 static method_ret food_type_apply(object *food, object *applier, int aflags);
31 static void eat_special_food(object *who, object *food);
32 static int dragon_eat_flesh(object *op, object *meal);
33 
37 void init_type_food(void) {
41 }
42 
43 static void check_heal_and_mana(object *who, object *food) {
44  /* check for hp, sp change */
45  if (food->stats.hp != 0 && !is_wraith_pl(who)) {
46  if (QUERY_FLAG(food, FLAG_CURSED)) {
47  safe_strncpy(who->contr->killer, food->name,
48  sizeof(who->contr->killer));
49  hit_player(who, food->stats.hp, food, AT_POISON, 1);
51  "Eck!...that was poisonous!");
52  } else {
53  if (food->stats.hp > 0)
55  "You begin to feel better.");
56  else
58  "Eck!...that was poisonous!");
59  who->stats.hp += food->stats.hp;
60  }
61  }
62  if (food->stats.sp != 0) {
63  if (QUERY_FLAG(food, FLAG_CURSED)) {
65  "You are drained of mana!");
66  who->stats.sp -= food->stats.sp;
67  if (who->stats.sp < 0)
68  who->stats.sp = 0;
69  } else {
71  "You feel a rush of magical energy!");
72  who->stats.sp += food->stats.sp;
73  /* place limit on max sp from food? */
74  }
75  }
76 }
77 
78 static void eat_common(object* applier, object* food) {
79  char buf[MAX_BUF];
80  int capacity_remaining = MAX_FOOD - applier->stats.food;
81  applier->stats.food += food->stats.food;
82  if (capacity_remaining < food->stats.food)
83  applier->stats.hp += capacity_remaining / 50;
84  else
85  applier->stats.hp += food->stats.food / 50;
86  if (applier->stats.hp > applier->stats.maxhp)
87  applier->stats.hp = applier->stats.maxhp;
88  if (applier->stats.food > MAX_FOOD)
89  applier->stats.food = MAX_FOOD;
90  const int wasted_food = food->stats.food - capacity_remaining;
91  if (wasted_food > 0) {
92  const int thresh = MAX_FOOD/4;
93  int speed_penalty;
94  if (wasted_food > thresh) {
95  speed_penalty = 10;
96  } else {
97  speed_penalty = 5;
98  }
99 
100  if (slow_living_by(applier, speed_penalty)) {
101  snprintf(buf, sizeof(buf),
102  "%s the %s makes you %s full. You feel yourself moving %s "
103  "slower.",
104  food->type == DRINK ? "Drinking" : "Eating", food->name,
105  wasted_food > thresh ? "very" : "rather",
106  wasted_food > thresh ? "much" : "somewhat");
109  }
110  }
111 }
112 
121 static method_ret food_type_apply(object *food, object *applier, int aflags) {
122  (void)aflags;
123  if (QUERY_FLAG(food, FLAG_NO_PICK)) {
125  MSG_TYPE_APPLY_FAILURE, "You can't %s that!",
126  food->type == DRINK ? "drink" : "eat");
127  return METHOD_OK;
128  }
129 
130  if (applier->type != PLAYER) {
131  applier->stats.hp = applier->stats.maxhp;
132  } else {
133  char buf[MAX_BUF];
134  if (food->type == FLESH && is_dragon_pl(applier)) {
135  /* check if this is a dragon (player), eating some flesh */
136  if (!QUERY_FLAG(food, FLAG_CURSED)) {
137  dragon_eat_flesh(applier, food);
138  eat_common(applier, food);
139  }
140  check_heal_and_mana(applier, food);
141  } else if (is_old_wraith_pl(applier)) {
142  /* Check for old wraith player, give them the feeding skill */
143  object *skill = give_skill_by_name(applier, "wraith feed");
144  if (skill) {
146  link_player_skills(applier);
147 
148  snprintf(buf, sizeof(buf), "You have been dead for too long to taste %s, ", food->name);
150  buf);
152  "and seem to have obtained a taste for living flesh.");
153  } else
154  LOG(llevError, "wraith feed skill not found\n");
155  } else if (is_wraith_pl(applier)) {
156  /* Wraith player gets no food from eating. */
157  snprintf(buf, sizeof(buf), "You can no longer taste %s, and do not feel less hungry after %s it.", food->name, food->type == DRINK ? "drinking" : "eating");
159  buf);
160  } else {
161  /* usual case - not a wraith or a dragon eating non-flesh */
162  if (!QUERY_FLAG(food, FLAG_CURSED)) {
163  if (!is_dragon_pl(applier)) {
164  /* eating message for normal players*/
165  if (food->type == DRINK)
166  snprintf(buf, sizeof(buf), "Ahhh...that %s tasted good.", food->name);
167  else
168  snprintf(buf, sizeof(buf), "The %s tasted %s", food->name, food->type == FLESH ? "terrible!" : "good.");
169  } else {
170  /* eating message for dragon players*/
171  snprintf(buf, sizeof(buf), "The %s tasted terrible!", food->name);
172  }
174  eat_common(applier, food);
175  }
176  check_heal_and_mana(applier, food);
177 
178  /* special food hack -b.t. */
179  if (food->title)
180  eat_special_food(applier, food);
181  }
182  }
183  if (food->type == DRINK) {
184  play_sound_map(SOUND_TYPE_ITEM, applier, 0, "drink");
185  }
186  apply_handle_yield(food);
188  return METHOD_OK;
189 }
190 
203 static void eat_special_food(object *who, object *food) {
204  object *force;
205  int i, did_one = 0;
206  int8_t k;
207 
209 
210  for (i = 0; i < NUM_STATS; i++) {
211  k = get_attr_value(&food->stats, i);
212  if (k) {
213  set_attr_value(&force->stats, i, k);
214  did_one = 1;
215  }
216  }
217 
218  /* check if we can protect the eater */
219  for (i = 0; i < NROFATTACKS; i++) {
220  if (food->resist[i] > 0) {
221  force->resist[i] = food->resist[i]/2;
222  did_one = 1;
223  }
224  }
225 
226  /* Check to affect hp/sp/grace regen. Since sp and hp do direct heals, we use maxhp/maxsp/maxgrace */
227  if (food->stats.maxgrace != 0) {
228  force->stats.grace = food->stats.maxgrace;
229  did_one = 1;
230  }
231  if (food->stats.maxsp != 0) {
232  force->stats.sp = food->stats.maxsp;
233  did_one = 1;
234  }
235  if (food->stats.maxhp != 0) {
236  force->stats.hp = food->stats.maxhp;
237  did_one = 1;
238  }
239  /* Allow for food to attune/repel/deny a spell path for a duration */
240  if (food->path_attuned != 0) {
241  force->path_attuned = food->path_attuned;
242  did_one = 1;
243  }
244  if (food->path_repelled != 0) {
245  force->path_repelled = food->path_repelled;
246  did_one = 1;
247  }
248  if (food->path_denied != 0) {
249  force->path_denied = food->path_denied;
250  did_one = 1;
251  }
252 
253  /* Allow for some flags on the food item to transfer to the force, too. */
254  /* Set of transferrable flags from the food to the force, as these print a message in change_abil:
255  FLAG_BLIND
256  FLAG_SEE_IN_DARK
257  FLAG_STEALTH
258  FLAG_MAKE_INVIS
259  FLAG_XRAYS
260  FLAG_UNDEAD
261  FLAG_LIFESAVE
262  FLAG_REFL_MISSILE
263  FLAG_REFL_SPELL
264  */
265  if (QUERY_FLAG(food, FLAG_BLIND)) {
267  did_one = 1;
268  }
269  if (QUERY_FLAG(food, FLAG_SEE_IN_DARK)) {
271  did_one = 1;
272  }
273  if (QUERY_FLAG(food, FLAG_STEALTH)) {
275  did_one = 1;
276  }
277  if (QUERY_FLAG(food, FLAG_MAKE_INVIS)) {
279  did_one = 1;
280  }
281  if (QUERY_FLAG(food, FLAG_XRAYS)) {
283  did_one = 1;
284  }
285  if (QUERY_FLAG(food, FLAG_UNDEAD)) {
287  did_one = 1;
288  }
289  if (QUERY_FLAG(food, FLAG_LIFESAVE)) {
291  did_one = 1;
292  }
293  if (QUERY_FLAG(food, FLAG_REFL_MISSILE)) {
295  did_one = 1;
296  }
297  if (QUERY_FLAG(food, FLAG_REFL_SPELL)) {
299  did_one = 1;
300  }
301  /* Allow for food to temporarily grant move types */
302  if (food->move_type != 0) {
303  force->move_type = food->move_type;
304  did_one = 1;
305  }
306  /* And to grant atacktypes */
307  if (food->attacktype != 0) {
308  force->attacktype = food->attacktype;
309  did_one = 1;
310  }
311  /* And luck, wc, and ac */
312  if (food->stats.wc != 0) {
313  force->stats.wc = food->stats.wc;
314  did_one = 1;
315  }
316  if (food->stats.ac != 0) {
317  force->stats.ac = food->stats.ac;
318  did_one = 1;
319  }
320  if (food->stats.luck != 0) {
321  force->stats.luck = food->stats.luck;
322  did_one = 1;
323  }
324 
325  if (did_one) {
326  force->speed = MOVE_PER_SECOND;
328  /* bigger morsel of food = longer effect time */
329  force->duration = food->stats.food/4;
334  if (food->other_arch != NULL && who->map != NULL) {
336  }
337  } else {
339  }
340 
341  fix_object(who);
342 }
343 
357 static int dragon_eat_flesh(object *op, object *meal) {
358  object *skin = NULL; /* pointer to dragon skin force*/
359  object *abil = NULL; /* pointer to dragon ability force*/
360 
361  char buf[MAX_BUF]; /* tmp. string buffer */
362  double chance; /* improvement-chance of one resist type */
363  double totalchance = 1; /* total chance of gaining one resistance */
364  double bonus = 0; /* level bonus (improvement is easier at lowlevel) */
365  double mbonus = 0; /* monster bonus */
366  int atnr_winner[NROFATTACKS]; /* winning candidates for resistance improvement */
367  int winners = 0; /* number of winners */
368  int i; /* index */
369 
370  /* let's make sure and doublecheck the parameters */
371  if (meal->type != FLESH || !is_dragon_pl(op))
372  return 0;
373 
374  /* now grab the 'dragon_skin'- and 'dragon_ability'-forces
375  * from the player's inventory
376  */
377  skin = object_find_by_type_and_arch_name(op, FORCE, "dragon_skin_force");
378  abil = object_find_by_type_and_arch_name(op, FORCE, "dragon_ability_force");
379 
380  /* if either skin or ability are missing, this is an old player
381  * which is not to be considered a dragon -> bail out
382  */
383  if (skin == NULL || abil == NULL)
384  return 0;
385 
386  /*LOG(llevDebug, "-> player: %d, flesh: %d\n", op->level, meal->level);*/
387 
388  /* on to the interesting part: chances for adding resistance */
389  for (i = 0; i < NROFATTACKS; i++) {
390  if (meal->resist[i] > 0 && atnr_is_dragon_enabled(i)) {
391  /* got positive resistance, now calculate improvement chance (0-100) */
392 
393  /* this bonus makes resistance increase easier at lower levels */
394  bonus = (settings.max_level-op->level)*30./((double)settings.max_level);
395  if (i == abil->stats.exp)
396  bonus += 5; /* additional bonus for resistance of ability-focus */
397 
398  /* monster bonus increases with level, because high-level
399  * flesh is too rare
400  */
401  mbonus = op->level*20./((double)settings.max_level);
402 
403  chance = (((double)MIN(op->level+bonus, meal->level+bonus+mbonus))*100./((double)settings.max_level))-skin->resist[i];
404 
405  if (chance >= 0.)
406  chance += 1.;
407  else
408  chance = (chance < -12) ? 0. : 1./pow(2., -chance);
409 
410  /* chance is proportional to amount of resistance (max. 50) */
411  chance *= ((double)(MIN(meal->resist[i], 50)))/50.;
412 
413  /* doubled chance for resistance of ability-focus */
414  if (i == abil->stats.exp)
415  chance = MIN(100., chance*2.);
416 
417  /* now make the throw and save all winners (Don't insert luck bonus here!) */
418  if (RANDOM()%10000 < (unsigned int)(chance*100)) {
419  atnr_winner[winners] = i;
420  winners++;
421  }
422 
423  if (chance >= 0.01)
424  totalchance *= 1-chance/100;
425 
426  /*LOG(llevDebug, " %s: bonus %.1f, chance %.1f\n", attacks[i], bonus, chance);*/
427  }
428  }
429 
430  /* inverse totalchance as until now we have the failure-chance */
431  totalchance = 100-totalchance*100;
432  /* print message according to totalchance */
433  if (totalchance > 50.)
434  snprintf(buf, sizeof(buf), "Hmm! The %s tasted delicious!", meal->name);
435  else if (totalchance > 10.)
436  snprintf(buf, sizeof(buf), "The %s tasted very good.", meal->name);
437  else if (totalchance > 1.)
438  snprintf(buf, sizeof(buf), "The %s tasted good.", meal->name);
439  else if (totalchance > 0.1)
440  snprintf(buf, sizeof(buf), "The %s tasted bland.", meal->name);
441  else if (totalchance >= 0.01)
442  snprintf(buf, sizeof(buf), "The %s had a boring taste.", meal->name);
443  else if (meal->last_eat > 0 && atnr_is_dragon_enabled(meal->last_eat))
444  snprintf(buf, sizeof(buf), "The %s tasted strange.", meal->name);
445  else
446  snprintf(buf, sizeof(buf), "The %s had no taste.", meal->name);
448  buf);
449 
450  /* now choose a winner if we have any */
451  i = -1;
452  if (winners > 0)
453  i = atnr_winner[RANDOM()%winners];
454 
455  if (i >= 0 && i < NROFATTACKS && skin->resist[i] < 95) {
456  /* resistance increased! */
457  skin->resist[i]++;
458  fix_object(op);
459 
461  "Your skin is now more resistant to %s!",
462  change_resist_msg[i]);
463  }
464 
465  /* if this flesh contains a new ability focus, we mark it
466  * into the ability_force and it will take effect on next level
467  */
468  if (meal->last_eat > 0
470  && meal->last_eat != abil->last_eat) {
471  abil->last_eat = meal->last_eat; /* write:last_eat <new attnr focus> */
472 
473  if (meal->last_eat != abil->stats.exp) {
475  "Your metabolism prepares to focus on %s!",
476  change_resist_msg[meal->last_eat]);
478  "The change will happen at level %d",
479  abil->level+1);
480  } else {
482  "Your metabolism will continue to focus on %s.",
483  change_resist_msg[meal->last_eat]);
484  abil->last_eat = 0;
485  }
486  }
487  return 1;
488 }
living::exp
int64_t exp
Definition: living.h:47
PLAYER
@ PLAYER
Definition: object.h:112
global.h
settings
struct Settings settings
Definition: init.cpp:139
SOUND_TYPE_ITEM
#define SOUND_TYPE_ITEM
Definition: newclient.h:338
safe_strncpy
#define safe_strncpy
Definition: compat.h:27
living::maxhp
int16_t maxhp
Definition: living.h:41
Settings::max_level
int16_t max_level
Definition: global.h:302
AT_POISON
#define AT_POISON
Definition: attack.h:86
init_type_food
void init_type_food(void)
Definition: food.cpp:37
llevError
@ llevError
Definition: logger.h:11
object::path_attuned
uint32_t path_attuned
Definition: object.h:353
LOG
void LOG(LogLevel logLevel, const char *format,...)
Definition: logger.cpp:58
FLAG_UNDEAD
#define FLAG_UNDEAD
Definition: define.h:270
SET_FLAG
#define SET_FLAG(xyz, p)
Definition: define.h:224
FLESH
@ FLESH
Definition: object.h:192
QUERY_FLAG
#define QUERY_FLAG(xyz, p)
Definition: define.h:226
FLAG_REFL_MISSILE
#define FLAG_REFL_MISSILE
Definition: define.h:273
FLAG_SEE_IN_DARK
#define FLAG_SEE_IN_DARK
Definition: define.h:337
dragon_eat_flesh
static int dragon_eat_flesh(object *op, object *meal)
Definition: food.cpp:357
register_apply
void register_apply(int ob_type, apply_func method)
Definition: ob_types.cpp:62
METHOD_OK
#define METHOD_OK
Definition: ob_methods.h:15
give_skill_by_name
object * give_skill_by_name(object *op, const char *skill_name)
Definition: living.cpp:1784
MSG_TYPE_APPLY_CURSED
#define MSG_TYPE_APPLY_CURSED
Definition: newclient.h:608
MSG_TYPE_ATTRIBUTE
#define MSG_TYPE_ATTRIBUTE
Definition: newclient.h:408
draw_ext_info_format
void draw_ext_info_format(int flags, int pri, const object *pl, uint8_t type, uint8_t subtype, const char *format,...) PRINTF_ARGS(6
MIN
#define MIN(x, y)
Definition: compat.h:21
play_sound_map
void play_sound_map(int8_t sound_type, object *emitter, int dir, const char *action)
Definition: sounds.cpp:113
fix_object
void fix_object(object *op)
Definition: living.cpp:1132
NDI_RED
#define NDI_RED
Definition: newclient.h:248
NROFATTACKS
#define NROFATTACKS
Definition: attack.h:17
object::title
sstring title
Definition: object.h:325
FLAG_APPLIED
#define FLAG_APPLIED
Definition: define.h:235
object::level
int16_t level
Definition: object.h:361
FLAG_STEALTH
#define FLAG_STEALTH
Definition: define.h:312
FLAG_BLIND
#define FLAG_BLIND
Definition: define.h:336
buf
StringBuffer * buf
Definition: readable.cpp:1565
object_insert_in_ob
object * object_insert_in_ob(object *op, object *where)
Definition: object.cpp:2857
eat_special_food
static void eat_special_food(object *who, object *food)
Definition: food.cpp:203
object::resist
int16_t resist[NROFATTACKS]
Definition: object.h:351
FLAG_NO_PICK
#define FLAG_NO_PICK
Definition: define.h:239
object::path_denied
uint32_t path_denied
Definition: object.h:355
object::path_repelled
uint32_t path_repelled
Definition: object.h:354
autojail.who
who
Definition: autojail.py:3
food_type_apply
static method_ret food_type_apply(object *food, object *applier, int aflags)
Definition: food.cpp:121
object_free_drop_inventory
void object_free_drop_inventory(object *ob)
Definition: object.cpp:1560
slow_living_by
int slow_living_by(object *op, const int speed_penalty)
Definition: attack.cpp:2227
object_decrease_nrof_by_one
#define object_decrease_nrof_by_one(xyz)
Definition: compat.h:32
MSG_TYPE_ATTRIBUTE_RACE
#define MSG_TYPE_ATTRIBUTE_RACE
Definition: newclient.h:567
MSG_TYPE_ATTRIBUTE_BAD_EFFECT_START
#define MSG_TYPE_ATTRIBUTE_BAD_EFFECT_START
Definition: newclient.h:568
MSG_TYPE_APPLY_SUCCESS
#define MSG_TYPE_APPLY_SUCCESS
Definition: newclient.h:606
MSG_TYPE_ATTRIBUTE_PROTECTION_GAIN
#define MSG_TYPE_ATTRIBUTE_PROTECTION_GAIN
Definition: newclient.h:558
object::move_type
MoveType move_type
Definition: object.h:436
set_attr_value
void set_attr_value(living *stats, int attr, int8_t value)
Definition: living.cpp:218
FLAG_MAKE_INVIS
#define FLAG_MAKE_INVIS
Definition: define.h:328
change_resist_msg
const char *const change_resist_msg[NROFATTACKS]
Definition: init.cpp:70
object::last_eat
int32_t last_eat
Definition: object.h:366
object_update_speed
void object_update_speed(object *op)
Definition: object.cpp:1349
object::type
uint8_t type
Definition: object.h:348
living::food
int32_t food
Definition: living.h:48
eat_common
static void eat_common(object *applier, object *food)
Definition: food.cpp:78
sproto.h
living::sp
int16_t sp
Definition: living.h:42
FLAG_CAN_USE_SKILL
#define FLAG_CAN_USE_SKILL
Definition: define.h:321
nlohmann::detail::void
j template void())
Definition: json.hpp:4099
object_insert_in_map_at
object * object_insert_in_map_at(object *op, mapstruct *m, object *originator, int flag, int x, int y)
Definition: object.cpp:2100
object::other_arch
struct archetype * other_arch
Definition: object.h:425
MAX_BUF
#define MAX_BUF
Definition: define.h:35
INS_ON_TOP
#define INS_ON_TOP
Definition: object.h:583
is_wraith_pl
int is_wraith_pl(object *op)
Definition: player.cpp:173
create_archetype
object * create_archetype(const char *name)
Definition: arch.cpp:278
living::wc
int8_t wc
Definition: living.h:37
RANDOM
#define RANDOM()
Definition: define.h:644
living::maxgrace
int16_t maxgrace
Definition: living.h:45
change_abil
int change_abil(object *op, object *tmp)
Definition: living.cpp:394
method_ret
char method_ret
Definition: ob_methods.h:14
ob_types.h
sounds.h
FLAG_REFL_SPELL
#define FLAG_REFL_SPELL
Definition: define.h:275
NDI_UNIQUE
#define NDI_UNIQUE
Definition: newclient.h:265
apply_handle_yield
void apply_handle_yield(object *tmp)
Definition: apply.cpp:122
object::name
sstring name
Definition: object.h:319
is_dragon_pl
int is_dragon_pl(const object *op)
Definition: player.cpp:122
living::maxsp
int16_t maxsp
Definition: living.h:43
give.op
op
Definition: give.py:33
hit_player
int hit_player(object *op, int dam, object *hitter, uint32_t type, int full_hit)
Definition: attack.cpp:1903
living::ac
int8_t ac
Definition: living.h:38
atnr_is_dragon_enabled
int atnr_is_dragon_enabled(int attacknr)
Definition: player.cpp:103
object_find_by_type_and_arch_name
object * object_find_by_type_and_arch_name(const object *who, int type, const char *name)
Definition: object.cpp:4277
arch_to_object
object * arch_to_object(archetype *at)
Definition: arch.cpp:229
MSG_TYPE_APPLY_FAILURE
#define MSG_TYPE_APPLY_FAILURE
Definition: newclient.h:607
FLAG_IS_USED_UP
#define FLAG_IS_USED_UP
Definition: define.h:260
draw_ext_info
void draw_ext_info(int flags, int pri, const object *pl, uint8_t type, uint8_t subtype, const char *message)
Definition: main.cpp:308
FOOD
@ FOOD
Definition: object.h:117
check_heal_and_mana
static void check_heal_and_mana(object *who, object *food)
Definition: food.cpp:43
DRINK
@ DRINK
Definition: object.h:162
FLAG_XRAYS
#define FLAG_XRAYS
Definition: define.h:300
ob_methods.h
is_old_wraith_pl
int is_old_wraith_pl(object *op)
Definition: player.cpp:185
object::stats
living stats
Definition: object.h:378
get_attr_value
int8_t get_attr_value(const living *stats, int attr)
Definition: living.cpp:313
link_player_skills
void link_player_skills(object *op)
Definition: player.cpp:287
MSG_TYPE_APPLY
#define MSG_TYPE_APPLY
Definition: newclient.h:411
object::attacktype
uint32_t attacktype
Definition: object.h:352
FLAG_CURSED
#define FLAG_CURSED
Definition: define.h:316
MOVE_PER_SECOND
static const float MOVE_PER_SECOND
Definition: tod.h:54
NUM_STATS
@ NUM_STATS
Definition: living.h:18
MAX_FOOD
static const int32_t MAX_FOOD
Definition: define.h:461
living::hp
int16_t hp
Definition: living.h:40
living::luck
int8_t luck
Definition: living.h:39
FORCE
@ FORCE
Definition: object.h:229
FLAG_LIFESAVE
#define FLAG_LIFESAVE
Definition: define.h:305
FORCE_NAME
#define FORCE_NAME
Definition: spells.h:169
dragon_attune.force
force
Definition: dragon_attune.py:45