FieldModelInstruction.cpp 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850
  1. /*
  2. * V-Gears
  3. * Copyright (C) 2022 V-Gears Team
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation, either version 3 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License
  16. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. */
  18. #include <iostream>
  19. #include <sstream>
  20. #include <stdexcept>
  21. #include <boost/format.hpp>
  22. #include "decompiler/field/instruction/FieldModelInstruction.h"
  23. #include "decompiler/field/FieldEngine.h"
  24. #include "decompiler/field/FieldCodeGenerator.h"
  25. #include "decompiler/field/FieldDisassembler.h"
  26. static bool TryParseFloat(const std::string& value, float& parsed_value){
  27. size_t parsed_length = 0;
  28. try{
  29. parsed_value = std::stof(value, &parsed_length);
  30. return parsed_length == value.size();
  31. }
  32. catch (const std::invalid_argument&){
  33. return false;
  34. }
  35. catch (const std::out_of_range&){
  36. return false;
  37. }
  38. }
  39. void FieldModelInstruction::ProcessInst(
  40. Function& func, ValueStack&, Engine* engine, CodeGenerator *code_gen
  41. ){
  42. FieldEngine& eng = static_cast<FieldEngine&>(*engine);
  43. FunctionMetaData md(func.metadata);
  44. //std::cout << "Model opcode: " << opcode_ << "\n";
  45. switch (opcode_){
  46. case OPCODES::JOIN: ProcessJOIN(code_gen); break;
  47. case OPCODES::SPLIT: ProcessSPLIT(code_gen); break;
  48. case OPCODES::BLINK: code_gen->WriteTodo(md.GetEntityName(), "BLINK"); break;
  49. case (OPCODES::KAWAI << 8) | OPCODES_KAWAI::EYETX:
  50. code_gen->WriteTodo(md.GetEntityName(), "EYETX");
  51. break;
  52. case (OPCODES::KAWAI << 8) | OPCODES_KAWAI::TRNSP:
  53. code_gen->WriteTodo(md.GetEntityName(), "TRNSP");
  54. break;
  55. case (OPCODES::KAWAI << 8) | OPCODES_KAWAI::AMBNT:
  56. code_gen->WriteTodo(md.GetEntityName(), "AMBNT");
  57. break;
  58. case (OPCODES::KAWAI << 8) | OPCODES_KAWAI::Unknown03:
  59. code_gen->WriteTodo(md.GetEntityName(), "Unknown03");
  60. break;
  61. case (OPCODES::KAWAI << 8) | OPCODES_KAWAI::Unknown04:
  62. code_gen->WriteTodo(md.GetEntityName(), "Unknown04");
  63. break;
  64. case (OPCODES::KAWAI << 8) | OPCODES_KAWAI::Unknown05:
  65. code_gen->WriteTodo(md.GetEntityName(), "Unknown05");
  66. break;
  67. case (OPCODES::KAWAI << 8) | OPCODES_KAWAI::LIGHT:
  68. code_gen->WriteTodo(md.GetEntityName(), "LIGHT");
  69. break;
  70. case (OPCODES::KAWAI << 8) | OPCODES_KAWAI::Unknown07:
  71. code_gen->WriteTodo(md.GetEntityName(), "Unknown07");
  72. break;
  73. case (OPCODES::KAWAI << 8) | OPCODES_KAWAI::Unknown08:
  74. code_gen->WriteTodo(md.GetEntityName(), "Unknown08");
  75. break;
  76. case (OPCODES::KAWAI << 8) | OPCODES_KAWAI::Unknown09:
  77. code_gen->WriteTodo(md.GetEntityName(), "Unknown09");
  78. break;
  79. case (OPCODES::KAWAI << 8) | OPCODES_KAWAI::SBOBJ:
  80. code_gen->WriteTodo(md.GetEntityName(), "SBOBJ");
  81. break;
  82. case (OPCODES::KAWAI << 8) | OPCODES_KAWAI::Unknown0B:
  83. code_gen->WriteTodo(md.GetEntityName(), "Unknown0B");
  84. break;
  85. case (OPCODES::KAWAI << 8) | OPCODES_KAWAI::Unknown0C:
  86. code_gen->WriteTodo(md.GetEntityName(), "Unknown0C");
  87. break;
  88. case (OPCODES::KAWAI << 8) | OPCODES_KAWAI::SHINE:
  89. code_gen->WriteTodo(md.GetEntityName(), "SHINE");
  90. break;
  91. case (OPCODES::KAWAI << 8) | OPCODES_KAWAI::RESET:
  92. code_gen->WriteTodo(md.GetEntityName(), "RESET");
  93. break;
  94. case OPCODES::KAWIW: code_gen->WriteTodo(md.GetEntityName(), "KAWIW"); break;
  95. case OPCODES::PMOVA: code_gen->WriteTodo(md.GetEntityName(), "PMOVA"); break;
  96. case OPCODES::PDIRA: code_gen->WriteTodo(md.GetEntityName(), "PDIRA"); break;
  97. case OPCODES::PTURA: code_gen->WriteTodo(md.GetEntityName(), "PTURA"); break;
  98. case OPCODES::PGTDR: code_gen->WriteTodo(md.GetEntityName(), "PGTDR"); break;
  99. case OPCODES::PXYZI: code_gen->WriteTodo(md.GetEntityName(), "PXYZI"); break;
  100. case OPCODES::TLKON: ProcessTLKON(code_gen, md.GetEntityName()); break;
  101. case OPCODES::PC:
  102. // The character is the one in the opcode. The one of CHAR is the model of the field's
  103. // list, and it is not the same for all (for Aeris it is 1, and she is character 3).
  104. ProcessPC(code_gen, md.GetEntityName(), params_[0]->GetUnsigned());
  105. break;
  106. case OPCODES::opCodeCHAR: ProcessCHAR(code_gen, md.GetEntityName()); break;
  107. case OPCODES::DFANM: ProcessDFANM(code_gen, md.GetEntityName(), md.GetCharacterId()); break;
  108. case OPCODES::ANIME1:
  109. ProcessANIME1(code_gen, md.GetEntityName(), md.GetCharacterId());
  110. break;
  111. case OPCODES::VISI: ProcessVISI(code_gen, md.GetEntityName()); break;
  112. case OPCODES::XYZI: ProcessXYZI(code_gen, md.GetEntityName()); break;
  113. case OPCODES::XYI: ProcessXYI(code_gen, md.GetEntityName()); break;
  114. case OPCODES::XYZ: code_gen->WriteTodo(md.GetEntityName(), "XYZ"); break;
  115. case OPCODES::MOVE: ProcessMOVE(code_gen, md.GetEntityName()); break;
  116. case OPCODES::CMOVE: code_gen->WriteTodo(md.GetEntityName(), "CMOVE"); break;
  117. case OPCODES::MOVA: ProcessMOVA(code_gen, md.GetEntityName(), eng); break;
  118. case OPCODES::TURA: ProcessTURA(code_gen, md.GetEntityName(), eng); break;
  119. case OPCODES::ANIMW: ProcessANIMW(code_gen, md.GetEntityName()); break;
  120. case OPCODES::FMOVE: ProcessFMOVE(code_gen, md.GetEntityName()); break;
  121. case OPCODES::ANIME2:
  122. ProcessANIME2(code_gen, md.GetEntityName(), md.GetCharacterId());
  123. break;
  124. case OPCODES::ANIM_1:
  125. ProcessANIM_1(code_gen, md.GetEntityName(), md.GetCharacterId());
  126. break;
  127. case OPCODES::CANIM1:
  128. ProcessCANIM1(code_gen, md.GetEntityName(), md.GetCharacterId());
  129. break;
  130. case OPCODES::CANM_1:
  131. ProcessCANM_1(code_gen, md.GetEntityName(), md.GetCharacterId());
  132. break;
  133. case OPCODES::MSPED: ProcessMSPED(code_gen, md.GetEntityName()); break;
  134. case OPCODES::DIR: ProcessDIR(code_gen, md.GetEntityName()); break;
  135. case OPCODES::TURNGEN: ProcessTURNGEN(code_gen, md.GetEntityName()); break;
  136. case OPCODES::TURN: code_gen->WriteTodo(md.GetEntityName(), "TURN"); break;
  137. case OPCODES::DIRA: ProcessDIRA(code_gen, md.GetEntityName(), eng); break;
  138. case OPCODES::GETDIR: code_gen->WriteTodo(md.GetEntityName(), "GETDIR"); break;
  139. case OPCODES::GETAXY: ProcessGETAXY(code_gen); break;
  140. case OPCODES::GETAI: ProcessGETAI(code_gen, eng); break;
  141. case OPCODES::ANIM_2:
  142. ProcessANIM_2(code_gen, md.GetEntityName(), md.GetCharacterId());
  143. break;
  144. case OPCODES::CANIM2:
  145. ProcessCANIM2(code_gen, md.GetEntityName(), md.GetCharacterId());
  146. break;
  147. case OPCODES::CANM_2:
  148. ProcessCANM_2(code_gen, md.GetEntityName(), md.GetCharacterId());
  149. break;
  150. case OPCODES::ASPED: ProcessASPED(code_gen, md.GetEntityName()); break;
  151. case OPCODES::CC: ProcessCC(code_gen, eng); break;
  152. case OPCODES::JUMP: ProcessJUMP(code_gen, md.GetEntityName()); break;
  153. case OPCODES::AXYZI: ProcessAXYZI(code_gen); break;
  154. case OPCODES::LADER: ProcessLADER(code_gen, md.GetEntityName(), md.GetCharacterId()); break;
  155. case OPCODES::OFST: ProcessOFST(code_gen, md.GetEntityName()); break;
  156. case OPCODES::OFSTW:
  157. code_gen->AddOutputLine("self." + md.GetEntityName() + ":offset_sync()");
  158. break;
  159. case OPCODES::TALKR: ProcessTALKR(code_gen, md.GetEntityName()); break;
  160. case OPCODES::SLIDR: ProcessSLIDR(code_gen, md.GetEntityName()); break;
  161. case OPCODES::SOLID: ProcessSOLID(code_gen, md.GetEntityName()); break;
  162. case OPCODES::TLKR2: ProcessTALKR(code_gen, md.GetEntityName()); break;
  163. case OPCODES::SLDR2: code_gen->WriteTodo(md.GetEntityName(), "SLDR2"); break;
  164. case OPCODES::CCANM: code_gen->WriteTodo(md.GetEntityName(), "CCANM"); break;
  165. case OPCODES::FCFIX: code_gen->WriteTodo(md.GetEntityName(), "FCFIX"); break;
  166. case OPCODES::ANIMB: code_gen->WriteTodo(md.GetEntityName(), "ANIMB"); break;
  167. case OPCODES::TURNW: code_gen->WriteTodo(md.GetEntityName(), "TURNW"); break;
  168. default:
  169. code_gen->AddOutputLine(FieldCodeGenerator::FormatInstructionNotImplemented(
  170. md.GetEntityName(), address_, opcode_
  171. ));
  172. }
  173. }
  174. void FieldModelInstruction::ProcessJOIN(CodeGenerator* code_gen){
  175. code_gen->AddOutputLine("join_party(" + std::to_string(params_[0]->GetUnsigned()) + ")");
  176. }
  177. void FieldModelInstruction::ProcessSPLIT(CodeGenerator* code_gen){
  178. const std::vector<ValuePtr> parameters = GetParams();
  179. if (parameters.size() != 13){
  180. code_gen->WriteTodo("FieldModelInstruction", "SPLIT with malformed parameters");
  181. return;
  182. }
  183. for (const ValuePtr& parameter : parameters){
  184. if (parameter == nullptr || !parameter->IsInteger()){
  185. code_gen->WriteTodo("FieldModelInstruction", "SPLIT with invalid parameter");
  186. return;
  187. }
  188. }
  189. FieldCodeGenerator* cg = static_cast<FieldCodeGenerator*>(code_gen);
  190. const float scale = 128.0f * cg->GetScaleFactor();
  191. const auto& ax = FieldCodeGenerator::FormatValueOrVariable(
  192. cg->GetFormatter(), params_[0]->GetUnsigned(), params_[6]->GetSigned(),
  193. FieldCodeGenerator::ValueType::Float, scale
  194. );
  195. const auto& ay = FieldCodeGenerator::FormatValueOrVariable(
  196. cg->GetFormatter(), params_[1]->GetUnsigned(), params_[7]->GetSigned(),
  197. FieldCodeGenerator::ValueType::Float, scale
  198. );
  199. const auto& ar = FieldCodeGenerator::FormatValueOrVariable(
  200. cg->GetFormatter(), params_[2]->GetUnsigned(), params_[8]->GetSigned(),
  201. FieldCodeGenerator::ValueType::Float, scale
  202. );
  203. const auto& bx = FieldCodeGenerator::FormatValueOrVariable(
  204. cg->GetFormatter(), params_[3]->GetUnsigned(), params_[9]->GetSigned(),
  205. FieldCodeGenerator::ValueType::Float, scale
  206. );
  207. const auto& by = FieldCodeGenerator::FormatValueOrVariable(
  208. cg->GetFormatter(), params_[4]->GetUnsigned(), params_[10]->GetSigned(),
  209. FieldCodeGenerator::ValueType::Float, scale
  210. );
  211. const auto& br = FieldCodeGenerator::FormatValueOrVariable(
  212. cg->GetFormatter(), params_[5]->GetUnsigned(), params_[11]->GetSigned(),
  213. FieldCodeGenerator::ValueType::Float, scale
  214. );
  215. const auto& speed = params_[12]->GetUnsigned();
  216. code_gen->AddOutputLine((
  217. boost::format("split_party(%1%, %2%, %3%, %4%, %5%, %6%, %7%)")
  218. % ax % ay % ar % bx % by % br % speed
  219. ).str());
  220. }
  221. void FieldModelInstruction::ProcessTLKON(CodeGenerator* code_gen, const std::string& entity){
  222. code_gen->AddOutputLine((
  223. boost::format("self.%1%:set_talkable(%2%)")
  224. % entity % FieldCodeGenerator::FormatInvertedBool(params_[0]->GetUnsigned())
  225. ).str());
  226. }
  227. void FieldModelInstruction::ProcessPC(
  228. CodeGenerator* code_gen, const std::string& entity, int char_id
  229. ){
  230. code_gen->AddOutputLine(
  231. (boost::format("set_entity_to_character(\"%1%\", %2%)") % entity % char_id).str()
  232. );
  233. }
  234. void FieldModelInstruction::ProcessCHAR(CodeGenerator* code_gen, const std::string& entity){
  235. code_gen->AddOutputLine(
  236. (boost::format("self.%1% = entity_manager:get_entity(\"%1%\")") % entity).str()
  237. );
  238. // A model that has no animation playing is not drawn. The ones with no "Idle" animation need
  239. // a default one, which a DFANM later in the script replaces.
  240. FieldCodeGenerator* cg = static_cast<FieldCodeGenerator*>(code_gen);
  241. const std::string animation = cg->GetFormatter().GetDefaultAnimationName(params_[0]->GetSigned());
  242. // The walk and run animations are the second and the third of the model.
  243. const std::string walk = cg->GetFormatter().GetMoveAnimationName(params_[0]->GetSigned(), 1);
  244. const std::string run = cg->GetFormatter().GetMoveAnimationName(params_[0]->GetSigned(), 2);
  245. if (!walk.empty() && !run.empty() && (walk != "Walk" || run != "Run")){
  246. code_gen->AddOutputLine((
  247. boost::format("self.%1%:set_move_animations(\"%2%\", \"%3%\")") % entity % walk % run
  248. ).str());
  249. }
  250. if (!animation.empty()){
  251. code_gen->AddOutputLine((
  252. boost::format("self.%1%:set_default_animation(\"%2%\") -- No Idle animation")
  253. % entity % animation
  254. ).str());
  255. code_gen->AddOutputLine(
  256. (boost::format("self.%1%:play_animation(\"%2%\")") % entity % animation).str()
  257. );
  258. }
  259. }
  260. void FieldModelInstruction::ProcessDFANM(
  261. CodeGenerator* code_gen, const std::string& entity, int char_id
  262. ){
  263. // ID will be fixed-up downstream.
  264. FieldCodeGenerator* cg = static_cast<FieldCodeGenerator*>(code_gen);
  265. auto animation_id = params_[0]->GetUnsigned();
  266. // TODO: check for zero.
  267. auto speed = 1.0f / params_[1]->GetUnsigned();
  268. code_gen->AddOutputLine((
  269. boost::format("self.%1%:set_default_animation(\"%2%\") -- speed %3%")
  270. % entity % cg->GetFormatter().GetFriendlyAnimationName(char_id, animation_id) % speed
  271. ).str());
  272. code_gen->AddOutputLine((
  273. boost::format("self.%1%:play_animation(\"%2%\")")
  274. % entity % cg->GetFormatter().GetFriendlyAnimationName(char_id, animation_id)
  275. ).str());
  276. }
  277. void FieldModelInstruction::ProcessANIME1(
  278. CodeGenerator* code_gen, const std::string& entity, int char_id
  279. ){
  280. // ID will be fixed-up downstream.
  281. FieldCodeGenerator* cg = static_cast<FieldCodeGenerator*>(code_gen);
  282. auto animation_id = params_[0]->GetUnsigned();
  283. // TODO: check for zero.
  284. auto speed = 1.0f / params_[1]->GetUnsigned();
  285. code_gen->AddOutputLine((
  286. boost::format("self.%1%:play_animation(\"%2%\") -- speed %3%")
  287. % entity % cg->GetFormatter().GetFriendlyAnimationName(char_id, animation_id) % speed
  288. ).str());
  289. code_gen->AddOutputLine((boost::format("self.%1%:animation_sync()") % entity).str());
  290. }
  291. void FieldModelInstruction::ProcessMOVA(
  292. CodeGenerator* code_gen, const std::string& entity, const FieldEngine& engine
  293. ){
  294. // Walks to the other entity, and waits until it is there.
  295. const auto& target = engine.EntityByIndex(params_[0]->GetUnsigned());
  296. code_gen->AddOutputLine((
  297. boost::format("self.%1%:move_to_entity(entity_manager:get_entity(\"%2%\"))")
  298. % entity % target.GetName()
  299. ).str());
  300. code_gen->AddOutputLine((boost::format("self.%1%:move_sync()") % entity).str());
  301. }
  302. void FieldModelInstruction::ProcessTALKR(CodeGenerator* code_gen, const std::string& entity){
  303. // The range is given in the same units as the positions.
  304. FieldCodeGenerator* cg = static_cast<FieldCodeGenerator*>(code_gen);
  305. const float scale = 128.0f * cg->GetScaleFactor();
  306. const std::string range_value = FieldCodeGenerator::FormatValueOrVariable(
  307. cg->GetFormatter(), params_[0]->GetUnsigned() + params_[1]->GetUnsigned(),
  308. params_[2]->GetUnsigned(), FieldCodeGenerator::ValueType::Float, scale
  309. );
  310. float range = 0;
  311. if (TryParseFloat(range_value, range)){
  312. code_gen->AddOutputLine(
  313. (boost::format("self.%1%:set_talk_radius(%2%)") % entity % range).str()
  314. );
  315. }
  316. else{
  317. // A variable: the scale is not applied yet.
  318. code_gen->AddOutputLine(
  319. "self." + entity + ":set_talk_radius((" + range_value + ") / " + std::to_string(scale) + ")"
  320. );
  321. }
  322. }
  323. void FieldModelInstruction::ProcessDIRA(
  324. CodeGenerator* code_gen, const std::string& entity, const FieldEngine& engine
  325. ){
  326. // Turns the model towards another entity at once.
  327. const auto& target = engine.EntityByIndex(params_[0]->GetUnsigned());
  328. code_gen->AddOutputLine((
  329. boost::format("self.%1%:face_entity(entity_manager:get_entity(\"%2%\"))")
  330. % entity % target.GetName()
  331. ).str());
  332. }
  333. void FieldModelInstruction::ProcessASPED(CodeGenerator* code_gen, const std::string& entity){
  334. // The value is the speed of the animations: 16 is the normal one, 32 double and 8 half.
  335. FieldCodeGenerator* cg = static_cast<FieldCodeGenerator*>(code_gen);
  336. const std::string speed_value = FieldCodeGenerator::FormatValueOrVariable(
  337. cg->GetFormatter(), params_[0]->GetUnsigned() + params_[1]->GetUnsigned(),
  338. params_[2]->GetUnsigned()
  339. );
  340. float speed = 0;
  341. if (TryParseFloat(speed_value, speed)){
  342. code_gen->AddOutputLine((
  343. boost::format("self.%1%:set_animation_speed(%2%)") % entity % (speed / 16.0f)
  344. ).str());
  345. }
  346. else{
  347. code_gen->AddOutputLine(
  348. "self." + entity + ":set_animation_speed((" + speed_value + ") / 16.0)"
  349. );
  350. }
  351. }
  352. void FieldModelInstruction::ProcessANIME2(
  353. CodeGenerator* code_gen, const std::string& entity, int char_id
  354. ){
  355. // Like ANIME1, but the script doesn't wait for the animation to end.
  356. FieldCodeGenerator* cg = static_cast<FieldCodeGenerator*>(code_gen);
  357. auto animation_id = params_[0]->GetUnsigned();
  358. auto speed = 1.0f / params_[1]->GetUnsigned();
  359. code_gen->AddOutputLine((
  360. boost::format("self.%1%:play_animation(\"%2%\") -- speed %3%")
  361. % entity % cg->GetFormatter().GetFriendlyAnimationName(char_id, animation_id) % speed
  362. ).str());
  363. }
  364. void FieldModelInstruction::ProcessANIM_1(
  365. CodeGenerator* code_gen, const std::string& entity, int char_id
  366. ){
  367. // Like ANIM_2, but the script doesn't wait for the animation to end.
  368. FieldCodeGenerator* cg = static_cast<FieldCodeGenerator*>(code_gen);
  369. auto animation_id = params_[0]->GetUnsigned();
  370. auto speed = 1.0f / params_[1]->GetUnsigned();
  371. code_gen->AddOutputLine((
  372. boost::format("self.%1%:play_animation_stop(\"%2%\") -- speed %3%")
  373. % entity % cg->GetFormatter().GetFriendlyAnimationName(char_id, animation_id) % speed
  374. ).str());
  375. }
  376. void FieldModelInstruction::ProcessCANIM1(
  377. CodeGenerator* code_gen, const std::string& entity, int char_id
  378. ){
  379. // Like CANIM2, but the script doesn't wait for the animation to end.
  380. FieldCodeGenerator* cg = static_cast<FieldCodeGenerator*>(code_gen);
  381. auto animation_id = params_[0]->GetUnsigned();
  382. auto start_frame = params_[1]->GetUnsigned() / 30.0f;
  383. auto end_frame = params_[2]->GetUnsigned() / 30.0f;
  384. auto speed = 1.0f / params_[3]->GetUnsigned();
  385. code_gen->AddOutputLine((
  386. boost::format("self.%1%:play_animation(\"%2%\", %3%, %4%) -- speed %5%")
  387. % entity % cg->GetFormatter().GetFriendlyAnimationName(char_id, animation_id)
  388. % start_frame % end_frame % speed
  389. ).str());
  390. }
  391. void FieldModelInstruction::ProcessCANM_1(
  392. CodeGenerator* code_gen, const std::string& entity, int char_id
  393. ){
  394. // Like CANM_2, but the script doesn't wait for the animation to end.
  395. FieldCodeGenerator* cg = static_cast<FieldCodeGenerator*>(code_gen);
  396. auto animation_id = params_[0]->GetUnsigned();
  397. auto start_frame = params_[1]->GetUnsigned() / 30.0f;
  398. auto end_frame = params_[2]->GetUnsigned() / 30.0f;
  399. auto speed = 1.0f / params_[3]->GetUnsigned();
  400. code_gen->AddOutputLine((
  401. boost::format("self.%1%:play_animation_stop(\"%2%\", %3%, %4%) -- speed %5%")
  402. % entity % cg->GetFormatter().GetFriendlyAnimationName(char_id, animation_id)
  403. % start_frame % end_frame % speed
  404. ).str());
  405. }
  406. void FieldModelInstruction::ProcessANIMW(CodeGenerator* code_gen, const std::string& entity){
  407. // Waits for the animation that was started with one of the non-waiting opcodes.
  408. code_gen->AddOutputLine((boost::format("self.%1%:animation_sync()") % entity).str());
  409. }
  410. void FieldModelInstruction::ProcessVISI(CodeGenerator* code_gen, const std::string& entity){
  411. code_gen->AddOutputLine((
  412. boost::format("self.%1%:set_visible(%2%)")
  413. % entity % FieldCodeGenerator::FormatBool(params_[0]->GetUnsigned())
  414. ).str());
  415. }
  416. void FieldModelInstruction::ProcessXYZI(CodeGenerator* code_gen, const std::string& entity){
  417. FieldCodeGenerator* cg = static_cast<FieldCodeGenerator*>(code_gen);
  418. const float scale = 128.0f * cg->GetScaleFactor();
  419. const auto& x = FieldCodeGenerator::FormatValueOrVariable(
  420. cg->GetFormatter(), params_[0]->GetUnsigned(), params_[4]->GetSigned(),
  421. FieldCodeGenerator::ValueType::Float, scale
  422. );
  423. const auto& y = FieldCodeGenerator::FormatValueOrVariable(
  424. cg->GetFormatter(), params_[1]->GetUnsigned(), params_[5]->GetSigned(),
  425. FieldCodeGenerator::ValueType::Float, scale
  426. );
  427. const auto& z = FieldCodeGenerator::FormatValueOrVariable(
  428. cg->GetFormatter(), params_[2]->GetUnsigned(), params_[6]->GetSigned(),
  429. FieldCodeGenerator::ValueType::Float, scale
  430. );
  431. const auto& triangle_id = FieldCodeGenerator::FormatValueOrVariable(
  432. cg->GetFormatter(), params_[3]->GetUnsigned(), params_[7]->GetUnsigned()
  433. );
  434. code_gen->AddOutputLine((
  435. boost::format("self.%1%:set_position(%2%, %3%, %4%) -- triangle ID %5%")
  436. % entity % x % y % z % triangle_id
  437. ).str());
  438. }
  439. void FieldModelInstruction::ProcessXYI(CodeGenerator* code_gen, const std::string& entity){
  440. FieldCodeGenerator* cg = static_cast<FieldCodeGenerator*>(code_gen);
  441. const float scale = 128.0f * cg->GetScaleFactor();
  442. // Constants are already scaled by the formatter, but values read from a bank (usually stored
  443. // by AXYZI) are in field units, so they need to be scaled here.
  444. const auto format_coordinate = [&](uint32 bank, int16 value){
  445. const auto& formatted = FieldCodeGenerator::FormatValueOrVariable(
  446. cg->GetFormatter(), bank, value, FieldCodeGenerator::ValueType::Float, scale
  447. );
  448. if (bank == 0) return formatted;
  449. return "(" + formatted + ") / " + std::to_string(scale);
  450. };
  451. const auto x = format_coordinate(params_[0]->GetUnsigned(), params_[4]->GetSigned());
  452. const auto y = format_coordinate(params_[1]->GetUnsigned(), params_[5]->GetSigned());
  453. const auto& triangle_id = FieldCodeGenerator::FormatValueOrVariable(
  454. cg->GetFormatter(), params_[3]->GetUnsigned(), params_[6]->GetUnsigned()
  455. );
  456. code_gen->AddOutputLine((
  457. boost::format("self.%1%:set_position_xy(%2%, %3%) -- triangle ID %4%")
  458. % entity % x % y % triangle_id
  459. ).str());
  460. }
  461. void FieldModelInstruction::ProcessMOVE(CodeGenerator* code_gen, const std::string& entity){
  462. FieldCodeGenerator* cg = static_cast<FieldCodeGenerator*>(code_gen);
  463. const float scale = 128.0f * cg->GetScaleFactor();
  464. const auto& x = FieldCodeGenerator::FormatValueOrVariable(
  465. cg->GetFormatter(), params_[0]->GetUnsigned(), params_[2]->GetSigned(),
  466. FieldCodeGenerator::ValueType::Float, scale
  467. );
  468. const auto& y = FieldCodeGenerator::FormatValueOrVariable(
  469. cg->GetFormatter(), params_[1]->GetUnsigned(), params_[3]->GetSigned(),
  470. FieldCodeGenerator::ValueType::Float, scale
  471. );
  472. code_gen->AddOutputLine(
  473. (boost::format("self.%1%:move_to_position(%2%, %3%)") % entity % x % y).str()
  474. );
  475. code_gen->AddOutputLine((boost::format("self.%1%:move_sync()") % entity).str());
  476. }
  477. void FieldModelInstruction::ProcessFMOVE(CodeGenerator* code_gen, const std::string& entity){
  478. FieldCodeGenerator* cg = static_cast<FieldCodeGenerator*>(code_gen);
  479. const float scale = 128.0f * cg->GetScaleFactor();
  480. const auto& x = FieldCodeGenerator::FormatValueOrVariable(
  481. cg->GetFormatter(), params_[0]->GetUnsigned(), params_[2]->GetSigned(),
  482. FieldCodeGenerator::ValueType::Float, scale
  483. );
  484. const auto& y = FieldCodeGenerator::FormatValueOrVariable(
  485. cg->GetFormatter(), params_[1]->GetUnsigned(), params_[3]->GetSigned(),
  486. FieldCodeGenerator::ValueType::Float, scale
  487. );
  488. // Like MOVE, but the animation is the one the script chose, not the walking one.
  489. code_gen->AddOutputLine((boost::format("self.%1%:move_auto_animation(false)") % entity).str());
  490. code_gen->AddOutputLine(
  491. (boost::format("self.%1%:move_to_position(%2%, %3%)") % entity % x % y).str()
  492. );
  493. code_gen->AddOutputLine((boost::format("self.%1%:move_sync()") % entity).str());
  494. code_gen->AddOutputLine((boost::format("self.%1%:move_auto_animation(true)") % entity).str());
  495. }
  496. void FieldModelInstruction::ProcessTURA(
  497. CodeGenerator* code_gen, const std::string& entity, const FieldEngine& engine
  498. ){
  499. FieldCodeGenerator* cg = static_cast<FieldCodeGenerator*>(code_gen);
  500. const auto& dest_entity = engine.EntityByIndex(params_[0]->GetSigned());
  501. std::string direction;
  502. switch (params_[1]->GetUnsigned()){
  503. case 0: direction = "Entity.CLOCKWISE"; break;
  504. case 1: direction = "Entity.ANTICLOCKWISE"; break;
  505. case 2: direction = "Entity.CLOSEST"; break;
  506. // Default to closest:
  507. default: direction = "Entity.CLOSEST"; break;
  508. }
  509. auto steps = params_[2]->GetUnsigned();
  510. const float scaled_steps = static_cast<float>(steps) / 30.0f;
  511. code_gen->AddOutputLine((
  512. boost::format("self.%1%:turn_to_entity(entity_manager:get_entity(\"%2%\"), %3%, %4%)")
  513. % entity % dest_entity.GetName() % direction % scaled_steps
  514. ).str());
  515. code_gen->AddOutputLine((boost::format("self.%1%:turn_sync()") % entity).str());
  516. }
  517. void FieldModelInstruction::ProcessMSPED(CodeGenerator* code_gen, const std::string& entity){
  518. FieldCodeGenerator* cg = static_cast<FieldCodeGenerator*>(code_gen);
  519. const float scale = 128.0f * cg->GetScaleFactor();
  520. const auto& speed = FieldCodeGenerator::FormatValueOrVariable(
  521. cg->GetFormatter(), params_[1]->GetUnsigned(), params_[2]->GetUnsigned(),
  522. FieldCodeGenerator::ValueType::Float, 256.0f * scale / 30.0f
  523. );
  524. code_gen->AddOutputLine(
  525. (boost::format("self.%1%:set_move_auto_speed(%2%)") % entity % speed).str()
  526. );
  527. }
  528. void FieldModelInstruction::ProcessDIR(CodeGenerator* code_gen, const std::string& entity){
  529. FieldCodeGenerator* cg = static_cast<FieldCodeGenerator*>(code_gen);
  530. const auto& degrees = FieldCodeGenerator::FormatValueOrVariable(
  531. cg->GetFormatter(), params_[0]->GetUnsigned(), params_[1]->GetUnsigned(),
  532. FieldCodeGenerator::ValueType::Float, 256.0f / 360.0f
  533. );
  534. code_gen->AddOutputLine((boost::format("self.%1%:set_rotation(%2%)") % entity % degrees).str());
  535. }
  536. void FieldModelInstruction::ProcessTURNGEN(CodeGenerator* code_gen, const std::string& entity){
  537. FieldCodeGenerator* cg = static_cast<FieldCodeGenerator*>(code_gen);
  538. const auto& degrees = FieldCodeGenerator::FormatValueOrVariable(
  539. cg->GetFormatter(), params_[1]->GetUnsigned(), params_[2]->GetUnsigned(),
  540. FieldCodeGenerator::ValueType::Float, 256.0f / 360.0f
  541. );
  542. std::string direction;
  543. switch (params_[3]->GetUnsigned()){
  544. case 0: direction = "Entity.CLOCKWISE"; break;
  545. case 1: direction = "Entity.ANTICLOCKWISE"; break;
  546. case 2: direction = "Entity.CLOSEST"; break;
  547. // Default to closest:
  548. default: direction = "Entity.CLOSEST"; break;
  549. }
  550. auto steps = params_[4]->GetUnsigned();
  551. std::string step_type;
  552. switch (params_[5]->GetUnsigned()){
  553. case 1: step_type = "Entity.LINEAR"; break;
  554. case 2: step_type = "Entity.SMOOTH"; break;
  555. // Default to smooth
  556. default: step_type = "Entity.SMOOTH"; break;
  557. }
  558. const float scaled_steps = static_cast<float>(steps) / 30.0f;
  559. code_gen->AddOutputLine((
  560. boost::format("self.%1%:turn_to_direction(%2%, %3%, %4%, %5%)")
  561. % entity % degrees % direction % step_type % scaled_steps
  562. ).str());
  563. code_gen->AddOutputLine((boost::format("self.%1%:turn_sync()") % entity).str());
  564. }
  565. void FieldModelInstruction::ProcessGETAI(CodeGenerator* code_gen, const FieldEngine& engine){
  566. FieldCodeGenerator* cg = static_cast<FieldCodeGenerator*>(code_gen);
  567. // TODO: check for assignment to literal.
  568. const auto& variable = FieldCodeGenerator::FormatValueOrVariable(
  569. cg->GetFormatter(), params_[1]->GetUnsigned(), params_[3]->GetUnsigned()
  570. );
  571. const auto& entity = engine.EntityByIndex(params_[2]->GetUnsigned());
  572. code_gen->AddOutputLine(
  573. (boost::format("%1% = entity_manager:get_entity(\"%2%\"):get_move_triangle_id()")
  574. % variable % entity.GetName()
  575. ).str());
  576. }
  577. void FieldModelInstruction::ProcessANIM_2(
  578. CodeGenerator* code_gen, const std::string& entity, int char_id
  579. ){
  580. // ID will be fixed-up downstream.
  581. FieldCodeGenerator* cg = static_cast<FieldCodeGenerator*>(code_gen);
  582. auto animation_id = params_[0]->GetUnsigned();
  583. // TODO: check for zero.
  584. auto speed = 1.0f / params_[1]->GetUnsigned();
  585. code_gen->AddOutputLine((
  586. boost::format("self.%1%:play_animation_stop(\"%2%\") -- speed %3%")
  587. % entity % cg->GetFormatter().GetFriendlyAnimationName(char_id, animation_id) % speed
  588. ).str());
  589. code_gen->AddOutputLine((boost::format("self.%1%:animation_sync()") % entity).str());
  590. }
  591. void FieldModelInstruction::ProcessCANIM2(
  592. CodeGenerator* code_gen, const std::string& entity, int char_id
  593. ){
  594. FieldCodeGenerator* cg = static_cast<FieldCodeGenerator*>(code_gen);
  595. auto animation_id = params_[0]->GetUnsigned();
  596. auto start_frame = params_[1]->GetUnsigned() / 30.0f;
  597. auto end_frame = params_[2]->GetUnsigned() / 30.0f;
  598. // TODO: check for zero.
  599. auto speed = 1.0f / params_[3]->GetUnsigned();
  600. code_gen->AddOutputLine((
  601. boost::format("self.%1%:play_animation(\"%2%\", %3%, %4%) -- speed %5%")
  602. % entity % cg->GetFormatter().GetFriendlyAnimationName(char_id, animation_id)
  603. % start_frame % end_frame % speed
  604. ).str());
  605. code_gen->AddOutputLine((boost::format("self.%1%:animation_sync()") % entity).str());
  606. }
  607. void FieldModelInstruction::ProcessCANM_2(
  608. CodeGenerator* code_gen, const std::string& entity, int char_id
  609. ){
  610. FieldCodeGenerator* cg = static_cast<FieldCodeGenerator*>(code_gen);
  611. // ID will be fixed-up downstream.
  612. auto animation_id = params_[0]->GetUnsigned();
  613. auto start_frame = params_[1]->GetUnsigned() / 30.0f;
  614. auto end_frame = params_[2]->GetUnsigned() / 30.0f;
  615. // TODO: check for zero.
  616. auto speed = 1.0f / params_[3]->GetUnsigned();
  617. code_gen->AddOutputLine((
  618. boost::format("self.%1%:play_animation_stop(\"%2%\", %3%, %4%) -- speed %5%")
  619. % entity % cg->GetFormatter().GetFriendlyAnimationName(char_id, animation_id)
  620. % start_frame % end_frame % speed
  621. ).str());
  622. code_gen->AddOutputLine((boost::format("self.%1%:animation_sync()") % entity).str());
  623. }
  624. void FieldModelInstruction::ProcessCC(CodeGenerator* code_gen, const FieldEngine& engine){
  625. const auto& entity = engine.EntityByIndex(params_[0]->GetUnsigned());
  626. code_gen->AddOutputLine(
  627. (boost::format("entity_manager:set_player_entity(\"%1%\")") % entity.GetName()).str()
  628. );
  629. }
  630. void FieldModelInstruction::ProcessJUMP(CodeGenerator* code_gen, const std::string& entity){
  631. FieldCodeGenerator* cg = static_cast<FieldCodeGenerator*>(code_gen);
  632. const float scale = 128.0f * cg->GetScaleFactor();
  633. const std::string x_value = FieldCodeGenerator::FormatValueOrVariable(
  634. cg->GetFormatter(), params_[0]->GetUnsigned(), params_[4]->GetSigned(),
  635. FieldCodeGenerator::ValueType::Float, scale
  636. );
  637. const std::string y_value = FieldCodeGenerator::FormatValueOrVariable(
  638. cg->GetFormatter(), params_[1]->GetUnsigned(), params_[5]->GetSigned(),
  639. FieldCodeGenerator::ValueType::Float, scale
  640. );
  641. float x = 0;
  642. float y = 0;
  643. const bool literal_coordinates
  644. = TryParseFloat(x_value, x) && TryParseFloat(y_value, y);
  645. int i = atoi(FieldCodeGenerator::FormatValueOrVariable(
  646. cg->GetFormatter(), params_[2]->GetUnsigned(), params_[6]->GetSigned()
  647. ).c_str());
  648. int steps = atoi(FieldCodeGenerator::FormatValueOrVariable(
  649. cg->GetFormatter(), params_[3]->GetUnsigned(), params_[7]->GetSigned()
  650. ).c_str());
  651. //x *= 0.00781250273224;
  652. //y *= 0.00781250273224;
  653. // Always force reduce steps and bound it between 1 and 5.
  654. steps /= 6;
  655. if (steps < 1) steps = 1;
  656. if (steps > 5) steps = 5;
  657. // TODO: Z hardcoded as -1, handled in Entity::ScriptJumpToPosition.
  658. // TODO: Hardcoded 0.5 in seconds. Calculate using distance.
  659. if (literal_coordinates){
  660. code_gen->AddOutputLine((
  661. boost::format("self.%1%:jump_to_position(%2%, %3%, -1, 0.5, %4%) -- %5% steps.")
  662. % entity % x % y % i % steps
  663. ).str());
  664. }
  665. else{
  666. code_gen->AddOutputLine(
  667. "self." + entity + ":jump_to_position(" + x_value + ", " + y_value
  668. + ", -1, 0.5, " + std::to_string(i) + ")"
  669. );
  670. }
  671. code_gen->AddOutputLine((boost::format("self.%1%:jump_sync()") % entity).str());
  672. }
  673. void FieldModelInstruction::ProcessAXYZI(CodeGenerator* code_gen){
  674. FieldCodeGenerator* cg = static_cast<FieldCodeGenerator*>(code_gen);
  675. const float scale = 128.0f * cg->GetScaleFactor();
  676. code_gen->AddOutputLine((
  677. boost::format("axyzi(%1%, %2%, %3%, %4%, %5%, %6%, %7%, %8%, %9%, %10%)")
  678. % params_[0]->GetSigned() % params_[1]->GetSigned() % params_[2]->GetSigned()
  679. % params_[3]->GetSigned() % params_[4]->GetSigned() % params_[5]->GetSigned()
  680. % params_[6]->GetSigned() % params_[7]->GetSigned() % params_[8]->GetSigned()
  681. % scale
  682. ).str());
  683. }
  684. void FieldModelInstruction::ProcessGETAXY(CodeGenerator* code_gen){
  685. // Stores the X and Y of an entity (given by its index in the field) in two variables, in
  686. // field units. The script of a line, for example, keeps looking at where the player is.
  687. FieldCodeGenerator* cg = static_cast<FieldCodeGenerator*>(code_gen);
  688. const float scale = 128.0f * cg->GetScaleFactor();
  689. code_gen->AddOutputLine((
  690. boost::format("getaxy(%1%, %2%, %3%, %4%, %5%, %6%)")
  691. % params_[0]->GetSigned() % params_[1]->GetSigned() % params_[2]->GetSigned()
  692. % params_[3]->GetSigned() % params_[4]->GetSigned() % scale
  693. ).str());
  694. }
  695. void FieldModelInstruction::ProcessLADER(
  696. CodeGenerator* code_gen, const std::string& entity, int char_id
  697. ){
  698. FieldCodeGenerator* cg = static_cast<FieldCodeGenerator*>(code_gen);
  699. const float scale = 128.0f * cg->GetScaleFactor();
  700. const auto& x = FieldCodeGenerator::FormatValueOrVariable(
  701. cg->GetFormatter(), params_[0]->GetUnsigned(), params_[4]->GetSigned(),
  702. FieldCodeGenerator::ValueType::Float, scale
  703. );
  704. const auto& y = FieldCodeGenerator::FormatValueOrVariable(
  705. cg->GetFormatter(), params_[1]->GetUnsigned(), params_[5]->GetSigned(),
  706. FieldCodeGenerator::ValueType::Float, scale
  707. );
  708. const auto& z = FieldCodeGenerator::FormatValueOrVariable(
  709. cg->GetFormatter(), params_[2]->GetUnsigned(), params_[6]->GetSigned(),
  710. FieldCodeGenerator::ValueType::Float, scale
  711. );
  712. uint end_triangle = atoi(FieldCodeGenerator::FormatValueOrVariable(
  713. cg->GetFormatter(), params_[3]->GetUnsigned(), params_[7]->GetUnsigned()
  714. ).c_str());
  715. uint keys = params_[8]->GetUnsigned();
  716. uint animation = params_[9]->GetUnsigned();
  717. //float orientation = params_[10]->GetUnsigned() / (256.0f / 360.0f);
  718. const auto& orientation = FieldCodeGenerator::FormatValueOrVariable(
  719. cg->GetFormatter(), 0, params_[10]->GetSigned(),
  720. FieldCodeGenerator::ValueType::Float, 256.0f / 360.0f
  721. );
  722. uint speed = params_[11]->GetUnsigned();
  723. // TODO: Orientation and speed not set.
  724. code_gen->AddOutputLine((
  725. boost::format(
  726. "self.%1%:linear_to_position(%2%, %3%, %4%, %5%, \"%10%\", %6%, %7%) "
  727. "-- Animation %8% -- Speed %9%"
  728. ) % entity % x % y % z % keys % orientation % end_triangle % animation % speed
  729. % cg->GetFormatter().GetFriendlyAnimationName(char_id, animation)
  730. ).str());
  731. code_gen->AddOutputLine((boost::format("self.%1%:linear_sync()") % entity).str());
  732. }
  733. void FieldModelInstruction::ProcessSLIDR(CodeGenerator* code_gen, const std::string& entity){
  734. FieldCodeGenerator* cg = static_cast<FieldCodeGenerator*>(code_gen);
  735. const float scale = 128.0f * cg->GetScaleFactor();
  736. // The documentation says the value should be in param_[1], but it's actually on param_[2]
  737. const std::string radius_value = FieldCodeGenerator::FormatValueOrVariable(
  738. cg->GetFormatter(), params_[0]->GetUnsigned(), params_[2]->GetUnsigned(),
  739. FieldCodeGenerator::ValueType::Float, scale
  740. );
  741. float radius = 0;
  742. if (TryParseFloat(radius_value, radius)){
  743. code_gen->AddOutputLine((
  744. boost::format("self.%1%:set_solid_radius(%2%)")
  745. % entity % radius
  746. ).str());
  747. }
  748. else{
  749. code_gen->AddOutputLine(
  750. "self." + entity + ":set_solid_radius(" + radius_value + ")"
  751. );
  752. }
  753. }
  754. void FieldModelInstruction::ProcessSOLID(CodeGenerator* code_gen, const std::string& entity){
  755. // Lines (triggers) can run this opcode too, but they don't have a model to make solid, so
  756. // the entity may not exist in the script.
  757. code_gen->AddOutputLine((
  758. boost::format("if self.%1% ~= nil then self.%1%:set_solid(%2%) end")
  759. % entity % FieldCodeGenerator::FormatInvertedBool(params_[0]->GetUnsigned())
  760. ).str());
  761. }
  762. void FieldModelInstruction::ProcessOFST(CodeGenerator* code_gen, const std::string& entity){
  763. FieldCodeGenerator* cg = static_cast<FieldCodeGenerator*>(code_gen);
  764. const float scale = 128.0f * cg->GetScaleFactor();
  765. const std::string x_value = FieldCodeGenerator::FormatValueOrVariable(
  766. cg->GetFormatter(), params_[0]->GetUnsigned(), params_[5]->GetSigned(),
  767. FieldCodeGenerator::ValueType::Float, scale
  768. );
  769. const std::string y_value = FieldCodeGenerator::FormatValueOrVariable(
  770. cg->GetFormatter(), params_[1]->GetUnsigned(),params_[6]->GetSigned(),
  771. FieldCodeGenerator::ValueType::Float, scale
  772. );
  773. const std::string z_value = FieldCodeGenerator::FormatValueOrVariable(
  774. cg->GetFormatter(), params_[2]->GetUnsigned(), params_[7]->GetSigned(),
  775. FieldCodeGenerator::ValueType::Float, scale
  776. );
  777. const std::string speed_value = FieldCodeGenerator::FormatValueOrVariable(
  778. cg->GetFormatter(), params_[3]->GetUnsigned(), params_[8]->GetUnsigned()
  779. );
  780. float x = 0;
  781. float y = 0;
  782. float z = 0;
  783. float speed = 0;
  784. const bool literal_values
  785. = TryParseFloat(x_value, x) && TryParseFloat(y_value, y)
  786. && TryParseFloat(z_value, z) && TryParseFloat(speed_value, speed);
  787. std::string move_type;
  788. switch (params_[4]->GetUnsigned()){
  789. case 0: move_type = "Entity.NONE"; break;
  790. case 1: move_type = "Entity.LINEAR"; break;
  791. case 2: move_type = "Entity.SMOOTH"; break;
  792. default:
  793. code_gen->WriteTodo(entity, "OFST with unsupported movement type");
  794. return;
  795. }
  796. if (literal_values){
  797. speed /= 30.0f;
  798. code_gen->AddOutputLine((
  799. boost::format("self.%1%:offset_to_position(%2%, %3%, %4%, %5%, %6%)")
  800. % entity % x % y % z % move_type % speed
  801. ).str());
  802. }
  803. else{
  804. code_gen->AddOutputLine(
  805. "self." + entity + ":offset_to_position(" + x_value + ", " + y_value
  806. + ", " + z_value + ", " + move_type + ", (" + speed_value + ") / 30.0)"
  807. );
  808. }
  809. }