Guest User

Untitled

a guest
Mar 13th, 2019
139
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 31.80 KB | None | 0 0
  1. #include "stdafx.h"
  2. #include "constants.h"
  3. #include "item.h"
  4. #include "item_manager.h"
  5. #include "unique_item.h"
  6. #include "packet.h"
  7. #include "desc.h"
  8. #include "char.h"
  9. #include "dragon_soul_table.h"
  10. #include "log.h"
  11. #include "DragonSoul.h"
  12. #include <boost/lexical_cast.hpp>
  13.  
  14. typedef std::vector <std::string> TTokenVector;
  15.  
  16. int Gamble(std::vector<float>& vec_probs)
  17. {
  18. float range = 0.f;
  19. for (int i = 0; i < vec_probs.size(); i++)
  20. {
  21. range += vec_probs[i];
  22. }
  23. float fProb = fnumber(0.f, range);
  24. float sum = 0.f;
  25. for (int idx = 0; idx < vec_probs.size(); idx++)
  26. {
  27. sum += vec_probs[idx];
  28. if (sum >= fProb)
  29. return idx;
  30. }
  31. return -1;
  32. }
  33.  
  34. // 가중치 테이블(prob_lst)을 받아 random_set.size()개의 index를 선택하여 random_set을 return
  35. bool MakeDistinctRandomNumberSet(std::list <float> prob_lst, OUT std::vector<int>& random_set)
  36. {
  37. int size = prob_lst.size();
  38. int n = random_set.size();
  39. if (size < n)
  40. return false;
  41.  
  42. std::vector <int> select_bit(size, 0);
  43. for (int i = 0; i < n; i++)
  44. {
  45. float range = 0.f;
  46. for (std::list <float>::iterator it = prob_lst.begin(); it != prob_lst.end(); it++)
  47. {
  48. range += *it;
  49. }
  50. float r = fnumber (0.f, range);
  51. float sum = 0.f;
  52. int idx = 0;
  53. for (std::list <float>::iterator it = prob_lst.begin(); it != prob_lst.end(); it++)
  54. {
  55. while (select_bit[idx++]);
  56.  
  57. sum += *it;
  58. if (sum >= r)
  59. {
  60. select_bit[idx - 1] = 1;
  61. random_set[i] = idx - 1;
  62. prob_lst.erase(it);
  63. break;
  64. }
  65. }
  66. }
  67. return true;
  68. }
  69.  
  70. /* 용혼석 Vnum에 대한 comment
  71. * ITEM VNUM을 10만 자리부터, FEDCBA라고 한다면
  72. * FE : 용혼석 종류. D : 등급
  73. * C : 단계 B : 강화
  74. * A : 여벌의 번호들...
  75. */
  76.  
  77. BYTE GetType(DWORD dwVnum)
  78. {
  79. return (dwVnum / 10000);
  80. }
  81.  
  82. BYTE GetGradeIdx(DWORD dwVnum)
  83. {
  84. return (dwVnum / 1000) % 10;
  85. }
  86.  
  87. BYTE GetStepIdx(DWORD dwVnum)
  88. {
  89. return (dwVnum / 100) % 10;
  90. }
  91.  
  92. BYTE GetStrengthIdx(DWORD dwVnum)
  93. {
  94. return (dwVnum / 10) % 10;
  95. }
  96.  
  97. bool DSManager::ReadDragonSoulTableFile(const char * c_pszFileName)
  98. {
  99. m_pTable = new DragonSoulTable();
  100. return m_pTable->ReadDragonSoulTableFile(c_pszFileName);
  101. }
  102.  
  103. void DSManager::GetDragonSoulInfo(DWORD dwVnum, BYTE& bType, BYTE& bGrade, BYTE& bStep, BYTE& bStrength) const
  104. {
  105. bType = GetType(dwVnum);
  106. bGrade = GetGradeIdx(dwVnum);
  107. bStep = GetStepIdx(dwVnum);
  108. bStrength = GetStrengthIdx(dwVnum);
  109. }
  110.  
  111. bool DSManager::IsValidCellForThisItem(const LPITEM pItem, const TItemPos& Cell) const
  112. {
  113. if (NULL == pItem)
  114. return false;
  115.  
  116. WORD wBaseCell = GetBasePosition(pItem);
  117. if (WORD_MAX == wBaseCell)
  118. return false;
  119.  
  120. if (Cell.window_type != DRAGON_SOUL_INVENTORY
  121. || (Cell.cell < wBaseCell || Cell.cell >= wBaseCell + DRAGON_SOUL_BOX_SIZE))
  122. {
  123. return false;
  124. }
  125. else
  126. return true;
  127.  
  128. }
  129.  
  130. WORD DSManager::GetBasePosition(const LPITEM pItem) const
  131. {
  132. if (NULL == pItem)
  133. return WORD_MAX;
  134.  
  135. BYTE type, grade_idx, step_idx, strength_idx;
  136. GetDragonSoulInfo(pItem->GetVnum(), type, grade_idx, step_idx, strength_idx);
  137.  
  138. BYTE col_type = pItem->GetSubType();
  139. BYTE row_type = grade_idx;
  140. if (row_type > DRAGON_SOUL_GRADE_MAX)
  141. return WORD_MAX;
  142.  
  143. return col_type * DRAGON_SOUL_STEP_MAX * DRAGON_SOUL_BOX_SIZE + row_type * DRAGON_SOUL_BOX_SIZE;
  144. }
  145.  
  146. bool DSManager::RefreshItemAttributes(LPITEM pDS)
  147. {
  148. if (!pDS->IsDragonSoul())
  149. {
  150. sys_err ("This item(ID : %d) is not DragonSoul.", pDS->GetID());
  151. return false;
  152. }
  153.  
  154. BYTE ds_type, grade_idx, step_idx, strength_idx;
  155. GetDragonSoulInfo(pDS->GetVnum(), ds_type, grade_idx, step_idx, strength_idx);
  156.  
  157. DragonSoulTable::TVecApplys vec_basic_applys;
  158. DragonSoulTable::TVecApplys vec_addtional_applys;
  159.  
  160. if (!m_pTable->GetBasicApplys(ds_type, vec_basic_applys))
  161. {
  162. sys_err ("There is no BasicApply about %d type dragon soul.", ds_type);
  163. return false;
  164. }
  165.  
  166. if (!m_pTable->GetAdditionalApplys(ds_type, vec_addtional_applys))
  167. {
  168. sys_err ("There is no AdditionalApply about %d type dragon soul.", ds_type);
  169. return false;
  170. }
  171.  
  172. // add_min과 add_max는 더미로 읽음.
  173. int basic_apply_num, add_min, add_max;
  174. if (!m_pTable->GetApplyNumSettings(ds_type, grade_idx, basic_apply_num, add_min, add_max))
  175. {
  176. sys_err ("In ApplyNumSettings, INVALID VALUES Group type(%d), GRADE idx(%d)", ds_type, grade_idx);
  177. return false;
  178. }
  179.  
  180. float fWeight = 0.f;
  181. if (!m_pTable->GetWeight(ds_type, grade_idx, step_idx, strength_idx, fWeight))
  182. {
  183. return false;
  184. }
  185. fWeight /= 100.f;
  186.  
  187. int n = MIN(basic_apply_num, vec_basic_applys.size());
  188. for (int i = 0; i < n; i++)
  189. {
  190. const SApply& basic_apply = vec_basic_applys[i];
  191. BYTE bType = basic_apply.apply_type;
  192. short sValue = (short)(ceil((float)basic_apply.apply_value * fWeight - 0.01f));
  193.  
  194. pDS->SetForceAttribute(i, bType, sValue);
  195. }
  196.  
  197. for (int i = DRAGON_SOUL_ADDITIONAL_ATTR_START_IDX; i < ITEM_ATTRIBUTE_MAX_NUM; i++)
  198. {
  199. BYTE bType = pDS->GetAttributeType(i);
  200. short sValue = 0;
  201. if (APPLY_NONE == bType)
  202. continue;
  203. for (int j = 0; j < vec_addtional_applys.size(); j++)
  204. {
  205. if (vec_addtional_applys[j].apply_type == bType)
  206. {
  207. sValue = vec_addtional_applys[j].apply_value;
  208. break;
  209. }
  210. }
  211. pDS->SetForceAttribute(i, bType, (short)(ceil((float)sValue * fWeight - 0.01f)));
  212. }
  213. return true;
  214. }
  215.  
  216. bool DSManager::PutAttributes(LPITEM pDS)
  217. {
  218. if (!pDS->IsDragonSoul())
  219. {
  220. sys_err ("This item(ID : %d) is not DragonSoul.", pDS->GetID());
  221. return false;
  222. }
  223.  
  224. BYTE ds_type, grade_idx, step_idx, strength_idx;
  225. GetDragonSoulInfo(pDS->GetVnum(), ds_type, grade_idx, step_idx, strength_idx);
  226.  
  227. DragonSoulTable::TVecApplys vec_basic_applys;
  228. DragonSoulTable::TVecApplys vec_addtional_applys;
  229.  
  230. if (!m_pTable->GetBasicApplys(ds_type, vec_basic_applys))
  231. {
  232. sys_err ("There is no BasicApply about %d type dragon soul.", ds_type);
  233. return false;
  234. }
  235. if (!m_pTable->GetAdditionalApplys(ds_type, vec_addtional_applys))
  236. {
  237. sys_err ("There is no AdditionalApply about %d type dragon soul.", ds_type);
  238. return false;
  239. }
  240.  
  241.  
  242. int basic_apply_num, add_min, add_max;
  243. if (!m_pTable->GetApplyNumSettings(ds_type, grade_idx, basic_apply_num, add_min, add_max))
  244. {
  245. sys_err ("In ApplyNumSettings, INVALID VALUES Group type(%d), GRADE idx(%d)", ds_type, grade_idx);
  246. return false;
  247. }
  248.  
  249. float fWeight = 0.f;
  250. if (!m_pTable->GetWeight(ds_type, grade_idx, step_idx, strength_idx, fWeight))
  251. {
  252. return false;
  253. }
  254. fWeight /= 100.f;
  255.  
  256. int n = MIN(basic_apply_num, vec_basic_applys.size());
  257. for (int i = 0; i < n; i++)
  258. {
  259. const SApply& basic_apply = vec_basic_applys[i];
  260. BYTE bType = basic_apply.apply_type;
  261. short sValue = (short)(ceil((float)basic_apply.apply_value * fWeight - 0.01f));
  262.  
  263. pDS->SetForceAttribute(i, bType, sValue);
  264. }
  265.  
  266. BYTE additional_attr_num = MIN(number (add_min, add_max), 3);
  267.  
  268. std::vector <int> random_set;
  269. if (additional_attr_num > 0)
  270. {
  271. random_set.resize(additional_attr_num);
  272. std::list <float> list_probs;
  273. for (int i = 0; i < vec_addtional_applys.size(); i++)
  274. {
  275. list_probs.push_back(vec_addtional_applys[i].prob);
  276. }
  277. if (!MakeDistinctRandomNumberSet(list_probs, random_set))
  278. {
  279. sys_err ("MakeDistinctRandomNumberSet error.");
  280. return false;
  281. }
  282.  
  283. for (int i = 0; i < additional_attr_num; i++)
  284. {
  285. int r = random_set[i];
  286. const SApply& additional_attr = vec_addtional_applys[r];
  287. BYTE bType = additional_attr.apply_type;
  288. short sValue = (short)(ceil((float)additional_attr.apply_value * fWeight - 0.01f));
  289.  
  290. pDS->SetForceAttribute(DRAGON_SOUL_ADDITIONAL_ATTR_START_IDX + i, bType, sValue);
  291. }
  292. }
  293.  
  294. return true;
  295. }
  296.  
  297. bool DSManager::DragonSoulItemInitialize(LPITEM pItem)
  298. {
  299. if (NULL == pItem || !pItem->IsDragonSoul())
  300. return false;
  301. PutAttributes(pItem);
  302. int time = DSManager::instance().GetDuration(pItem);
  303. if (time > 0)
  304. pItem->SetSocket(ITEM_SOCKET_REMAIN_SEC, time);
  305. return true;
  306. }
  307.  
  308. DWORD DSManager::MakeDragonSoulVnum(BYTE bType, BYTE grade, BYTE step, BYTE refine)
  309. {
  310. return bType * 10000 + grade * 1000 + step * 100 + refine * 10;
  311. }
  312.  
  313. int DSManager::GetDuration(const LPITEM pItem) const
  314. {
  315. return pItem->GetDuration();
  316. }
  317.  
  318. // 용혼석을 받아서 용심을 추출하는 함수
  319. bool DSManager::ExtractDragonHeart(LPCHARACTER ch, LPITEM pItem, LPITEM pExtractor)
  320. {
  321. if (NULL == ch || NULL == pItem)
  322. return false;
  323. if (pItem->IsEquipped())
  324. {
  325. ch->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("착용 중인 용혼석은 추출할 수 없습니다."));
  326. return false;
  327. }
  328.  
  329. DWORD dwVnum = pItem->GetVnum();
  330. BYTE ds_type, grade_idx, step_idx, strength_idx;
  331. GetDragonSoulInfo(dwVnum, ds_type, grade_idx, step_idx, strength_idx);
  332.  
  333. int iBonus = 0;
  334.  
  335. if (NULL != pExtractor)
  336. {
  337. iBonus = pExtractor->GetValue(0);
  338. }
  339.  
  340. std::vector <float> vec_chargings;
  341. std::vector <float> vec_probs;
  342.  
  343. if (!m_pTable->GetDragonHeartExtValues(ds_type, grade_idx, vec_chargings, vec_probs))
  344. {
  345. return false;
  346. }
  347.  
  348. int idx = Gamble(vec_probs);
  349.  
  350. if (-1 == idx)
  351. {
  352. sys_err ("Gamble is failed. ds_type(%d), grade_idx(%d)", ds_type, grade_idx);
  353. return false;
  354. }
  355.  
  356. float fCharge = vec_chargings[idx] * (100 + iBonus) / 100.f;
  357. fCharge = std::MINMAX <float> (0.f, fCharge, 100.f);
  358.  
  359. if (fCharge < FLT_EPSILON)
  360. {
  361. pItem->SetCount(pItem->GetCount() - 1);
  362. if (NULL != pExtractor)
  363. {
  364. pExtractor->SetCount(pExtractor->GetCount() - 1);
  365. }
  366.  
  367. ch->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("용심 추출에 실패하였습니다."));
  368. return false;
  369. }
  370. else
  371. {
  372. LPITEM pDH = ITEM_MANAGER::instance().CreateItem(DRAGON_HEART_VNUM);
  373.  
  374. if (NULL == pDH)
  375. {
  376. sys_err ("Cannot create DRAGON_HEART(%d).", DRAGON_HEART_VNUM);
  377. return NULL;
  378. }
  379.  
  380. pItem->SetCount(pItem->GetCount() - 1);
  381. if (NULL != pExtractor)
  382. {
  383. pExtractor->SetCount(pExtractor->GetCount() - 1);
  384. }
  385.  
  386. int iCharge = (int)(fCharge + 0.5f);
  387. pDH->SetSocket(ITEM_SOCKET_CHARGING_AMOUNT_IDX, iCharge);
  388. ch->AutoGiveItem(pDH, true);
  389.  
  390. std::string s = boost::lexical_cast <std::string> (iCharge);
  391. s += "%s";
  392. ch->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("용심 추출에 성공하였습니다."));
  393. return true;
  394. }
  395. }
  396.  
  397. // 특정 용혼석을 장비창에서 제거할 때에 성공 여부를 결정하고, 실패시 부산물을 주는 함수.
  398. bool DSManager::PullOut(LPCHARACTER ch, TItemPos DestCell, LPITEM& pItem, LPITEM pExtractor)
  399. {
  400. if (NULL == ch || NULL == pItem)
  401. {
  402. sys_err ("NULL POINTER. ch(%p) or pItem(%p)", ch, pItem);
  403. return false;
  404. }
  405.  
  406. // 목표 위치가 valid한지 검사 후, valid하지 않다면 임의의 빈 공간을 찾는다.
  407. if (!IsValidCellForThisItem(pItem, DestCell))
  408. {
  409. int iEmptyCell = ch->GetEmptyDragonSoulInventory(pItem);
  410. if (iEmptyCell < 0)
  411. {
  412. ch->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("소지품에 빈 공간이 없습니다."));
  413. return false;
  414. }
  415. else
  416. {
  417. DestCell.window_type = DRAGON_SOUL_INVENTORY;
  418. DestCell.cell = iEmptyCell;
  419. }
  420. }
  421.  
  422. if (!pItem->IsEquipped() || !pItem->RemoveFromCharacter())
  423. return false;
  424.  
  425. bool bSuccess;
  426. DWORD dwByProduct = 0;
  427. int iBonus = 0;
  428. float fProb;
  429. float fDice;
  430. // 용혼석 추출 성공 여부 결정.
  431. {
  432. BYTE ds_type, grade_idx, step_idx, strength_idx;
  433. GetDragonSoulInfo(pItem->GetVnum(), ds_type, grade_idx, step_idx, strength_idx);
  434.  
  435. // 추출 정보가 없다면 일단 무조건 성공하는 것이라 생각하자.
  436. if (!m_pTable->GetDragonSoulExtValues(ds_type, grade_idx, fProb, dwByProduct))
  437. {
  438. pItem->AddToCharacter(ch, DestCell);
  439. return true;
  440. }
  441.  
  442.  
  443. if (NULL != pExtractor)
  444. {
  445. iBonus = pExtractor->GetValue(ITEM_VALUE_DRAGON_SOUL_POLL_OUT_BONUS_IDX);
  446. pExtractor->SetCount(pExtractor->GetCount() - 1);
  447. }
  448. fDice = fnumber(0.f, 100.f);
  449. bSuccess = fDice <= (fProb * (100 + iBonus) / 100.f);
  450. }
  451.  
  452. // 캐릭터의 용혼석 추출 및 추가 혹은 제거. 부산물 제공.
  453. {
  454. char buf[128];
  455.  
  456. if (bSuccess)
  457. {
  458. if (pExtractor)
  459. {
  460. sprintf(buf, "dice(%d) prob(%d + %d) EXTR(VN:%d)", (int)fDice, (int)fProb, iBonus, pExtractor->GetVnum());
  461. }
  462. else
  463. {
  464. sprintf(buf, "dice(%f) prob(%f)", fDice, fProb);
  465. }
  466. ch->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("용혼석 추출에 성공하였습니다."));
  467. pItem->AddToCharacter(ch, DestCell);
  468. return true;
  469. }
  470. else
  471. {
  472. if (pExtractor)
  473. {
  474. sprintf(buf, "dice(%d) prob(%d + %d) EXTR(VN:%d) ByProd(VN:%d)", (int)fDice, (int)fProb, iBonus, pExtractor->GetVnum(), dwByProduct);
  475. }
  476. else
  477. {
  478. sprintf(buf, "dice(%d) prob(%d) ByProd(VNUM:%d)", (int)fDice, (int)fProb, dwByProduct);
  479. }
  480. M2_DESTROY_ITEM(pItem);
  481. pItem = NULL;
  482. if (dwByProduct)
  483. {
  484. LPITEM pByProduct = ch->AutoGiveItem(dwByProduct, true);
  485. if (pByProduct)
  486. ch->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("용혼석 추출에 실패하여 %s를 얻었습니다."), pByProduct->GetName());
  487. else
  488. ch->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("용혼석 추출에 실패하였습니다."));
  489. }
  490. else
  491. ch->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("용혼석 추출에 실패하였습니다."));
  492. }
  493. }
  494.  
  495. return bSuccess;
  496. }
  497.  
  498. bool DSManager::DoRefineGrade(LPCHARACTER ch, TItemPos (&aItemPoses)[DRAGON_SOUL_REFINE_GRID_SIZE])
  499. {
  500. if (NULL == ch)
  501. return false;
  502.  
  503. if (NULL == aItemPoses)
  504. {
  505. return false;
  506. }
  507.  
  508. if (!ch->DragonSoul_RefineWindow_CanRefine())
  509. {
  510. sys_err ("%s do not activate DragonSoulRefineWindow. But how can he come here?", ch->GetName());
  511. ch->ChatPacket(CHAT_TYPE_INFO, "[SYSTEM ERROR]You cannot upgrade dragon soul without refine window.");
  512. return false;
  513. }
  514.  
  515. // 혹시나 모를 중복되는 item pointer 없애기 위해서 set 사용
  516. // 이상한 패킷을 보낼 경우, 중복된 TItemPos가 있을 수도 있고, 잘못된 TItemPos가 있을 수도 있다.
  517. std::set <LPITEM> set_items;
  518. for (int i = 0; i < DRAGON_SOUL_REFINE_GRID_SIZE; i++)
  519. {
  520. if (aItemPoses[i].IsEquipPosition())
  521. return false;
  522. LPITEM pItem = ch->GetItem(aItemPoses[i]);
  523. if (NULL != pItem)
  524. {
  525. // 용혼석이 아닌 아이템이 개량창에 있을 수 없다.
  526. if (!pItem->IsDragonSoul())
  527. {
  528. ch->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("등급 개량에 필요한 재료가 아닙니다."));
  529. SendRefineResultPacket(ch, DS_SUB_HEADER_REFINE_FAIL_INVALID_MATERIAL, TItemPos(pItem->GetWindow(), pItem->GetCell()));
  530.  
  531. return false;
  532. }
  533.  
  534. set_items.insert(pItem);
  535. }
  536. }
  537.  
  538. if (set_items.size() == 0)
  539. {
  540. SendRefineResultPacket(ch, DS_SUB_HEADER_REFINE_FAIL_NOT_ENOUGH_MATERIAL, NPOS);
  541. return false;
  542. }
  543.  
  544. int count = set_items.size();
  545. int need_count = 0;
  546. int fee = 0;
  547. std::vector <float> vec_probs;
  548.  
  549. BYTE ds_type, grade_idx, step_idx, strength_idx;
  550. int result_grade;
  551.  
  552. // 가장 처음 것을 강화의 기준으로 삼는다.
  553. std::set <LPITEM>::iterator it = set_items.begin();
  554. {
  555. LPITEM pItem = *it;
  556.  
  557. GetDragonSoulInfo(pItem->GetVnum(), ds_type, grade_idx, step_idx, strength_idx);
  558.  
  559. if (!m_pTable->GetRefineGradeValues(ds_type, grade_idx, need_count, fee, vec_probs))
  560. {
  561. ch->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("등급 개량할 수 없는 용혼석입니다."));
  562. SendRefineResultPacket(ch, DS_SUB_HEADER_REFINE_FAIL_INVALID_MATERIAL, TItemPos(pItem->GetWindow(), pItem->GetCell()));
  563.  
  564. return false;
  565. }
  566. }
  567. while (++it != set_items.end())
  568. {
  569. LPITEM pItem = *it;
  570.  
  571. // 클라 ui에서 장착한 아이템은 개량창에 올릴 수 없도록 막았기 때문에,
  572. // 별도의 알림 처리는 안함.
  573. if (pItem->IsEquipped())
  574. {
  575. return false;
  576. }
  577.  
  578. if (ds_type != GetType(pItem->GetVnum()) || grade_idx != GetGradeIdx(pItem->GetVnum()))
  579. {
  580. ch->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("등급 개량에 필요한 재료가 아닙니다."));
  581. SendRefineResultPacket(ch, DS_SUB_HEADER_REFINE_FAIL_INVALID_MATERIAL, TItemPos(pItem->GetWindow(), pItem->GetCell()));
  582.  
  583. return false;
  584. }
  585. }
  586.  
  587. // 클라에서 한번 갯수 체크를 하기 때문에 count != need_count라면 invalid 클라일 가능성이 크다.
  588. if (count != need_count)
  589. {
  590. sys_err ("Possiblity of invalid client. Name %s", ch->GetName());
  591. BYTE bSubHeader = count < need_count? DS_SUB_HEADER_REFINE_FAIL_NOT_ENOUGH_MATERIAL : DS_SUB_HEADER_REFINE_FAIL_TOO_MUCH_MATERIAL;
  592. SendRefineResultPacket(ch, bSubHeader, NPOS);
  593. return false;
  594. }
  595.  
  596. if (ch->GetGold() < fee)
  597. {
  598. ch->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("개량을 하기 위한 돈이 부족합니다."));
  599. SendRefineResultPacket(ch, DS_SUB_HEADER_REFINE_FAIL_NOT_ENOUGH_MONEY, NPOS);
  600. return false;
  601. }
  602.  
  603. if (-1 == (result_grade = Gamble(vec_probs)))
  604. {
  605. sys_err ("Gamble failed. See RefineGardeTables' probabilities");
  606. return false;
  607. }
  608.  
  609. LPITEM pResultItem = ITEM_MANAGER::instance().CreateItem(MakeDragonSoulVnum(ds_type, (BYTE)result_grade, 0, 0));
  610.  
  611. if (NULL == pResultItem)
  612. {
  613. sys_err ("INVALID DRAGON SOUL(%d)", MakeDragonSoulVnum(ds_type, (BYTE)result_grade, 0, 0));
  614. return false;
  615. }
  616.  
  617. ch->PointChange(POINT_GOLD, -fee);
  618. int left_count = need_count;
  619.  
  620. for (std::set <LPITEM>::iterator it = set_items.begin(); it != set_items.end(); it++)
  621. {
  622. LPITEM pItem = *it;
  623. int n = pItem->GetCount();
  624. if (left_count > n)
  625. {
  626. pItem->RemoveFromCharacter();
  627. M2_DESTROY_ITEM(pItem);
  628. left_count -= n;
  629. }
  630. else
  631. {
  632. pItem->SetCount(n - left_count);
  633. }
  634. }
  635.  
  636. ch->AutoGiveItem(pResultItem, true);
  637.  
  638. if (result_grade > grade_idx)
  639. {
  640. char buf[128];
  641. sprintf(buf, "GRADE : %d -> %d", grade_idx, result_grade);
  642. ch->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("등급 개량에 성공했습니다."));
  643. SendRefineResultPacket(ch, DS_SUB_HEADER_REFINE_SUCCEED, TItemPos (pResultItem->GetWindow(), pResultItem->GetCell()));
  644. return true;
  645. }
  646. else
  647. {
  648. char buf[128];
  649. sprintf(buf, "GRADE : %d -> %d", grade_idx, result_grade);
  650. ch->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("등급 개량에 실패했습니다."));
  651. SendRefineResultPacket(ch, DS_SUB_HEADER_REFINE_FAIL, TItemPos (pResultItem->GetWindow(), pResultItem->GetCell()));
  652. return false;
  653. }
  654. }
  655.  
  656. bool DSManager::DoRefineStep(LPCHARACTER ch, TItemPos (&aItemPoses)[DRAGON_SOUL_REFINE_GRID_SIZE])
  657. {
  658. if (NULL == ch)
  659. return false;
  660. if (NULL == aItemPoses)
  661. {
  662. return false;
  663. }
  664.  
  665. if (!ch->DragonSoul_RefineWindow_CanRefine())
  666. {
  667. sys_err ("%s do not activate DragonSoulRefineWindow. But how can he come here?", ch->GetName());
  668. ch->ChatPacket(CHAT_TYPE_INFO, "[SYSTEM ERROR]You cannot use dragon soul refine window.");
  669. return false;
  670. }
  671.  
  672. // 혹시나 모를 중복되는 item pointer 없애기 위해서 set 사용
  673. // 이상한 패킷을 보낼 경우, 중복된 TItemPos가 있을 수도 있고, 잘못된 TItemPos가 있을 수도 있다.
  674. std::set <LPITEM> set_items;
  675. for (int i = 0; i < DRAGON_SOUL_REFINE_GRID_SIZE; i++)
  676. {
  677. LPITEM pItem = ch->GetItem(aItemPoses[i]);
  678. if (NULL != pItem)
  679. {
  680. // 용혼석이 아닌 아이템이 개량창에 있을 수 없다.
  681. if (!pItem->IsDragonSoul())
  682. {
  683. ch->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("단계 개량에 필요한 재료가 아닙니다."));
  684. SendRefineResultPacket(ch, DS_SUB_HEADER_REFINE_FAIL_INVALID_MATERIAL, TItemPos(pItem->GetWindow(), pItem->GetCell()));
  685. return false;
  686. }
  687. set_items.insert(pItem);
  688. }
  689. }
  690.  
  691. if (set_items.size() == 0)
  692. {
  693. SendRefineResultPacket(ch, DS_SUB_HEADER_REFINE_FAIL_NOT_ENOUGH_MATERIAL, NPOS);
  694. return false;
  695. }
  696.  
  697. std::string stGroupName;
  698. int count = set_items.size();
  699. int need_count = 0;
  700. int fee = 0;
  701. std::vector <float> vec_probs;
  702.  
  703. BYTE ds_type, grade_idx, step_idx, strength_idx;
  704. int result_step;
  705.  
  706. // 가장 처음 것을 강화의 기준으로 삼는다.
  707. std::set <LPITEM>::iterator it = set_items.begin();
  708. {
  709. LPITEM pItem = *it;
  710. GetDragonSoulInfo(pItem->GetVnum(), ds_type, grade_idx, step_idx, strength_idx);
  711.  
  712. if (!m_pTable->GetRefineStepValues(ds_type, step_idx, need_count, fee, vec_probs))
  713. {
  714. ch->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("단계 개량할 수 없는 용혼석입니다."));
  715. SendRefineResultPacket(ch, DS_SUB_HEADER_REFINE_FAIL_INVALID_MATERIAL, TItemPos(pItem->GetWindow(), pItem->GetCell()));
  716. return false;
  717. }
  718. }
  719.  
  720. while(++it != set_items.end())
  721. {
  722. LPITEM pItem = *it;
  723. // 클라 ui에서 장착한 아이템은 개량창에 올릴 수 없도록 막았기 때문에,
  724. // 별도의 알림 처리는 안함.
  725. if (pItem->IsEquipped())
  726. {
  727. return false;
  728. }
  729. if (ds_type != GetType(pItem->GetVnum()) || grade_idx != GetGradeIdx(pItem->GetVnum()) || step_idx != GetStepIdx(pItem->GetVnum()))
  730. {
  731. ch->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("단계 개량에 필요한 재료가 아닙니다."));
  732. SendRefineResultPacket(ch, DS_SUB_HEADER_REFINE_FAIL_INVALID_MATERIAL, TItemPos(pItem->GetWindow(), pItem->GetCell()));
  733. return false;
  734. }
  735. }
  736.  
  737. // 클라에서 한번 갯수 체크를 하기 때문에 count != need_count라면 invalid 클라일 가능성이 크다.
  738. if (count != need_count)
  739. {
  740. sys_err ("Possiblity of invalid client. Name %s", ch->GetName());
  741. BYTE bSubHeader = count < need_count? DS_SUB_HEADER_REFINE_FAIL_NOT_ENOUGH_MATERIAL : DS_SUB_HEADER_REFINE_FAIL_TOO_MUCH_MATERIAL;
  742. SendRefineResultPacket(ch, bSubHeader, NPOS);
  743. return false;
  744. }
  745.  
  746. if (ch->GetGold() < fee)
  747. {
  748. ch->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("개량을 하기 위한 돈이 부족합니다."));
  749. SendRefineResultPacket(ch, DS_SUB_HEADER_REFINE_FAIL_NOT_ENOUGH_MONEY, NPOS);
  750. return false;
  751. }
  752.  
  753. if (-1 == (result_step = Gamble(vec_probs)))
  754. {
  755. sys_err ("Gamble failed. See RefineStepTables' probabilities");
  756. return false;
  757. }
  758.  
  759. LPITEM pResultItem = ITEM_MANAGER::instance().CreateItem(MakeDragonSoulVnum(ds_type, grade_idx, (BYTE)result_step, 0));
  760.  
  761. if (NULL == pResultItem)
  762. {
  763. sys_err ("INVALID DRAGON SOUL(%d)", MakeDragonSoulVnum(ds_type, grade_idx, (BYTE)result_step, 0));
  764. return false;
  765. }
  766.  
  767. ch->PointChange(POINT_GOLD, -fee);
  768. int left_count = need_count;
  769. for (std::set <LPITEM>::iterator it = set_items.begin(); it != set_items.end(); it++)
  770. {
  771. LPITEM pItem = *it;
  772. int n = pItem->GetCount();
  773. if (left_count > n)
  774. {
  775. pItem->RemoveFromCharacter();
  776. M2_DESTROY_ITEM(pItem);
  777. left_count -= n;
  778. }
  779. else
  780. {
  781. pItem->SetCount(n - left_count);
  782. }
  783. }
  784.  
  785. ch->AutoGiveItem(pResultItem, true);
  786. if (result_step > step_idx)
  787. {
  788. char buf[128];
  789. sprintf(buf, "STEP : %d -> %d", step_idx, result_step);
  790. ch->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("단계 개량에 성공했습니다."));
  791. SendRefineResultPacket(ch, DS_SUB_HEADER_REFINE_SUCCEED, TItemPos (pResultItem->GetWindow(), pResultItem->GetCell()));
  792. return true;
  793. }
  794. else
  795. {
  796. char buf[128];
  797. sprintf(buf, "STEP : %d -> %d", step_idx, result_step);
  798. ch->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("단계 개량에 실패했습니다."));
  799. SendRefineResultPacket(ch, DS_SUB_HEADER_REFINE_FAIL, TItemPos (pResultItem->GetWindow(), pResultItem->GetCell()));
  800. return false;
  801. }
  802. }
  803.  
  804. bool IsDragonSoulRefineMaterial(LPITEM pItem)
  805. {
  806. if (pItem->GetType() != ITEM_MATERIAL)
  807. return false;
  808. return (pItem->GetSubType() == MATERIAL_DS_REFINE_NORMAL ||
  809. pItem->GetSubType() == MATERIAL_DS_REFINE_BLESSED ||
  810. pItem->GetSubType() == MATERIAL_DS_REFINE_HOLLY);
  811. }
  812.  
  813. bool DSManager::DoRefineStrength(LPCHARACTER ch, TItemPos (&aItemPoses)[DRAGON_SOUL_REFINE_GRID_SIZE])
  814. {
  815. if (NULL == ch)
  816. return false;
  817. if (NULL == aItemPoses)
  818. {
  819. return false;
  820. }
  821.  
  822. if (!ch->DragonSoul_RefineWindow_CanRefine())
  823. {
  824. sys_err ("%s do not activate DragonSoulRefineWindow. But how can he come here?", ch->GetName());
  825. ch->ChatPacket(CHAT_TYPE_INFO, "[SYSTEM ERROR]You cannot use dragon soul refine window.");
  826. return false;
  827. }
  828.  
  829. // 혹시나 모를 중복되는 item pointer 없애기 위해서 set 사용
  830. // 이상한 패킷을 보낼 경우, 중복된 TItemPos가 있을 수도 있고, 잘못된 TItemPos가 있을 수도 있다.
  831. std::set <LPITEM> set_items;
  832. for (int i = 0; i < DRAGON_SOUL_REFINE_GRID_SIZE; i++)
  833. {
  834. LPITEM pItem = ch->GetItem(aItemPoses[i]);
  835. if (pItem)
  836. {
  837. set_items.insert(pItem);
  838. }
  839. }
  840. if (set_items.size() == 0)
  841. {
  842. return false;
  843. }
  844.  
  845. int fee;
  846.  
  847. LPITEM pRefineStone = NULL;
  848. LPITEM pDragonSoul = NULL;
  849. for (std::set <LPITEM>::iterator it = set_items.begin(); it != set_items.end(); it++)
  850. {
  851. LPITEM pItem = *it;
  852. // 클라 ui에서 장착한 아이템은 개량창에 올릴 수 없도록 막았기 때문에,
  853. // 별도의 알림 처리는 안함.
  854. if (pItem->IsEquipped())
  855. {
  856. return false;
  857. }
  858.  
  859. // 용혼석과 강화석만이 개량창에 있을 수 있다.
  860. // 그리고 하나씩만 있어야한다.
  861. if (pItem->IsDragonSoul())
  862. {
  863. if (pDragonSoul != NULL)
  864. {
  865. SendRefineResultPacket(ch, DS_SUB_HEADER_REFINE_FAIL_TOO_MUCH_MATERIAL, TItemPos(pItem->GetWindow(), pItem->GetCell()));
  866. return false;
  867. }
  868. pDragonSoul = pItem;
  869. }
  870. else if(IsDragonSoulRefineMaterial(pItem))
  871. {
  872. if (pRefineStone != NULL)
  873. {
  874. SendRefineResultPacket(ch, DS_SUB_HEADER_REFINE_FAIL_TOO_MUCH_MATERIAL, TItemPos(pItem->GetWindow(), pItem->GetCell()));
  875. return false;
  876. }
  877. pRefineStone = pItem;
  878. }
  879. else
  880. {
  881. ch->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("강화에 필요한 재료가 아닙니다."));
  882. SendRefineResultPacket(ch, DS_SUB_HEADER_REFINE_FAIL_INVALID_MATERIAL, TItemPos(pItem->GetWindow(), pItem->GetCell()));
  883. return false;
  884. }
  885. }
  886.  
  887. BYTE bType = 0, bGrade = 0, bStep = 0, bStrength = 0;
  888.  
  889. if (!pDragonSoul || !pRefineStone)
  890. {
  891. SendRefineResultPacket(ch, DS_SUB_HEADER_REFINE_FAIL_NOT_ENOUGH_MATERIAL, NPOS);
  892.  
  893. return false;
  894. }
  895.  
  896. if (NULL != pDragonSoul)
  897. {
  898. GetDragonSoulInfo(pDragonSoul->GetVnum(), bType, bGrade, bStep, bStrength);
  899.  
  900. float fWeight = 0.f;
  901. // 가중치 값이 없다면 강화할 수 없는 용혼석
  902. if (!m_pTable->GetWeight(bType, bGrade, bStep, bStrength + 1, fWeight))
  903. {
  904. ch->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("강화할 수 없는 용혼석입니다."));
  905. SendRefineResultPacket(ch, DS_SUB_HEADER_REFINE_FAIL_MAX_REFINE, TItemPos(pDragonSoul->GetWindow(), pDragonSoul->GetCell()));
  906. return false;
  907. }
  908. // 강화했을 때 가중치가 0이라면 더 이상 강화되서는 안된다.
  909. if (fWeight < FLT_EPSILON)
  910. {
  911. ch->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("강화할 수 없는 용혼석입니다."));
  912. SendRefineResultPacket(ch, DS_SUB_HEADER_REFINE_FAIL_MAX_REFINE, TItemPos(pDragonSoul->GetWindow(), pDragonSoul->GetCell()));
  913. return false;
  914. }
  915. }
  916.  
  917. float fProb;
  918. if (!m_pTable->GetRefineStrengthValues(bType, pRefineStone->GetSubType(), bStrength, fee, fProb))
  919. {
  920. ch->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("강화할 수 없는 용혼석입니다."));
  921. SendRefineResultPacket(ch, DS_SUB_HEADER_REFINE_FAIL_INVALID_MATERIAL, TItemPos(pDragonSoul->GetWindow(), pDragonSoul->GetCell()));
  922.  
  923. return false;
  924. }
  925.  
  926. if (ch->GetGold() < fee)
  927. {
  928. ch->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("개량을 하기 위한 돈이 부족합니다."));
  929. SendRefineResultPacket(ch, DS_SUB_HEADER_REFINE_FAIL_NOT_ENOUGH_MONEY, NPOS);
  930. return false;
  931. }
  932.  
  933. ch->PointChange(POINT_GOLD, -fee);
  934. LPITEM pResult = NULL;
  935. BYTE bSubHeader;
  936.  
  937. if (fnumber(0.f, 100.f) <= fProb)
  938. {
  939. pResult = ITEM_MANAGER::instance().CreateItem(MakeDragonSoulVnum(bType, bGrade, bStep, bStrength + 1));
  940. if (NULL == pResult)
  941. {
  942. sys_err ("INVALID DRAGON SOUL(%d)", MakeDragonSoulVnum(bType, bGrade, bStep, bStrength + 1));
  943. return false;
  944. }
  945. pDragonSoul->RemoveFromCharacter();
  946.  
  947. pDragonSoul->CopyAttributeTo(pResult);
  948. RefreshItemAttributes(pResult);
  949.  
  950. pDragonSoul->SetCount(pDragonSoul->GetCount() - 1);
  951. pRefineStone->SetCount(pRefineStone->GetCount() - 1);
  952.  
  953. char buf[128];
  954. sprintf(buf, "STRENGTH : %d -> %d", bStrength, bStrength + 1);
  955. ch->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("강화에 성공했습니다."));
  956. ch->AutoGiveItem(pResult, true);
  957. bSubHeader = DS_SUB_HEADER_REFINE_SUCCEED;
  958. }
  959. else
  960. {
  961. if (bStrength != 0)
  962. {
  963. pResult = ITEM_MANAGER::instance().CreateItem(MakeDragonSoulVnum(bType, bGrade, bStep, bStrength - 1));
  964. if (NULL == pResult)
  965. {
  966. sys_err ("INVALID DRAGON SOUL(%d)", MakeDragonSoulVnum(bType, bGrade, bStep, bStrength - 1));
  967. return false;
  968. }
  969. pDragonSoul->CopyAttributeTo(pResult);
  970. RefreshItemAttributes(pResult);
  971. }
  972. bSubHeader = DS_SUB_HEADER_REFINE_FAIL;
  973.  
  974. char buf[128];
  975. sprintf(buf, "STRENGTH : %d -> %d", bStrength, bStrength - 1);
  976. // strength강화는 실패시 깨질 수도 있어, 원본 아이템을 바탕으로 로그를 남김.
  977.  
  978. ch->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("강화에 실패했습니다."));
  979. pDragonSoul->SetCount(pDragonSoul->GetCount() - 1);
  980. pRefineStone->SetCount(pRefineStone->GetCount() - 1);
  981. if (NULL != pResult)
  982. ch->AutoGiveItem(pResult, true);
  983.  
  984. }
  985.  
  986. SendRefineResultPacket(ch, bSubHeader, NULL == pResult? NPOS : TItemPos (pResult->GetWindow(), pResult->GetCell()));
  987.  
  988. return true;
  989. }
  990.  
  991. void DSManager::SendRefineResultPacket(LPCHARACTER ch, BYTE bSubHeader, const TItemPos& pos)
  992. {
  993. TPacketGCDragonSoulRefine pack;
  994. pack.bSubType = bSubHeader;
  995.  
  996. if (pos.IsValidItemPosition())
  997. {
  998. pack.Pos = pos;
  999. }
  1000. LPDESC d = ch->GetDesc();
  1001. if (NULL == d)
  1002. {
  1003. return ;
  1004. }
  1005. else
  1006. {
  1007. d->Packet(&pack, sizeof(pack));
  1008. }
  1009. }
  1010.  
  1011. int DSManager::LeftTime(LPITEM pItem) const
  1012. {
  1013. if (pItem == NULL)
  1014. return false;
  1015.  
  1016. // 일단은 timer based on wear인 용혼석만 시간 다 되어도 안 없어진다.
  1017. if (pItem->GetProto()->cLimitTimerBasedOnWearIndex >= 0)
  1018. {
  1019. return pItem->GetSocket(ITEM_SOCKET_REMAIN_SEC);
  1020. }
  1021. // 다른 limit type인 용혼석들은 시간 되면 모두 사라지기 때문에 여기 들어온 아이템은 일단 시간이 남았다고 판단.
  1022. else
  1023. {
  1024. return INT_MAX;
  1025. }
  1026. }
  1027.  
  1028. bool DSManager::IsTimeLeftDragonSoul(LPITEM pItem) const
  1029. {
  1030. if (pItem == NULL)
  1031. return false;
  1032.  
  1033. // 일단은 timer based on wear인 용혼석만 시간 다 되어도 안 없어진다.
  1034. if (pItem->GetProto()->cLimitTimerBasedOnWearIndex >= 0)
  1035. {
  1036. return pItem->GetSocket(ITEM_SOCKET_REMAIN_SEC) > 0;
  1037. }
  1038. // 다른 limit type인 용혼석들은 시간 되면 모두 사라지기 때문에 여기 들어온 아이템은 일단 시간이 남았다고 판단.
  1039. else
  1040. {
  1041. return true;
  1042. }
  1043. }
  1044.  
  1045. bool DSManager::IsActiveDragonSoul(LPITEM pItem) const
  1046. {
  1047. return pItem->GetSocket(ITEM_SOCKET_DRAGON_SOUL_ACTIVE_IDX);
  1048. }
  1049.  
  1050. bool DSManager::ActivateDragonSoul(LPITEM pItem)
  1051. {
  1052. if (NULL == pItem)
  1053. return false;
  1054. LPCHARACTER pOwner = pItem->GetOwner();
  1055. if (NULL == pOwner)
  1056. return false;
  1057.  
  1058. int deck_idx = pOwner->DragonSoul_GetActiveDeck();
  1059.  
  1060. if (deck_idx < 0)
  1061. return false;
  1062.  
  1063. if (INVENTORY_MAX_NUM + WEAR_MAX_NUM + DS_SLOT_MAX * deck_idx <= pItem->GetCell() &&
  1064. pItem->GetCell() < INVENTORY_MAX_NUM + WEAR_MAX_NUM + DS_SLOT_MAX * (deck_idx + 1))
  1065. {
  1066. if (IsTimeLeftDragonSoul(pItem) && !IsActiveDragonSoul(pItem))
  1067. {
  1068. char buf[128];
  1069. sprintf (buf, "LEFT TIME(%d)", LeftTime(pItem));
  1070. pItem->ModifyPoints(true);
  1071. pItem->SetSocket(ITEM_SOCKET_DRAGON_SOUL_ACTIVE_IDX, 1);
  1072.  
  1073. pItem->StartTimerBasedOnWearExpireEvent();
  1074. }
  1075. return true;
  1076. }
  1077. else
  1078. return false;
  1079. }
  1080.  
  1081. bool DSManager::DeactivateDragonSoul(LPITEM pItem, bool bSkipRefreshOwnerActiveState)
  1082. {
  1083. if (NULL == pItem)
  1084. return false;
  1085.  
  1086. LPCHARACTER pOwner = pItem->GetOwner();
  1087. if (NULL == pOwner)
  1088. return false;
  1089.  
  1090. if (!IsActiveDragonSoul(pItem))
  1091. return false;
  1092.  
  1093. char buf[128];
  1094. pItem->StopTimerBasedOnWearExpireEvent();
  1095. pItem->SetSocket(ITEM_SOCKET_DRAGON_SOUL_ACTIVE_IDX, 0);
  1096. pItem->ModifyPoints(false);
  1097.  
  1098. sprintf (buf, "LEFT TIME(%d)", LeftTime(pItem));
  1099.  
  1100. if (false == bSkipRefreshOwnerActiveState)
  1101. RefreshDragonSoulState(pOwner);
  1102.  
  1103. return true;
  1104. }
  1105.  
  1106. void DSManager::RefreshDragonSoulState(LPCHARACTER ch)
  1107. {
  1108. if (NULL == ch)
  1109. return ;
  1110. for (int i = WEAR_MAX_NUM; i < WEAR_MAX_NUM + DS_SLOT_MAX * DRAGON_SOUL_DECK_MAX_NUM; i++)
  1111. {
  1112. LPITEM pItem = ch->GetWear(i);
  1113. if (pItem != NULL)
  1114. {
  1115. if(IsActiveDragonSoul(pItem))
  1116. {
  1117. return;
  1118. }
  1119. }
  1120. }
  1121. ch->DragonSoul_DeactivateAll();
  1122. }
  1123.  
  1124. DSManager::DSManager()
  1125. {
  1126. m_pTable = NULL;
  1127. }
  1128.  
  1129. DSManager::~DSManager()
  1130. {
  1131. if (m_pTable)
  1132. delete m_pTable;
  1133. }
Advertisement
Add Comment
Please, Sign In to add comment