[Scummvm-git-logs] scummvm master -> 1e9337f5c704ad7425df951f9c31f2099d17de50

mgerhardy noreply at scummvm.org
Fri Jan 2 22:33:37 UTC 2026


This automated email contains information about 3 new commits which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .

Summary:
1a23d89821 TWINE: reference to original name
e029e88c0d TWINE: LBA2: implemented a few more opcodes
1e9337f5c7 TWINE: LBA2: started with filling the rain stubs


Commit: 1a23d8982155a43406e3160ea755caeb351737de
    https://github.com/scummvm/scummvm/commit/1a23d8982155a43406e3160ea755caeb351737de
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-01-02T23:26:37+01:00

Commit Message:
TWINE: reference to original name

Changed paths:
    engines/twine/scene/grid.h


diff --git a/engines/twine/scene/grid.h b/engines/twine/scene/grid.h
index 74d7b0ec0ab..5c9446f06dc 100644
--- a/engines/twine/scene/grid.h
+++ b/engines/twine/scene/grid.h
@@ -22,7 +22,7 @@
 #ifndef TWINE_SCENE_GRID_H
 #define TWINE_SCENE_GRID_H
 
-#define WATER_BRICK (0xF1)
+#define WATER_BRICK (0xF1) // CJ_WATER
 
 #include "common/scummsys.h"
 #include "twine/parser/blocklibrary.h"


Commit: e029e88c0d3e2750f43ff0dffb86553404e855d8
    https://github.com/scummvm/scummvm/commit/e029e88c0d3e2750f43ff0dffb86553404e855d8
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-01-02T23:26:46+01:00

Commit Message:
TWINE: LBA2: implemented a few more opcodes

Changed paths:
    engines/twine/audio/sound.h
    engines/twine/renderer/redraw.cpp
    engines/twine/renderer/redraw.h
    engines/twine/scene/collision.cpp
    engines/twine/scene/collision.h
    engines/twine/scene/grid.h
    engines/twine/scene/scene.h
    engines/twine/script/script_life_v2.cpp
    engines/twine/script/script_move_v2.cpp
    engines/twine/script/script_move_v2.h
    engines/twine/shared.h


diff --git a/engines/twine/audio/sound.h b/engines/twine/audio/sound.h
index 21172090dc0..5f202131c59 100644
--- a/engines/twine/audio/sound.h
+++ b/engines/twine/audio/sound.h
@@ -70,6 +70,10 @@ private:
 	/** Remove a sample from the channel usage list */
 	void removeChannelWatch(int32 channelIdx);
 public:
+	int32 _parmSampleVolume = 127;
+	int32 _parmSampleDecalage = 0;
+	int32 _parmSampleFrequence = 0;
+
 	Sound(TwinEEngine *engine);
 	~Sound();
 
