@@ -825,6 +825,8 @@ bool Game_Interpreter::ExecuteCommand(lcf::rpg::EventCommand const& com) {
825
825
return CommandManiacControlStrings (com);
826
826
case Cmd::Maniac_CallCommand:
827
827
return CommandManiacCallCommand (com);
828
+ case static_cast <Game_Interpreter::Cmd>(2052 ):// Cmd:: EasyRpg_InterpolateVariable
829
+ return CommandAnimateVariable (com);
828
830
default :
829
831
return true ;
830
832
}
@@ -5112,3 +5114,334 @@ int Game_Interpreter::ManiacBitmask(int value, int mask) const {
5112
5114
5113
5115
return value;
5114
5116
}
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
+
0 commit comments