Skip to content

Commit c4f589b

Browse files
committed
NewCommand 2052 - AnimateVariable
@raw 2052, "linear/bounceOut", 0, 1, 0, 0, 0, 240, 0, 640, 0, 0 ` CommandInterpolateVariable("easeTypeStart/easeTypeEnd",[targetIsVar, target, StartIsVar, start, endIsVar, end, durationIsVar, duration, pauseIsVar, pause]) ` cubic bezier is broken. There are some libraries about it, I'll see what to do. I'll need help with this one. It's almost fully working.
1 parent 48c0468 commit c4f589b

File tree

2 files changed

+334
-0
lines changed

2 files changed

+334
-0
lines changed

src/game_interpreter.cpp

+333
Original file line numberDiff line numberDiff line change
@@ -825,6 +825,8 @@ bool Game_Interpreter::ExecuteCommand(lcf::rpg::EventCommand const& com) {
825825
return CommandManiacControlStrings(com);
826826
case Cmd::Maniac_CallCommand:
827827
return CommandManiacCallCommand(com);
828+
case static_cast<Game_Interpreter::Cmd>(2052)://Cmd:: EasyRpg_InterpolateVariable
829+
return CommandAnimateVariable(com);
828830
default:
829831
return true;
830832
}
@@ -5112,3 +5114,334 @@ int Game_Interpreter::ManiacBitmask(int value, int mask) const {
51125114

51135115
return value;
51145116
}
5117+
std::vector<double> parseBezier(const std::string& bezierParams) {
5118+
std::vector<double> params;
5119+
std::string temp;
5120+
size_t startPos = bezierParams.find("(") + 1;
5121+
size_t endPos = bezierParams.find(")");
5122+
std::string valuesString = bezierParams.substr(startPos, endPos - startPos);
5123+
5124+
size_t commaPos = valuesString.find(",");
5125+
while (commaPos != std::string::npos) {
5126+
temp = valuesString.substr(0, commaPos);
5127+
params.push_back(std::stod(temp));
5128+
valuesString = valuesString.substr(commaPos + 1);
5129+
commaPos = valuesString.find(",");
5130+
}
5131+
// Push the last value into the vector
5132+
params.push_back(std::stod(valuesString));
5133+
5134+
return params;
5135+
}
5136+
5137+
//FIXME: cubicBezier is completely Broken
5138+
// references to how it should work:
5139+
// https://matthewlein.com/tools/ceaser
5140+
// https://cubic-bezier.com/
5141+
5142+
double cubicBezier(double t, double p0, double p1, double p2, double p3) {
5143+
double u = 1 - t;
5144+
double tt = t * t;
5145+
double uu = u * u;
5146+
double uuu = uu * u;
5147+
double ttt = tt * t;
5148+
5149+
double p = uuu * p0; // (1-t)^3
5150+
double q = 3 * uu * t * p1; // 3t(1-t)^2
5151+
double r = 3 * u * tt * p2; // 3(1-t)t^2
5152+
double s = ttt * p3; // t^3
5153+
5154+
return p + q + r + s;
5155+
}
5156+
5157+
double getEasedT(const std::string& easingType, double t, double b, double c, double d) {
5158+
if (easingType == "linear") {
5159+
return c * t / d + b;
5160+
}
5161+
else if (easingType == "quadIn") {
5162+
t /= d;
5163+
return c * t * t + b;
5164+
}
5165+
else if (easingType == "quadOut") {
5166+
t /= d;
5167+
return -c * t * (t - 2) + b;
5168+
}
5169+
else if (easingType == "quadInOut") {
5170+
t /= d / 2;
5171+
if (t < 1) {
5172+
return c / 2 * t * t + b;
5173+
}
5174+
else {
5175+
t -= 1;
5176+
return -c / 2 * (t * (t - 2) - 1) + b;
5177+
}
5178+
}
5179+
else if (easingType == "cubicIn") {
5180+
t /= d;
5181+
return c * t * t * t + b;
5182+
}
5183+
else if (easingType == "cubicOut") {
5184+
t = (t / d) - 1;
5185+
return c * (t * t * t + 1) + b;
5186+
}
5187+
else if (easingType == "cubicInOut") {
5188+
t /= d / 2;
5189+
if (t < 1) {
5190+
return c / 2 * t * t * t + b;
5191+
}
5192+
else {
5193+
t -= 2;
5194+
return c / 2 * (t * t * t + 2) + b;
5195+
}
5196+
}
5197+
else if (easingType == "sinIn") {
5198+
return -c * cos(t / d * (M_PI / 2)) + c + b;
5199+
}
5200+
else if (easingType == "sinOut") {
5201+
return c * sin(t / d * (M_PI / 2)) + b;
5202+
}
5203+
else if (easingType == "sinInOut") {
5204+
return -c / 2 * (cos(M_PI * t / d) - 1) + b;
5205+
}
5206+
else if (easingType == "expoIn") {
5207+
return c * pow(2, 10 * (t / d - 1)) + b;
5208+
}
5209+
else if (easingType == "expoOut") {
5210+
return c * (-pow(2, -10 * t / d) + 1) + b;
5211+
}
5212+
else if (easingType == "expoInOut") {
5213+
t /= d / 2;
5214+
if (t < 1) {
5215+
return c / 2 * pow(2, 10 * (t - 1)) + b;
5216+
}
5217+
else {
5218+
t -= 1;
5219+
return c / 2 * (-pow(2, -10 * t) + 2) + b;
5220+
}
5221+
}
5222+
else if (easingType == "circIn") {
5223+
t /= d;
5224+
return -c * (sqrt(1 - t * t) - 1) + b;
5225+
}
5226+
else if (easingType == "circOut") {
5227+
t = (t / d) - 1;
5228+
return c * sqrt(1 - t * t) + b;
5229+
}
5230+
else if (easingType == "circInOut") {
5231+
t /= d / 2;
5232+
if (t < 1) {
5233+
return -c / 2 * (sqrt(1 - t * t) - 1) + b;
5234+
}
5235+
else {
5236+
t -= 2;
5237+
return c / 2 * (sqrt(1 - t * t) + 1) + b;
5238+
}
5239+
}
5240+
else if (easingType == "elasticIn") {
5241+
if (t == 0) {
5242+
return b;
5243+
}
5244+
if ((t /= d) == 1) {
5245+
return b + c;
5246+
}
5247+
5248+
double p = d * 0.3;
5249+
double a = c;
5250+
double s = p / 4;
5251+
5252+
double postFix = a * pow(2, 10 * (t -= 1)); // this is a fix, again, with post-increment operators
5253+
return -(postFix * sin((t * d - s) * (2 * M_PI) / p)) + b;
5254+
}
5255+
else if (easingType == "elasticOut") {
5256+
if (t == 0) {
5257+
return b;
5258+
}
5259+
if ((t /= d) == 1) {
5260+
return b + c;
5261+
}
5262+
5263+
double p = d * 0.3;
5264+
double a = c;
5265+
double s = p / 4;
5266+
5267+
return (a * pow(2, -10 * t) * sin((t * d - s) * (2 * M_PI) / p) + c + b);
5268+
}
5269+
else if (easingType == "elasticInOut") {
5270+
if (t == 0) {
5271+
return b;
5272+
}
5273+
if ((t /= d / 2) == 2) {
5274+
return b + c;
5275+
}
5276+
5277+
double p = d * (0.3 * 1.5);
5278+
double a = c;
5279+
double s = p / 4;
5280+
5281+
if (t < 1) {
5282+
double postFix = a * pow(2, 10 * (t -= 1)); // this is a fix, again, with post-increment operators
5283+
return -0.5 * (postFix * sin((t * d - s) * (2 * M_PI) / p)) + b;
5284+
}
5285+
5286+
double postFix = a * pow(2, -10 * (t -= 1)); // this is a fix, again, with post-increment operators
5287+
return postFix * sin((t * d - s) * (2 * M_PI) / p) * 0.5 + c + b;
5288+
}
5289+
else if (easingType == "bounceIn") {
5290+
return c - getEasedT("bounceOut", d - t, 0, c, d) + b;
5291+
}
5292+
else if (easingType == "bounceOut") {
5293+
if ((t /= d) < (1 / 2.75)) {
5294+
return c * (7.5625 * t * t) + b;
5295+
}
5296+
else if (t < (2 / 2.75)) {
5297+
t -= (1.5 / 2.75);
5298+
return c * (7.5625 * t * t + 0.75) + b;
5299+
}
5300+
else if (t < (2.5 / 2.75)) {
5301+
t -= (2.25 / 2.75);
5302+
return c * (7.5625 * t * t + 0.9375) + b;
5303+
}
5304+
else {
5305+
t -= (2.625 / 2.75);
5306+
return c * (7.5625 * t * t + 0.984375) + b;
5307+
}
5308+
}
5309+
else if (easingType == "bounceInOut") {
5310+
if (t < d / 2) {
5311+
return getEasedT("bounceIn", t * 2, 0, c, d) * 0.5 + b;
5312+
}
5313+
else {
5314+
return getEasedT("bounceOut", t * 2 - d, 0, c, d) * 0.5 + c * 0.5 + b;
5315+
}
5316+
}
5317+
if (easingType.substr(0, 6) == "bezier") {
5318+
std::vector < double > bezierParams = parseBezier(easingType.substr(7));
5319+
if (bezierParams.size() == 4) {
5320+
return cubicBezier(t / d, bezierParams[0], bezierParams[1], bezierParams[2], bezierParams[3]);
5321+
}
5322+
}
5323+
5324+
return c * t / d + b; // Default to linear easing if the easing type is not recognized
5325+
}
5326+
5327+
std::vector<double> interpolate(double start, double end, double duration, const std::string& easingTypeAtStart, const std::string& easingTypeAtEnd) {
5328+
std::vector<double> interpolatedValues;
5329+
interpolatedValues.push_back(start);
5330+
5331+
// Calculate the number of steps based on the duration
5332+
int numSteps = static_cast<int>(duration); // Convert duration to an integer
5333+
double stepSize = 1.0 / numSteps;
5334+
5335+
// Calculate the halfway point
5336+
double halfway = start + (end - start) * 0.5;
5337+
5338+
if (easingTypeAtEnd == "null") {
5339+
// Use easingTypeAtStart for the entire animation
5340+
for (int step = 1; step <= numSteps; ++step) {
5341+
double t = step * stepSize;
5342+
double easedT = getEasedT(easingTypeAtStart, t, 0, 1, 1); // Call getEasedT with appropriate parameters
5343+
double interpolatedValue = start + easedT * (end - start);
5344+
interpolatedValues.push_back(interpolatedValue);
5345+
}
5346+
}
5347+
else {
5348+
// Generate the first half of the interpolation
5349+
for (int step = 1; step <= numSteps / 2; ++step) {
5350+
double t = step * stepSize;
5351+
double normalizedT = t / 0.5; // Normalize the time for the first half
5352+
double easedT = getEasedT(easingTypeAtStart, normalizedT, 0, 1, 1); // Call getEasedT with appropriate parameters
5353+
double interpolatedValue = start + easedT * (halfway - start);
5354+
interpolatedValues.push_back(interpolatedValue);
5355+
}
5356+
5357+
// Generate the second half of the interpolation
5358+
for (int step = numSteps / 2 + 1; step <= numSteps; ++step) {
5359+
double t = step * stepSize;
5360+
double normalizedT = (t - 0.5) / 0.5; // Normalize the time for the second half
5361+
double easedT = getEasedT(easingTypeAtEnd, normalizedT, 0, 1, 1); // Call getEasedT with appropriate parameters
5362+
double interpolatedValue = halfway + easedT * (end - halfway);
5363+
interpolatedValues.push_back(interpolatedValue);
5364+
}
5365+
}
5366+
5367+
interpolatedValues.push_back(end);
5368+
5369+
return interpolatedValues;
5370+
}
5371+
5372+
bool Game_Interpreter::CommandAnimateVariable(lcf::rpg::EventCommand const& com) {
5373+
// CommandInterpolateVariable("typeStart/typeEnd",[useVarTarget, target, useVarStart, start, useVarEnd, end, useVarDuration, duration])
5374+
5375+
auto* frame = GetFramePtr();
5376+
const auto& list = frame->commands;
5377+
auto& index = frame->current_command;
5378+
5379+
int i = frame->current_command + 1;
5380+
5381+
// Extract parameters: target, start, end, and duration for the animation
5382+
int32_t target = ValueOrVariable(com.parameters[0], com.parameters[1]);
5383+
int32_t start = ValueOrVariable(com.parameters[2], com.parameters[3]);
5384+
int32_t end = ValueOrVariable(com.parameters[4], com.parameters[5]);
5385+
int32_t duration = ValueOrVariable(com.parameters[6], com.parameters[7]);
5386+
5387+
// Prepare animation-related commands
5388+
lcf::rpg::EventCommand waitCom;
5389+
waitCom.code = int(Cmd::Wait);
5390+
5391+
lcf::rpg::EventCommand updateVarCom;
5392+
updateVarCom.code = int(Cmd::ControlVars);
5393+
std::vector<int32_t> updateVarParams = { 0, static_cast<int32_t>(target), 0, 0, 0, static_cast<int32_t>(end) };
5394+
updateVarCom.parameters = lcf::DBArray<int32_t>(updateVarParams.begin(), updateVarParams.end());
5395+
5396+
lcf::rpg::EventCommand branchCom;
5397+
branchCom.code = int(Cmd::ShowChoiceOption);
5398+
5399+
// Extract easing information
5400+
std::string easeStart = ToString(com.string);
5401+
std::string easeEnd = "null";
5402+
5403+
std::size_t pos = easeStart.find('/');
5404+
5405+
if (pos != std::string::npos) {
5406+
easeEnd = easeStart.substr(pos + 1);
5407+
easeStart = easeStart.substr(0, pos);
5408+
}
5409+
5410+
// Check if new commands don't exist in the timeline yet
5411+
if (!(i < frame->commands.size() && frame->commands.at(i).code == int(Cmd::ShowChoiceOption))) {
5412+
// Insert animation commands
5413+
Output::Debug("inserting animation commands");
5414+
std::vector<double> interpolatedValues = interpolate(start, end, duration, easeStart, easeEnd);
5415+
5416+
// Insert ShowChoiceOption command
5417+
// This helps me isolating all the "keyframes" commands inside a nested commands, it also helps to avoid creating a repeated list.
5418+
// It's problematic when "start", "end" and "duration" are variables.
5419+
frame->commands.insert(frame->commands.begin() + i, branchCom);
5420+
i++;
5421+
5422+
// Insert updateVarCom and waitCom commands for each interpolated value
5423+
for (int value : interpolatedValues) {
5424+
updateVarParams.back() = value;
5425+
updateVarCom.parameters = lcf::DBArray<int32_t>(updateVarParams.begin(), updateVarParams.end());
5426+
updateVarCom.indent = com.indent + 1;
5427+
5428+
frame->commands.insert(frame->commands.begin() + i, updateVarCom);
5429+
i++;
5430+
frame->commands.insert(frame->commands.begin() + i, waitCom);
5431+
i++;
5432+
}
5433+
5434+
// Insert ShowChoiceEnd command
5435+
branchCom.code = int(Cmd::ShowChoiceEnd);
5436+
frame->commands.insert(frame->commands.begin() + i, branchCom);
5437+
i++;
5438+
}
5439+
else {
5440+
Output::Debug("Animated Commands Already Exists");
5441+
}
5442+
5443+
// Update current_command index and return true to indicate success
5444+
frame->current_command = index + 2;
5445+
return false;
5446+
}
5447+

src/game_interpreter.h

+1
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,7 @@ class Game_Interpreter
289289
bool CommandManiacSetGameOption(lcf::rpg::EventCommand const& com);
290290
bool CommandManiacControlStrings(lcf::rpg::EventCommand const& com);
291291
bool CommandManiacCallCommand(lcf::rpg::EventCommand const& com);
292+
bool CommandAnimateVariable(lcf::rpg::EventCommand const& com);
292293

293294
int DecodeInt(lcf::DBArray<int32_t>::const_iterator& it);
294295
const std::string DecodeString(lcf::DBArray<int32_t>::const_iterator& it);

0 commit comments

Comments
 (0)