/*
* V-Gears
* Copyright (C) 2022 V-Gears Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include
#include
#include
#include
#include "decompiler/field/instruction/FieldModelInstruction.h"
#include "decompiler/field/FieldEngine.h"
#include "decompiler/field/FieldCodeGenerator.h"
#include "decompiler/field/FieldDisassembler.h"
static bool TryParseFloat(const std::string& value, float& parsed_value){
size_t parsed_length = 0;
try{
parsed_value = std::stof(value, &parsed_length);
return parsed_length == value.size();
}
catch (const std::invalid_argument&){
return false;
}
catch (const std::out_of_range&){
return false;
}
}
void FieldModelInstruction::ProcessInst(
Function& func, ValueStack&, Engine* engine, CodeGenerator *code_gen
){
FieldEngine& eng = static_cast(*engine);
FunctionMetaData md(func.metadata);
//std::cout << "Model opcode: " << opcode_ << "\n";
switch (opcode_){
case OPCODES::JOIN: ProcessJOIN(code_gen); break;
case OPCODES::SPLIT: ProcessSPLIT(code_gen); break;
case OPCODES::BLINK: code_gen->WriteTodo(md.GetEntityName(), "BLINK"); break;
case (OPCODES::KAWAI << 8) | OPCODES_KAWAI::EYETX:
code_gen->WriteTodo(md.GetEntityName(), "EYETX");
break;
case (OPCODES::KAWAI << 8) | OPCODES_KAWAI::TRNSP:
code_gen->WriteTodo(md.GetEntityName(), "TRNSP");
break;
case (OPCODES::KAWAI << 8) | OPCODES_KAWAI::AMBNT:
code_gen->WriteTodo(md.GetEntityName(), "AMBNT");
break;
case (OPCODES::KAWAI << 8) | OPCODES_KAWAI::Unknown03:
code_gen->WriteTodo(md.GetEntityName(), "Unknown03");
break;
case (OPCODES::KAWAI << 8) | OPCODES_KAWAI::Unknown04:
code_gen->WriteTodo(md.GetEntityName(), "Unknown04");
break;
case (OPCODES::KAWAI << 8) | OPCODES_KAWAI::Unknown05:
code_gen->WriteTodo(md.GetEntityName(), "Unknown05");
break;
case (OPCODES::KAWAI << 8) | OPCODES_KAWAI::LIGHT:
code_gen->WriteTodo(md.GetEntityName(), "LIGHT");
break;
case (OPCODES::KAWAI << 8) | OPCODES_KAWAI::Unknown07:
code_gen->WriteTodo(md.GetEntityName(), "Unknown07");
break;
case (OPCODES::KAWAI << 8) | OPCODES_KAWAI::Unknown08:
code_gen->WriteTodo(md.GetEntityName(), "Unknown08");
break;
case (OPCODES::KAWAI << 8) | OPCODES_KAWAI::Unknown09:
code_gen->WriteTodo(md.GetEntityName(), "Unknown09");
break;
case (OPCODES::KAWAI << 8) | OPCODES_KAWAI::SBOBJ:
code_gen->WriteTodo(md.GetEntityName(), "SBOBJ");
break;
case (OPCODES::KAWAI << 8) | OPCODES_KAWAI::Unknown0B:
code_gen->WriteTodo(md.GetEntityName(), "Unknown0B");
break;
case (OPCODES::KAWAI << 8) | OPCODES_KAWAI::Unknown0C:
code_gen->WriteTodo(md.GetEntityName(), "Unknown0C");
break;
case (OPCODES::KAWAI << 8) | OPCODES_KAWAI::SHINE:
code_gen->WriteTodo(md.GetEntityName(), "SHINE");
break;
case (OPCODES::KAWAI << 8) | OPCODES_KAWAI::RESET:
code_gen->WriteTodo(md.GetEntityName(), "RESET");
break;
case OPCODES::KAWIW: code_gen->WriteTodo(md.GetEntityName(), "KAWIW"); break;
case OPCODES::PMOVA: code_gen->WriteTodo(md.GetEntityName(), "PMOVA"); break;
case OPCODES::PDIRA: code_gen->WriteTodo(md.GetEntityName(), "PDIRA"); break;
case OPCODES::PTURA: code_gen->WriteTodo(md.GetEntityName(), "PTURA"); break;
case OPCODES::PGTDR: code_gen->WriteTodo(md.GetEntityName(), "PGTDR"); break;
case OPCODES::PXYZI: code_gen->WriteTodo(md.GetEntityName(), "PXYZI"); break;
case OPCODES::TLKON: ProcessTLKON(code_gen, md.GetEntityName()); break;
case OPCODES::PC: ProcessPC(code_gen, md.GetEntityName(), md.GetCharacterId()); break;
case OPCODES::opCodeCHAR: ProcessCHAR(code_gen, md.GetEntityName()); break;
case OPCODES::DFANM: ProcessDFANM(code_gen, md.GetEntityName(), md.GetCharacterId()); break;
case OPCODES::ANIME1:
ProcessANIME1(code_gen, md.GetEntityName(), md.GetCharacterId());
break;
case OPCODES::VISI: ProcessVISI(code_gen, md.GetEntityName()); break;
case OPCODES::XYZI: ProcessXYZI(code_gen, md.GetEntityName()); break;
case OPCODES::XYI: code_gen->WriteTodo(md.GetEntityName(), "XYI"); break;
case OPCODES::XYZ: code_gen->WriteTodo(md.GetEntityName(), "XYZ"); break;
case OPCODES::MOVE: ProcessMOVE(code_gen, md.GetEntityName()); break;
case OPCODES::CMOVE: code_gen->WriteTodo(md.GetEntityName(), "CMOVE"); break;
case OPCODES::MOVA: ProcessMOVA(code_gen, md.GetEntityName(), eng); break;
case OPCODES::TURA: ProcessTURA(code_gen, md.GetEntityName(), eng); break;
case OPCODES::ANIMW: ProcessANIMW(code_gen, md.GetEntityName()); break;
case OPCODES::FMOVE: ProcessFMOVE(code_gen, md.GetEntityName()); break;
case OPCODES::ANIME2:
ProcessANIME2(code_gen, md.GetEntityName(), md.GetCharacterId());
break;
case OPCODES::ANIM_1:
ProcessANIM_1(code_gen, md.GetEntityName(), md.GetCharacterId());
break;
case OPCODES::CANIM1:
ProcessCANIM1(code_gen, md.GetEntityName(), md.GetCharacterId());
break;
case OPCODES::CANM_1:
ProcessCANM_1(code_gen, md.GetEntityName(), md.GetCharacterId());
break;
case OPCODES::MSPED: ProcessMSPED(code_gen, md.GetEntityName()); break;
case OPCODES::DIR: ProcessDIR(code_gen, md.GetEntityName()); break;
case OPCODES::TURNGEN: ProcessTURNGEN(code_gen, md.GetEntityName()); break;
case OPCODES::TURN: code_gen->WriteTodo(md.GetEntityName(), "TURN"); break;
case OPCODES::DIRA: ProcessDIRA(code_gen, md.GetEntityName(), eng); break;
case OPCODES::GETDIR: code_gen->WriteTodo(md.GetEntityName(), "GETDIR"); break;
case OPCODES::GETAXY: code_gen->WriteTodo(md.GetEntityName(), "GETAXY"); break;
case OPCODES::GETAI: ProcessGETAI(code_gen, eng); break;
case OPCODES::ANIM_2:
ProcessANIM_2(code_gen, md.GetEntityName(), md.GetCharacterId());
break;
case OPCODES::CANIM2:
ProcessCANIM2(code_gen, md.GetEntityName(), md.GetCharacterId());
break;
case OPCODES::CANM_2:
ProcessCANM_2(code_gen, md.GetEntityName(), md.GetCharacterId());
break;
case OPCODES::ASPED: ProcessASPED(code_gen, md.GetEntityName()); break;
case OPCODES::CC: ProcessCC(code_gen, eng); break;
case OPCODES::JUMP: ProcessJUMP(code_gen, md.GetEntityName()); break;
case OPCODES::AXYZI: ProcessAXYZI(code_gen); break;
case OPCODES::LADER: ProcessLADER(code_gen, md.GetEntityName(), md.GetCharacterId()); break;
case OPCODES::OFST: ProcessOFST(code_gen, md.GetEntityName()); break;
case OPCODES::OFSTW:
code_gen->AddOutputLine("self." + md.GetEntityName() + ":offset_sync()");
break;
case OPCODES::TALKR: ProcessTALKR(code_gen, md.GetEntityName()); break;
case OPCODES::SLIDR: ProcessSLIDR(code_gen, md.GetEntityName()); break;
case OPCODES::SOLID: ProcessSOLID(code_gen, md.GetEntityName()); break;
case OPCODES::TLKR2: ProcessTALKR(code_gen, md.GetEntityName()); break;
case OPCODES::SLDR2: code_gen->WriteTodo(md.GetEntityName(), "SLDR2"); break;
case OPCODES::CCANM: code_gen->WriteTodo(md.GetEntityName(), "CCANM"); break;
case OPCODES::FCFIX: code_gen->WriteTodo(md.GetEntityName(), "FCFIX"); break;
case OPCODES::ANIMB: code_gen->WriteTodo(md.GetEntityName(), "ANIMB"); break;
case OPCODES::TURNW: code_gen->WriteTodo(md.GetEntityName(), "TURNW"); break;
default:
code_gen->AddOutputLine(FieldCodeGenerator::FormatInstructionNotImplemented(
md.GetEntityName(), address_, opcode_
));
}
}
void FieldModelInstruction::ProcessJOIN(CodeGenerator* code_gen){
code_gen->AddOutputLine("join_party(" + std::to_string(params_[0]->GetUnsigned()) + ")");
}
void FieldModelInstruction::ProcessSPLIT(CodeGenerator* code_gen){
const std::vector parameters = GetParams();
if (parameters.size() != 13){
code_gen->WriteTodo("FieldModelInstruction", "SPLIT with malformed parameters");
return;
}
for (const ValuePtr& parameter : parameters){
if (parameter == nullptr || !parameter->IsInteger()){
code_gen->WriteTodo("FieldModelInstruction", "SPLIT with invalid parameter");
return;
}
}
FieldCodeGenerator* cg = static_cast(code_gen);
const float scale = 128.0f * cg->GetScaleFactor();
const auto& ax = FieldCodeGenerator::FormatValueOrVariable(
cg->GetFormatter(), params_[0]->GetUnsigned(), params_[6]->GetSigned(),
FieldCodeGenerator::ValueType::Float, scale
);
const auto& ay = FieldCodeGenerator::FormatValueOrVariable(
cg->GetFormatter(), params_[1]->GetUnsigned(), params_[7]->GetSigned(),
FieldCodeGenerator::ValueType::Float, scale
);
const auto& ar = FieldCodeGenerator::FormatValueOrVariable(
cg->GetFormatter(), params_[2]->GetUnsigned(), params_[8]->GetSigned(),
FieldCodeGenerator::ValueType::Float, scale
);
const auto& bx = FieldCodeGenerator::FormatValueOrVariable(
cg->GetFormatter(), params_[3]->GetUnsigned(), params_[9]->GetSigned(),
FieldCodeGenerator::ValueType::Float, scale
);
const auto& by = FieldCodeGenerator::FormatValueOrVariable(
cg->GetFormatter(), params_[4]->GetUnsigned(), params_[10]->GetSigned(),
FieldCodeGenerator::ValueType::Float, scale
);
const auto& br = FieldCodeGenerator::FormatValueOrVariable(
cg->GetFormatter(), params_[5]->GetUnsigned(), params_[11]->GetSigned(),
FieldCodeGenerator::ValueType::Float, scale
);
const auto& speed = params_[12]->GetUnsigned();
code_gen->AddOutputLine((
boost::format("split_party(%1%, %2%, %3%, %4%, %5%, %6%, %7%)")
% ax % ay % ar % bx % by % br % speed
).str());
}
void FieldModelInstruction::ProcessTLKON(CodeGenerator* code_gen, const std::string& entity){
code_gen->AddOutputLine((
boost::format("self.%1%:set_talkable(%2%)")
% entity % FieldCodeGenerator::FormatInvertedBool(params_[0]->GetUnsigned())
).str());
}
void FieldModelInstruction::ProcessPC(
CodeGenerator* code_gen, const std::string& entity, int char_id
){
code_gen->AddOutputLine(
(boost::format("set_entity_to_character(\"%1%\", %2%)") % entity % char_id).str()
);
}
void FieldModelInstruction::ProcessCHAR(CodeGenerator* code_gen, const std::string& entity){
code_gen->AddOutputLine(
(boost::format("self.%1% = entity_manager:get_entity(\"%1%\")") % entity).str()
);
// A model that has no animation playing is not drawn. The ones with no "Idle" animation need
// a default one, which a DFANM later in the script replaces.
FieldCodeGenerator* cg = static_cast(code_gen);
const std::string animation = cg->GetFormatter().GetDefaultAnimationName(params_[0]->GetSigned());
// The walk and run animations are the second and the third of the model.
const std::string walk = cg->GetFormatter().GetMoveAnimationName(params_[0]->GetSigned(), 1);
const std::string run = cg->GetFormatter().GetMoveAnimationName(params_[0]->GetSigned(), 2);
if (!walk.empty() && !run.empty() && (walk != "Walk" || run != "Run")){
code_gen->AddOutputLine((
boost::format("self.%1%:set_move_animations(\"%2%\", \"%3%\")") % entity % walk % run
).str());
}
if (!animation.empty()){
code_gen->AddOutputLine((
boost::format("self.%1%:set_default_animation(\"%2%\") -- No Idle animation")
% entity % animation
).str());
code_gen->AddOutputLine(
(boost::format("self.%1%:play_animation(\"%2%\")") % entity % animation).str()
);
}
}
void FieldModelInstruction::ProcessDFANM(
CodeGenerator* code_gen, const std::string& entity, int char_id
){
// ID will be fixed-up downstream.
FieldCodeGenerator* cg = static_cast(code_gen);
auto animation_id = params_[0]->GetUnsigned();
// TODO: check for zero.
auto speed = 1.0f / params_[1]->GetUnsigned();
code_gen->AddOutputLine((
boost::format("self.%1%:set_default_animation(\"%2%\") -- speed %3%")
% entity % cg->GetFormatter().GetFriendlyAnimationName(char_id, animation_id) % speed
).str());
code_gen->AddOutputLine((
boost::format("self.%1%:play_animation(\"%2%\")")
% entity % cg->GetFormatter().GetFriendlyAnimationName(char_id, animation_id)
).str());
}
void FieldModelInstruction::ProcessANIME1(
CodeGenerator* code_gen, const std::string& entity, int char_id
){
// ID will be fixed-up downstream.
FieldCodeGenerator* cg = static_cast(code_gen);
auto animation_id = params_[0]->GetUnsigned();
// TODO: check for zero.
auto speed = 1.0f / params_[1]->GetUnsigned();
code_gen->AddOutputLine((
boost::format("self.%1%:play_animation(\"%2%\") -- speed %3%")
% entity % cg->GetFormatter().GetFriendlyAnimationName(char_id, animation_id) % speed
).str());
code_gen->AddOutputLine((boost::format("self.%1%:animation_sync()") % entity).str());
}
void FieldModelInstruction::ProcessMOVA(
CodeGenerator* code_gen, const std::string& entity, const FieldEngine& engine
){
// Walks to the other entity, and waits until it is there.
const auto& target = engine.EntityByIndex(params_[0]->GetUnsigned());
code_gen->AddOutputLine((
boost::format("self.%1%:move_to_entity(entity_manager:get_entity(\"%2%\"))")
% entity % target.GetName()
).str());
code_gen->AddOutputLine((boost::format("self.%1%:move_sync()") % entity).str());
}
void FieldModelInstruction::ProcessTALKR(CodeGenerator* code_gen, const std::string& entity){
// The range is given in the same units as the positions.
FieldCodeGenerator* cg = static_cast(code_gen);
const float scale = 128.0f * cg->GetScaleFactor();
const std::string range_value = FieldCodeGenerator::FormatValueOrVariable(
cg->GetFormatter(), params_[0]->GetUnsigned() + params_[1]->GetUnsigned(),
params_[2]->GetUnsigned(), FieldCodeGenerator::ValueType::Float, scale
);
float range = 0;
if (TryParseFloat(range_value, range)){
code_gen->AddOutputLine(
(boost::format("self.%1%:set_talk_radius(%2%)") % entity % range).str()
);
}
else{
// A variable: the scale is not applied yet.
code_gen->AddOutputLine(
"self." + entity + ":set_talk_radius((" + range_value + ") / " + std::to_string(scale) + ")"
);
}
}
void FieldModelInstruction::ProcessDIRA(
CodeGenerator* code_gen, const std::string& entity, const FieldEngine& engine
){
// Turns the model towards another entity at once.
const auto& target = engine.EntityByIndex(params_[0]->GetUnsigned());
code_gen->AddOutputLine((
boost::format("self.%1%:face_entity(entity_manager:get_entity(\"%2%\"))")
% entity % target.GetName()
).str());
}
void FieldModelInstruction::ProcessASPED(CodeGenerator* code_gen, const std::string& entity){
// The value is the speed of the animations: 16 is the normal one, 32 double and 8 half.
FieldCodeGenerator* cg = static_cast(code_gen);
const std::string speed_value = FieldCodeGenerator::FormatValueOrVariable(
cg->GetFormatter(), params_[0]->GetUnsigned() + params_[1]->GetUnsigned(),
params_[2]->GetUnsigned()
);
float speed = 0;
if (TryParseFloat(speed_value, speed)){
code_gen->AddOutputLine((
boost::format("self.%1%:set_animation_speed(%2%)") % entity % (speed / 16.0f)
).str());
}
else{
code_gen->AddOutputLine(
"self." + entity + ":set_animation_speed((" + speed_value + ") / 16.0)"
);
}
}
void FieldModelInstruction::ProcessANIME2(
CodeGenerator* code_gen, const std::string& entity, int char_id
){
// Like ANIME1, but the script doesn't wait for the animation to end.
FieldCodeGenerator* cg = static_cast(code_gen);
auto animation_id = params_[0]->GetUnsigned();
auto speed = 1.0f / params_[1]->GetUnsigned();
code_gen->AddOutputLine((
boost::format("self.%1%:play_animation(\"%2%\") -- speed %3%")
% entity % cg->GetFormatter().GetFriendlyAnimationName(char_id, animation_id) % speed
).str());
}
void FieldModelInstruction::ProcessANIM_1(
CodeGenerator* code_gen, const std::string& entity, int char_id
){
// Like ANIM_2, but the script doesn't wait for the animation to end.
FieldCodeGenerator* cg = static_cast(code_gen);
auto animation_id = params_[0]->GetUnsigned();
auto speed = 1.0f / params_[1]->GetUnsigned();
code_gen->AddOutputLine((
boost::format("self.%1%:play_animation_stop(\"%2%\") -- speed %3%")
% entity % cg->GetFormatter().GetFriendlyAnimationName(char_id, animation_id) % speed
).str());
}
void FieldModelInstruction::ProcessCANIM1(
CodeGenerator* code_gen, const std::string& entity, int char_id
){
// Like CANIM2, but the script doesn't wait for the animation to end.
FieldCodeGenerator* cg = static_cast(code_gen);
auto animation_id = params_[0]->GetUnsigned();
auto start_frame = params_[1]->GetUnsigned() / 30.0f;
auto end_frame = params_[2]->GetUnsigned() / 30.0f;
auto speed = 1.0f / params_[3]->GetUnsigned();
code_gen->AddOutputLine((
boost::format("self.%1%:play_animation(\"%2%\", %3%, %4%) -- speed %5%")
% entity % cg->GetFormatter().GetFriendlyAnimationName(char_id, animation_id)
% start_frame % end_frame % speed
).str());
}
void FieldModelInstruction::ProcessCANM_1(
CodeGenerator* code_gen, const std::string& entity, int char_id
){
// Like CANM_2, but the script doesn't wait for the animation to end.
FieldCodeGenerator* cg = static_cast(code_gen);
auto animation_id = params_[0]->GetUnsigned();
auto start_frame = params_[1]->GetUnsigned() / 30.0f;
auto end_frame = params_[2]->GetUnsigned() / 30.0f;
auto speed = 1.0f / params_[3]->GetUnsigned();
code_gen->AddOutputLine((
boost::format("self.%1%:play_animation_stop(\"%2%\", %3%, %4%) -- speed %5%")
% entity % cg->GetFormatter().GetFriendlyAnimationName(char_id, animation_id)
% start_frame % end_frame % speed
).str());
}
void FieldModelInstruction::ProcessANIMW(CodeGenerator* code_gen, const std::string& entity){
// Waits for the animation that was started with one of the non-waiting opcodes.
code_gen->AddOutputLine((boost::format("self.%1%:animation_sync()") % entity).str());
}
void FieldModelInstruction::ProcessVISI(CodeGenerator* code_gen, const std::string& entity){
code_gen->AddOutputLine((
boost::format("self.%1%:set_visible(%2%)")
% entity % FieldCodeGenerator::FormatBool(params_[0]->GetUnsigned())
).str());
}
void FieldModelInstruction::ProcessXYZI(CodeGenerator* code_gen, const std::string& entity){
FieldCodeGenerator* cg = static_cast(code_gen);
const float scale = 128.0f * cg->GetScaleFactor();
const auto& x = FieldCodeGenerator::FormatValueOrVariable(
cg->GetFormatter(), params_[0]->GetUnsigned(), params_[4]->GetSigned(),
FieldCodeGenerator::ValueType::Float, scale
);
const auto& y = FieldCodeGenerator::FormatValueOrVariable(
cg->GetFormatter(), params_[1]->GetUnsigned(), params_[5]->GetSigned(),
FieldCodeGenerator::ValueType::Float, scale
);
const auto& z = FieldCodeGenerator::FormatValueOrVariable(
cg->GetFormatter(), params_[2]->GetUnsigned(), params_[6]->GetSigned(),
FieldCodeGenerator::ValueType::Float, scale
);
const auto& triangle_id = FieldCodeGenerator::FormatValueOrVariable(
cg->GetFormatter(), params_[3]->GetUnsigned(), params_[7]->GetUnsigned()
);
code_gen->AddOutputLine((
boost::format("self.%1%:set_position(%2%, %3%, %4%) -- triangle ID %5%")
% entity % x % y % z % triangle_id
).str());
}
void FieldModelInstruction::ProcessMOVE(CodeGenerator* code_gen, const std::string& entity){
FieldCodeGenerator* cg = static_cast(code_gen);
const float scale = 128.0f * cg->GetScaleFactor();
const auto& x = FieldCodeGenerator::FormatValueOrVariable(
cg->GetFormatter(), params_[0]->GetUnsigned(), params_[2]->GetSigned(),
FieldCodeGenerator::ValueType::Float, scale
);
const auto& y = FieldCodeGenerator::FormatValueOrVariable(
cg->GetFormatter(), params_[1]->GetUnsigned(), params_[3]->GetSigned(),
FieldCodeGenerator::ValueType::Float, scale
);
code_gen->AddOutputLine(
(boost::format("self.%1%:move_to_position(%2%, %3%)") % entity % x % y).str()
);
code_gen->AddOutputLine((boost::format("self.%1%:move_sync()") % entity).str());
}
void FieldModelInstruction::ProcessFMOVE(CodeGenerator* code_gen, const std::string& entity){
FieldCodeGenerator* cg = static_cast(code_gen);
const float scale = 128.0f * cg->GetScaleFactor();
const auto& x = FieldCodeGenerator::FormatValueOrVariable(
cg->GetFormatter(), params_[0]->GetUnsigned(), params_[2]->GetSigned(),
FieldCodeGenerator::ValueType::Float, scale
);
const auto& y = FieldCodeGenerator::FormatValueOrVariable(
cg->GetFormatter(), params_[1]->GetUnsigned(), params_[3]->GetSigned(),
FieldCodeGenerator::ValueType::Float, scale
);
// Like MOVE, but the animation is the one the script chose, not the walking one.
code_gen->AddOutputLine((boost::format("self.%1%:move_auto_animation(false)") % entity).str());
code_gen->AddOutputLine(
(boost::format("self.%1%:move_to_position(%2%, %3%)") % entity % x % y).str()
);
code_gen->AddOutputLine((boost::format("self.%1%:move_sync()") % entity).str());
code_gen->AddOutputLine((boost::format("self.%1%:move_auto_animation(true)") % entity).str());
}
void FieldModelInstruction::ProcessTURA(
CodeGenerator* code_gen, const std::string& entity, const FieldEngine& engine
){
FieldCodeGenerator* cg = static_cast(code_gen);
const auto& dest_entity = engine.EntityByIndex(params_[0]->GetSigned());
std::string direction;
switch (params_[1]->GetUnsigned()){
case 0: direction = "Entity.CLOCKWISE"; break;
case 1: direction = "Entity.ANTICLOCKWISE"; break;
case 2: direction = "Entity.CLOSEST"; break;
// Default to closest:
default: direction = "Entity.CLOSEST"; break;
}
auto steps = params_[2]->GetUnsigned();
const float scaled_steps = static_cast(steps) / 30.0f;
code_gen->AddOutputLine((
boost::format("self.%1%:turn_to_entity(entity_manager:get_entity(\"%2%\"), %3%, %4%)")
% entity % dest_entity.GetName() % direction % scaled_steps
).str());
code_gen->AddOutputLine((boost::format("self.%1%:turn_sync()") % entity).str());
}
void FieldModelInstruction::ProcessMSPED(CodeGenerator* code_gen, const std::string& entity){
FieldCodeGenerator* cg = static_cast(code_gen);
const float scale = 128.0f * cg->GetScaleFactor();
const auto& speed = FieldCodeGenerator::FormatValueOrVariable(
cg->GetFormatter(), params_[1]->GetUnsigned(), params_[2]->GetUnsigned(),
FieldCodeGenerator::ValueType::Float, 256.0f * scale / 30.0f
);
code_gen->AddOutputLine(
(boost::format("self.%1%:set_move_auto_speed(%2%)") % entity % speed).str()
);
}
void FieldModelInstruction::ProcessDIR(CodeGenerator* code_gen, const std::string& entity){
FieldCodeGenerator* cg = static_cast(code_gen);
const auto& degrees = FieldCodeGenerator::FormatValueOrVariable(
cg->GetFormatter(), params_[0]->GetUnsigned(), params_[1]->GetUnsigned(),
FieldCodeGenerator::ValueType::Float, 256.0f / 360.0f
);
code_gen->AddOutputLine((boost::format("self.%1%:set_rotation(%2%)") % entity % degrees).str());
}
void FieldModelInstruction::ProcessTURNGEN(CodeGenerator* code_gen, const std::string& entity){
FieldCodeGenerator* cg = static_cast(code_gen);
const auto& degrees = FieldCodeGenerator::FormatValueOrVariable(
cg->GetFormatter(), params_[1]->GetUnsigned(), params_[2]->GetUnsigned(),
FieldCodeGenerator::ValueType::Float, 256.0f / 360.0f
);
std::string direction;
switch (params_[3]->GetUnsigned()){
case 0: direction = "Entity.CLOCKWISE"; break;
case 1: direction = "Entity.ANTICLOCKWISE"; break;
case 2: direction = "Entity.CLOSEST"; break;
// Default to closest:
default: direction = "Entity.CLOSEST"; break;
}
auto steps = params_[4]->GetUnsigned();
std::string step_type;
switch (params_[5]->GetUnsigned()){
case 1: step_type = "Entity.LINEAR"; break;
case 2: step_type = "Entity.SMOOTH"; break;
// Default to smooth
default: step_type = "Entity.SMOOTH"; break;
}
const float scaled_steps = static_cast(steps) / 30.0f;
code_gen->AddOutputLine((
boost::format("self.%1%:turn_to_direction(%2%, %3%, %4%, %5%)")
% entity % degrees % direction % step_type % scaled_steps
).str());
code_gen->AddOutputLine((boost::format("self.%1%:turn_sync()") % entity).str());
}
void FieldModelInstruction::ProcessGETAI(CodeGenerator* code_gen, const FieldEngine& engine){
FieldCodeGenerator* cg = static_cast(code_gen);
// TODO: check for assignment to literal.
const auto& variable = FieldCodeGenerator::FormatValueOrVariable(
cg->GetFormatter(), params_[1]->GetUnsigned(), params_[3]->GetUnsigned()
);
const auto& entity = engine.EntityByIndex(params_[2]->GetUnsigned());
code_gen->AddOutputLine(
(boost::format("%1% = entity_manager:get_entity(\"%2%\"):get_move_triangle_id()")
% variable % entity.GetName()
).str());
}
void FieldModelInstruction::ProcessANIM_2(
CodeGenerator* code_gen, const std::string& entity, int char_id
){
// ID will be fixed-up downstream.
FieldCodeGenerator* cg = static_cast(code_gen);
auto animation_id = params_[0]->GetUnsigned();
// TODO: check for zero.
auto speed = 1.0f / params_[1]->GetUnsigned();
code_gen->AddOutputLine((
boost::format("self.%1%:play_animation_stop(\"%2%\") -- speed %3%")
% entity % cg->GetFormatter().GetFriendlyAnimationName(char_id, animation_id) % speed
).str());
code_gen->AddOutputLine((boost::format("self.%1%:animation_sync()") % entity).str());
}
void FieldModelInstruction::ProcessCANIM2(
CodeGenerator* code_gen, const std::string& entity, int char_id
){
FieldCodeGenerator* cg = static_cast(code_gen);
auto animation_id = params_[0]->GetUnsigned();
auto start_frame = params_[1]->GetUnsigned() / 30.0f;
auto end_frame = params_[2]->GetUnsigned() / 30.0f;
// TODO: check for zero.
auto speed = 1.0f / params_[3]->GetUnsigned();
code_gen->AddOutputLine((
boost::format("self.%1%:play_animation(\"%2%\", %3%, %4%) -- speed %5%")
% entity % cg->GetFormatter().GetFriendlyAnimationName(char_id, animation_id)
% start_frame % end_frame % speed
).str());
code_gen->AddOutputLine((boost::format("self.%1%:animation_sync()") % entity).str());
}
void FieldModelInstruction::ProcessCANM_2(
CodeGenerator* code_gen, const std::string& entity, int char_id
){
FieldCodeGenerator* cg = static_cast(code_gen);
// ID will be fixed-up downstream.
auto animation_id = params_[0]->GetUnsigned();
auto start_frame = params_[1]->GetUnsigned() / 30.0f;
auto end_frame = params_[2]->GetUnsigned() / 30.0f;
// TODO: check for zero.
auto speed = 1.0f / params_[3]->GetUnsigned();
code_gen->AddOutputLine((
boost::format("self.%1%:play_animation_stop(\"%2%\", %3%, %4%) -- speed %5%")
% entity % cg->GetFormatter().GetFriendlyAnimationName(char_id, animation_id)
% start_frame % end_frame % speed
).str());
code_gen->AddOutputLine((boost::format("self.%1%:animation_sync()") % entity).str());
}
void FieldModelInstruction::ProcessCC(CodeGenerator* code_gen, const FieldEngine& engine){
const auto& entity = engine.EntityByIndex(params_[0]->GetUnsigned());
code_gen->AddOutputLine(
(boost::format("entity_manager:set_player_entity(\"%1%\")") % entity.GetName()).str()
);
}
void FieldModelInstruction::ProcessJUMP(CodeGenerator* code_gen, const std::string& entity){
FieldCodeGenerator* cg = static_cast(code_gen);
const float scale = 128.0f * cg->GetScaleFactor();
const std::string x_value = FieldCodeGenerator::FormatValueOrVariable(
cg->GetFormatter(), params_[0]->GetUnsigned(), params_[4]->GetSigned(),
FieldCodeGenerator::ValueType::Float, scale
);
const std::string y_value = FieldCodeGenerator::FormatValueOrVariable(
cg->GetFormatter(), params_[1]->GetUnsigned(), params_[5]->GetSigned(),
FieldCodeGenerator::ValueType::Float, scale
);
float x = 0;
float y = 0;
const bool literal_coordinates
= TryParseFloat(x_value, x) && TryParseFloat(y_value, y);
int i = atoi(FieldCodeGenerator::FormatValueOrVariable(
cg->GetFormatter(), params_[2]->GetUnsigned(), params_[6]->GetSigned()
).c_str());
int steps = atoi(FieldCodeGenerator::FormatValueOrVariable(
cg->GetFormatter(), params_[3]->GetUnsigned(), params_[7]->GetSigned()
).c_str());
//x *= 0.00781250273224;
//y *= 0.00781250273224;
// Always force reduce steps and bound it between 1 and 5.
steps /= 6;
if (steps < 1) steps = 1;
if (steps > 5) steps = 5;
// TODO: Z hardcoded as -1, handled in Entity::ScriptJumpToPosition.
// TODO: Hardcoded 0.5 in seconds. Calculate using distance.
if (literal_coordinates){
code_gen->AddOutputLine((
boost::format("self.%1%:jump_to_position(%2%, %3%, -1, 0.5, %4%) -- %5% steps.")
% entity % x % y % i % steps
).str());
}
else{
code_gen->AddOutputLine(
"self." + entity + ":jump_to_position(" + x_value + ", " + y_value
+ ", -1, 0.5, " + std::to_string(i) + ")"
);
}
code_gen->AddOutputLine((boost::format("self.%1%:jump_sync()") % entity).str());
}
void FieldModelInstruction::ProcessAXYZI(CodeGenerator* code_gen){
FieldCodeGenerator* cg = static_cast(code_gen);
const float scale = 128.0f * cg->GetScaleFactor();
code_gen->AddOutputLine((
boost::format("axyzi(%1%, %2%, %3%, %4%, %5%, %6%, %7%, %8%, %9%, %10%)")
% params_[0]->GetSigned() % params_[1]->GetSigned() % params_[2]->GetSigned()
% params_[3]->GetSigned() % params_[4]->GetSigned() % params_[5]->GetSigned()
% params_[6]->GetSigned() % params_[7]->GetSigned() % params_[8]->GetSigned()
% scale
).str());
}
void FieldModelInstruction::ProcessLADER(
CodeGenerator* code_gen, const std::string& entity, int char_id
){
FieldCodeGenerator* cg = static_cast(code_gen);
const float scale = 128.0f * cg->GetScaleFactor();
const auto& x = FieldCodeGenerator::FormatValueOrVariable(
cg->GetFormatter(), params_[0]->GetUnsigned(), params_[4]->GetSigned(),
FieldCodeGenerator::ValueType::Float, scale
);
const auto& y = FieldCodeGenerator::FormatValueOrVariable(
cg->GetFormatter(), params_[1]->GetUnsigned(), params_[5]->GetSigned(),
FieldCodeGenerator::ValueType::Float, scale
);
const auto& z = FieldCodeGenerator::FormatValueOrVariable(
cg->GetFormatter(), params_[2]->GetUnsigned(), params_[6]->GetSigned(),
FieldCodeGenerator::ValueType::Float, scale
);
uint end_triangle = atoi(FieldCodeGenerator::FormatValueOrVariable(
cg->GetFormatter(), params_[3]->GetUnsigned(), params_[7]->GetUnsigned()
).c_str());
uint keys = params_[8]->GetUnsigned();
uint animation = params_[9]->GetUnsigned();
//float orientation = params_[10]->GetUnsigned() / (256.0f / 360.0f);
const auto& orientation = FieldCodeGenerator::FormatValueOrVariable(
cg->GetFormatter(), 0, params_[10]->GetSigned(),
FieldCodeGenerator::ValueType::Float, 256.0f / 360.0f
);
uint speed = params_[11]->GetUnsigned();
// TODO: Orientation and speed not set.
code_gen->AddOutputLine((
boost::format(
"self.%1%:linear_to_position(%2%, %3%, %4%, %5%, \"%10%\", %6%, %7%) "
"-- Animation %8% -- Speed %9%"
) % entity % x % y % z % keys % orientation % end_triangle % animation % speed
% cg->GetFormatter().GetFriendlyAnimationName(char_id, animation)
).str());
code_gen->AddOutputLine((boost::format("self.%1%:linear_sync()") % entity).str());
}
void FieldModelInstruction::ProcessSLIDR(CodeGenerator* code_gen, const std::string& entity){
FieldCodeGenerator* cg = static_cast(code_gen);
const float scale = 128.0f * cg->GetScaleFactor();
// The documentation says the value should be in param_[1], but it's actually on param_[2]
const std::string radius_value = FieldCodeGenerator::FormatValueOrVariable(
cg->GetFormatter(), params_[0]->GetUnsigned(), params_[2]->GetUnsigned(),
FieldCodeGenerator::ValueType::Float, scale
);
float radius = 0;
if (TryParseFloat(radius_value, radius)){
code_gen->AddOutputLine((
boost::format("self.%1%:set_solid_radius(%2%)")
% entity % radius
).str());
}
else{
code_gen->AddOutputLine(
"self." + entity + ":set_solid_radius(" + radius_value + ")"
);
}
}
void FieldModelInstruction::ProcessSOLID(CodeGenerator* code_gen, const std::string& entity){
code_gen->AddOutputLine((
boost::format("self.%1%:set_solid(%2%)")
% entity % FieldCodeGenerator::FormatInvertedBool(params_[0]->GetUnsigned())
).str());
}
void FieldModelInstruction::ProcessOFST(CodeGenerator* code_gen, const std::string& entity){
FieldCodeGenerator* cg = static_cast(code_gen);
const float scale = 128.0f * cg->GetScaleFactor();
const std::string x_value = FieldCodeGenerator::FormatValueOrVariable(
cg->GetFormatter(), params_[0]->GetUnsigned(), params_[5]->GetSigned(),
FieldCodeGenerator::ValueType::Float, scale
);
const std::string y_value = FieldCodeGenerator::FormatValueOrVariable(
cg->GetFormatter(), params_[1]->GetUnsigned(),params_[6]->GetSigned(),
FieldCodeGenerator::ValueType::Float, scale
);
const std::string z_value = FieldCodeGenerator::FormatValueOrVariable(
cg->GetFormatter(), params_[2]->GetUnsigned(), params_[7]->GetSigned(),
FieldCodeGenerator::ValueType::Float, scale
);
const std::string speed_value = FieldCodeGenerator::FormatValueOrVariable(
cg->GetFormatter(), params_[3]->GetUnsigned(), params_[8]->GetUnsigned()
);
float x = 0;
float y = 0;
float z = 0;
float speed = 0;
const bool literal_values
= TryParseFloat(x_value, x) && TryParseFloat(y_value, y)
&& TryParseFloat(z_value, z) && TryParseFloat(speed_value, speed);
std::string move_type;
switch (params_[4]->GetUnsigned()){
case 0: move_type = "Entity.NONE"; break;
case 1: move_type = "Entity.LINEAR"; break;
case 2: move_type = "Entity.SMOOTH"; break;
default:
code_gen->WriteTodo(entity, "OFST with unsupported movement type");
return;
}
if (literal_values){
speed /= 30.0f;
code_gen->AddOutputLine((
boost::format("self.%1%:offset_to_position(%2%, %3%, %4%, %5%, %6%)")
% entity % x % y % z % move_type % speed
).str());
}
else{
code_gen->AddOutputLine(
"self." + entity + ":offset_to_position(" + x_value + ", " + y_value
+ ", " + z_value + ", " + move_type + ", (" + speed_value + ") / 30.0)"
);
}
}