diff --git a/engines/twine/renderer/redraw.cpp b/engines/twine/renderer/redraw.cpp
index 24f4ef0fef0..3a55dfc5a87 100644
--- a/engines/twine/renderer/redraw.cpp
+++ b/engines/twine/renderer/redraw.cpp
@@ -855,10 +855,13 @@ void Redraw::setRenderText(const Common::String &text) {
 }
 
 void Redraw::renderText() {
-	if (_textDisappearTime <= _engine->timerRef) {
+	if (_text.empty()) {
 		return;
 	}
-	if (_text.empty()) {
+
+	if (_textDisappearTime != -1 && _engine->timerRef > _textDisappearTime) {
+		_text.clear();
+		_textDisappearTime = -1;
 		return;
 	}
 	_engine->_text->setFontColor(COLOR_WHITE);
@@ -874,6 +877,10 @@ void Redraw::renderText() {
 	addPhysBox(redraw);
 }
 
+void Redraw::fillBackground(uint8 color) {
+	_engine->_frontVideoBuffer.fillRect(Common::Rect(0, 0, _engine->width(), _engine->height()), color);
+}
+
 void Redraw::drawScene(bool flagflip) { // AffScene
 	int32 tmp_projPosX = _projPosScreen.x;
 	int32 tmp_projPosY = _projPosScreen.y;
diff --git a/engines/twine/renderer/redraw.h b/engines/twine/renderer/redraw.h
index 678746f3bbb..b2089df25ec 100644
--- a/engines/twine/renderer/redraw.h
+++ b/engines/twine/renderer/redraw.h
@@ -141,6 +141,8 @@ private:
 	void renderOverlays();
 	void renderText();
 
+	void fillBackground(uint8 color);
+
 public:
 	Redraw(TwinEEngine *engine);
 
diff --git a/engines/twine/scene/collision.cpp b/engines/twine/scene/collision.cpp
index e12e7de8db9..bb8b8cd3672 100644
--- a/engines/twine/scene/collision.cpp
+++ b/engines/twine/scene/collision.cpp
@@ -516,4 +516,9 @@ int32 Collision::extraCheckExtraCol(ExtraListStruct *extra, int32 extraIdx) cons
 	return -1;
 }
 
+void Collision::doImpact(int32 num, int32 x, int32 y, int32 z, int32 owner) {
+	debugC(3, kDebugLevels::kDebugCollision, "Collision::doImpact(%i, %i, %i, %i, %i)", num, x, y, z, owner);
+	// TODO: Implement me
+}
+
 } // namespace TwinE
diff --git a/engines/twine/scene/collision.h b/engines/twine/scene/collision.h
index c0beca7f5bb..d60896189f8 100644
--- a/engines/twine/scene/collision.h
+++ b/engines/twine/scene/collision.h
@@ -51,6 +51,8 @@ public:
 	 */
 	bool checkZvOnZv(int32 actorIdx1, int32 actorIdx2) const;
 
+	void doImpact(int32 num, int32 x, int32 y, int32 z, int32 owner);
+
 	/**
 	 * Reajust actor position in scene according with brick shape bellow actor
 	 * @param brickShape Shape of brick bellow the actor
diff --git a/engines/twine/scene/grid.h b/engines/twine/scene/grid.h
index 5c9446f06dc..712519b155f 100644
--- a/engines/twine/scene/grid.h
+++ b/engines/twine/scene/grid.h
@@ -199,6 +199,8 @@ public:
 	/** Current grid camera x, y and z coordinates */
 	IVec3 _worldCube; // WorldXCube WorldYCube
 
+	int32 _addBetaCam = 0;
+
 	/** Flag to know if the engine is using celling grids */
 	int16 _zoneGrm = 0;
 	/** Current celling grid index */
diff --git a/engines/twine/scene/scene.h b/engines/twine/scene/scene.h
index 401b450557a..dcd4abde649 100644
--- a/engines/twine/scene/scene.h
+++ b/engines/twine/scene/scene.h
@@ -177,6 +177,7 @@ public:
 	uint8 _island = 0;
 	uint8 _shadowLevel = 0; // lba2
 	uint8 _modeLabyrinthe = 0; // lba2
+	uint8 _cinemaMode = 0; // lba2
 	uint8 _currentCubeX = 0; // lba2
 	uint8 _currentCubeY = 0; // lba2
 
diff --git a/engines/twine/script/script_life_v2.cpp b/engines/twine/script/script_life_v2.cpp
index 6394f8d7ce3..a2586d3a26d 100644
--- a/engines/twine/script/script_life_v2.cpp
+++ b/engines/twine/script/script_life_v2.cpp
@@ -19,6 +19,8 @@
  *
  */
 
+#include "twine/scene/grid.h"
+#include "twine/scene/collision.h"
 #include "twine/script/script_life_v2.h"
 #include "twine/audio/sound.h"
 #include "twine/audio/music.h"
@@ -212,8 +214,15 @@ int32 ScriptLifeV2::lPALETTE(TwinEEngine *engine, LifeScriptContext &ctx) {
 int32 ScriptLifeV2::lFADE_TO_PAL(TwinEEngine *engine, LifeScriptContext &ctx) {
 	const int32 palIndex = engine->_screens->mapLba2Palette(ctx.stream.readByte());
 	debugC(3, kDebugLevels::kDebugScriptsLife, "LIFE::FADE_TO_PAL(%i)", palIndex);
-	// TODO: implement
-	return -1;
+	engine->saveTimer(false);
+	HQR::getPaletteEntry(engine->_screens->_ptrPal, Resources::HQR_RESS_FILE, palIndex);
+	engine->_screens->fadeToPal(engine->_screens->_ptrPal);
+	engine->_screens->_flagPalettePcx = true;
+	if (palIndex == 3) { // Black palette?
+		engine->_screens->_flagFade = true;
+	}
+	engine->restoreTimer();
+	return 0;
 }
 
 int32 ScriptLifeV2::lPLAY_MUSIC(TwinEEngine *engine, LifeScriptContext &ctx) {
@@ -445,27 +454,51 @@ int32 ScriptLifeV2::lANIM_TEXTURE(TwinEEngine *engine, LifeScriptContext &ctx) {
 }
 
 int32 ScriptLifeV2::lADD_MESSAGE_OBJ(TwinEEngine *engine, LifeScriptContext &ctx) {
+	const int32 actorIdx = ctx.stream.readByte();
 	const TextId textIdx = (TextId)ctx.stream.readSint16LE();
-	debugC(3, kDebugLevels::kDebugScriptsLife, "LIFE::lADD_MESSAGE_OBJ(%i)", (int)textIdx);
-	// TODO: implement me
-	return -1;
+	debugC(3, kDebugLevels::kDebugScriptsLife, "LIFE::lADD_MESSAGE_OBJ(%i, %i)", (int)actorIdx, (int)textIdx);
+
+	if (engine->_scene->getActor(actorIdx)->_lifePoint > 0) {
+		engine->saveTimer(false);
+		engine->testRestoreModeSVGA(true);
+		if (engine->_text->_showDialogueBubble) {
+			engine->_redraw->drawBubble(actorIdx);
+		}
+		engine->_text->setFontCrossColor(engine->_scene->getActor(actorIdx)->_talkColor);
+		engine->_scene->_talkingActor = (int16)actorIdx;
+		engine->setPalette(engine->_screens->_ptrPal);
+		engine->_text->drawTextProgressive(textIdx);
+		engine->_redraw->drawScene(true);
+		engine->restoreTimer();
+	}
+	return 0;
 }
 
 int32 ScriptLifeV2::lADD_MESSAGE(TwinEEngine *engine, LifeScriptContext &ctx) {
 	const TextId textIdx = (TextId)ctx.stream.readSint16LE();
 	debugC(3, kDebugLevels::kDebugScriptsLife, "LIFE::lADD_MESSAGE(%i)", (int)textIdx);
-	// TODO: implement me
-	return -1;
+
+	engine->saveTimer(false);
+	engine->testRestoreModeSVGA(true);
+	if (engine->_text->_showDialogueBubble) {
+		engine->_redraw->drawBubble(ctx.actorIdx);
+	}
+	engine->_text->setFontCrossColor(ctx.actor->_talkColor);
+	engine->_scene->_talkingActor = (int16)ctx.actorIdx;
+	engine->setPalette(engine->_screens->_ptrPal);
+	engine->_text->drawTextProgressive(textIdx);
+	engine->_redraw->drawScene(true);
+	engine->restoreTimer();
+	return 0;
 }
 
 int32 ScriptLifeV2::lCAMERA_CENTER(TwinEEngine *engine, LifeScriptContext &ctx) {
 	const int32 angle = ClampAngle(ToAngle(ctx.stream.readByte() * 1024));
 	debugC(3, kDebugLevels::kDebugScriptsLife, "LIFE::lCAMERA_CENTER(%i)", (int)angle);
-	// TODO: implement me - see centerOnActor in grid
-	// AddBetaCam = num ;
-	// CameraCenter( 2 ) ;
-	// FirstTime = AFF_ALL_FLIP ;
-	return -1;
+	engine->_grid->_addBetaCam = angle;
+	engine->_grid->centerOnActor(engine->_scene->getActor(2));
+	engine->_redraw->_firstTime = true;
+	return 0;
 }
 
 int32 ScriptLifeV2::lBUBBLE(TwinEEngine *engine, LifeScriptContext &ctx) {
@@ -482,10 +515,22 @@ int32 ScriptLifeV2::lNO_CHOC(TwinEEngine *engine, LifeScriptContext &ctx) {
 }
 
 int32 ScriptLifeV2::lCINEMA_MODE(TwinEEngine *engine, LifeScriptContext &ctx) {
-	const uint8 val = ctx.stream.readByte();
-	debugC(3, kDebugLevels::kDebugScriptsLife, "LIFE::lCINEMA_MODE(%i)", (int)val);
-	// TODO: implement me
-	return -1;
+	const uint8 num = ctx.stream.readByte();
+	debugC(3, kDebugLevels::kDebugScriptsLife, "LIFE::lCINEMA_MODE(%i)", (int)num);
+	if (num != engine->_scene->_cinemaMode) {
+		engine->_scene->_cinemaMode = num;
+		if (!num) {
+			engine->_redraw->_firstTime = true;
+			// TODO: DureeCycleCinema = BoundRegleTrois( 1, 500, 39, ClipWindowYMin ) ;
+		} else {
+			// TODO: DureeCycleCinema = BoundRegleTrois( 1, 500, 39, 39-ClipWindowYMin ) ;
+			engine->_gameState->setGameFlag(GAMEFLAG_ESC, 0);
+		}
+		// TODO: DebCycleCinema = ClipWindowYMin ;
+		// TODO: LastYCinema = ClipWindowYMin ;
+		// TODO: TimerCinema = TimerRefHR ;
+	}
+	return 0;
 }
 
 int32 ScriptLifeV2::lSAVE_HERO(TwinEEngine *engine, LifeScriptContext &ctx) {
@@ -565,9 +610,18 @@ int32 ScriptLifeV2::lPLAY_ACF(TwinEEngine *engine, LifeScriptContext &ctx) {
 	} while (true);
 	debugC(3, kDebugLevels::kDebugScriptsLife, "LIFE::lPLAY_ACF(%s)", movie);
 
+	if (!engine->_screens->_flagFade) {
+		// TODO: FadeToBlackAndSamples( PtrPal ) ;
+	}
+	engine->_screens->_flagFade = true;
+
 	engine->_movie->playMovie(movie);
 	// TODO: lba2 is doing more stuff here - reset the cinema mode, init the scene and palette stuff
+	// if (CubeMode==CUBE_INTERIEUR) InitGrille( NumCube ) ;
+	// RazListPartFlow() ;
+	// ChoicePalette() ;
 	engine->setPalette(engine->_screens->_ptrPal);
+	engine->_screens->_flagFade = true;
 	engine->restoreTimer();
 	engine->_redraw->_firstTime = true;
 
@@ -632,10 +686,24 @@ int32 ScriptLifeV2::lSET_CHANGE_CUBE(TwinEEngine *engine, LifeScriptContext &ctx
 }
 
 int32 ScriptLifeV2::lMESSAGE_ZOE(TwinEEngine *engine, LifeScriptContext &ctx) {
-	const int16 textIdx = ctx.stream.readSint16LE();
+	const TextId textIdx = (TextId)ctx.stream.readSint16LE();
 	debugC(3, kDebugLevels::kDebugScriptsLife, "LIFE::lMESSAGE_ZOE(%i)", (int)textIdx);
-	// TODO: implement me
-	return -1;
+
+	engine->_scene->_talkingActor = OWN_ACTOR_SCENE_INDEX;
+	ActorStruct *hero = engine->_scene->getActor(OWN_ACTOR_SCENE_INDEX);
+	int32 oldColor = hero->_talkColor;
+	hero->_talkColor = 1; // COUL_ZOE
+
+	engine->saveTimer(false);
+	engine->testRestoreModeSVGA(true);
+	engine->_text->setFontCrossColor(hero->_talkColor);
+	engine->setPalette(engine->_screens->_ptrPal);
+	engine->_text->drawTextProgressive(textIdx);
+	engine->_redraw->drawScene(true);
+	engine->restoreTimer();
+
+	hero->_talkColor = oldColor;
+	return 0;
 }
 
 int32 ScriptLifeV2::lACTION(TwinEEngine *engine, LifeScriptContext &ctx) {
@@ -648,9 +716,9 @@ int32 ScriptLifeV2::lSET_FRAME(TwinEEngine *engine, LifeScriptContext &ctx) {
 	const int frame = ctx.stream.readByte();
 	debugC(3, kDebugLevels::kDebugScriptsLife, "LIFE::lSET_FRAME(%i)", (int)frame);
 	if (!ctx.actor->_flags.bSprite3D) {
-		// TODO: ObjectSetFrame(ctx.actorIdx, frame);
+		engine->_actor->setFrame(ctx.actorIdx, frame);
 	}
-	return -1;
+	return 0;
 }
 
 int32 ScriptLifeV2::lSET_SPRITE(TwinEEngine *engine, LifeScriptContext &ctx) {
@@ -683,21 +751,18 @@ int32 ScriptLifeV2::lIMPACT_OBJ(TwinEEngine *engine, LifeScriptContext &ctx) {
 	debugC(3, kDebugLevels::kDebugScriptsLife, "LIFE::lIMPACT_OBJ(%i, %i, %i)", (int)num, (int)n, (int)y);
 	ActorStruct *otherActor = engine->_scene->getActor(num);
 	if (otherActor->_lifePoint > 0) {
-		// TODO: DoImpact(n, otherActor->_pos.x, otherActor->_pos.y + y, otherActor->_pos.z, num);
+		engine->_collision->doImpact(n, otherActor->posObj().x, otherActor->posObj().y + y, otherActor->posObj().z, num);
 	}
-	return -1;
+	return 0;
 }
 
 int32 ScriptLifeV2::lIMPACT_POINT(TwinEEngine *engine, LifeScriptContext &ctx) {
 	const uint8 brickTrackId = ctx.stream.readByte();
 	const uint16 n = ctx.stream.readUint16LE();
 	debugC(3, kDebugLevels::kDebugScriptsLife, "LIFE::lIMPACT_POINT(%i, %i)", (int)brickTrackId, (int)n);
-	// const IVec3 &pos = engine->_scene->_sceneTracks[brickTrackId];
-	// int16 x0 = pos.x;
-	// int16 y0 = pos.y;
-	// int16 z0 = pos.z;
-	// TODO: DoImpact(n, x0, y0, z0, ctx.actorIdx);
-	return -1;
+	const IVec3 &pos = engine->_scene->_sceneTracks[brickTrackId];
+	engine->_collision->doImpact(n, pos.x, pos.y, pos.z, ctx.actorIdx);
+	return 0;
 }
 
 // ECHELLE
@@ -810,43 +875,63 @@ int32 ScriptLifeV2::lRESTORE_COMPORTEMENT(TwinEEngine *engine, LifeScriptContext
 }
 
 int32 ScriptLifeV2::lSAMPLE(TwinEEngine *engine, LifeScriptContext &ctx) {
-	const int16 sample = ctx.stream.readSint16LE();
-	debugC(3, kDebugLevels::kDebugScriptsLife, "LIFE::lSAMPLE(%i)", (int)sample);
-	// TODO: HQ_3D_MixSample(sample, 0x1000, 0, 1, ctx.actor->posObj());
-	return -1;
+	const uint16 sampleIdx = ctx.stream.readUint16LE();
+	debugC(3, kDebugLevels::kDebugScriptsLife, "LIFE::lSAMPLE(%i)", (int)sampleIdx);
+	engine->_sound->mixSample3D(sampleIdx, 0x1000, 1, ctx.actor->posObj(), ctx.actorIdx);
+	return 0;
 }
 
 int32 ScriptLifeV2::lSAMPLE_RND(TwinEEngine *engine, LifeScriptContext &ctx) {
-	const int16 sample = ctx.stream.readSint16LE();
-	debugC(3, kDebugLevels::kDebugScriptsLife, "LIFE::lSAMPLE_RND(%i)", (int)sample);
-	// TODO: HQ_3D_MixSample(sample, 0x800, 0x1000, 1, ctx.actor->posObj());
-	return -1;
+	const uint16 sampleIdx = ctx.stream.readUint16LE();
+	debugC(3, kDebugLevels::kDebugScriptsLife, "LIFE::lSAMPLE_RND(%i)", (int)sampleIdx);
+	int32 frequency = 0x800 + engine->getRandomNumber(0x800);
+	engine->_sound->mixSample3D(sampleIdx, frequency, 1, ctx.actor->posObj(), ctx.actorIdx);
+	return 0;
 }
 
 int32 ScriptLifeV2::lSAMPLE_ALWAYS(TwinEEngine *engine, LifeScriptContext &ctx) {
-	const int16 sample = ctx.stream.readSint16LE();
-	debugC(3, kDebugLevels::kDebugScriptsLife, "LIFE::lSAMPLE_ALWAYS(%i)", (int)sample);
-	// TODO:
-	return -1;
+	const uint16 sampleIdx = ctx.stream.readUint16LE();
+	debugC(3, kDebugLevels::kDebugScriptsLife, "LIFE::lSAMPLE_ALWAYS(%i)", (int)sampleIdx);
+	if (!engine->_sound->isSamplePlaying(sampleIdx)) {
+		engine->_sound->mixSample3D(sampleIdx, 0x1000, 1, ctx.actor->posObj(), ctx.actorIdx);
+	}
+	return 0;
 }
 
 int32 ScriptLifeV2::lSAMPLE_STOP(TwinEEngine *engine, LifeScriptContext &ctx) {
-	const int16 sample = ctx.stream.readSint16LE();
-	debugC(3, kDebugLevels::kDebugScriptsLife, "LIFE::lSAMPLE_STOP(%i)", (int)sample);
-	// TODO:
-	return -1;
+	const uint16 sampleIdx = ctx.stream.readUint16LE();
+	debugC(3, kDebugLevels::kDebugScriptsLife, "LIFE::lSAMPLE_STOP(%i)", (int)sampleIdx);
+	engine->_sound->stopSample(sampleIdx);
+	return 0;
 }
 
 int32 ScriptLifeV2::lREPEAT_SAMPLE(TwinEEngine *engine, LifeScriptContext &ctx) {
-	const int16 sample = ctx.stream.readSint16LE();
-	uint8 repeat = ctx.stream.readByte();
-	debugC(3, kDebugLevels::kDebugScriptsLife, "LIFE::lREPEAT_SAMPLE(%i, %i)", (int)sample, (int)repeat);
-	// TODO: HQ_3D_MixSample(sample, 0x1000, 0, repeat, ctx.actor->posObj());
-	return -1;
+	const uint16 sampleIdx = ctx.stream.readUint16LE();
+	const uint8 num = ctx.stream.readByte();
+	debugC(3, kDebugLevels::kDebugScriptsLife, "LIFE::lREPEAT_SAMPLE(%i, %i)", (int)sampleIdx, (int)num);
+	engine->_sound->mixSample3D(sampleIdx, 0x1000, num, ctx.actor->posObj(), ctx.actorIdx);
+	return 0;
 }
 
 int32 ScriptLifeV2::lBACKGROUND(TwinEEngine *engine, LifeScriptContext &ctx) {
-	return -1;
+	const uint8 val = ctx.stream.readByte();
+	debugC(3, kDebugLevels::kDebugScriptsLife, "LIFE::lBACKGROUND(%i)", (int)val);
+	if (val != 0) {
+		if (!ctx.actor->_flags.bIsBackgrounded) {
+			ctx.actor->_flags.bIsBackgrounded = 1;
+			if (ctx.actor->_workFlags.bWasDrawn) {
+				engine->_redraw->_firstTime = true;
+			}
+		}
+	} else {
+		if (ctx.actor->_flags.bIsBackgrounded) {
+			ctx.actor->_flags.bIsBackgrounded = 0;
+			if (ctx.actor->_workFlags.bWasDrawn) {
+				engine->_redraw->_firstTime = true;
+			}
+		}
+	}
+	return 0;
 }
 
 int32 ScriptLifeV2::lSET_FLAG_GAME(TwinEEngine *engine, LifeScriptContext &ctx) {
diff --git a/engines/twine/script/script_move_v2.cpp b/engines/twine/script/script_move_v2.cpp
index ea6f5221eab..c68620a07a2 100644
--- a/engines/twine/script/script_move_v2.cpp
+++ b/engines/twine/script/script_move_v2.cpp
@@ -20,6 +20,7 @@
  */
 
 #include "twine/script/script_move_v2.h"
+#include "twine/audio/sound.h"
 #include "twine/resources/resources.h"
 #include "twine/twine.h"
 
@@ -54,7 +55,7 @@ static const ScriptMoveFunction function_map[] = {
 	{"CLOSE", ScriptMove::mCLOSE},
 	{"WAIT_DOOR", ScriptMove::mWAIT_DOOR},
 	{"SAMPLE_RND", ScriptMove::mSAMPLE_RND},
-	{"SAMPLE_ALWAYS", ScriptMove::mSAMPLE_ALWAYS},
+	{"SAMPLE_ALWAYS", ScriptMoveV2::mSAMPLE_ALWAYS},
 	{"SAMPLE_STOP", ScriptMove::mSAMPLE_STOP},
 	{"PLAY_FLA", ScriptMove::mPLAY_FLA},
 	{"REPEAT_SAMPLE", ScriptMove::mREPEAT_SAMPLE},
@@ -155,6 +156,15 @@ int32 ScriptMoveV2::mSPRITE(TwinEEngine *engine, MoveScriptContext &ctx) {
 	return 0;
 }
 
+int32 ScriptMoveV2::mSAMPLE_ALWAYS(TwinEEngine *engine, MoveScriptContext &ctx) {
+	int32 sampleIdx = ctx.stream.readSint16LE();
+	debugC(3, kDebugLevels::kDebugScriptsMove, "MOVE::SAMPLE_ALWAYS(%i)", (int)sampleIdx);
+	if (!engine->_sound->isSamplePlaying(sampleIdx)) {
+		engine->_sound->mixSample3D(sampleIdx, engine->_sound->_parmSampleFrequence, engine->_sound->_parmSampleVolume, ctx.actor->posObj(), ctx.actorIdx);
+	}
+	return 0;
+}
+
 int32 ScriptMoveV2::mSET_FRAME(TwinEEngine *engine, MoveScriptContext &ctx) {
 	const uint8 num = ctx.stream.readByte();
 	if (!ctx.actor->_flags.bSprite3D) {
@@ -179,41 +189,94 @@ int32 ScriptMoveV2::mSET_FRAME_3DS(TwinEEngine *engine, MoveScriptContext &ctx)
 }
 
 int32 ScriptMoveV2::mSET_START_3DS(TwinEEngine *engine, MoveScriptContext &ctx) {
-	return -1;
+	int32 num = ctx.stream.readByte();
+	if (ctx.actor->_flags.bHasSpriteAnim3D) {
+		const T_ANIM_3DS *anim = engine->_resources->getAnim(ctx.actor->A3DS.Num);
+		if (num > (anim->Fin - anim->Deb)) {
+			num = anim->Fin - anim->Deb;
+		}
+
+		num += anim->Deb;
+
+		ctx.actor->A3DS.Deb = num;
+	}
+	return 0;
 }
 
 int32 ScriptMoveV2::mSET_END_3DS(TwinEEngine *engine, MoveScriptContext &ctx) {
-	return -1;
+	int32 num = ctx.stream.readByte();
+	if (ctx.actor->_flags.bHasSpriteAnim3D) {
+		const T_ANIM_3DS *anim = engine->_resources->getAnim(ctx.actor->A3DS.Num);
+		if (num > (anim->Fin - anim->Deb)) {
+			num = anim->Fin - anim->Deb;
+		}
+
+		num += anim->Deb;
+
+		ctx.actor->A3DS.Fin = num;
+	}
+	return 0;
 }
 
 int32 ScriptMoveV2::mSTART_ANIM_3DS(TwinEEngine *engine, MoveScriptContext &ctx) {
-	return -1;
+	const int32 num = ctx.stream.readByte(); // NbFps
+	if (ctx.actor->_flags.bHasSpriteAnim3D) {
+		engine->_actor->initSprite(ctx.actor->A3DS.Deb, ctx.actorIdx);
+		ctx.actor->SizeSHit = (int16)num;
+	}
+	return 0;
 }
 
 int32 ScriptMoveV2::mSTOP_ANIM_3DS(TwinEEngine *engine, MoveScriptContext &ctx) {
-	return -1;
+	if (ctx.actor->_flags.bHasSpriteAnim3D) {
+		ctx.actor->SizeSHit = 0;
+	}
+	return 0;
 }
 
 int32 ScriptMoveV2::mWAIT_ANIM_3DS(TwinEEngine *engine, MoveScriptContext &ctx) {
-	return -1;
+	if (ctx.actor->_flags.bHasSpriteAnim3D) {
+		if (ctx.actor->_sprite != ctx.actor->A3DS.Fin && ctx.actor->SizeSHit != 0) {
+			ctx.undo(1); // OffsetTrack--
+			return 1; // wait
+		}
+	}
+	return 0;
 }
 
 int32 ScriptMoveV2::mWAIT_FRAME_3DS(TwinEEngine *engine, MoveScriptContext &ctx) {
-	return -1;
+	int32 num = ctx.stream.readByte();
+	if (ctx.actor->_flags.bHasSpriteAnim3D) {
+		const T_ANIM_3DS *anim = engine->_resources->getAnim(ctx.actor->A3DS.Num);
+		if (num > (anim->Fin - anim->Deb)) {
+			num = anim->Fin - anim->Deb;
+		}
+
+		num += anim->Deb;
+
+		if (ctx.actor->_sprite != num) {
+			ctx.undo(2); // OffsetTrack -= 2
+			return 1; // wait
+		}
+	}
+	return 0;
 }
 
 // DECALAGE
 int32 ScriptMoveV2::mOFFSET(TwinEEngine *engine, MoveScriptContext &ctx) {
-	return -1;
+	engine->_sound->_parmSampleDecalage = ctx.stream.readSint16LE();
+	return 0;
 }
 
 // FREQUENCE
 int32 ScriptMoveV2::mFREQUENCY(TwinEEngine *engine, MoveScriptContext &ctx) {
-	return -1;
+	engine->_sound->_parmSampleFrequence = ctx.stream.readSint16LE();
+	return 0;
 }
 
 int32 ScriptMoveV2::mVOLUME(TwinEEngine *engine, MoveScriptContext &ctx) {
-	return -1;
+	engine->_sound->_parmSampleVolume = ctx.stream.readByte();
+	return 0;
 }
 
 ScriptMoveV2::ScriptMoveV2(TwinEEngine *engine) : ScriptMove(engine, function_map, ARRAYSIZE(function_map)) {
diff --git a/engines/twine/script/script_move_v2.h b/engines/twine/script/script_move_v2.h
index 3a0e632c561..94d9607b3bf 100644
--- a/engines/twine/script/script_move_v2.h
+++ b/engines/twine/script/script_move_v2.h
@@ -33,6 +33,7 @@ public:
 	static int32 mWAIT_NB_DIZIEME(TwinEEngine *engine, MoveScriptContext &ctx);
 	static int32 mSPRITE(TwinEEngine *engine, MoveScriptContext &ctx);
 	static int32 mWAIT_NB_SECOND_RND(TwinEEngine *engine, MoveScriptContext &ctx);
+	static int32 mSAMPLE_ALWAYS(TwinEEngine *engine, MoveScriptContext &ctx);
 	static int32 mSET_FRAME(TwinEEngine *engine, MoveScriptContext &ctx);
 	static int32 mSET_FRAME_3DS(TwinEEngine *engine, MoveScriptContext &ctx);
 	static int32 mSET_START_3DS(TwinEEngine *engine, MoveScriptContext &ctx);
diff --git a/engines/twine/shared.h b/engines/twine/shared.h
index f1cc992dace..67b2c57d1b8 100644
--- a/engines/twine/shared.h
+++ b/engines/twine/shared.h
@@ -83,6 +83,8 @@
 // Twinsun explosion
 #define GAMEFLAG_VIDEO_EXPLODE2 219
 
+#define GAMEFLAG_ESC 249
+
 // lba2 Kashes or Zlitos
 #define GAMEFLAG_MONEY 8
 // FLAG_ARDOISE


Commit: 1e9337f5c704ad7425df951f9c31f2099d17de50
    https://github.com/scummvm/scummvm/commit/1e9337f5c704ad7425df951f9c31f2099d17de50
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-01-02T23:33:10+01:00

Commit Message:
TWINE: LBA2: started with filling the rain stubs

Changed paths:
    engines/twine/renderer/renderer.h
    engines/twine/scene/rain.cpp
    engines/twine/scene/rain.h
    engines/twine/scene/scene.cpp
    engines/twine/twine.cpp


diff --git a/engines/twine/renderer/renderer.h b/engines/twine/renderer/renderer.h
index d22a109c1c6..07c740186d4 100644
--- a/engines/twine/renderer/renderer.h
+++ b/engines/twine/renderer/renderer.h
@@ -297,6 +297,22 @@ public:
 	void draw3dObject(int32 x, int32 y, const BodyData &bodyData, int32 angle, int32 cameraZoom);
 
 	void asmTexturedTriangleNoClip(const ComputedVertex vertexCoordinates[3], const ComputedVertex textureCoordinates[3], const uint8 *holomapImage, uint32 holomapImageSize);
+
+	inline IVec3 getCameraPosition() const {
+		return _cameraPos;
+	}
+
+	inline IVec3 getCameraRotation() const {
+		return _cameraRot;
+	}
+
+	inline int32 getLFactorX() const {
+		return _lFactorX;
+	}
+
+	inline int32 getLFactorY() const {
+		return _lFactorY;
+	}
 };
 
 inline void Renderer::setCameraRotation(int32 x, int32 y, int32 z) {
diff --git a/engines/twine/scene/rain.cpp b/engines/twine/scene/rain.cpp
index a3e885133bb..561351c3a97 100644
--- a/engines/twine/scene/rain.cpp
+++ b/engines/twine/scene/rain.cpp
@@ -20,22 +20,151 @@
  */
 
 #include "twine/scene/rain.h"
+#include "twine/renderer/renderer.h"
+#include "twine/scene/grid.h"
+#include "twine/twine.h"
 
 namespace TwinE {
 
+#define RAIN_VX 200
+#define RAIN_VY 2500
+#define RAIN_WEIGHT 30
+#define RAIN_STOP 0
+#define RAIN_DELTA_X 128
+#define RAIN_DELTA_Y 256
+#define RAIN_DELTA_Z 128
+
 void Rain::InitOneRain(T_RAIN *pt) {
+	IVec3 cameraPos = _engine->_renderer->getCameraPosition();
+
+	int32 rndy = _engine->getRandomNumber(cameraPos.y + 10000);
+
+	pt->YRain = cameraPos.y + rndy;
+
+	rndy = rndy / 2 + 15000;
+
+	pt->XRain = cameraPos.x - rndy + _engine->getRandomNumber(30000);
+	pt->ZRain = cameraPos.z - rndy + _engine->getRandomNumber(30000);
+	pt->Timer = 0;
 }
 
 void Rain::InitRain() {
+	for (int32 i = 0; i < MAX_RAIN; i++) {
+		InitOneRain(&TabRain[i]);
+	}
+
+	LastTimer = 0;
 }
 
 void Rain::GereRain() {
+	int32 temp = _engine->timerRef;
+	DeltaRain = LastTimer ? (temp - LastTimer) * 10 : 0;
+	LastTimer = temp;
+
+	for (int32 i = 0; i < MAX_RAIN; i++) {
+		if (!TabRain[i].Timer) {
+			TabRain[i].XRain += DeltaRain / 2;
+			TabRain[i].ZRain += DeltaRain / 2;
+			TabRain[i].YRain -= DeltaRain;
+		}
+	}
 }
 
 void Rain::ClearImpactRain() {
+	for (int32 i = 0; i < MAX_RAIN; i++) {
+		if (TabRain[i].Timer) {
+			InitOneRain(&TabRain[i]);
+		}
+	}
 }
 
 void Rain::AffRain() {
+	int32 lFactorX = _engine->_renderer->getLFactorX();
+	int32 lFactorY = _engine->_renderer->getLFactorY();
+	IVec3 cameraRot = _engine->_renderer->getCameraRotation();
+	int32 cameraZr = cameraRot.z;
+
+	// ClipZFar approximation
+	int32 clipZFar = 14000; // Default value from CREDITS.CPP
+	int32 startZFog = 5000;    // Default value from CREDITS.CPP
+
+	for (int32 i = 0; i < MAX_RAIN; i++) {
+		if (TabRain[i].Timer) {
+			int32 dt = LastTimer - TabRain[i].Timer;
+
+			int32 c = TabRain[i].XRain >> 16;
+			int32 x = (int16)(TabRain[i].XRain & 0xFFFF);
+			int32 y = TabRain[i].YRain;
+			int32 z = TabRain[i].ZRain;
+
+			int32 xp, yp;
+			yp = (RAIN_VY - RAIN_WEIGHT * dt) * dt / 256;
+			if (yp < 0) {
+				yp = 0;
+				xp = RAIN_VX * RAIN_VY / RAIN_WEIGHT / 256;
+			} else {
+				xp = RAIN_VX * dt / 256;
+				yp = (yp * lFactorY) / z;
+			}
+
+			xp = (xp * lFactorX) / z;
+
+			int32 x0 = x - xp;
+			int32 x1 = x + xp;
+
+			// int32 y0 = y - yp;
+			// int32 y1 = y;
+
+			// z = ruleThree32(0, 65535, clipZFar, z);
+
+			// Draw splash
+			_engine->_workVideoBuffer.drawLine(x, y, x0, y - yp, c);
+			_engine->_workVideoBuffer.drawLine(x, y, x1, y - yp, c);
+
+			if (dt && !yp) {
+				InitOneRain(&TabRain[i]);
+			}
+		} else {
+			if (TabRain[i].YRain <= RAIN_STOP) {
+				InitOneRain(&TabRain[i]);
+				continue;
+			}
+
+			IVec3 p1 = _engine->_renderer->longWorldRot(TabRain[i].XRain - RAIN_DELTA_X, TabRain[i].YRain + RAIN_DELTA_Y, TabRain[i].ZRain - RAIN_DELTA_Z);
+			IVec3 proj1 = _engine->_renderer->projectPoint(p1);
+
+			int32 xp = proj1.x;
+			int32 yp = proj1.y;
+			int32 z0 = ruleThree32(0, 65535, clipZFar, cameraZr - p1.z);
+
+			IVec3 p2 = _engine->_renderer->longWorldRot(TabRain[i].XRain, TabRain[i].YRain, TabRain[i].ZRain);
+			IVec3 proj2 = _engine->_renderer->projectPoint(p2);
+
+			int32 Z0 = cameraZr - p2.z;
+			// int32 z1 = ruleThree32(0, 65535, clipZFar, Z0);
+
+			int32 c = boundRuleThree(16 * 3 + 10, 16 * 3 + 3, clipZFar - startZFog, Z0);
+
+			// Draw rain drop
+			_engine->_workVideoBuffer.drawLine(xp, yp, proj2.x, proj2.y, c);
+
+			// Check collision with ground
+			int32 groundHeight = 0;
+			IVec3 pos(TabRain[i].XRain, TabRain[i].YRain, TabRain[i].ZRain);
+			_engine->_grid->getBlockBufferGround(pos, groundHeight);
+
+			if (TabRain[i].YRain <= groundHeight) {
+				// Splash
+				TabRain[i].XRain = ((xp & 0xFFFF) | (c << 16));
+				TabRain[i].YRain = yp;
+				TabRain[i].ZRain = z0;
+				TabRain[i].Timer = LastTimer;
+			}
+		}
+	}
+}
+
+Rain::Rain(TwinEEngine *engine) : _engine(engine) {
 }
 
 } // namespace TwinE
diff --git a/engines/twine/scene/rain.h b/engines/twine/scene/rain.h
index f5b22878b8b..396e6457028 100644
--- a/engines/twine/scene/rain.h
+++ b/engines/twine/scene/rain.h
@@ -27,19 +27,25 @@
 
 namespace TwinE {
 
+#define MAX_RAIN 200
+
 class Rain {
 private:
-	//TwinEEngine *_engine;
+	TwinEEngine *_engine;
+	int32 LastTimer = 0;
+	int32 DeltaRain = 0;
 
 public:
 	struct T_RAIN {
-		int32 XRain;
-		int32 YRain;
-		int32 ZRain;
-		int32 Timer;
+		int32 XRain = 0;
+		int32 YRain = 0;
+		int32 ZRain = 0;
+		int32 Timer = 0;
 	};
 
-	Rain(TwinEEngine *engine) /*: _engine(engine) */ {}
+	T_RAIN TabRain[MAX_RAIN];
+
+	Rain(TwinEEngine *engine);
 
 	void InitOneRain(T_RAIN *pt);
 	void InitRain();
diff --git a/engines/twine/scene/scene.cpp b/engines/twine/scene/scene.cpp
index 59a6046eabf..d79f2562068 100644
--- a/engines/twine/scene/scene.cpp
+++ b/engines/twine/scene/scene.cpp
@@ -20,6 +20,7 @@
  */
 
 #include "twine/scene/scene.h"
+#include "twine/scene/rain.h"
 #include "common/config-manager.h"
 #include "common/file.h"
 #include "common/memstream.h"
@@ -484,6 +485,7 @@ bool Scene::loadScene(int32 index) {
 	if (_engine->isLBA1()) {
 		return loadSceneLBA1();
 	} else if (_engine->isLBA2()) {
+		_engine->_rain->InitRain();
 		return loadSceneLBA2();
 	}
 
diff --git a/engines/twine/twine.cpp b/engines/twine/twine.cpp
index c903de2629a..b0d498940e3 100644
--- a/engines/twine/twine.cpp
+++ b/engines/twine/twine.cpp
@@ -918,6 +918,10 @@ bool TwinEEngine::runGameEngine() { // mainLoopInteration
 
 	_movements->update();
 
+	if (isLBA2()) {
+		_rain->GereRain();
+	}
+
 	_debugState->update();
 
 	if (_menuOptions->flagCredits) {
@@ -1237,6 +1241,10 @@ bool TwinEEngine::runGameEngine() { // mainLoopInteration
 
 	_redraw->drawScene(_redraw->_firstTime);
 
+	if (isLBA2()) {
+		_rain->AffRain();
+	}
+
 	// workaround to fix hero redraw after drowning
 	if (_actor->_cropBottomScreen && _redraw->_firstTime) {
 		_scene->_sceneHero->_flags.bIsInvisible = 1;




More information about the Scummvm-git-logs mailing list