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