1. #ifndef __INC_GAME_MOTION_H__
  2. #define __INC_GAME_MOTION_H__
  3. #include "../../common/d3dtype.h"
  4. #include "../../common/CommonDefines.h"
  5. enum EMotionMode
  6. {
  7. MOTION_MODE_GENERAL,
  8. MOTION_MODE_ONEHAND_SWORD,
  9. MOTION_MODE_TWOHAND_SWORD,
  10. MOTION_MODE_DUALHAND_SWORD,
  11. MOTION_MODE_BOW,
  12. MOTION_MODE_BELL,
  13. MOTION_MODE_FAN,
  14. MOTION_MODE_HORSE,
  15. #ifdef ENABLE_WOLFMAN_CHARACTER
  16. MOTION_MODE_CLAW,
  17. #endif
  18. #ifdef OFFLINE_FARM
  19. MOTION_MODE_HORSE_ONEHAND_SWORD,
  20. MOTION_MODE_HORSE_TWOHAND_SWORD,
  21. MOTION_MODE_HORSE_DUALHAND_SWORD,
  22. MOTION_MODE_HORSE_BOW,
  23. MOTION_MODE_HORSE_BELL,
  24. MOTION_MODE_HORSE_FAN,
  25. MOTION_MODE_HORSE_CLAW,
  26. #endif
  27. MOTION_MODE_MAX_NUM
  28. };
  29. enum EPublicMotion
  30. {
  31. MOTION_NONE,
  32. MOTION_WAIT,
  33. MOTION_WALK,
  34. MOTION_RUN,
  35. MOTION_CHANGE_WEAPON,
  36. MOTION_DAMAGE,
  37. MOTION_DAMAGE_FLYING,
  38. MOTION_STAND_UP,
  39. MOTION_DAMAGE_BACK,
  40. MOTION_DAMAGE_FLYING_BACK,
  41. MOTION_STAND_UP_BACK,
  42. MOTION_DEAD,
  43. MOTION_DEAD_BACK,
  44. MOTION_NORMAL_ATTACK,
  45. MOTION_COMBO_ATTACK_1,
  46. MOTION_COMBO_ATTACK_2,
  47. MOTION_COMBO_ATTACK_3,
  48. MOTION_COMBO_ATTACK_4,
  49. MOTION_COMBO_ATTACK_5,
  50. MOTION_COMBO_ATTACK_6,
  51. MOTION_COMBO_ATTACK_7,
  52. MOTION_COMBO_ATTACK_8,
  53. MOTION_INTRO_WAIT,
  54. MOTION_INTRO_SELECTED,
  55. MOTION_INTRO_NOT_SELECTED,
  56. MOTION_SPAWN,
  57. MOTION_FISHING_THROW,
  58. MOTION_FISHING_WAIT,
  59. MOTION_FISHING_STOP,
  60. MOTION_FISHING_REACT,
  61. MOTION_FISHING_CATCH,
  62. MOTION_FISHING_FAIL,
  63. MOTION_STOP,
  64. MOTION_SPECIAL_1,
  65. MOTION_SPECIAL_2, // 34
  66. MOTION_SPECIAL_3, // 35
  67. MOTION_SPECIAL_4, // 36
  68. MOTION_SPECIAL_5, // 37
  69. PUBLIC_MOTION_END,
  70. MOTION_MAX_NUM = PUBLIC_MOTION_END,
  71. };
  72. #ifdef OFFLINE_FARM
  73. struct CDynamicSphereInstance // EterLib\CollisionData.h
  74. {
  75. D3DXVECTOR3 v3Position;
  76. D3DXVECTOR3 v3LastPosition;
  77. float fRadius;
  78. };
  79. typedef CDynamicSphereInstance THitTimePosition;
  80. #endif
  81. class CMob;
  82. class CMotion
  83. {
  84. #ifdef OFFLINE_FARM
  85. public:
  86. static constexpr float gsc_fAttackRadius = 20.0f; // ref: CActorInstance::__NormalAttackProcess
  87. typedef std::map<float, THitTimePosition> THitTimePositionMapEx;
  88. struct SCharacterMotionData
  89. {
  90. uint8_t race{ 0 };
  91. uint8_t weapon_type{ 0 };
  92. uint8_t combo_index{ 0 };
  93. bool riding{ false };
  94. };
  95. typedef std::map<SCharacterMotionData*, THitTimePositionMapEx> THitTimePositionMap;
  96. public:
  97. #endif
  98. public:
  99. CMotion();
  100. ~CMotion();
  101. bool LoadFromFile(const char * c_pszFileName);
  102. bool LoadMobSkillFromFile(const char * c_pszFileName, CMob * pMob, int iSkillIndex);
  103. float GetDuration() const;
  104. #ifdef OFFLINE_FARM
  105. float GetInputDuration() const;
  106. float GetWeaponLength() const;
  107. float GetExternalForce() const;
  108. THitTimePositionMapEx GetHitPositionData(const uint8_t c_nRace, const uint8_t c_nWeaponType, const uint8_t c_nComboIndex, bool c_bRiding) const;
  109. #endif
  110. const D3DVECTOR & GetAccumVector() const;
  111. bool IsEmpty();
  112. protected:
  113. bool m_isEmpty;
  114. float m_fDuration;
  115. #ifdef OFFLINE_FARM
  116. float m_fInputDuration;
  117. float m_fWeaponLength;
  118. float m_fExternalForce;
  119. std::string m_strBoneName;
  120. THitTimePositionMap m_mapHitPosition;
  121. #endif
  122. bool m_isAccumulation;
  123. D3DVECTOR m_vec3Accumulation;
  124. };
  125. typedef DWORD MOTION_KEY;
  126. #define MAKE_MOTION_KEY(mode, index) ( ((mode) << 24) | ((index) << 8) | (0) )
  127. #define MAKE_RANDOM_MOTION_KEY(mode, index, type) ( ((mode) << 24) | ((index) << 8) | (type) )
  128. #define GET_MOTION_MODE(key) ((BYTE) ((key >> 24) & 0xFF))
  129. #define GET_MOTION_INDEX(key) ((WORD) ((key >> 8) & 0xFFFF))
  130. #define GET_MOTION_SUB_INDEX(key) ((BYTE) ((key) & 0xFF))
  131. class CMotionSet
  132. {
  133. public:
  134. typedef std::map<DWORD, CMotion *> TContainer;
  135. typedef TContainer::iterator iterator;
  136. typedef TContainer::const_iterator const_iterator;
  137. public:
  138. CMotionSet();
  139. ~CMotionSet();
  140. void Insert(DWORD dwKey, CMotion * pkMotion);
  141. bool Load(const char * szFileName, int mode, int motion);
  142. const CMotion * GetMotion(DWORD dwKey) const;
  143. #ifdef OFFLINE_FARM
  144. CMotion::THitTimePositionMapEx GetHitPositionData(const uint8_t c_nRace, const uint8_t c_nWeaponType, const uint8_t c_nComboIndex, bool c_bRiding);
  145. #endif
  146. protected:
  147. // DWORD = MOTION_KEY
  148. TContainer m_map_pkMotion;
  149. };
  150. class CMotionManager : public singleton<CMotionManager>
  151. {
  152. public:
  153. #ifdef OFFLINE_FARM
  154. struct SCharacterSkillData
  155. {
  156. float fRadius;
  157. D3DVECTOR v3Position;
  158. float fStartingTime;
  159. };
  160. #endif
  161. typedef std::map<DWORD, CMotionSet *> TContainer;
  162. typedef TContainer::iterator iterator;
  163. CMotionManager();
  164. virtual ~CMotionManager();
  165. bool Build();
  166. const CMotionSet * GetMotionSet(DWORD dwVnum);
  167. const CMotion * GetMotion(DWORD dwVnum, DWORD dwKey);
  168. float GetMotionDuration(DWORD dwVnum, DWORD dwKey);
  169. #ifdef OFFLINE_FARM
  170. DWORD GetMotionInputDuration(DWORD dwVnum, DWORD dwKey);
  171. float GetMotionWeaponLength(DWORD dwVnum, DWORD dwKey);
  172. float GetMotionExternalForce(DWORD dwVnum, DWORD dwKey);
  173. CMotion::THitTimePositionMapEx GetHitPositionData(const uint8_t c_nRace, const uint8_t c_nWeaponType, const uint8_t c_nComboIndex, bool c_bRiding);
  174. bool GetSkillData(BYTE byRace, BYTE bySkillGrade, DWORD dwSkillVnum, std::vector <SCharacterSkillData>& vecrfData);
  175. void RegisterPCSkillMotion(BYTE byRace, BYTE bySkillGrade, DWORD dwSkillVnum, const std::vector <SCharacterSkillData>& vecData);
  176. #endif
  177. // POLYMORPH_BUG_FIX
  178. float GetNormalAttackDuration(DWORD dwVnum);
  179. // END_OF_POLYMORPH_BUG_FIX
  180. protected:
  181. // DWORD = JOB or MONSTER VNUM
  182. TContainer m_map_pkMotionSet;
  183. // POLYMORPH_BUG_FIX
  184. std::map<DWORD, float> m_map_normalAttackDuration;
  185. // END_OF_POLYMORPH_BUG_FIX
  186. #ifdef OFFLINE_FARM
  187. std::map <std::tuple <BYTE /* byRace */, BYTE /* bySkillGrade */, DWORD /* dwSkillVnum */>, std::vector <SCharacterSkillData> /* vecData */> m_mapSkillData;
  188. #endif
  189. };
  190. #endif
  191. #include "stdafx.h"
  192. #include "../../common/stl.h"
  193. #include "constants.h"
  194. #include "motion.h"
  195. #include "text_file_loader.h"
  196. #include "mob_manager.h"
  197. #include "char.h"
  198. #include "../../common/CommonDefines.h"
  199. // POLYMORPH_BUG_FIX
  200. static float MSA_GetNormalAttackDuration(const char* msaPath)
  201. {
  202. float duration = 99.0f;
  203. FILE * fp = fopen(msaPath, "rt");
  204. if (!fp)
  205. return duration;
  206. char line[1024];
  207. while (fgets(line, sizeof(line), fp))
  208. {
  209. char key[1024];
  210. char val[1024];
  211. sscanf(line, "%s %s", key, val);
  212. if (strcmp(key, "MotionDuration") == 0)
  213. {
  214. duration = atof(val);
  215. break;
  216. }
  217. }
  218. fclose(fp);
  219. return duration;
  220. }
  221. static float MOB_GetNormalAttackDuration(TMobTable* mobTable)
  222. {
  223. float minDuration = 99.0f;
  224. const char * folder = mobTable->szFolder;
  225. char motlistPath[1024];
  226. snprintf(motlistPath, sizeof(motlistPath), "data/monster/%s/motlist.txt", folder);
  227. FILE * fp = fopen(motlistPath, "rt");
  228. if (!fp)
  229. return minDuration;
  230. char line[1024];
  231. while (fgets(line, sizeof(line), fp))
  232. {
  233. char mode[1024];
  234. char type[1024];
  235. char msaName[1024];
  236. int percent;
  237. sscanf(line, "%s %s %s %d", mode, type, msaName, &percent);
  238. if (strcmp(mode, "GENERAL") == 0 && strncmp(type, "NORMAL_ATTACK", 13) == 0)
  239. {
  240. char msaPath[1024];
  241. snprintf(msaPath, sizeof(msaPath), "data/monster/%s/%s", folder, msaName);
  242. float curDuration = MSA_GetNormalAttackDuration(msaPath);
  243. if (curDuration < minDuration)
  244. minDuration = curDuration;
  245. }
  246. }
  247. fclose(fp);
  248. return minDuration;
  249. }
  250. // END_OF_POLYMORPH_BUG_FIX
  251. static const char* GetMotionFileName(TMobTable* mobTable, EPublicMotion motion)
  252. {
  253. char buf[1024];
  254. const char * folder = mobTable->szFolder;
  255. snprintf(buf, sizeof(buf), "data/monster/%s/motlist.txt", folder);
  256. FILE * fp = fopen(buf, "rt");
  257. char * v[4];
  258. if (fp != NULL)
  259. {
  260. const char* field = NULL;
  261. switch (motion)
  262. {
  263. case MOTION_WALK : field = "WALK"; break;
  264. case MOTION_RUN : field = "RUN"; break;
  265. case MOTION_NORMAL_ATTACK : field = "NORMAL_ATTACK"; break;
  266. case MOTION_SPECIAL_1 : field = "SPECIAL"; break;
  267. case MOTION_SPECIAL_2 : field = "SPECIAL1"; break;
  268. case MOTION_SPECIAL_3 : field = "SPECIAL2"; break;
  269. case MOTION_SPECIAL_4 : field = "SPECIAL3"; break;
  270. case MOTION_SPECIAL_5 : field = "SPECIAL4"; break;
  271. default:
  272. fclose(fp);
  273. sys_err("Motion: no process for this motion(%d) vnum(%d)", motion, mobTable->dwVnum);
  274. return NULL;
  275. }
  276. while (fgets(buf, 1024, fp))
  277. {
  278. v[0] = strtok(buf, " \t\r\n");
  279. v[1] = strtok(NULL, " \t\r\n");
  280. v[2] = strtok(NULL, " \t\r\n");
  281. v[3] = strtok(NULL, " \t\r\n");
  282. if (NULL != v[0] && NULL != v[1] && NULL != v[2] && NULL != v[3] && !strcasecmp(v[1], field))
  283. {
  284. fclose(fp);
  285. static std::string str;
  286. str = "data/monster/";
  287. str += folder;
  288. str += "/";
  289. str += v[2];
  290. return str.c_str();
  291. }
  292. }
  293. fclose(fp);
  294. }
  295. else
  296. {
  297. sys_err("Motion: %s have not motlist.txt vnum(%d) folder(%s)", folder, mobTable->dwVnum, mobTable->szFolder);
  298. }
  299. return NULL;
  300. }
  301. static void LoadMotion(CMotionSet* pMotionSet, TMobTable* mob_table, EPublicMotion motion)
  302. {
  303. const char* cpFileName = GetMotionFileName(mob_table, motion);
  304. if (cpFileName == NULL)
  305. {
  306. return;
  307. }
  308. CMotion* pMotion = M2_NEW CMotion;
  309. if (pMotion->LoadFromFile(cpFileName) == true)
  310. {
  311. if (motion == MOTION_RUN
  312. && 0.0f == pMotion->GetAccumVector().y
  313. && !IS_SET(mob_table->dwAIFlag, AIFLAG_NOMOVE)) // @warme013 (don't show this message for non-movable mobs)
  314. sys_err("cannot find accumulation data in file '%s'", cpFileName);
  315. pMotionSet->Insert(MAKE_MOTION_KEY(MOTION_MODE_GENERAL, motion), pMotion);
  316. }
  317. else
  318. {
  319. M2_DELETE(pMotion);
  320. sys_err("Motion: Load failed vnum(%d) motion(%d) file(%s)", mob_table->dwVnum, motion, cpFileName);
  321. }
  322. }
  323. static void LoadSkillMotion(CMotionSet* pMotionSet, CMob* pMob, EPublicMotion motion)
  324. {
  325. int idx = 0;
  326. switch (motion)
  327. {
  328. case MOTION_SPECIAL_1 : idx = 0; break;
  329. case MOTION_SPECIAL_2 : idx = 1; break;
  330. case MOTION_SPECIAL_3 : idx = 2; break;
  331. case MOTION_SPECIAL_4 : idx = 3; break;
  332. case MOTION_SPECIAL_5 : idx = 4; break;
  333. default :
  334. return;
  335. }
  336. TMobTable* mob_table = &pMob->m_table;
  337. if (mob_table->Skills[idx].dwVnum == 0) return;
  338. const char* cpFileName = GetMotionFileName(mob_table, motion);
  339. if (cpFileName == NULL) return;
  340. CMotion* pMotion = M2_NEW CMotion;
  341. if (pMotion->LoadMobSkillFromFile(cpFileName, pMob, idx) == true)
  342. {
  343. pMotionSet->Insert(MAKE_MOTION_KEY(MOTION_MODE_GENERAL, motion), pMotion);
  344. }
  345. else
  346. {
  347. if (mob_table->Skills[idx].dwVnum != 0)
  348. {
  349. sys_err("Motion: Skill exist but no motion data for index %d mob %u skill %u",
  350. idx, mob_table->dwVnum, mob_table->Skills[idx].dwVnum);
  351. }
  352. M2_DELETE(pMotion);
  353. }
  354. }
  355. CMotionManager::CMotionManager()
  356. {
  357. }
  358. CMotionManager::~CMotionManager()
  359. {
  360. iterator it = m_map_pkMotionSet.begin();
  361. for ( ; it != m_map_pkMotionSet.end(); ++it) {
  362. M2_DELETE(it->second);
  363. }
  364. }
  365. const CMotionSet * CMotionManager::GetMotionSet(DWORD dwVnum)
  366. {
  367. iterator it = m_map_pkMotionSet.find(dwVnum);
  368. if (m_map_pkMotionSet.end() == it)
  369. return NULL;
  370. return it->second;
  371. }
  372. const CMotion * CMotionManager::GetMotion(DWORD dwVnum, DWORD dwKey)
  373. {
  374. const CMotionSet * pkMotionSet = GetMotionSet(dwVnum);
  375. if (!pkMotionSet)
  376. return NULL;
  377. return pkMotionSet->GetMotion(dwKey);
  378. }
  379. float CMotionManager::GetMotionDuration(DWORD dwVnum, DWORD dwKey)
  380. {
  381. const CMotion * pkMotion = GetMotion(dwVnum, dwKey);
  382. return pkMotion ? pkMotion->GetDuration() : 0.0f;
  383. }
  384. #ifdef OFFLINE_FARM
  385. DWORD CMotionManager::GetMotionInputDuration(DWORD dwVnum, DWORD dwKey)
  386. {
  387. const CMotion* pkMotion = GetMotion(dwVnum, dwKey);
  388. if (pkMotion)
  389. {
  390. float fDuration = pkMotion->GetInputDuration();
  391. if (fDuration)
  392. {
  393. return static_cast<uint32_t>(std::round(fDuration * 1000));
  394. }
  395. }
  396. return 0;
  397. }
  398. float CMotionManager::GetMotionWeaponLength(DWORD dwVnum, DWORD dwKey)
  399. {
  400. const CMotion* pkMotion = GetMotion(dwVnum, dwKey);
  401. return pkMotion ? pkMotion->GetWeaponLength() : 0.0f;
  402. }
  403. float CMotionManager::GetMotionExternalForce(DWORD dwVnum, DWORD dwKey)
  404. {
  405. const CMotion* pkMotion = GetMotion(dwVnum, dwKey);
  406. return pkMotion ? pkMotion->GetExternalForce() : 0.0f;
  407. }
  408. CMotion::THitTimePositionMapEx CMotionManager::GetHitPositionData(const uint8_t c_nRace, const uint8_t c_nWeaponType, const uint8_t c_nComboIndex, bool c_bRiding)
  409. {
  410. auto it = m_map_pkMotionSet.begin();
  411. for (; it != m_map_pkMotionSet.end(); ++it)
  412. {
  413. const auto pkMotionSet = it->second;
  414. if (!pkMotionSet)
  415. continue;
  416. const auto mapHitPosition = pkMotionSet->GetHitPositionData(c_nRace, c_nWeaponType, c_nComboIndex, c_bRiding);
  417. if (!mapHitPosition.empty())
  418. return mapHitPosition;
  419. }
  420. return CMotion::THitTimePositionMapEx();
  421. }
  422. bool CMotionManager::GetSkillData(BYTE byRace, BYTE bySkillGrade, DWORD dwSkillVnum, std::vector <SCharacterSkillData>& vecrfData)
  423. {
  424. for (const auto& it : m_mapSkillData)
  425. {
  426. const auto& [byCurRace, byCurrSkillGrade, dwCurSkillVnum] = it.first;
  427. if (byCurRace == byRace && byCurrSkillGrade == bySkillGrade && dwCurSkillVnum == dwSkillVnum)
  428. {
  429. vecrfData = it.second;
  430. return true;
  431. }
  432. }
  433. return false;
  434. }
  435. void CMotionManager::RegisterPCSkillMotion(BYTE byRace, BYTE bySkillGrade, DWORD dwSkillVnum, const std::vector<SCharacterSkillData>& vecData)
  436. {
  437. m_mapSkillData[std::make_tuple(byRace, bySkillGrade, dwSkillVnum)] = vecData;
  438. }
  439. #endif
  440. // POLYMORPH_BUG_FIX
  441. float CMotionManager::GetNormalAttackDuration(DWORD dwVnum)
  442. {
  443. std::map<DWORD, float>::iterator f = m_map_normalAttackDuration.find(dwVnum);
  444. if (m_map_normalAttackDuration.end() == f)
  445. return 0.0f;
  446. else
  447. return f->second;
  448. }
  449. // END_OF_POLYMORPH_BUG_FIX
  450. enum EMotionEventType
  451. {
  452. MOTION_EVENT_TYPE_NONE, // 0
  453. MOTION_EVENT_TYPE_EFFECT, // 1
  454. MOTION_EVENT_TYPE_SCREEN_WAVING, // 2
  455. MOTION_EVENT_TYPE_SCREEN_FLASHING, // 3
  456. MOTION_EVENT_TYPE_SPECIAL_ATTACKING, // 4
  457. MOTION_EVENT_TYPE_SOUND, // 5
  458. MOTION_EVENT_TYPE_FLY, // 6
  459. MOTION_EVENT_TYPE_CHARACTER_SHOW, // 7
  460. MOTION_EVENT_TYPE_CHARACTER_HIDE, // 8
  461. MOTION_EVENT_TYPE_WARP, // 9
  462. MOTION_EVENT_TYPE_EFFECT_TO_TARGET, // 10
  463. #ifdef ENABLE_WOLFMAN_CHARACTER
  464. MOTION_EVENT_TYPE_RELATIVE_MOVE_ON, // 11
  465. MOTION_EVENT_TYPE_RELATIVE_MOVE_OFF, // 12
  466. #endif
  467. MOTION_EVENT_TYPE_MAX_NUM, // 13
  468. };
  469. bool CMotionManager::Build()
  470. {
  471. const char * c_apszFolderName[MAIN_RACE_MAX_NUM] =
  472. {
  473. "data/pc/warrior",
  474. "data/pc/assassin",
  475. "data/pc/sura",
  476. "data/pc/shaman",
  477. "data/pc2/warrior",
  478. "data/pc2/assassin",
  479. "data/pc2/sura",
  480. "data/pc2/shaman",
  481. #ifdef ENABLE_WOLFMAN_CHARACTER
  482. "data/pc3/wolfman",
  483. #endif
  484. };
  485. for (int i = 0; i < MAIN_RACE_MAX_NUM; ++i)
  486. {
  487. CMotionSet * pkMotionSet = M2_NEW CMotionSet;
  488. m_map_pkMotionSet.emplace(i, pkMotionSet);
  489. char sz[256];
  490. snprintf(sz, sizeof(sz), "%s/general/run.msa", c_apszFolderName[i]);
  491. pkMotionSet->Load(sz, MOTION_MODE_GENERAL, MOTION_RUN);
  492. snprintf(sz, sizeof(sz), "%s/general/walk.msa", c_apszFolderName[i]);
  493. pkMotionSet->Load(sz, MOTION_MODE_GENERAL, MOTION_WALK);
  494. snprintf(sz, sizeof(sz), "%s/twohand_sword/run.msa", c_apszFolderName[i]);
  495. pkMotionSet->Load(sz, MOTION_MODE_TWOHAND_SWORD, MOTION_RUN);
  496. snprintf(sz, sizeof(sz), "%s/twohand_sword/walk.msa", c_apszFolderName[i]);
  497. pkMotionSet->Load(sz, MOTION_MODE_TWOHAND_SWORD, MOTION_WALK);
  498. snprintf(sz, sizeof(sz), "%s/onehand_sword/run.msa", c_apszFolderName[i]);
  499. pkMotionSet->Load(sz, MOTION_MODE_ONEHAND_SWORD, MOTION_RUN);
  500. snprintf(sz, sizeof(sz), "%s/onehand_sword/walk.msa", c_apszFolderName[i]);
  501. pkMotionSet->Load(sz, MOTION_MODE_ONEHAND_SWORD, MOTION_WALK);
  502. snprintf(sz, sizeof(sz), "%s/dualhand_sword/run.msa", c_apszFolderName[i]);
  503. pkMotionSet->Load(sz, MOTION_MODE_DUALHAND_SWORD, MOTION_RUN);
  504. snprintf(sz, sizeof(sz), "%s/dualhand_sword/walk.msa", c_apszFolderName[i]);
  505. pkMotionSet->Load(sz, MOTION_MODE_DUALHAND_SWORD, MOTION_WALK);
  506. snprintf(sz, sizeof(sz), "%s/bow/run.msa", c_apszFolderName[i]);
  507. pkMotionSet->Load(sz, MOTION_MODE_BOW, MOTION_RUN);
  508. snprintf(sz, sizeof(sz), "%s/bow/walk.msa", c_apszFolderName[i]);
  509. pkMotionSet->Load(sz, MOTION_MODE_BOW, MOTION_WALK);
  510. snprintf(sz, sizeof(sz), "%s/bell/run.msa", c_apszFolderName[i]);
  511. pkMotionSet->Load(sz, MOTION_MODE_BELL, MOTION_RUN);
  512. snprintf(sz, sizeof(sz), "%s/bell/walk.msa", c_apszFolderName[i]);
  513. pkMotionSet->Load(sz, MOTION_MODE_BELL, MOTION_WALK);
  514. snprintf(sz, sizeof(sz), "%s/fan/run.msa", c_apszFolderName[i]);
  515. pkMotionSet->Load(sz, MOTION_MODE_FAN, MOTION_RUN);
  516. snprintf(sz, sizeof(sz), "%s/fan/walk.msa", c_apszFolderName[i]);
  517. pkMotionSet->Load(sz, MOTION_MODE_FAN, MOTION_WALK);
  518. snprintf(sz, sizeof(sz), "%s/horse/run.msa", c_apszFolderName[i]);
  519. pkMotionSet->Load(sz, MOTION_MODE_HORSE, MOTION_RUN);
  520. snprintf(sz, sizeof(sz), "%s/horse/walk.msa", c_apszFolderName[i]);
  521. pkMotionSet->Load(sz, MOTION_MODE_HORSE, MOTION_WALK);
  522. #ifdef ENABLE_WOLFMAN_CHARACTER
  523. snprintf(sz, sizeof(sz), "%s/claw/run.msa", c_apszFolderName[i]);
  524. pkMotionSet->Load(sz, MOTION_MODE_CLAW, MOTION_RUN);
  525. snprintf(sz, sizeof(sz), "%s/claw/walk.msa", c_apszFolderName[i]);
  526. pkMotionSet->Load(sz, MOTION_MODE_CLAW, MOTION_WALK);
  527. #endif
  528. #ifdef OFFLINE_FARM
  529. // Load combo attack motions
  530. for (int j = 1; j <= 7; ++j) // 7=max combo attack count
  531. {
  532. if (i == MAIN_RACE_WARRIOR_M || i == MAIN_RACE_WARRIOR_W)
  533. {
  534. snprintf(sz, sizeof(sz), "%s/twohand_sword/combo_0%d.msa", c_apszFolderName[i], j);
  535. pkMotionSet->Load(sz, MOTION_MODE_TWOHAND_SWORD, (EPublicMotion)(MOTION_COMBO_ATTACK_1 + j - 1));
  536. snprintf(sz, sizeof(sz), "%s/onehand_sword/combo_0%d.msa", c_apszFolderName[i], j);
  537. pkMotionSet->Load(sz, MOTION_MODE_ONEHAND_SWORD, (EPublicMotion)(MOTION_COMBO_ATTACK_1 + j - 1));
  538. }
  539. else if (i == MAIN_RACE_ASSASSIN_M || i == MAIN_RACE_ASSASSIN_W)
  540. {
  541. snprintf(sz, sizeof(sz), "%s/dualhand_sword/combo_0%d.msa", c_apszFolderName[i], j);
  542. pkMotionSet->Load(sz, MOTION_MODE_DUALHAND_SWORD, (EPublicMotion)(MOTION_COMBO_ATTACK_1 + j - 1));
  543. snprintf(sz, sizeof(sz), "%s/onehand_sword/combo_0%d.msa", c_apszFolderName[i], j);
  544. pkMotionSet->Load(sz, MOTION_MODE_ONEHAND_SWORD, (EPublicMotion)(MOTION_COMBO_ATTACK_1 + j - 1));
  545. // Bow has no combo attack
  546. }
  547. else if (i == MAIN_RACE_SURA_M || i == MAIN_RACE_SURA_W)
  548. {
  549. snprintf(sz, sizeof(sz), "%s/onehand_sword/combo_0%d.msa", c_apszFolderName[i], j);
  550. pkMotionSet->Load(sz, MOTION_MODE_ONEHAND_SWORD, (EPublicMotion)(MOTION_COMBO_ATTACK_1 + j - 1));
  551. }
  552. else if (i == MAIN_RACE_SHAMAN_M || i == MAIN_RACE_SHAMAN_W)
  553. {
  554. snprintf(sz, sizeof(sz), "%s/bell/combo_0%d.msa", c_apszFolderName[i], j);
  555. pkMotionSet->Load(sz, MOTION_MODE_BELL, (EPublicMotion)(MOTION_COMBO_ATTACK_1 + j - 1));
  556. snprintf(sz, sizeof(sz), "%s/fan/combo_0%d.msa", c_apszFolderName[i], j);
  557. pkMotionSet->Load(sz, MOTION_MODE_FAN, (EPublicMotion)(MOTION_COMBO_ATTACK_1 + j - 1));
  558. }
  559. #ifdef ENABLE_WOLFMAN_CHARACTER
  560. else if (i == MAIN_RACE_WOLFMAN_M)
  561. {
  562. snprintf(sz, sizeof(sz), "%s/claw/combo_0%d.msa", c_apszFolderName[i], j);
  563. pkMotionSet->Load(sz, MOTION_MODE_CLAW, (EPublicMotion)(MOTION_COMBO_ATTACK_1 + j - 1));
  564. }
  565. #endif
  566. }
  567. for (int j = 1; j <= 3; ++j) // 3=max horse combo attack count
  568. {
  569. if (i == MAIN_RACE_WARRIOR_M || i == MAIN_RACE_WARRIOR_W)
  570. {
  571. snprintf(sz, sizeof(sz), "%s/horse_twohand_sword/combo_0%d.msa", c_apszFolderName[i], j);
  572. pkMotionSet->Load(sz, MOTION_MODE_HORSE_TWOHAND_SWORD, (EPublicMotion)(MOTION_COMBO_ATTACK_1 + j - 1));
  573. snprintf(sz, sizeof(sz), "%s/horse_onehand_sword/combo_0%d.msa", c_apszFolderName[i], j);
  574. pkMotionSet->Load(sz, MOTION_MODE_HORSE_ONEHAND_SWORD, (EPublicMotion)(MOTION_COMBO_ATTACK_1 + j - 1));
  575. }
  576. else if (i == MAIN_RACE_ASSASSIN_M || i == MAIN_RACE_ASSASSIN_W)
  577. {
  578. snprintf(sz, sizeof(sz), "%s/horse_dualhand_sword/combo_0%d.msa", c_apszFolderName[i], j);
  579. pkMotionSet->Load(sz, MOTION_MODE_HORSE_DUALHAND_SWORD, (EPublicMotion)(MOTION_COMBO_ATTACK_1 + j - 1));
  580. snprintf(sz, sizeof(sz), "%s/horse_onehand_sword/combo_0%d.msa", c_apszFolderName[i], j);
  581. pkMotionSet->Load(sz, MOTION_MODE_HORSE_ONEHAND_SWORD, (EPublicMotion)(MOTION_COMBO_ATTACK_1 + j - 1));
  582. }
  583. else if (i == MAIN_RACE_SURA_M || i == MAIN_RACE_SURA_W)
  584. {
  585. snprintf(sz, sizeof(sz), "%s/horse_onehand_sword/combo_0%d.msa", c_apszFolderName[i], j);
  586. pkMotionSet->Load(sz, MOTION_MODE_HORSE_ONEHAND_SWORD, (EPublicMotion)(MOTION_COMBO_ATTACK_1 + j - 1));
  587. }
  588. else if (i == MAIN_RACE_SHAMAN_M || i == MAIN_RACE_SHAMAN_W)
  589. {
  590. snprintf(sz, sizeof(sz), "%s/horse_bell/combo_0%d.msa", c_apszFolderName[i], j);
  591. pkMotionSet->Load(sz, MOTION_MODE_HORSE_BELL, (EPublicMotion)(MOTION_COMBO_ATTACK_1 + j - 1));
  592. snprintf(sz, sizeof(sz), "%s/horse_fan/combo_0%d.msa", c_apszFolderName[i], j);
  593. pkMotionSet->Load(sz, MOTION_MODE_HORSE_FAN, (EPublicMotion)(MOTION_COMBO_ATTACK_1 + j - 1));
  594. }
  595. #ifdef ENABLE_WOLFMAN_CHARACTER
  596. else if (i == MAIN_RACE_WOLFMAN_M)
  597. {
  598. snprintf(sz, sizeof(sz), "%s/horse_claw/combo_0%d.msa", c_apszFolderName[i], j);
  599. pkMotionSet->Load(sz, MOTION_MODE_HORSE_CLAW, (EPublicMotion)(MOTION_COMBO_ATTACK_1 + j - 1));
  600. }
  601. #endif
  602. }
  603. #endif
  604. extern std::vector <DWORD> GetSkillSet(uint8_t race, uint8_t skill_group);
  605. extern std::string GetSkillCodeName(DWORD dwVnum);
  606. #ifdef OFFLINE_FARM
  607. // Skill motions
  608. if (i == MAIN_RACE_WARRIOR_M || i == MAIN_RACE_WARRIOR_W)
  609. {
  610. for (int j = 0; j < SKILL_GROUP_MAX_NUM; ++j)
  611. {
  612. const auto vecSkillSet = GetSkillSet(i, j);
  613. for (const auto dwSkillVnum : vecSkillSet)
  614. {
  615. const auto strSkillCodeName = GetSkillCodeName(dwSkillVnum);
  616. if (strSkillCodeName.empty())
  617. continue;
  618. for (int k = 1; k < SKILL_GRADE_COUNT + 1; ++k)
  619. {
  620. if (k == 1)
  621. snprintf(sz, sizeof(sz), "%s/skill/%s.msa", c_apszFolderName[i], strSkillCodeName.c_str());
  622. else
  623. snprintf(sz, sizeof(sz), "%s/skill/%s_%d.msa", c_apszFolderName[i], strSkillCodeName.c_str(), k);
  624. if (!pkMotionSet->Load(sz, MOTION_MODE_GENERAL, (EPublicMotion)(MOTION_SPECIAL_1 + k)))
  625. {
  626. sys_err("W-Motion: Load failed vnum(%d) motion(%d) file(%s)", i, MOTION_SPECIAL_1 + k, sz);
  627. }
  628. }
  629. }
  630. }
  631. }
  632. else if (i == MAIN_RACE_ASSASSIN_M || i == MAIN_RACE_ASSASSIN_W)
  633. {
  634. for (int j = 0; j < SKILL_GROUP_MAX_NUM; ++j)
  635. {
  636. const auto vecSkillSet = GetSkillSet(i, j);
  637. for (const auto dwSkillVnum : vecSkillSet)
  638. {
  639. const auto strSkillCodeName = GetSkillCodeName(dwSkillVnum);
  640. if (strSkillCodeName.empty())
  641. continue;
  642. for (int k = 1; k < SKILL_GRADE_COUNT + 1; ++k)
  643. {
  644. if (k == 1)
  645. snprintf(sz, sizeof(sz), "%s/skill/%s.msa", c_apszFolderName[i], strSkillCodeName.c_str());
  646. else
  647. snprintf(sz, sizeof(sz), "%s/skill/%s_%d.msa", c_apszFolderName[i], strSkillCodeName.c_str(), k);
  648. if (k > 1)
  649. {
  650. if (strSkillCodeName == "gigung") // only one motion for some skills
  651. continue;
  652. }
  653. if (!pkMotionSet->Load(sz, MOTION_MODE_GENERAL, (EPublicMotion)(MOTION_SPECIAL_1 + k)))
  654. {
  655. sys_err("A-Motion: Load failed vnum(%d) motion(%d) file(%s)", i, MOTION_SPECIAL_1 + k, sz);
  656. }
  657. }
  658. }
  659. }
  660. }
  661. else if (i == MAIN_RACE_SURA_M || i == MAIN_RACE_SURA_W)
  662. {
  663. for (int j = 0; j < SKILL_GROUP_MAX_NUM; ++j)
  664. {
  665. const auto vecSkillSet = GetSkillSet(i, j);
  666. for (const auto dwSkillVnum : vecSkillSet)
  667. {
  668. const auto strSkillCodeName = GetSkillCodeName(dwSkillVnum);
  669. if (strSkillCodeName.empty())
  670. continue;
  671. for (int k = 1; k < SKILL_GRADE_COUNT + 1; ++k)
  672. {
  673. if (k == 1)
  674. snprintf(sz, sizeof(sz), "%s/skill/%s.msa", c_apszFolderName[i], strSkillCodeName.c_str());
  675. else
  676. snprintf(sz, sizeof(sz), "%s/skill/%s_%d.msa", c_apszFolderName[i], strSkillCodeName.c_str(), k);
  677. if (!pkMotionSet->Load(sz, MOTION_MODE_GENERAL, (EPublicMotion)(MOTION_SPECIAL_1 + k)))
  678. {
  679. sys_err("S-Motion: Load failed vnum(%d) motion(%d) file(%s)", i, MOTION_SPECIAL_1 + k, sz);
  680. }
  681. }
  682. }
  683. }
  684. }
  685. else if (i == MAIN_RACE_SHAMAN_M || i == MAIN_RACE_SHAMAN_W)
  686. {
  687. for (int j = 0; j < SKILL_GROUP_MAX_NUM; ++j)
  688. {
  689. const auto vecSkillSet = GetSkillSet(i, j);
  690. for (const auto dwSkillVnum : vecSkillSet)
  691. {
  692. const auto strSkillCodeName = GetSkillCodeName(dwSkillVnum);
  693. if (strSkillCodeName.empty())
  694. continue;
  695. for (int k = 1; k < SKILL_GRADE_COUNT + 1; ++k)
  696. {
  697. if (k == 1)
  698. snprintf(sz, sizeof(sz), "%s/skill/%s.msa", c_apszFolderName[i], strSkillCodeName.c_str());
  699. else
  700. snprintf(sz, sizeof(sz), "%s/skill/%s_%d.msa", c_apszFolderName[i], strSkillCodeName.c_str(), k);
  701. if (k == 1)
  702. {
  703. if (strSkillCodeName == "hosin" || strSkillCodeName == "boho" || strSkillCodeName == "gicheon" || // for some skills, first motion does not exist
  704. strSkillCodeName == "jeongeop" || strSkillCodeName == "kwaesok")
  705. continue;
  706. }
  707. if (!pkMotionSet->Load(sz, MOTION_MODE_GENERAL, (EPublicMotion)(MOTION_SPECIAL_1 + k)))
  708. {
  709. sys_err("SH-Motion: Load failed vnum(%d) motion(%d) file(%s)", i, MOTION_SPECIAL_1 + k, sz);
  710. }
  711. }
  712. }
  713. }
  714. }
  715. #endif
  716. #ifdef OFFLINE_FARM
  717. #ifdef ENABLE_WOLFMAN_CHARACTER
  718. else if (i == MAIN_RACE_WOLFMAN_M)
  719. {
  720. for (int j = 0; j < SKILL_GROUP_MAX_NUM; ++j)
  721. {
  722. const auto vecSkillSet = GetSkillSet(i, j);
  723. for (const auto dwSkillVnum : vecSkillSet)
  724. {
  725. const auto strSkillCodeName = GetSkillCodeName(dwSkillVnum);
  726. if (strSkillCodeName.empty())
  727. continue;
  728. for (int k = 1; k < SKILL_GRADE_COUNT + 1; ++k)
  729. {
  730. if (k == 1)
  731. snprintf(sz, sizeof(sz), "%s/skill/%s.msa", c_apszFolderName[i], strSkillCodeName.c_str());
  732. else
  733. snprintf(sz, sizeof(sz), "%s/skill/%s_%d.msa", c_apszFolderName[i], strSkillCodeName.c_str(), k);
  734. if (!pkMotionSet->Load(sz, MOTION_MODE_GENERAL, (EPublicMotion)(MOTION_SPECIAL_1 + k)))
  735. {
  736. sys_err("WM-Motion: Load failed vnum(%d) motion(%d) file(%s)", i, MOTION_SPECIAL_1 + k, sz);
  737. }
  738. }
  739. }
  740. }
  741. }
  742. #endif
  743. #endif
  744. }
  745. CMobManager::iterator it = CMobManager::instance().begin();
  746. while (it != CMobManager::instance().end())
  747. {
  748. CMob * pkMob = (it++)->second;
  749. TMobTable * t = &pkMob->m_table;
  750. if ('\0' != t->szFolder[0])
  751. {
  752. CMotionSet * pkMotionSet = M2_NEW CMotionSet;
  753. m_map_pkMotionSet.emplace(t->dwVnum, pkMotionSet);
  754. LoadMotion(pkMotionSet, t, MOTION_WALK);
  755. LoadMotion(pkMotionSet, t, MOTION_RUN);
  756. LoadMotion(pkMotionSet, t, MOTION_NORMAL_ATTACK);
  757. LoadSkillMotion(pkMotionSet, pkMob, MOTION_SPECIAL_1);
  758. LoadSkillMotion(pkMotionSet, pkMob, MOTION_SPECIAL_2);
  759. LoadSkillMotion(pkMotionSet, pkMob, MOTION_SPECIAL_3);
  760. LoadSkillMotion(pkMotionSet, pkMob, MOTION_SPECIAL_4);
  761. LoadSkillMotion(pkMotionSet, pkMob, MOTION_SPECIAL_5);
  762. // POLYMORPH_BUG_FIX
  763. float normalAttackDuration = MOB_GetNormalAttackDuration(t);
  764. sys_log(0, "mob_normal_attack_duration:%d:%s:%.2f", t->dwVnum, t->szFolder, normalAttackDuration);
  765. m_map_normalAttackDuration.emplace(t->dwVnum, normalAttackDuration);
  766. // END_OF_POLYMORPH_BUG_FIX
  767. }
  768. }
  769. return true;
  770. }
  771. CMotionSet::CMotionSet()
  772. {
  773. }
  774. CMotionSet::~CMotionSet()
  775. {
  776. iterator it = m_map_pkMotion.begin();
  777. for ( ; it != m_map_pkMotion.end(); ++it) {
  778. M2_DELETE(it->second);
  779. }
  780. }
  781. const CMotion * CMotionSet::GetMotion(DWORD dwKey) const
  782. {
  783. const_iterator it = m_map_pkMotion.find(dwKey);
  784. if (it == m_map_pkMotion.end())
  785. return NULL;
  786. return it->second;
  787. }
  788. #ifdef OFFLINE_FARM
  789. CMotion::THitTimePositionMapEx CMotionSet::GetHitPositionData(const uint8_t c_nRace, const uint8_t c_nWeaponType, const uint8_t c_nComboIndex, bool c_bRiding)
  790. {
  791. auto it = m_map_pkMotion.begin();
  792. for (; it != m_map_pkMotion.end(); ++it)
  793. {
  794. const auto pkMotion = it->second;
  795. if (!pkMotion)
  796. continue;
  797. const auto& mapData = pkMotion->GetHitPositionData(c_nRace, c_nWeaponType, c_nComboIndex, c_bRiding);
  798. if (!mapData.empty())
  799. {
  800. return mapData;
  801. }
  802. }
  803. return {};
  804. }
  805. #endif
  806. void CMotionSet::Insert(DWORD dwKey, CMotion * pkMotion)
  807. {
  808. m_map_pkMotion.emplace(dwKey, pkMotion);
  809. }
  810. bool CMotionSet::Load(const char * szFileName, int mode, int motion)
  811. {
  812. CMotion * pkMotion = M2_NEW CMotion;
  813. if (!pkMotion->LoadFromFile(szFileName))
  814. {
  815. M2_DELETE(pkMotion);
  816. return false;
  817. }
  818. Insert(MAKE_MOTION_KEY(mode, motion), pkMotion);
  819. return true;
  820. }
  821. CMotion::CMotion() : m_isEmpty(true), m_fDuration(0.0f), m_isAccumulation(false)
  822. {
  823. m_vec3Accumulation.x = 0.0f;
  824. m_vec3Accumulation.y = 0.0f;
  825. m_vec3Accumulation.z = 0.0f;
  826. #ifdef OFFLINE_FARM
  827. m_fInputDuration = 0.0f;
  828. m_fWeaponLength = 0.0f;
  829. m_fExternalForce = 0.0f;
  830. #endif
  831. }
  832. CMotion::~CMotion()
  833. {
  834. #ifdef OFFLINE_FARM
  835. for (THitTimePositionMap::iterator it = m_mapHitPosition.begin(); it != m_mapHitPosition.end(); ++it)
  836. {
  837. M2_DELETE(it->first);
  838. }
  839. #endif
  840. }
  841. std::vector<std::string> SplitString(const std::string& str, char delimiter)
  842. {
  843. std::vector<std::string> tokens;
  844. std::string token;
  845. size_t start = 0;
  846. size_t end = 0;
  847. while ((end = str.find(delimiter, start)) != std::string::npos)
  848. {
  849. token = str.substr(start, end - start);
  850. tokens.push_back(token);
  851. start = end + 1;
  852. }
  853. tokens.push_back(str.substr(start));
  854. return tokens;
  855. }
  856. #ifdef OFFLINE_FARM
  857. bool ParseSkillFile(const char* c_pszFileName, int& race, int& job, int& skillVnum, int& skillGrade, std::string& codeName)
  858. {
  859. const std::string strFileName = c_pszFileName;
  860. std::vector<std::string> pathParts = SplitString(strFileName, '/');
  861. if (pathParts.size() < 4)
  862. {
  863. sys_err("Invalid skill file path: %s", c_pszFileName);
  864. return false;
  865. }
  866. // Get the job part (should be pc, pc2, or pc3)
  867. const std::string& jobPart = pathParts[1];
  868. // Get the race part
  869. const std::string& racePart = pathParts[2];
  870. // Get the last part (filename with extension)
  871. std::string fileName = pathParts[pathParts.size() - 1];
  872. // Split filename by '.' to get the code name
  873. std::vector<std::string> fileNameParts = SplitString(fileName, '.');
  874. if (fileNameParts.empty())
  875. {
  876. sys_err("Invalid skill file name: %s", c_pszFileName);
  877. return false;
  878. }
  879. codeName = fileNameParts[0];
  880. if (codeName.find("_") == std::string::npos)
  881. {
  882. skillGrade = 0;
  883. }
  884. else
  885. {
  886. if (codeName.find("split_slash") != std::string::npos || codeName.find("wind_death") != std::string::npos ||
  887. codeName.find("blue_possession") != std::string::npos || codeName.find("red_possession") != std::string::npos ||
  888. codeName.find("reef_attack") != std::string::npos)
  889. {
  890. std::vector<std::string> codeNameParts = SplitString(codeName, '_');
  891. if (codeNameParts.size() == 3)
  892. {
  893. codeName = codeNameParts[0] + "_" + codeNameParts[1];
  894. str_to_number(skillGrade, codeNameParts[2].c_str());
  895. skillGrade -= 1;
  896. }
  897. else
  898. {
  899. codeName = codeNameParts[0] + "_" + codeNameParts[1];
  900. skillGrade = 0;
  901. }
  902. }
  903. else
  904. {
  905. std::vector<std::string> codeNameParts = SplitString(codeName, '_');
  906. if (codeNameParts.size() != 2)
  907. {
  908. sys_err("Invalid skill code name: %s", codeName.c_str());
  909. return false;
  910. }
  911. codeName = codeNameParts[0];
  912. str_to_number(skillGrade, codeNameParts[1].c_str());
  913. skillGrade -= 1;
  914. }
  915. }
  916. // Parse race
  917. if (racePart == "warrior")
  918. race = JOB_WARRIOR;
  919. else if (racePart == "assassin")
  920. race = JOB_ASSASSIN;
  921. else if (racePart == "sura")
  922. race = JOB_SURA;
  923. else if (racePart == "shaman")
  924. race = JOB_SHAMAN;
  925. #ifdef ENABLE_WOLFMAN_CHARACTER
  926. else if (racePart == "wolfman")
  927. race = JOB_WOLFMAN;
  928. #endif
  929. else
  930. {
  931. sys_err("Invalid race part: %s", racePart.c_str());
  932. return false;
  933. }
  934. // Parse job
  935. if (jobPart == "pc")
  936. {
  937. job = 0;
  938. }
  939. else if (jobPart == "pc2")
  940. {
  941. job = 1;
  942. }
  943. #ifdef ENABLE_WOLFMAN_CHARACTER
  944. else if (jobPart == "pc3")
  945. {
  946. job = 0;
  947. }
  948. #endif
  949. else
  950. {
  951. sys_err("Invalid job part: %s", jobPart.c_str());
  952. return false;
  953. }
  954. // Get skill vnum from code name
  955. extern DWORD GetSkillVnumFromCodeName(const std::string & strCodeName);
  956. skillVnum = GetSkillVnumFromCodeName(codeName);
  957. if (skillVnum == 0)
  958. {
  959. sys_err("Invalid skill code name: %s", codeName.c_str());
  960. return false;
  961. }
  962. return true;
  963. }
  964. #endif
  965. bool CMotion::LoadMobSkillFromFile(const char * c_pszFileName, CMob* pMob, int iSkillIndex)
  966. {
  967. CTextFileLoader rkTextFileLoader;
  968. if (!rkTextFileLoader.Load(c_pszFileName))
  969. return false;
  970. //if (rkTextFileLoader.IsEmpty())
  971. //return false;
  972. rkTextFileLoader.SetTop();
  973. if (!rkTextFileLoader.GetTokenFloat("motionduration", &m_fDuration))
  974. {
  975. sys_err("Motion: no motion duration %s", c_pszFileName);
  976. return false;
  977. }
  978. //if (rkTextFileLoader.GetTokenPosition("accumulation", &m_vec3Accumulation))
  979. //m_isAccumulation = true;
  980. std::string strNodeName;
  981. for (DWORD i = 0; i < rkTextFileLoader.GetChildNodeCount(); ++i)
  982. {
  983. //CTextFileLoader::CGotoChild GotoChild(rkTextFileLoader, i);
  984. rkTextFileLoader.SetChildNode(i);
  985. rkTextFileLoader.GetCurrentNodeName(&strNodeName);
  986. if (0 == strNodeName.compare("motioneventdata"))
  987. {
  988. DWORD dwMotionEventDataCount;
  989. if (!rkTextFileLoader.GetTokenDoubleWord("motioneventdatacount", &dwMotionEventDataCount))
  990. continue;
  991. for (DWORD j = 0; j < dwMotionEventDataCount; ++j)
  992. {
  993. if (!rkTextFileLoader.SetChildNode("event", j))
  994. {
  995. sys_err("Motion: no event data %d %s", j, c_pszFileName);
  996. return false;
  997. }
  998. int iType;
  999. if (!rkTextFileLoader.GetTokenInteger("motioneventtype", &iType))
  1000. {
  1001. sys_err("Motion: no motioneventtype data %s", c_pszFileName);
  1002. return false;
  1003. }
  1004. //float fRadius;
  1005. D3DVECTOR v3Position;
  1006. switch (iType)
  1007. {
  1008. case MOTION_EVENT_TYPE_FLY:
  1009. case MOTION_EVENT_TYPE_EFFECT:
  1010. case MOTION_EVENT_TYPE_SCREEN_WAVING:
  1011. case MOTION_EVENT_TYPE_SOUND:
  1012. case MOTION_EVENT_TYPE_CHARACTER_SHOW:
  1013. case MOTION_EVENT_TYPE_CHARACTER_HIDE:
  1014. case MOTION_EVENT_TYPE_WARP:
  1015. case MOTION_EVENT_TYPE_EFFECT_TO_TARGET:
  1016. #ifdef ENABLE_WOLFMAN_CHARACTER
  1017. case MOTION_EVENT_TYPE_RELATIVE_MOVE_ON:
  1018. case MOTION_EVENT_TYPE_RELATIVE_MOVE_OFF:
  1019. #endif
  1020. rkTextFileLoader.SetParentNode();
  1021. continue;
  1022. case MOTION_EVENT_TYPE_SPECIAL_ATTACKING:
  1023. if (!rkTextFileLoader.SetChildNode("spheredata", 0))
  1024. {
  1025. sys_err("Motion: no sphere data %s", c_pszFileName);
  1026. return false;
  1027. }
  1028. //if (!rTextFileLoader.GetTokenFloat("radius", &fRadius))
  1029. //return false;
  1030. if (!rkTextFileLoader.GetTokenPosition("position", &v3Position))
  1031. {
  1032. sys_err("Motion: no position data %s", c_pszFileName);
  1033. return false;
  1034. }
  1035. rkTextFileLoader.SetParentNode();
  1036. break;
  1037. default:
  1038. assert(!" CRaceMotionData::LoadMotionData - Strange Event Type");
  1039. return false;
  1040. break;
  1041. }
  1042. float fStartingTime;
  1043. if (!rkTextFileLoader.GetTokenFloat("startingtime", &fStartingTime))
  1044. {
  1045. sys_err("Motion: no startingtime data %s", c_pszFileName);
  1046. return false;
  1047. }
  1048. pMob->AddSkillSplash(iSkillIndex, 100 + DWORD(fStartingTime * 1000), -(DWORD)(v3Position.y));
  1049. rkTextFileLoader.SetParentNode();
  1050. }
  1051. }
  1052. rkTextFileLoader.SetParentNode();
  1053. }
  1054. pMob->m_mobSkillInfo[iSkillIndex].dwSkillVnum = pMob->m_table.Skills[iSkillIndex].dwVnum;
  1055. pMob->m_mobSkillInfo[iSkillIndex].bSkillLevel = pMob->m_table.Skills[iSkillIndex].bLevel;
  1056. m_isEmpty = false;
  1057. return true;
  1058. }
  1059. bool CMotion::LoadFromFile(const char * c_pszFileName)
  1060. {
  1061. CTextFileLoader loader;
  1062. if (!loader.Load(c_pszFileName))
  1063. {
  1064. sys_log(0, "Motion: LoadFromFile fail: %s", c_pszFileName);
  1065. return false;
  1066. }
  1067. if (!loader.GetTokenFloat("motionduration", &m_fDuration))
  1068. {
  1069. sys_err("Motion: %s does not have a duration", c_pszFileName);
  1070. return false;
  1071. }
  1072. if (loader.GetTokenPosition("accumulation", &m_vec3Accumulation))
  1073. m_isAccumulation = true;
  1074. #ifdef OFFLINE_FARM
  1075. if (!strstr(c_pszFileName, "/monster/"))
  1076. {
  1077. std::string strNodeName;
  1078. for (DWORD i = 0; i < loader.GetChildNodeCount(); ++i)
  1079. {
  1080. loader.SetChildNode(i);
  1081. loader.GetCurrentNodeName(&strNodeName);
  1082. if (0 == strNodeName.compare("comboinputdata"))
  1083. {
  1084. float fComboInputDataTime;
  1085. if (loader.GetTokenFloat("inputlimittime", &fComboInputDataTime))
  1086. {
  1087. m_fInputDuration = fComboInputDataTime;
  1088. }
  1089. else
  1090. {
  1091. sys_err("ComboInputData: %s no input limit time", c_pszFileName);
  1092. }
  1093. }
  1094. if (!strstr(c_pszFileName, "/skill/") && 0 == strNodeName.compare("attackingdata"))
  1095. {
  1096. if (loader.GetTokenFloat("externalforce", &m_fExternalForce))
  1097. {
  1098. sys_log(0, "ExternalForce: %s %f", c_pszFileName, m_fExternalForce);
  1099. }
  1100. else
  1101. {
  1102. sys_err("AttackingData: %s no external force", c_pszFileName);
  1103. }
  1104. // try to handle "HitDataCount" from AttackingData group
  1105. DWORD dwHitDataCount;
  1106. if (loader.GetTokenDoubleWord("hitdatacount", &dwHitDataCount))
  1107. {
  1108. for (DWORD j = 0; j < dwHitDataCount; ++j)
  1109. {
  1110. if (!loader.SetChildNode("hitdata", j))
  1111. {
  1112. sys_err("Motion: no event data %d %s", j, c_pszFileName);
  1113. return false;
  1114. }
  1115. TTokenVector* tv;
  1116. if (loader.GetTokenVector("hitposition", &tv))
  1117. {
  1118. TTokenVector::iterator it = tv->begin();
  1119. THitTimePositionMapEx mapHitPosition;
  1120. while (it != tv->end())
  1121. {
  1122. float time;
  1123. THitTimePosition hp;
  1124. time = atof(it++->c_str());
  1125. hp.v3LastPosition.x = atof(it++->c_str());
  1126. hp.v3LastPosition.y = atof(it++->c_str());
  1127. hp.v3LastPosition.z = atof(it++->c_str());
  1128. hp.v3Position.x = atof(it++->c_str());
  1129. hp.v3Position.y = atof(it++->c_str());
  1130. hp.v3Position.z = atof(it++->c_str());
  1131. hp.fRadius = gsc_fAttackRadius;
  1132. mapHitPosition[time] = hp;
  1133. }
  1134. // F:/Git/martysama58/s3ll_svfiles/main/srv1/share/data/pc/warrior/onehand_sword/combo_01.msa
  1135. // get combo index from file name
  1136. std::string strFileName = c_pszFileName;
  1137. std::string strComboIndex = strFileName.substr(strFileName.find_last_of("_") + 1);
  1138. strComboIndex = strComboIndex.substr(0, strComboIndex.find_last_of("."));
  1139. int iComboIndex = atoi(strComboIndex.c_str());
  1140. if (iComboIndex < 1 || iComboIndex > 7)
  1141. {
  1142. sys_err("CMotion::LoadFromFile :: invalid combo index %d", iComboIndex);
  1143. abort();
  1144. }
  1145. iComboIndex--; // 1-based to 0-based
  1146. // get weapon type from file name
  1147. std::string strWeaponType = strFileName.substr(0, strFileName.find_last_of("/"));
  1148. strWeaponType = strWeaponType.substr(strWeaponType.find_last_of("/") + 1, strWeaponType.size());
  1149. int iWeaponType = 0;
  1150. if (std::string::npos != strWeaponType.find("onehand_sword"))
  1151. iWeaponType = WEAPON_SWORD;
  1152. else if (std::string::npos != strWeaponType.find("dualhand_sword"))
  1153. iWeaponType = WEAPON_DAGGER;
  1154. else if (std::string::npos != strWeaponType.find("bow"))
  1155. iWeaponType = WEAPON_BOW;
  1156. else if (std::string::npos != strWeaponType.find("twohand_sword"))
  1157. iWeaponType = WEAPON_TWO_HANDED;
  1158. else if (std::string::npos != strWeaponType.find("bell"))
  1159. iWeaponType = WEAPON_BELL;
  1160. else if (std::string::npos != strWeaponType.find("fan"))
  1161. iWeaponType = WEAPON_FAN;
  1162. #ifdef ENABLE_WOLFMAN_CHARACTER
  1163. else if (std::string::npos != strWeaponType.find("claw"))
  1164. iWeaponType = WEAPON_CLAW;
  1165. #endif
  1166. else
  1167. {
  1168. sys_err("CMotion::LoadFromFile :: unknown weapon type %s", strWeaponType.c_str());
  1169. abort();
  1170. }
  1171. const auto bIsRiding = strWeaponType.find("horse_") != std::string::npos;
  1172. // get character race from file name
  1173. std::string strRace = strFileName.substr(0, strFileName.find_last_of("/"));
  1174. strRace = strRace.substr(0, strRace.find_last_of("/"));
  1175. strRace = strRace.substr(strRace.find_last_of("/") + 1, strRace.size());
  1176. int iRace = 0;
  1177. if (0 == strRace.compare("warrior"))
  1178. iRace = JOB_WARRIOR;
  1179. else if (0 == strRace.compare("assassin"))
  1180. iRace = JOB_ASSASSIN;
  1181. else if (0 == strRace.compare("sura"))
  1182. iRace = JOB_SURA;
  1183. else if (0 == strRace.compare("shaman"))
  1184. iRace = JOB_SHAMAN;
  1185. #ifdef ENABLE_WOLFMAN_CHARACTER
  1186. else if (0 == strRace.compare("wolfman"))
  1187. iRace = JOB_WOLFMAN;
  1188. #endif
  1189. else
  1190. {
  1191. sys_err("CMotion::LoadFromFile :: unknown race %s", strRace.c_str());
  1192. abort();
  1193. }
  1194. // skip useless data based on weapon type and race combination
  1195. if (iRace == JOB_WARRIOR && (iWeaponType != WEAPON_TWO_HANDED && iWeaponType != WEAPON_SWORD))
  1196. {
  1197. loader.SetParentNode();
  1198. continue;
  1199. }
  1200. else if (iRace == JOB_ASSASSIN && (iWeaponType != WEAPON_DAGGER && iWeaponType != WEAPON_BOW && iWeaponType != WEAPON_SWORD))
  1201. {
  1202. loader.SetParentNode();
  1203. continue;
  1204. }
  1205. else if (iRace == JOB_SURA && iWeaponType != WEAPON_SWORD)
  1206. {
  1207. loader.SetParentNode();
  1208. continue;
  1209. }
  1210. else if (iRace == JOB_SHAMAN && (iWeaponType != WEAPON_BELL && iWeaponType != WEAPON_FAN))
  1211. {
  1212. loader.SetParentNode();
  1213. continue;
  1214. }
  1215. #ifdef ENABLE_WOLFMAN_CHARACTER
  1216. else if (iRace == JOB_WOLFMAN && iWeaponType != WEAPON_CLAW)
  1217. {
  1218. loader.SetParentNode();
  1219. continue;
  1220. }
  1221. #endif
  1222. auto cmd = new SCharacterMotionData();
  1223. cmd->race = iRace;
  1224. cmd->weapon_type = iWeaponType;
  1225. cmd->combo_index = iComboIndex;
  1226. cmd->riding = bIsRiding;
  1227. m_mapHitPosition.emplace(cmd, mapHitPosition);
  1228. }
  1229. // try to handle "WeaponLength" float from AttackingData group to m_fWeaponLength
  1230. if (loader.GetTokenFloat("weaponlength", &m_fWeaponLength))
  1231. {
  1232. sys_log(0, "WeaponLength: %f", m_fWeaponLength);
  1233. }
  1234. else
  1235. {
  1236. sys_err("WeaponLength: not found");
  1237. }
  1238. // try to handle "AttackingBone" float from AttackingData group to m_strBoneName
  1239. if (loader.GetTokenString("attackingbone", &m_strBoneName))
  1240. {
  1241. sys_log(0, "AttackingBone: %s", m_strBoneName.c_str());
  1242. }
  1243. else
  1244. {
  1245. sys_err("AttackingBone: not found");
  1246. }
  1247. loader.SetParentNode();
  1248. }
  1249. }
  1250. else
  1251. {
  1252. TTokenVector* tv;
  1253. if (loader.GetTokenVector("hitposition", &tv))
  1254. {
  1255. TTokenVector::iterator it = tv->begin();
  1256. THitTimePositionMapEx mapHitPosition;
  1257. while (it != tv->end())
  1258. {
  1259. float time;
  1260. THitTimePosition hp;
  1261. time = atof(it++->c_str());
  1262. hp.v3LastPosition.x = atof(it++->c_str());
  1263. hp.v3LastPosition.y = atof(it++->c_str());
  1264. hp.v3LastPosition.z = atof(it++->c_str());
  1265. hp.v3Position.x = atof(it++->c_str());
  1266. hp.v3Position.y = atof(it++->c_str());
  1267. hp.v3Position.z = atof(it++->c_str());
  1268. hp.fRadius = gsc_fAttackRadius;
  1269. mapHitPosition[time] = hp;
  1270. }
  1271. // F:/Git/martysama58/s3ll_svfiles/main/srv1/share/data/pc/warrior/onehand_sword/combo_01.msa
  1272. // get combo index from file name
  1273. std::string strFileName = c_pszFileName;
  1274. std::string strComboIndex = strFileName.substr(strFileName.find_last_of("_") + 1);
  1275. strComboIndex = strComboIndex.substr(0, strComboIndex.find_last_of("."));
  1276. int iComboIndex = atoi(strComboIndex.c_str());
  1277. if (iComboIndex < 1 || iComboIndex > 7)
  1278. {
  1279. sys_err("CMotion::LoadFromFile :: invalid combo index %d", iComboIndex);
  1280. abort();
  1281. }
  1282. iComboIndex--; // 1-based to 0-based
  1283. // get weapon type from file name
  1284. std::string strWeaponType = strFileName.substr(0, strFileName.find_last_of("/"));
  1285. strWeaponType = strWeaponType.substr(strWeaponType.find_last_of("/") + 1, strWeaponType.size());
  1286. int iWeaponType = 0;
  1287. if (0 == strWeaponType.compare("onehand_sword"))
  1288. iWeaponType = WEAPON_SWORD;
  1289. else if (0 == strWeaponType.compare("dualhand_sword"))
  1290. iWeaponType = WEAPON_DAGGER;
  1291. else if (0 == strWeaponType.compare("bow"))
  1292. iWeaponType = WEAPON_BOW;
  1293. else if (0 == strWeaponType.compare("twohand_sword"))
  1294. iWeaponType = WEAPON_TWO_HANDED;
  1295. else if (0 == strWeaponType.compare("bell"))
  1296. iWeaponType = WEAPON_BELL;
  1297. else if (0 == strWeaponType.compare("fan"))
  1298. iWeaponType = WEAPON_FAN;
  1299. #ifdef ENABLE_WOLFMAN_CHARACTER
  1300. else if (0 == strWeaponType.compare("claw"))
  1301. iWeaponType = WEAPON_CLAW;
  1302. #endif
  1303. else
  1304. {
  1305. sys_err("CMotion::LoadFromFile :: unknown weapon type %s", strWeaponType.c_str());
  1306. abort();
  1307. }
  1308. const auto bIsRiding = strWeaponType.find("horse_") != std::string::npos;
  1309. // get character race from file name
  1310. std::string strRace = strFileName.substr(0, strFileName.find_last_of("/"));
  1311. strRace = strRace.substr(0, strRace.find_last_of("/"));
  1312. strRace = strRace.substr(strRace.find_last_of("/") + 1, strRace.size());
  1313. int iRace = 0;
  1314. if (0 == strRace.compare("warrior"))
  1315. iRace = JOB_WARRIOR;
  1316. else if (0 == strRace.compare("assassin"))
  1317. iRace = JOB_ASSASSIN;
  1318. else if (0 == strRace.compare("sura"))
  1319. iRace = JOB_SURA;
  1320. else if (0 == strRace.compare("shaman"))
  1321. iRace = JOB_SHAMAN;
  1322. #ifdef ENABLE_WOLFMAN_CHARACTER
  1323. else if (0 == strRace.compare("wolfman"))
  1324. iRace = JOB_WOLFMAN;
  1325. #endif
  1326. else
  1327. {
  1328. sys_err("CMotion::LoadFromFile :: unknown race %s", strRace.c_str());
  1329. abort();
  1330. }
  1331. // skip useless data based on weapon type and race combination
  1332. if (iRace == JOB_WARRIOR && (iWeaponType != WEAPON_TWO_HANDED && iWeaponType != WEAPON_SWORD))
  1333. {
  1334. continue;
  1335. }
  1336. else if (iRace == JOB_ASSASSIN && (iWeaponType != WEAPON_DAGGER && iWeaponType != WEAPON_BOW && iWeaponType != WEAPON_SWORD))
  1337. {
  1338. continue;
  1339. }
  1340. else if (iRace == JOB_SURA && iWeaponType != WEAPON_SWORD)
  1341. {
  1342. continue;
  1343. }
  1344. else if (iRace == JOB_SHAMAN && (iWeaponType != WEAPON_BELL && iWeaponType != WEAPON_FAN))
  1345. {
  1346. continue;
  1347. }
  1348. #ifdef ENABLE_WOLFMAN_CHARACTER
  1349. else if (iRace == JOB_WOLFMAN && iWeaponType != WEAPON_CLAW)
  1350. {
  1351. continue;
  1352. }
  1353. #endif
  1354. auto cmd = new SCharacterMotionData();
  1355. cmd->race = iRace;
  1356. cmd->weapon_type = iWeaponType;
  1357. cmd->combo_index = iComboIndex;
  1358. cmd->riding = bIsRiding;
  1359. m_mapHitPosition.emplace(cmd, mapHitPosition);
  1360. }
  1361. // try to handle "WeaponLength" float from AttackingData group to m_fWeaponLength
  1362. if (loader.GetTokenFloat("weaponlength", &m_fWeaponLength))
  1363. {
  1364. sys_log(0, "WeaponLength: %f", m_fWeaponLength);
  1365. }
  1366. else
  1367. {
  1368. sys_err("WeaponLength: not found");
  1369. }
  1370. // try to handle "AttackingBone" float from AttackingData group to m_strBoneName
  1371. if (loader.GetTokenString("attackingbone", &m_strBoneName))
  1372. {
  1373. sys_log(0, "AttackingBone: %s", m_strBoneName.c_str());
  1374. }
  1375. else
  1376. {
  1377. sys_err("AttackingBone: not found");
  1378. }
  1379. }
  1380. }
  1381. if (strstr(c_pszFileName, "/skill/") && 0 == strNodeName.compare("motioneventdata"))
  1382. {
  1383. DWORD dwMotionEventDataCount;
  1384. if (!loader.GetTokenDoubleWord("motioneventdatacount", &dwMotionEventDataCount))
  1385. continue;
  1386. std::vector <CMotionManager::SCharacterSkillData> vecSkillData;
  1387. for (DWORD j = 0; j < dwMotionEventDataCount; ++j)
  1388. {
  1389. if (!loader.SetChildNode("event", j))
  1390. {
  1391. sys_err("Motion: no event data %d %s", j, c_pszFileName);
  1392. return false;
  1393. }
  1394. int iType;
  1395. if (!loader.GetTokenInteger("motioneventtype", &iType))
  1396. {
  1397. sys_err("Motion: no motioneventtype data %s", c_pszFileName);
  1398. return false;
  1399. }
  1400. float fRadius;
  1401. D3DVECTOR v3Position;
  1402. switch (iType)
  1403. {
  1404. case MOTION_EVENT_TYPE_FLY:
  1405. case MOTION_EVENT_TYPE_EFFECT:
  1406. case MOTION_EVENT_TYPE_SCREEN_WAVING:
  1407. case MOTION_EVENT_TYPE_SOUND:
  1408. case MOTION_EVENT_TYPE_CHARACTER_SHOW:
  1409. case MOTION_EVENT_TYPE_CHARACTER_HIDE:
  1410. case MOTION_EVENT_TYPE_WARP:
  1411. case MOTION_EVENT_TYPE_EFFECT_TO_TARGET:
  1412. #ifdef ENABLE_WOLFMAN_CHARACTER
  1413. case MOTION_EVENT_TYPE_RELATIVE_MOVE_ON:
  1414. case MOTION_EVENT_TYPE_RELATIVE_MOVE_OFF:
  1415. #endif
  1416. loader.SetParentNode();
  1417. continue;
  1418. case MOTION_EVENT_TYPE_SPECIAL_ATTACKING:
  1419. if (!loader.SetChildNode("spheredata", 0))
  1420. {
  1421. sys_err("Motion: no sphere data %s", c_pszFileName);
  1422. return false;
  1423. }
  1424. if (!loader.GetTokenFloat("radius", &fRadius))
  1425. {
  1426. sys_err("Motion: no radius data %s", c_pszFileName);
  1427. return false;
  1428. }
  1429. if (!loader.GetTokenPosition("position", &v3Position))
  1430. {
  1431. sys_err("Motion: no position data %s", c_pszFileName);
  1432. return false;
  1433. }
  1434. loader.SetParentNode();
  1435. break;
  1436. default:
  1437. assert(!" CRaceMotionData::LoadMotionData - Strange Event Type");
  1438. return false;
  1439. break;
  1440. }
  1441. float fStartingTime;
  1442. if (!loader.GetTokenFloat("startingtime", &fStartingTime))
  1443. {
  1444. sys_err("Motion: no startingtime data %s", c_pszFileName);
  1445. return false;
  1446. }
  1447. CMotionManager::SCharacterSkillData skillData;
  1448. skillData.fRadius = fRadius;
  1449. skillData.v3Position = v3Position;
  1450. skillData.fStartingTime = fStartingTime;
  1451. vecSkillData.push_back(skillData);
  1452. loader.SetParentNode();
  1453. }
  1454. int race, job, skillVnum, skillGrade;
  1455. std::string codeName;
  1456. if (!ParseSkillFile(c_pszFileName, race, job, skillVnum, skillGrade, codeName))
  1457. {
  1458. sys_err("Failed to parse skill file: %s", c_pszFileName);
  1459. return false;
  1460. }
  1461. CMotionManager::instance().RegisterPCSkillMotion(race, skillGrade, skillVnum, vecSkillData);
  1462. }
  1463. loader.SetParentNode();
  1464. }
  1465. }
  1466. #endif
  1467. sys_log(1, "%-48s %.3f %f", strchr(c_pszFileName, '/') + 1, GetDuration(), GetAccumVector().y);
  1468. m_isEmpty = false;
  1469. return true;
  1470. }
  1471. float CMotion::GetDuration() const
  1472. {
  1473. return m_fDuration;
  1474. }
  1475. #ifdef OFFLINE_FARM
  1476. float CMotion::GetInputDuration() const
  1477. {
  1478. return m_fInputDuration;
  1479. }
  1480. float CMotion::GetWeaponLength() const
  1481. {
  1482. return m_fWeaponLength;
  1483. }
  1484. float CMotion::GetExternalForce() const
  1485. {
  1486. return m_fExternalForce;
  1487. }
  1488. CMotion::THitTimePositionMapEx CMotion::GetHitPositionData(const uint8_t c_nRace, const uint8_t c_nWeaponType, const uint8_t c_nComboIndex, bool c_bRiding) const
  1489. {
  1490. for (const auto& [chr, mapHitPosition] : m_mapHitPosition)
  1491. {
  1492. if (chr->race == c_nRace && chr->weapon_type == c_nWeaponType && chr->combo_index == c_nComboIndex && chr->riding == c_bRiding)
  1493. {
  1494. return mapHitPosition;
  1495. }
  1496. }
  1497. return {};
  1498. }
  1499. #endif
  1500. const D3DVECTOR & CMotion::GetAccumVector() const
  1501. {
  1502. return m_vec3Accumulation;
  1503. }
  1504. bool CMotion::IsEmpty()
  1505. {
  1506. return m_isEmpty;
  1507. }