cooking.c 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567
  1. enum CookingMethodType
  2. {
  3. NONE = 0, //no cooking method available
  4. BAKING = 1,
  5. BOILING = 2,
  6. DRYING = 3,
  7. TIME = 4,
  8. COUNT
  9. }
  10. class Cooking
  11. {
  12. static const float TIME_WITH_SUPPORT_MATERIAL_COEF = 1.0; //! time modifier used when not using support material
  13. static const float TIME_WITHOUT_SUPPORT_MATERIAL_COEF = 2.0; //! time modifier used when using support material
  14. static const float COOKING_FOOD_TIME_INC_VALUE = 2; //! time increase when cooking a food
  15. static const float COOKING_LARD_DECREASE_COEF = 25; //! how many units from quantity of lard are remove at each stage
  16. static const float COOKING_FOOD_QUANTITY_DECREASE_AMOUNT_NONE = 25; //! how many units from quantity of item are removed at each FoodStage change when support material is NOT used
  17. static const float COOKING_FOOD_QUANTITY_DECREASE_AMOUNT_LARD = 0; //! NOT USED.
  18. static const float DEFAULT_COOKING_TEMPERATURE = 150; //default temperature for cooking (e.g. cooking on stick)
  19. static const float FOOD_MAX_COOKING_TEMPERATURE = 150; //
  20. static const float PARAM_BURN_DAMAGE_COEF = 0.05; //value for calculating damage on items located in fireplace CargoGrid
  21. static const float LIQUID_BOILING_POINT = 150; //boiling point for liquids
  22. static const float LIQUID_VAPOR_QUANTITY = 2; //vapor quantity
  23. typename COOKING_EQUIPMENT_POT = Pot;
  24. typename COOKING_EQUIPMENT_FRYINGPAN = FryingPan;
  25. typename COOKING_EQUIPMENT_CAULDRON = Cauldron;
  26. typename COOKING_INGREDIENT_LARD = Lard;
  27. protected float m_UpdateTime = 1; //set by the item/system using cooking
  28. void SetCookingUpdateTime(float val)
  29. {
  30. m_UpdateTime = val;
  31. }
  32. void ProcessItemToCook(notnull ItemBase pItem, ItemBase cookingEquip, Param2<CookingMethodType, float> pCookingMethod, out Param2<bool, bool> pStateFlags)
  33. {
  34. Edible_Base item_to_cook = Edible_Base.Cast(pItem);
  35. //! state flags are in order: is_done, is_burned
  36. pStateFlags = new Param2<bool, bool>(false, false);
  37. if (item_to_cook && item_to_cook.CanBeCooked())
  38. {
  39. //! update food
  40. UpdateCookingState(item_to_cook, pCookingMethod.param1, cookingEquip, pCookingMethod.param2);
  41. //check for done state for boiling and drying
  42. if (item_to_cook.IsFoodBoiled() || item_to_cook.IsFoodDried())
  43. {
  44. pStateFlags.param1 = true;
  45. }
  46. //! check for done state from baking (exclude Lard from baked items)
  47. else if (item_to_cook.IsFoodBaked() && item_to_cook.Type() != Lard)
  48. {
  49. pStateFlags.param1 = true;
  50. }
  51. //! check for burned state
  52. else if (item_to_cook.IsFoodBurned())
  53. {
  54. pStateFlags.param2 = true;
  55. }
  56. }
  57. else
  58. {
  59. //add temperature to item
  60. if (pItem != cookingEquip) //already handled by the fireplace directly!
  61. AddTemperatureToItem(pItem, null, 0);
  62. //damage item that can actually overheat?
  63. if (pItem.CanItemOverheat())
  64. {
  65. if (pItem.IsItemOverheated())
  66. {
  67. if (pItem.HasQuantity() && pItem.GetQuantityNormalized() > 0)
  68. {
  69. pItem.AddQuantity(-LIQUID_VAPOR_QUANTITY,!pItem.IsLiquidContainer()); //TODO: use other constant here, or calculate from qtyMax..
  70. }
  71. else
  72. {
  73. pItem.DecreaseHealth(PARAM_BURN_DAMAGE_COEF * 100);
  74. }
  75. }
  76. }
  77. else
  78. {
  79. pItem.DecreaseHealth(PARAM_BURN_DAMAGE_COEF * 100); //pItem.IsKindOf("Grenade_Base")
  80. }
  81. }
  82. }
  83. //COOKING PROCESS
  84. //--- Cooking with equipment (pot)
  85. //Returns 1 if the item changed its cooking stage, 0 if not
  86. int CookWithEquipment(ItemBase cooking_equipment, float cooking_time_coef = 1)
  87. {
  88. bool is_empty;
  89. //check cooking conditions
  90. if (cooking_equipment == null)
  91. {
  92. return 0;
  93. }
  94. if (cooking_equipment.IsRuined())
  95. {
  96. return 0;
  97. }
  98. //manage items in cooking equipment
  99. Param2<bool, bool> stateFlags = new Param2<bool, bool>(false, false); // 1st - done; 2nd - burned
  100. Param2<CookingMethodType, float> cookingMethodWithTime = GetCookingMethodWithTimeOverride(cooking_equipment);
  101. //! cooking time coef override
  102. if (cooking_time_coef != 1)
  103. {
  104. cookingMethodWithTime.param2 = cooking_time_coef;
  105. }
  106. CargoBase cargo = cooking_equipment.GetInventory().GetCargo();
  107. if (cargo)
  108. {
  109. is_empty = cargo.GetItemCount() == 0;
  110. //process items
  111. for (int i = 0; i < cargo.GetItemCount(); i++)
  112. {
  113. ProcessItemToCook(ItemBase.Cast(cargo.GetItem(i)), cooking_equipment, cookingMethodWithTime, stateFlags);
  114. }
  115. }
  116. else
  117. {
  118. ProcessItemToCook(cooking_equipment, cooking_equipment, cookingMethodWithTime, stateFlags);
  119. }
  120. //manage cooking equipment
  121. Bottle_Base bottle_base = Bottle_Base.Cast(cooking_equipment);
  122. if (bottle_base)
  123. {
  124. float cookingEquipmentTemp = bottle_base.GetTemperature();
  125. int liquidType = bottle_base.GetLiquidType();
  126. //handle water boiling
  127. if (liquidType != LIQUID_NONE && cookingEquipmentTemp >= Liquid.GetBoilThreshold(liquidType))
  128. {
  129. //remove agents
  130. bottle_base.RemoveAllAgentsExcept(eAgents.HEAVYMETAL);
  131. //vaporize liquid
  132. if (bottle_base.IsItemOverheated())
  133. bottle_base.AddQuantity(-LIQUID_VAPOR_QUANTITY);
  134. }
  135. //handle audio visuals
  136. if (bottle_base.Type() == COOKING_EQUIPMENT_POT || bottle_base.Type() == COOKING_EQUIPMENT_CAULDRON)
  137. bottle_base.RefreshAudioVisualsOnClient(cookingMethodWithTime.param1, stateFlags.param1, is_empty, stateFlags.param2);
  138. }
  139. FryingPan frying_pan = FryingPan.Cast(cooking_equipment);
  140. if (frying_pan && !bottle_base)
  141. {
  142. //handle audio visuals
  143. frying_pan.RefreshAudioVisualsOnClient(cookingMethodWithTime.param1, stateFlags.param1, is_empty, stateFlags.param2);
  144. }
  145. return 1;
  146. }
  147. //Returns 1 if the item changed its cooking stage, 0 if not
  148. int CookOnStick( Edible_Base item_to_cook, float cook_time_inc )
  149. {
  150. if ( item_to_cook && item_to_cook.CanBeCookedOnStick() )
  151. {
  152. //update food
  153. return UpdateCookingStateOnStick( item_to_cook, cook_time_inc );
  154. }
  155. return 0;
  156. }
  157. //Returns 1 if the item changed its cooking stage, 0 if not
  158. protected int UpdateCookingState(Edible_Base item_to_cook, CookingMethodType cooking_method, ItemBase cooking_equipment, float cooking_time_coef)
  159. {
  160. //food properties
  161. float food_temperature = item_to_cook.GetTemperature();
  162. //{min_temperature, time_to_cook, max_temperature (optional)}
  163. //get next stage name - if next stage is not defined, next stage name will be empty "" and no cooking properties (food_min_temp, food_time_to_cook, food_max_temp) will be set
  164. FoodStageType new_stage_type = item_to_cook.GetNextFoodStageType(cooking_method);
  165. float food_min_temp = 0;
  166. float food_time_to_cook = 0;
  167. //Set next stage cooking properties if next stage possible
  168. if (item_to_cook.CanChangeToNewStage(cooking_method)) // new_stage_type != NONE
  169. {
  170. array<float> next_stage_cooking_properties = new array<float>();
  171. next_stage_cooking_properties = FoodStage.GetAllCookingPropertiesForStage(new_stage_type, null, item_to_cook.GetType());
  172. food_min_temp = next_stage_cooking_properties.Get(eCookingPropertyIndices.MIN_TEMP); //checked after temperature is changed
  173. food_time_to_cook = next_stage_cooking_properties.Get(eCookingPropertyIndices.COOK_TIME);
  174. }
  175. //add temperature
  176. AddTemperatureToItem(item_to_cook, cooking_equipment, food_min_temp);
  177. //decrease qty of burned items (or cookable items that can't be burned)
  178. if (item_to_cook.IsItemOverheated())
  179. DecreaseCookedItemQuantity(item_to_cook,COOKING_FOOD_QUANTITY_DECREASE_AMOUNT_NONE);
  180. //add cooking time if the food can be cooked by this method
  181. if (food_min_temp > 0 && food_temperature >= food_min_temp)
  182. {
  183. //! enable cooking SoundEvent
  184. item_to_cook.MakeSoundsOnClient(true,cooking_method);
  185. float new_cooking_time = item_to_cook.GetCookingTime() + COOKING_FOOD_TIME_INC_VALUE * cooking_time_coef;
  186. item_to_cook.SetCookingTime(new_cooking_time);
  187. //progress to next stage
  188. if (item_to_cook.GetCookingTime() >= food_time_to_cook)
  189. {
  190. //! Change food stage to new, IF DIFFERENT
  191. if (item_to_cook.GetFoodStageType() != new_stage_type)
  192. {
  193. item_to_cook.ChangeFoodStage(new_stage_type);
  194. if (cooking_equipment && cooking_equipment != item_to_cook)
  195. {
  196. if (cooking_method == CookingMethodType.BAKING)
  197. {
  198. ItemBase lard = GetItemTypeFromCargo(COOKING_INGREDIENT_LARD, cooking_equipment);
  199. if (lard)
  200. {
  201. //decrease lard quantity
  202. float lardQuantity = lard.GetQuantity() - COOKING_LARD_DECREASE_COEF;
  203. lardQuantity = Math.Clamp(lardQuantity, 0, lard.GetQuantityMax());
  204. lard.SetQuantity(lardQuantity);
  205. }
  206. else
  207. {
  208. //! any foodstage without lard
  209. DecreaseCookedItemQuantity(item_to_cook, COOKING_FOOD_QUANTITY_DECREASE_AMOUNT_NONE);
  210. }
  211. }
  212. }
  213. else
  214. {
  215. //! any foodstage without lard
  216. DecreaseCookedItemQuantity(item_to_cook, COOKING_FOOD_QUANTITY_DECREASE_AMOUNT_NONE);
  217. }
  218. }
  219. //reset cooking time
  220. item_to_cook.ResetCookingTime();
  221. return 1;
  222. }
  223. }
  224. else
  225. {
  226. item_to_cook.MakeSoundsOnClient(false);
  227. }
  228. return 0;
  229. }
  230. //Returns 1 if the item changed its cooking stage, 0 if not
  231. protected int UpdateCookingStateOnStick( Edible_Base item_to_cook, float cook_time_inc )
  232. {
  233. //food properties
  234. float food_temperature = item_to_cook.GetTemperature();
  235. //{min_temperature, time_to_cook, max_temperature (optional)}
  236. //get next stage name - if next stage is not defined, next stage name will be empty "" and no cooking properties (food_min_temp, food_time_to_cook, food_max_temp) will be set
  237. FoodStageType new_stage_type = item_to_cook.GetNextFoodStageType( CookingMethodType.BAKING );
  238. float food_min_temp = 0;
  239. float food_time_to_cook = 0;
  240. bool is_done = false; // baked
  241. bool is_burned = false; // burned
  242. //Set next stage cooking properties if next stage possible
  243. if ( item_to_cook.CanChangeToNewStage( CookingMethodType.BAKING ) )
  244. {
  245. array<float> next_stage_cooking_properties = new array<float>;
  246. next_stage_cooking_properties = FoodStage.GetAllCookingPropertiesForStage( new_stage_type, null, item_to_cook.GetType() );
  247. food_min_temp = next_stage_cooking_properties.Get( eCookingPropertyIndices.MIN_TEMP );
  248. food_time_to_cook = next_stage_cooking_properties.Get( eCookingPropertyIndices.COOK_TIME );
  249. }
  250. if (item_to_cook.GetInventory().IsAttachment())
  251. {
  252. //add temperature
  253. AddTemperatureToItem(item_to_cook, null, food_min_temp);
  254. }
  255. //add cooking time if the food can be cooked by this method
  256. if (food_min_temp > 0 && food_temperature >= food_min_temp)
  257. {
  258. //refresh audio
  259. item_to_cook.MakeSoundsOnClient(true, CookingMethodType.BAKING);
  260. float new_cooking_time = item_to_cook.GetCookingTime() + cook_time_inc;
  261. item_to_cook.SetCookingTime( new_cooking_time );
  262. //progress to next stage
  263. if (item_to_cook.GetCookingTime() >= food_time_to_cook)
  264. {
  265. //! Change food stage to new, IF DIFFERENT
  266. if (item_to_cook.GetFoodStageType() != new_stage_type)
  267. {
  268. item_to_cook.ChangeFoodStage(new_stage_type);
  269. DecreaseCookedItemQuantity(item_to_cook, COOKING_FOOD_QUANTITY_DECREASE_AMOUNT_NONE);
  270. }
  271. //reset cooking time
  272. item_to_cook.ResetCookingTime();
  273. return 1;
  274. }
  275. }
  276. else
  277. {
  278. item_to_cook.MakeSoundsOnClient(false);
  279. }
  280. return 0;
  281. }
  282. void SmokeItem(Edible_Base item_to_cook, float cook_time_inc)
  283. {
  284. if (item_to_cook)
  285. {
  286. float new_cook_time = item_to_cook.GetCookingTime() + cook_time_inc;
  287. float drying_cook_time = FoodStage.GetCookingPropertyFromIndex(eCookingPropertyIndices.COOK_TIME, FoodStageType.DRIED, null, item_to_cook.GetType());
  288. float drying_cook_temp = FoodStage.GetCookingPropertyFromIndex(eCookingPropertyIndices.MIN_TEMP, FoodStageType.DRIED, null, item_to_cook.GetType());
  289. float itemTemp = item_to_cook.GetTemperature();
  290. if (itemTemp >= drying_cook_temp)
  291. {
  292. switch (item_to_cook.GetFoodStageType())
  293. {
  294. case FoodStageType.RAW:
  295. item_to_cook.SetCookingTime(new_cook_time);
  296. if (item_to_cook.GetCookingTime() >= drying_cook_time)
  297. {
  298. item_to_cook.ChangeFoodStage(FoodStageType.DRIED);
  299. item_to_cook.ResetCookingTime();
  300. }
  301. break;
  302. default:
  303. item_to_cook.SetCookingTime(new_cook_time);
  304. if (item_to_cook.GetCookingTime() >= drying_cook_time)
  305. {
  306. item_to_cook.ChangeFoodStage(FoodStageType.BURNED);
  307. item_to_cook.ResetCookingTime();
  308. }
  309. break;
  310. }
  311. }
  312. }
  313. }
  314. void TerminateCookingSounds(ItemBase pItem)
  315. {
  316. Edible_Base edible;
  317. if (pItem)
  318. {
  319. CargoBase cargo = pItem.GetInventory().GetCargo();
  320. if (cargo) // cookware
  321. {
  322. for (int i = 0; i < cargo.GetItemCount(); i++)
  323. {
  324. edible = Edible_Base.Cast(cargo.GetItem(i));
  325. if (edible)
  326. {
  327. edible.MakeSoundsOnClient(false);
  328. }
  329. }
  330. }
  331. else
  332. {
  333. edible = Edible_Base.Cast(pItem);
  334. if (edible)
  335. {
  336. edible.MakeSoundsOnClient(false);
  337. }
  338. }
  339. }
  340. }
  341. //! Cooking data
  342. protected ItemBase GetItemTypeFromCargo( typename item_type, ItemBase cooking_equipment )
  343. {
  344. CargoBase cargo = cooking_equipment.GetInventory().GetCargo();
  345. if (cargo)
  346. {
  347. for (int i = 0; i < cargo.GetItemCount(); i++)
  348. {
  349. EntityAI entity = cargo.GetItem(i);
  350. if (entity.Type() == item_type)
  351. {
  352. ItemBase item = ItemBase.Cast(entity);
  353. return item;
  354. }
  355. }
  356. }
  357. return null;
  358. }
  359. //! DEPREACTED
  360. protected CookingMethodType GetCookingMethod(ItemBase cooking_equipment)
  361. {
  362. if (cooking_equipment.Type() == COOKING_EQUIPMENT_POT || cooking_equipment.Type() == COOKING_EQUIPMENT_CAULDRON)
  363. {
  364. //has water, but not petrol dammit X)
  365. if (cooking_equipment.GetQuantity() > 0 && cooking_equipment.GetLiquidType() != LIQUID_GASOLINE)
  366. {
  367. return CookingMethodType.BOILING;
  368. }
  369. //has lard in cargo
  370. if (GetItemTypeFromCargo(COOKING_INGREDIENT_LARD, cooking_equipment))
  371. {
  372. return CookingMethodType.BAKING;
  373. }
  374. return CookingMethodType.DRYING;
  375. }
  376. if (cooking_equipment.Type() == COOKING_EQUIPMENT_FRYINGPAN)
  377. {
  378. if (GetItemTypeFromCargo(COOKING_INGREDIENT_LARD, cooking_equipment))
  379. {
  380. return CookingMethodType.BAKING;
  381. }
  382. return CookingMethodType.DRYING;
  383. }
  384. return CookingMethodType.NONE;
  385. }
  386. protected Param2<CookingMethodType, float> GetCookingMethodWithTimeOverride(ItemBase cooking_equipment)
  387. {
  388. Param2<CookingMethodType, float> val = new Param2<CookingMethodType, float>(CookingMethodType.NONE, TIME_WITH_SUPPORT_MATERIAL_COEF);
  389. switch (cooking_equipment.Type())
  390. {
  391. case COOKING_EQUIPMENT_POT:
  392. case COOKING_EQUIPMENT_CAULDRON:
  393. case COOKING_EQUIPMENT_FRYINGPAN:
  394. if (cooking_equipment.GetQuantity() > 0)
  395. {
  396. if (cooking_equipment.GetLiquidType() == LIQUID_GASOLINE)
  397. {
  398. //! when cooking in gasoline, jump to drying state(will be burnt then)
  399. val = new Param2<CookingMethodType, float>(CookingMethodType.DRYING, TIME_WITHOUT_SUPPORT_MATERIAL_COEF);
  400. break;
  401. }
  402. val = new Param2<CookingMethodType, float>(CookingMethodType.BOILING, TIME_WITH_SUPPORT_MATERIAL_COEF);
  403. break;
  404. }
  405. if (GetItemTypeFromCargo(COOKING_INGREDIENT_LARD, cooking_equipment))
  406. {
  407. //has lard in cargo, slower process
  408. val = new Param2<CookingMethodType, float>(CookingMethodType.BAKING, TIME_WITH_SUPPORT_MATERIAL_COEF);
  409. break;
  410. }
  411. if (cooking_equipment.GetInventory().GetCargo().GetItemCount() > 0)
  412. {
  413. val = new Param2<CookingMethodType, float>(CookingMethodType.BAKING, TIME_WITHOUT_SUPPORT_MATERIAL_COEF);
  414. break;
  415. }
  416. val = new Param2<CookingMethodType, float>(CookingMethodType.NONE, TIME_WITHOUT_SUPPORT_MATERIAL_COEF);
  417. break;
  418. default:
  419. val = new Param2<CookingMethodType, float>(CookingMethodType.BAKING, TIME_WITHOUT_SUPPORT_MATERIAL_COEF);
  420. break;
  421. }
  422. return val;
  423. }
  424. Edible_Base GetFoodOnStick( ItemBase stick_item )
  425. {
  426. Edible_Base food_on_stick = Edible_Base.Cast( stick_item.GetAttachmentByType( Edible_Base ) );
  427. return food_on_stick;
  428. }
  429. float GetTimeToCook( Edible_Base item_to_cook, CookingMethodType cooking_method )
  430. {
  431. FoodStageType food_stage_type = item_to_cook.GetNextFoodStageType( cooking_method );
  432. return FoodStage.GetCookingPropertyFromIndex( eCookingPropertyIndices.COOK_TIME, food_stage_type, null, item_to_cook.GetType());
  433. }
  434. float GetMinTempToCook( Edible_Base item_to_cook, CookingMethodType cooking_method )
  435. {
  436. FoodStageType food_stage_type = item_to_cook.GetNextFoodStageType( cooking_method );
  437. return FoodStage.GetCookingPropertyFromIndex( eCookingPropertyIndices.MIN_TEMP, food_stage_type, null, item_to_cook.GetType());
  438. }
  439. //add temperature to item
  440. protected void AddTemperatureToItem( ItemBase cooked_item, ItemBase cooking_equipment, float min_temperature )
  441. {
  442. if (!GetGame().IsServer())
  443. return;
  444. if (cooked_item == cooking_equipment) //solves direct cooking double heating
  445. return;
  446. if (cooked_item.CanHaveTemperature())
  447. {
  448. float itemTemp = cooked_item.GetTemperature();
  449. //set target temperature
  450. float targetTemp;
  451. if (!cooked_item.GetHierarchyRoot().GetCookingTargetTemperature(targetTemp)) //if not valid, fallback values enter the equation
  452. {
  453. if (cooking_equipment)
  454. targetTemp = cooking_equipment.GetTemperature();
  455. else
  456. targetTemp = DEFAULT_COOKING_TEMPERATURE;
  457. }
  458. //adjust temperature
  459. if (targetTemp != itemTemp)
  460. {
  461. float heatPermCoef = 1.0;
  462. if (cooking_equipment)
  463. heatPermCoef = cooking_equipment.GetHeatPermeabilityCoef();
  464. heatPermCoef *= cooked_item.GetHeatPermeabilityCoef();
  465. float tempCoef;
  466. if (itemTemp < min_temperature && targetTemp > itemTemp) //heating 'catch-up' only
  467. tempCoef = GameConstants.TEMP_COEF_COOKING_CATCHUP;
  468. else
  469. tempCoef = GameConstants.TEMP_COEF_COOKING_DEFAULT;
  470. cooked_item.SetTemperatureEx(new TemperatureDataInterpolated(targetTemp,ETemperatureAccessTypes.ACCESS_COOKING,m_UpdateTime,tempCoef,heatPermCoef));
  471. }
  472. }
  473. }
  474. protected void DecreaseCookedItemQuantity(notnull Edible_Base pItem, float pAmount = 0.0)
  475. {
  476. if (GetGame().IsServer())
  477. {
  478. float quantity = pItem.GetQuantity() - pAmount;
  479. quantity = Math.Clamp(quantity, 0, pItem.GetQuantityMax());
  480. pItem.SetQuantity(quantity);
  481. }
  482. }
  483. }