1 /* ScummVM - Graphic Adventure Engine
2 *
3 * ScummVM is the legal property of its developers, whose names
4 * are too numerous to list here. Please refer to the COPYRIGHT
5 * file distributed with this source distribution.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 *
21 */
22
23 #include "twine/script/script_move_v1.h"
24 #include "common/memstream.h"
25 #include "common/textconsole.h"
26 #include "common/util.h"
27 #include "twine/scene/animations.h"
28 #include "twine/audio/sound.h"
29 #include "twine/movies.h"
30 #include "twine/scene/movements.h"
31 #include "twine/renderer/redraw.h"
32 #include "twine/renderer/renderer.h"
33 #include "twine/renderer/screens.h"
34 #include "twine/scene/scene.h"
35 #include "twine/twine.h"
36
37 namespace TwinE {
38
39 struct MoveScriptContext {
40 int32 actorIdx;
41 ActorStruct *actor;
42 int32 numRepeatSample = 1;
43
44 Common::MemorySeekableReadWriteStream stream;
45
MoveScriptContextTwinE::MoveScriptContext46 MoveScriptContext(int32 _actorIdx, ActorStruct *_actor) : actorIdx(_actorIdx), actor(_actor), stream(actor->_moveScript, actor->_moveScriptSize) {
47 assert(actor->_positionInMoveScript >= 0);
48 stream.skip(actor->_positionInMoveScript);
49 }
50
undoTwinE::MoveScriptContext51 void undo(int32 bytes) {
52 assert(bytes >= 0);
53 // the additional 1 byte is for the opcode
54 stream.rewind(bytes + 1);
55 }
56 };
57
58 /**
59 * Returns @c -1 Need implementation, @c 0 Condition false, @c 1 - Condition true
60 */
61 typedef int32 ScriptMoveFunc(TwinEEngine *engine, MoveScriptContext &ctx);
62
63 struct ScriptMoveFunction {
64 const char *name;
65 ScriptMoveFunc *function;
66 };
67
68 #define MAPFUNC(name, func) \
69 { name, func }
70
71 /**
72 * End of Actor Move Script
73 * @note Opcode @c 0x00
74 */
mEND(TwinEEngine * engine,MoveScriptContext & ctx)75 static int32 mEND(TwinEEngine *engine, MoveScriptContext &ctx) {
76 ctx.actor->_positionInMoveScript = -1;
77 return 1;
78 }
79
80 /**
81 * No Operation
82 * @note Opcode @c 0x01
83 */
mNOP(TwinEEngine * engine,MoveScriptContext & ctx)84 static int32 mNOP(TwinEEngine *engine, MoveScriptContext &ctx) {
85 return 0;
86 }
87
88 /**
89 * Choose new body for the current actor (Parameter = File3D Body Instance)
90 * @note Opcode @c 0x02
91 */
mBODY(TwinEEngine * engine,MoveScriptContext & ctx)92 static int32 mBODY(TwinEEngine *engine, MoveScriptContext &ctx) {
93 BodyType bodyIdx = (BodyType)ctx.stream.readByte();
94 engine->_actor->initModelActor(bodyIdx, ctx.actorIdx);
95 return 0;
96 }
97
98 /**
99 * Choose new animation for the current actor (Parameter = File3D Animation Instance)
100 * @note Opcode @c 0x03
101 */
mANIM(TwinEEngine * engine,MoveScriptContext & ctx)102 static int32 mANIM(TwinEEngine *engine, MoveScriptContext &ctx) {
103 AnimationTypes animIdx = (AnimationTypes)ctx.stream.readByte();
104 if (engine->_animations->initAnim(animIdx, AnimType::kAnimationTypeLoop, AnimationTypes::kStanding, ctx.actorIdx)) {
105 return 0;
106 }
107 ctx.undo(1);
108 return 1;
109 }
110
111 /**
112 * Tell the actor to go to a new position (Parameter = Track Index)
113 * @note Opcode @c 0x04
114 */
mGOTO_POINT(TwinEEngine * engine,MoveScriptContext & ctx)115 static int32 mGOTO_POINT(TwinEEngine *engine, MoveScriptContext &ctx) {
116 engine->_scene->_currentScriptValue = ctx.stream.readByte();
117
118 const IVec3 &sp = engine->_scene->_sceneTracks[engine->_scene->_currentScriptValue];
119 const int32 newAngle = engine->_movements->getAngleAndSetTargetActorDistance(ctx.actor->_pos.x, ctx.actor->_pos.z, sp.x, sp.z);
120
121 if (ctx.actor->_staticFlags.bIsSpriteActor) {
122 ctx.actor->_angle = newAngle;
123 } else {
124 engine->_movements->moveActor(ctx.actor->_angle, newAngle, ctx.actor->_speed, &ctx.actor->_move);
125 }
126
127 if (engine->_movements->_targetActorDistance > 500) {
128 ctx.undo(1);
129 return 1;
130 }
131
132 return 0;
133 }
134
135 /**
136 * Wait the end of the current animation
137 * @note Opcode @c 0x05
138 */
mWAIT_ANIM(TwinEEngine * engine,MoveScriptContext & ctx)139 static int32 mWAIT_ANIM(TwinEEngine *engine, MoveScriptContext &ctx) {
140 if (!ctx.actor->_dynamicFlags.bAnimEnded) {
141 ctx.undo(0);
142 } else {
143 engine->_movements->clearRealAngle(ctx.actor);
144 }
145 return 1;
146 }
147
148 /**
149 * Loop a certain label (Parameter = Label Number)
150 * @note Opcode @c 0x06
151 */
mLOOP(TwinEEngine * engine,MoveScriptContext & ctx)152 static int32 mLOOP(TwinEEngine *engine, MoveScriptContext &ctx) {
153 ctx.actor->_positionInMoveScript = 0;
154 ctx.stream.seek(0);
155 return 0;
156 }
157
158 /**
159 * Make the actor turn around
160 * @note Opcode @c 0x07
161 */
mANGLE(TwinEEngine * engine,MoveScriptContext & ctx)162 static int32 mANGLE(TwinEEngine *engine, MoveScriptContext &ctx) {
163 const int16 angle = ToAngle(ctx.stream.readSint16LE());
164 if (ctx.actor->_staticFlags.bIsSpriteActor) {
165 return 0;
166 }
167 engine->_scene->_currentScriptValue = angle;
168 if (ctx.actor->_move.numOfStep == 0) {
169 engine->_movements->moveActor(ctx.actor->_angle, angle, ctx.actor->_speed, &ctx.actor->_move);
170 }
171 if (ctx.actor->_angle == angle) {
172 engine->_movements->clearRealAngle(ctx.actor);
173 return 0;
174 }
175 ctx.undo(2);
176 return 1;
177 }
178
179 /**
180 * Set new postion for the current actor (Parameter = Track Index)
181 * @note Opcode @c 0x08
182 */
mPOS_POINT(TwinEEngine * engine,MoveScriptContext & ctx)183 static int32 mPOS_POINT(TwinEEngine *engine, MoveScriptContext &ctx) {
184 engine->_scene->_currentScriptValue = ctx.stream.readByte();
185
186 const IVec3 &sp = engine->_scene->_sceneTracks[engine->_scene->_currentScriptValue];
187 if (ctx.actor->_staticFlags.bIsSpriteActor) {
188 ctx.actor->_speed = 0;
189 }
190
191 ctx.actor->_pos = sp;
192
193 return 0;
194 }
195
196 /**
197 * Specify a new label (Parameter = Label Number)
198 * @note Opcode @c 0x09
199 */
mLABEL(TwinEEngine * engine,MoveScriptContext & ctx)200 static int32 mLABEL(TwinEEngine *engine, MoveScriptContext &ctx) {
201 ctx.actor->_labelIdx = ctx.stream.readByte();
202 ctx.actor->_currentLabelPtr = ctx.stream.pos() - 2;
203 if (engine->_scene->_currentSceneIdx == LBA1SceneId::Proxima_Island_Museum && ctx.actor->_actorIdx == 2 &&
204 (ctx.actor->_labelIdx == 0 || ctx.actor->_labelIdx == 1)) {
205 engine->unlockAchievement("LBA_ACH_004");
206 }
207 return 0;
208 }
209
210 /**
211 * Go to a certain label (Parameter = Label Number)
212 * @note Opcode @c 0x0A
213 */
mGOTO(TwinEEngine * engine,MoveScriptContext & ctx)214 static int32 mGOTO(TwinEEngine *engine, MoveScriptContext &ctx) {
215 const int16 pos = ctx.stream.readSint16LE();
216 if (pos == -1) {
217 ctx.actor->_positionInMoveScript = -1;
218 return 1;
219 }
220 ctx.stream.seek(pos);
221 return 0;
222 }
223
224 /**
225 * Tell the actor to stop the current animation
226 * @note Opcode @c 0x0B
227 */
mSTOP(TwinEEngine * engine,MoveScriptContext & ctx)228 static int32 mSTOP(TwinEEngine *engine, MoveScriptContext &ctx) {
229 ctx.actor->_positionInMoveScript = -1;
230 return 1;
231 }
232
233 /**
234 * Tell the actor to go to a symbolic point
235 * @note Opcode @c 0x0C
236 */
mGOTO_SYM_POINT(TwinEEngine * engine,MoveScriptContext & ctx)237 static int32 mGOTO_SYM_POINT(TwinEEngine *engine, MoveScriptContext &ctx) {
238 engine->_scene->_currentScriptValue = ctx.stream.readByte();
239
240 const IVec3 &sp = engine->_scene->_sceneTracks[engine->_scene->_currentScriptValue];
241 const int32 newAngle = ANGLE_180 + engine->_movements->getAngleAndSetTargetActorDistance(ctx.actor->_pos.x, ctx.actor->_pos.z, sp.x, sp.z);
242
243 if (ctx.actor->_staticFlags.bIsSpriteActor) {
244 ctx.actor->_angle = newAngle;
245 } else {
246 engine->_movements->moveActor(ctx.actor->_angle, newAngle, ctx.actor->_speed, &ctx.actor->_move);
247 }
248
249 if (engine->_movements->_targetActorDistance > 500) {
250 ctx.undo(1);
251 return 1;
252 }
253
254 return 0;
255 }
256
257 /**
258 * Wait a certain number of frame update in the current animation
259 * @note Opcode @c 0x0D
260 */
mWAIT_NUM_ANIM(TwinEEngine * engine,MoveScriptContext & ctx)261 static int32 mWAIT_NUM_ANIM(TwinEEngine *engine, MoveScriptContext &ctx) {
262 bool abortMove = false;
263 const int32 animRepeats = ctx.stream.readByte();
264 int32 animPos = ctx.stream.readByte();
265 if (ctx.actor->_dynamicFlags.bAnimEnded) {
266 animPos++;
267
268 if (animPos == animRepeats) {
269 animPos = 0;
270 } else {
271 abortMove = true;
272 }
273
274 ctx.stream.rewind(1);
275 ctx.stream.writeByte(animPos);
276 } else {
277 abortMove = true;
278 }
279
280 if (abortMove) {
281 ctx.undo(2);
282 }
283
284 return abortMove ? 1 : 0;
285 }
286
287 /**
288 * Play a sample (Parameter = Sample index)
289 * @note Opcode @c 0x0E
290 */
mSAMPLE(TwinEEngine * engine,MoveScriptContext & ctx)291 static int32 mSAMPLE(TwinEEngine *engine, MoveScriptContext &ctx) {
292 int32 sampleIdx = ctx.stream.readSint16LE();
293 engine->_sound->playSample(sampleIdx, 1, ctx.actor->pos(), ctx.actorIdx);
294 return 0;
295 }
296
297 /**
298 * Tell the actor to go to a new position (Parameter = Track Index)
299 * @note Opcode @c 0x0F
300 */
mGOTO_POINT_3D(TwinEEngine * engine,MoveScriptContext & ctx)301 static int32 mGOTO_POINT_3D(TwinEEngine *engine, MoveScriptContext &ctx) {
302 const int32 trackId = ctx.stream.readByte();
303 if (!ctx.actor->_staticFlags.bIsSpriteActor) {
304 return 0;
305 }
306
307 engine->_scene->_currentScriptValue = trackId;
308
309 const IVec3 &sp = engine->_scene->_sceneTracks[engine->_scene->_currentScriptValue];
310 ctx.actor->_angle = engine->_movements->getAngleAndSetTargetActorDistance(ctx.actor->_pos.x, ctx.actor->_pos.z, sp.x, sp.z);
311 ctx.actor->_spriteActorRotation = engine->_movements->getAngleAndSetTargetActorDistance(ctx.actor->_pos.y, 0, sp.y, engine->_movements->_targetActorDistance);
312
313 if (engine->_movements->_targetActorDistance > 100) {
314 ctx.undo(1);
315 return 1;
316 }
317 ctx.actor->_pos = sp;
318
319 return 0;
320 }
321
322 /**
323 * Specify a new rotation speed for the current actor (Parameter = Rotation speed) [ 0 means fast, 32767 means slow ]
324 * @note Opcode @c 0x10
325 */
mSPEED(TwinEEngine * engine,MoveScriptContext & ctx)326 static int32 mSPEED(TwinEEngine *engine, MoveScriptContext &ctx) {
327 ctx.actor->_speed = ctx.stream.readSint16LE();
328
329 if (ctx.actor->_staticFlags.bIsSpriteActor) {
330 engine->_movements->setActorAngle(ANGLE_0, ctx.actor->_speed, ANGLE_17, &ctx.actor->_move);
331 }
332
333 return 0;
334 }
335
336 /**
337 * Set actor as background (Parameter = 1 (true); = 0 (false))
338 * @note Opcode @c 0x11
339 */
mBACKGROUND(TwinEEngine * engine,MoveScriptContext & ctx)340 static int32 mBACKGROUND(TwinEEngine *engine, MoveScriptContext &ctx) {
341 if (ctx.stream.readByte() != 0) {
342 if (!ctx.actor->_staticFlags.bIsBackgrounded) {
343 ctx.actor->_staticFlags.bIsBackgrounded = 1;
344 if (ctx.actor->_dynamicFlags.bIsVisible) {
345 engine->_redraw->_reqBgRedraw = true;
346 }
347 }
348 } else {
349 if (ctx.actor->_staticFlags.bIsBackgrounded) {
350 ctx.actor->_staticFlags.bIsBackgrounded = 0;
351 if (ctx.actor->_dynamicFlags.bIsVisible) {
352 engine->_redraw->_reqBgRedraw = true;
353 }
354 }
355 }
356
357 return 0;
358 }
359
360 /**
361 * Number os seconds to wait.
362 * @note Opcode @c 0x12
363 */
mWAIT_NUM_SECOND(TwinEEngine * engine,MoveScriptContext & ctx)364 static int32 mWAIT_NUM_SECOND(TwinEEngine *engine, MoveScriptContext &ctx) {
365 const int32 numSeconds = ctx.stream.readByte();
366 int32 currentTime = ctx.stream.readSint32LE();
367
368 if (currentTime == 0) {
369 currentTime = engine->_lbaTime + numSeconds * 50;
370 ctx.stream.rewind(4);
371 ctx.stream.writeSint32LE(currentTime);
372 }
373
374 if (engine->_lbaTime < currentTime) {
375 ctx.undo(5);
376 return 1;
377 }
378
379 ctx.stream.rewind(4);
380 ctx.stream.writeSint32LE(0);
381
382 return 0;
383 }
384
385 /**
386 * To not use Bodies.
387 * @note Opcode @c 0x13
388 */
mNO_BODY(TwinEEngine * engine,MoveScriptContext & ctx)389 static int32 mNO_BODY(TwinEEngine *engine, MoveScriptContext &ctx) {
390 engine->_actor->initModelActor(BodyType::btNone, ctx.actorIdx);
391 return 0;
392 }
393
394 /**
395 * Change actor orientation. (Parameter = New Angle)
396 * @note Opcode @c 0x14
397 */
mBETA(TwinEEngine * engine,MoveScriptContext & ctx)398 static int32 mBETA(TwinEEngine *engine, MoveScriptContext &ctx) {
399 const int16 beta = ctx.stream.readSint16LE();
400
401 ctx.actor->_angle = beta;
402
403 if (ctx.actor->_staticFlags.bIsSpriteActor) {
404 engine->_movements->clearRealAngle(ctx.actor);
405 }
406
407 return 0;
408 }
409
410 /**
411 * Open the door (left way) (Parameter = distance to open).
412 * @note Opcode @c 0x15
413 */
mOPEN_LEFT(TwinEEngine * engine,MoveScriptContext & ctx)414 static int32 mOPEN_LEFT(TwinEEngine *engine, MoveScriptContext &ctx) {
415 const int16 doorStatus = ctx.stream.readSint16LE();
416 if (ctx.actor->_staticFlags.bIsSpriteActor && ctx.actor->_staticFlags.bUsesClipping) {
417 ctx.actor->_angle = ANGLE_270;
418 ctx.actor->_doorStatus = doorStatus;
419 ctx.actor->_dynamicFlags.bIsSpriteMoving = 1;
420 ctx.actor->_speed = 1000;
421 engine->_movements->setActorAngle(ANGLE_0, ANGLE_351, ANGLE_17, &ctx.actor->_move);
422 }
423 if (engine->_scene->_currentSceneIdx == LBA1SceneId::Proxima_Island_Museum && ctx.actor->_actorIdx == 16) {
424 engine->unlockAchievement("LBA_ACH_009");
425 }
426 return 0;
427 }
428
429 /**
430 * Open the door (right way) (Parameter = distance to open).
431 * @note Opcode @c 0x16
432 */
mOPEN_RIGHT(TwinEEngine * engine,MoveScriptContext & ctx)433 static int32 mOPEN_RIGHT(TwinEEngine *engine, MoveScriptContext &ctx) {
434 const int16 doorStatus = ctx.stream.readSint16LE();
435 if (ctx.actor->_staticFlags.bIsSpriteActor && ctx.actor->_staticFlags.bUsesClipping) {
436 ctx.actor->_angle = ANGLE_90;
437 ctx.actor->_doorStatus = doorStatus;
438 ctx.actor->_dynamicFlags.bIsSpriteMoving = 1;
439 ctx.actor->_speed = 1000;
440 engine->_movements->setActorAngle(ANGLE_0, ANGLE_351, ANGLE_17, &ctx.actor->_move);
441 }
442 if (engine->_scene->_currentSceneIdx == LBA1SceneId::Proxima_Island_Museum && ctx.actor->_actorIdx == 16) {
443 engine->unlockAchievement("LBA_ACH_009");
444 }
445 return 0;
446 }
447
448 /**
449 * Open the door (up way) (Parameter = distance to open).
450 * @note Opcode @c 0x17
451 */
mOPEN_UP(TwinEEngine * engine,MoveScriptContext & ctx)452 static int32 mOPEN_UP(TwinEEngine *engine, MoveScriptContext &ctx) {
453 const int16 doorStatus = ctx.stream.readSint16LE();
454 if (ctx.actor->_staticFlags.bIsSpriteActor && ctx.actor->_staticFlags.bUsesClipping) {
455 ctx.actor->_angle = ANGLE_180;
456 ctx.actor->_doorStatus = doorStatus;
457 ctx.actor->_dynamicFlags.bIsSpriteMoving = 1;
458 ctx.actor->_speed = 1000;
459 engine->_movements->setActorAngle(ANGLE_0, ANGLE_351, ANGLE_17, &ctx.actor->_move);
460 }
461 if (engine->_scene->_currentSceneIdx == LBA1SceneId::Proxima_Island_Museum && ctx.actor->_actorIdx == 16) {
462 engine->unlockAchievement("LBA_ACH_009");
463 }
464 return 0;
465 }
466
467 /**
468 * Open the door (down way) (Parameter = distance to open).
469 * @note Opcode @c 0x18
470 */
mOPEN_DOWN(TwinEEngine * engine,MoveScriptContext & ctx)471 static int32 mOPEN_DOWN(TwinEEngine *engine, MoveScriptContext &ctx) {
472 const int16 doorStatus = ctx.stream.readSint16LE();
473 if (ctx.actor->_staticFlags.bIsSpriteActor && ctx.actor->_staticFlags.bUsesClipping) {
474 ctx.actor->_angle = ANGLE_0;
475 ctx.actor->_doorStatus = doorStatus;
476 ctx.actor->_dynamicFlags.bIsSpriteMoving = 1;
477 ctx.actor->_speed = 1000;
478 engine->_movements->setActorAngle(ANGLE_0, ANGLE_351, ANGLE_17, &ctx.actor->_move);
479 }
480 if (engine->_scene->_currentSceneIdx == LBA1SceneId::Proxima_Island_Museum && ctx.actor->_actorIdx == 16) {
481 engine->unlockAchievement("LBA_ACH_009");
482 }
483 return 0;
484 }
485
486 /**
487 * Close the door.
488 * @note Opcode @c 0x19
489 */
mCLOSE(TwinEEngine * engine,MoveScriptContext & ctx)490 static int32 mCLOSE(TwinEEngine *engine, MoveScriptContext &ctx) {
491 if (ctx.actor->_staticFlags.bIsSpriteActor && ctx.actor->_staticFlags.bUsesClipping) {
492 ctx.actor->_doorStatus = 0;
493 ctx.actor->_dynamicFlags.bIsSpriteMoving = 1;
494 ctx.actor->_speed = -1000;
495 engine->_movements->setActorAngle(ANGLE_0, -ANGLE_351, ANGLE_17, &ctx.actor->_move);
496 }
497 return 0;
498 }
499
500 /**
501 * Wait till door close.
502 * @note Opcode @c 0x1A
503 */
mWAIT_DOOR(TwinEEngine * engine,MoveScriptContext & ctx)504 static int32 mWAIT_DOOR(TwinEEngine *engine, MoveScriptContext &ctx) {
505 if (ctx.actor->_staticFlags.bIsSpriteActor && ctx.actor->_staticFlags.bUsesClipping) {
506 if (ctx.actor->_speed) {
507 ctx.undo(0);
508 return 1;
509 }
510 }
511 return 0;
512 }
513
514 /**
515 * Generate a random sample.
516 * @note Opcode @c 0x1B
517 */
mSAMPLE_RND(TwinEEngine * engine,MoveScriptContext & ctx)518 static int32 mSAMPLE_RND(TwinEEngine *engine, MoveScriptContext &ctx) {
519 int32 sampleIdx = ctx.stream.readSint16LE();
520 engine->_sound->playSample(sampleIdx, 1, ctx.actor->pos(), ctx.actorIdx);
521 return 0;
522 }
523
524 /**
525 * Play always the sample (Parameter = Sample index)
526 * @note Opcode @c 0x1C
527 */
mSAMPLE_ALWAYS(TwinEEngine * engine,MoveScriptContext & ctx)528 static int32 mSAMPLE_ALWAYS(TwinEEngine *engine, MoveScriptContext &ctx) {
529 int32 sampleIdx = ctx.stream.readSint16LE();
530 if (!engine->_sound->isSamplePlaying(sampleIdx)) { // if its not playing
531 engine->_sound->playSample(sampleIdx, -1, ctx.actor->pos(), ctx.actorIdx);
532 }
533 return 0;
534 }
535
536 /**
537 * Stop playing the sample
538 * @note Opcode @c 0x1D
539 */
mSAMPLE_STOP(TwinEEngine * engine,MoveScriptContext & ctx)540 static int32 mSAMPLE_STOP(TwinEEngine *engine, MoveScriptContext &ctx) {
541 int32 sampleIdx = ctx.stream.readSint16LE();
542 engine->_sound->stopSample(sampleIdx);
543 return 0;
544 }
545
546 /**
547 * Play FLA cutscenes (Parameter = Cutscene Name)
548 * @note Opcode @c 0x1E
549 */
mPLAY_FLA(TwinEEngine * engine,MoveScriptContext & ctx)550 static int32 mPLAY_FLA(TwinEEngine *engine, MoveScriptContext &ctx) {
551 int strIdx = 0;
552 char movie[64];
553 do {
554 const byte c = ctx.stream.readByte();
555 movie[strIdx++] = c;
556 if (c == '\0') {
557 break;
558 }
559 if (strIdx >= ARRAYSIZE(movie)) {
560 error("Max string size exceeded for fla name");
561 }
562 } while (true);
563
564 engine->_flaMovies->playFlaMovie(movie);
565 engine->setPalette(engine->_screens->_paletteRGBA);
566 engine->_screens->clearScreen();
567 return 0;
568 }
569
570 /**
571 * Repeat sample (Parameter = Sample index).
572 * @note Opcode @c 0x1F
573 */
mREPEAT_SAMPLE(TwinEEngine * engine,MoveScriptContext & ctx)574 static int32 mREPEAT_SAMPLE(TwinEEngine *engine, MoveScriptContext &ctx) {
575 ctx.numRepeatSample = ctx.stream.readSint16LE();
576 return 0;
577 }
578
579 /**
580 * Play a sample (Parameter = Sample index)
581 * @note Opcode @c 0x20
582 */
mSIMPLE_SAMPLE(TwinEEngine * engine,MoveScriptContext & ctx)583 static int32 mSIMPLE_SAMPLE(TwinEEngine *engine, MoveScriptContext &ctx) {
584 int32 sampleIdx = ctx.stream.readSint16LE();
585 engine->_sound->playSample(sampleIdx, ctx.numRepeatSample, ctx.actor->pos(), ctx.actorIdx);
586 ctx.numRepeatSample = 1;
587 return 0;
588 }
589
590 /**
591 * The actor rotate to Twinsen direction (Parameter = -1 (near); = 0 (far))
592 * @note Opcode @c 0x21
593 */
mFACE_HERO(TwinEEngine * engine,MoveScriptContext & ctx)594 static int32 mFACE_HERO(TwinEEngine *engine, MoveScriptContext &ctx) {
595 const int16 angle = ToAngle(ctx.stream.readSint16LE());
596 if (ctx.actor->_staticFlags.bIsSpriteActor) {
597 return 0;
598 }
599 engine->_scene->_currentScriptValue = angle;
600 if (engine->_scene->_currentScriptValue == -1 && ctx.actor->_move.numOfStep == 0) {
601 engine->_scene->_currentScriptValue = engine->_movements->getAngleAndSetTargetActorDistance(ctx.actor->pos(), engine->_scene->_sceneHero->pos());
602 engine->_movements->moveActor(ctx.actor->_angle, engine->_scene->_currentScriptValue, ctx.actor->_speed, &ctx.actor->_move);
603 ctx.stream.rewind(2);
604 ctx.stream.writeSint16LE(engine->_scene->_currentScriptValue);
605 }
606
607 if (ctx.actor->_angle != engine->_scene->_currentScriptValue) {
608 ctx.undo(2);
609 return 1;
610 }
611 engine->_movements->clearRealAngle(ctx.actor);
612 ctx.stream.rewind(2);
613 ctx.stream.writeSint16LE(-1);
614 return 0;
615 }
616
617 /**
618 * Generate an random angle for the current actor
619 * @note Opcode @c 0x22
620 */
mANGLE_RND(TwinEEngine * engine,MoveScriptContext & ctx)621 static int32 mANGLE_RND(TwinEEngine *engine, MoveScriptContext &ctx) {
622 const int16 val1 = ctx.stream.readSint16LE();
623 const int16 val2 = ctx.stream.readSint16LE();
624 if (ctx.actor->_staticFlags.bIsSpriteActor) {
625 return 0;
626 }
627
628 engine->_scene->_currentScriptValue = val2;
629
630 if (engine->_scene->_currentScriptValue == -1 && ctx.actor->_move.numOfStep == 0) {
631 if (engine->getRandomNumber() & 1) {
632 const int32 newAngle = ctx.actor->_angle + ANGLE_90 + (ABS(val1) >> 1);
633 engine->_scene->_currentScriptValue = ClampAngle(newAngle - engine->getRandomNumber(val1));
634 } else {
635 const int32 newAngle = ctx.actor->_angle - ANGLE_90 + (ABS(val1) >> 1);
636 engine->_scene->_currentScriptValue = ClampAngle(newAngle - engine->getRandomNumber(val1));
637 }
638
639 engine->_movements->moveActor(ctx.actor->_angle, engine->_scene->_currentScriptValue, ctx.actor->_speed, &ctx.actor->_move);
640 ctx.stream.rewind(2);
641 ctx.stream.writeSint16LE(engine->_scene->_currentScriptValue);
642 }
643
644 if (ctx.actor->_angle != engine->_scene->_currentScriptValue) {
645 ctx.undo(4);
646 return 1;
647 }
648 engine->_movements->clearRealAngle(ctx.actor);
649 ctx.stream.rewind(2);
650 ctx.stream.writeSint16LE(-1);
651 return 0;
652 }
653
654 static const ScriptMoveFunction function_map[] = {
655 /*0x00*/ MAPFUNC("END", mEND),
656 /*0x01*/ MAPFUNC("NOP", mNOP),
657 /*0x02*/ MAPFUNC("BODY", mBODY),
658 /*0x03*/ MAPFUNC("ANIM", mANIM),
659 /*0x04*/ MAPFUNC("GOTO_POINT", mGOTO_POINT),
660 /*0x05*/ MAPFUNC("WAIT_ANIM", mWAIT_ANIM),
661 /*0x06*/ MAPFUNC("LOOP", mLOOP),
662 /*0x07*/ MAPFUNC("ANGLE", mANGLE),
663 /*0x08*/ MAPFUNC("POS_POINT", mPOS_POINT),
664 /*0x09*/ MAPFUNC("LABEL", mLABEL),
665 /*0x0A*/ MAPFUNC("GOTO", mGOTO),
666 /*0x0B*/ MAPFUNC("STOP", mSTOP),
667 /*0x0C*/ MAPFUNC("GOTO_SYM_POINT", mGOTO_SYM_POINT),
668 /*0x0D*/ MAPFUNC("WAIT_NUM_ANIM", mWAIT_NUM_ANIM),
669 /*0x0E*/ MAPFUNC("SAMPLE", mSAMPLE),
670 /*0x0F*/ MAPFUNC("GOTO_POINT_3D", mGOTO_POINT_3D),
671 /*0x10*/ MAPFUNC("SPEED", mSPEED),
672 /*0x11*/ MAPFUNC("BACKGROUND", mBACKGROUND),
673 /*0x12*/ MAPFUNC("WAIT_NUM_SECOND", mWAIT_NUM_SECOND),
674 /*0x13*/ MAPFUNC("NO_BODY", mNO_BODY),
675 /*0x14*/ MAPFUNC("BETA", mBETA),
676 /*0x15*/ MAPFUNC("OPEN_LEFT", mOPEN_LEFT),
677 /*0x16*/ MAPFUNC("OPEN_RIGHT", mOPEN_RIGHT),
678 /*0x17*/ MAPFUNC("OPEN_UP", mOPEN_UP),
679 /*0x18*/ MAPFUNC("OPEN_DOWN", mOPEN_DOWN),
680 /*0x19*/ MAPFUNC("CLOSE", mCLOSE),
681 /*0x1A*/ MAPFUNC("WAIT_DOOR", mWAIT_DOOR),
682 /*0x1B*/ MAPFUNC("SAMPLE_RND", mSAMPLE_RND),
683 /*0x1C*/ MAPFUNC("SAMPLE_ALWAYS", mSAMPLE_ALWAYS),
684 /*0x1D*/ MAPFUNC("SAMPLE_STOP", mSAMPLE_STOP),
685 /*0x1E*/ MAPFUNC("PLAY_FLA", mPLAY_FLA),
686 /*0x1F*/ MAPFUNC("REPEAT_SAMPLE", mREPEAT_SAMPLE),
687 /*0x20*/ MAPFUNC("SIMPLE_SAMPLE", mSIMPLE_SAMPLE),
688 /*0x21*/ MAPFUNC("FACE_HERO", mFACE_HERO),
689 /*0x22*/ MAPFUNC("ANGLE_RND", mANGLE_RND)};
690
ScriptMove(TwinEEngine * engine)691 ScriptMove::ScriptMove(TwinEEngine *engine) : _engine(engine) {
692 }
693
processMoveScript(int32 actorIdx)694 void ScriptMove::processMoveScript(int32 actorIdx) {
695 ActorStruct *actor = _engine->_scene->getActor(actorIdx);
696
697 int32 end = -2;
698
699 MoveScriptContext ctx(actorIdx, actor);
700 do {
701 const byte scriptOpcode = ctx.stream.readByte();
702 if (scriptOpcode < ARRAYSIZE(function_map)) {
703 end = function_map[scriptOpcode].function(_engine, ctx);
704 } else {
705 error("Actor %d with wrong offset/opcode - Offset: %d/%d (opcode: %u)", actorIdx, (int)ctx.stream.pos() - 1, (int)ctx.stream.size(), scriptOpcode);
706 }
707
708 if (end < 0) {
709 warning("Actor %d Life script [%s] not implemented", actorIdx, function_map[scriptOpcode].name);
710 }
711 if (ctx.actor->_positionInMoveScript != -1) {
712 actor->_positionInMoveScript = ctx.stream.pos();
713 }
714 } while (end != 1);
715 }
716
717 } // namespace TwinE
718