[Scummvm-git-logs] scummvm master -> 81e063dd68acb27d811e7b95908d5d000120bc13
elasota
noreply at scummvm.org
Mon Mar 27 04:11:45 UTC 2023
This automated email contains information about 4 new commits which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .
Summary:
84efd1f6bd VCRUISE: Reduce the frame rate of soundless animations to match the original game's behavior.
5797eb17f0 VCRUISE: Add fast animations option
1720de0257 VCRUISE: Adjust static animation default speed
81e063dd68 VCRUISE: Adjust gyro default speed
Commit: 84efd1f6bd1d0ed2d623fce4f00f58eacfb9dc16
https://github.com/scummvm/scummvm/commit/84efd1f6bd1d0ed2d623fce4f00f58eacfb9dc16
Author: elasota (ejlasota at gmail.com)
Date: 2023-03-27T00:08:48-04:00
Commit Message:
VCRUISE: Reduce the frame rate of soundless animations to match the original game's behavior.
Changed paths:
engines/vcruise/runtime.cpp
engines/vcruise/runtime.h
diff --git a/engines/vcruise/runtime.cpp b/engines/vcruise/runtime.cpp
index 0651d42bd46..0f9a7572175 100644
--- a/engines/vcruise/runtime.cpp
+++ b/engines/vcruise/runtime.cpp
@@ -347,12 +347,18 @@ SoundParams3D::SoundParams3D() : minRange(0), maxRange(0), unknownRange(0) {
InventoryItem::InventoryItem() : itemID(0), highlighted(false) {
}
+Fraction::Fraction() : numerator(0), denominator(1) {
+}
+
+Fraction::Fraction(uint pNumerator, uint pDenominator) : numerator(pNumerator), denominator(pDenominator) {
+}
+
Runtime::Runtime(OSystem *system, Audio::Mixer *mixer, const Common::FSNode &rootFSNode, VCruiseGameID gameID)
: _system(system), _mixer(mixer), _roomNumber(1), _screenNumber(0), _direction(0), _haveHorizPanAnimations(false), _loadedRoomNumber(0), _activeScreenNumber(0),
_gameState(kGameStateBoot), _gameID(gameID), _havePendingScreenChange(false), _forceScreenChange(false), _havePendingReturnToIdleState(false), _havePendingCompletionCheck(false),
_scriptNextInstruction(0), _escOn(false), _debugMode(false), _panoramaDirectionFlags(0),
_loadedAnimation(0), _animPendingDecodeFrame(0), _animDisplayingFrame(0), _animFirstFrame(0), _animLastFrame(0), _animStopFrame(0),
- _animFrameRateLock(0), _animStartTime(0), _animFramesDecoded(0), _animDecoderState(kAnimDecoderStateStopped),
+ _animStartTime(0), _animFramesDecoded(0), _animDecoderState(kAnimDecoderStateStopped),
_animPlayWhileIdle(false), _idleIsOnInteraction(false), _idleHaveClickInteraction(false), _idleHaveDragInteraction(false), _idleInteractionID(0), _haveIdleStaticAnimation(false),
/*_loadedArea(0), */_lmbDown(false), _lmbDragging(false), _lmbReleaseWasClick(false), _lmbDownTime(0),
_delayCompletionTime(0),
@@ -530,6 +536,11 @@ bool Runtime::bootGame(bool newGame) {
error("Couldn't figure out what screen to start on");
}
+ if (_gameID == GID_REAH) {
+ _animSpeedRotation = Fraction(64, 3);
+ _animSpeedWalk = Fraction(16, 1);
+ }
+
return true;
}
@@ -715,7 +726,7 @@ bool Runtime::runWaitForFacingToAnim() {
continuePlayingAnimation(true, true, animEnded);
if (animEnded) {
- changeAnimation(_postFacingAnimDef, true);
+ changeAnimation(_postFacingAnimDef, _postFacingAnimDef.firstFrame, true, _animSpeedWalk);
_gameState = kGameStateWaitingForAnimation;
return true;
}
@@ -880,7 +891,7 @@ void Runtime::continuePlayingAnimation(bool loop, bool useStopFrame, bool &outAn
uint32 millis = 0;
// Avoid spamming event recorder as much if we don't actually need to fetch millis, but also only fetch it once here.
- if (_animFrameRateLock)
+ if (_animFrameRateLock.numerator)
millis = g_system->getMillis();
for (;;) {
@@ -890,9 +901,9 @@ void Runtime::continuePlayingAnimation(bool loop, bool useStopFrame, bool &outAn
needNewFrame = true;
needsFirstFrame = false;
} else {
- if (_animFrameRateLock) {
- // if ((millis - startTime) / 1000 * frameRate) >= framesDecoded
- if ((millis - _animStartTime) * static_cast<uint64>(_animFrameRateLock) >= (static_cast<uint64>(_animFramesDecoded) * 1000u))
+ if (_animFrameRateLock.numerator) {
+ // if ((millis - startTime) / 1000 * frameRate / frameRateDenominator) >= framesDecoded
+ if ((millis - _animStartTime) * static_cast<uint64>(_animFrameRateLock.numerator) >= (static_cast<uint64>(_animFramesDecoded) * static_cast<uint64>(_animFrameRateLock.denominator) * 1000u))
needNewFrame = true;
} else {
if (_animDecoder->getTimeToNextFrame() == 0)
@@ -1744,6 +1755,10 @@ void Runtime::changeAnimation(const AnimationDef &animDef, bool consumeFPSOverri
}
void Runtime::changeAnimation(const AnimationDef &animDef, uint initialFrame, bool consumeFPSOverride) {
+ changeAnimation(animDef, initialFrame, consumeFPSOverride, Fraction(0, 1));
+}
+
+void Runtime::changeAnimation(const AnimationDef &animDef, uint initialFrame, bool consumeFPSOverride, const Fraction &defaultFrameRate) {
debug("changeAnimation: %u -> %u Initial %u", animDef.firstFrame, animDef.lastFrame, initialFrame);
_animPlaylist.reset();
@@ -1809,18 +1824,24 @@ void Runtime::changeAnimation(const AnimationDef &animDef, uint initialFrame, bo
_animFirstFrame = animDef.firstFrame;
_animLastFrame = animDef.lastFrame;
_animConstraintRect = animDef.constraintRect;
- _animFrameRateLock = 0;
+ _animFrameRateLock = Fraction();
SfxData::PlaylistMap_t::const_iterator playlistIt = _sfxData.playlists.find(animDef.animName);
if (playlistIt != _sfxData.playlists.end())
_animPlaylist = playlistIt->_value;
- if (consumeFPSOverride) {
- _animFrameRateLock = _scriptEnv.fpsOverride;
+ if (consumeFPSOverride && _scriptEnv.fpsOverride) {
+ _animFrameRateLock = Fraction(_scriptEnv.fpsOverride, 1);
+ _scriptEnv.fpsOverride = 0;
+ } else {
+ if (_animDecoder && _animDecoder->getAudioTrackCount() == 0)
+ _animFrameRateLock = defaultFrameRate;
+ }
+
+ if (_animFrameRateLock.numerator) {
_animFramesDecoded = 0;
_animStartTime = g_system->getMillis();
- _scriptEnv.fpsOverride = 0;
}
debug(1, "Animation last frame set to %u", animDef.lastFrame);
@@ -2690,7 +2711,7 @@ void Runtime::scriptOpAnimR(ScriptArg_t arg) {
debug(1, "Running frame loop of %u - %u from frame %u", trimmedAnimation.firstFrame, trimmedAnimation.lastFrame, initialFrame);
- changeAnimation(trimmedAnimation, initialFrame, false);
+ changeAnimation(trimmedAnimation, initialFrame, false, _animSpeedRotation);
_gameState = kGameStatePanLeft;
} else if (_scriptEnv.panInteractionID == kPanRightInteraction) {
debug(1, "Pan-right interaction from direction %u", _direction);
@@ -2702,7 +2723,7 @@ void Runtime::scriptOpAnimR(ScriptArg_t arg) {
debug(1, "Running frame loop of %u - %u from frame %u", trimmedAnimation.firstFrame, trimmedAnimation.lastFrame, initialFrame);
- changeAnimation(trimmedAnimation, initialFrame, false);
+ changeAnimation(trimmedAnimation, initialFrame, false, _animSpeedRotation);
_gameState = kGameStatePanRight;
isRight = true;
@@ -2741,10 +2762,10 @@ void Runtime::scriptOpAnimF(ScriptArg_t arg) {
if (computeFaceDirectionAnimation(stackArgs[kAnimDefStackArgs + 2], faceDirectionAnimDef, initialFrame, stopFrame)) {
_postFacingAnimDef = animDef;
_animStopFrame = stopFrame;
- changeAnimation(*faceDirectionAnimDef, initialFrame, false);
+ changeAnimation(*faceDirectionAnimDef, initialFrame, false, _animSpeedRotation);
_gameState = kGameStateWaitingForFacingToAnim;
} else {
- changeAnimation(animDef, true);
+ changeAnimation(animDef, animDef.firstFrame, true, _animSpeedWalk);
_gameState = kGameStateWaitingForAnimation;
}
_screenNumber = stackArgs[kAnimDefStackArgs + 0];
@@ -2811,7 +2832,7 @@ void Runtime::scriptOpAnim(ScriptArg_t arg) {
TAKE_STACK(kAnimDefStackArgs + 2);
AnimationDef animDef = stackArgsToAnimDef(stackArgs + 0);
- changeAnimation(animDef, true);
+ changeAnimation(animDef, animDef.firstFrame, true, _animSpeedWalk);
_gameState = kGameStateWaitingForAnimation;
_screenNumber = stackArgs[kAnimDefStackArgs + 0];
diff --git a/engines/vcruise/runtime.h b/engines/vcruise/runtime.h
index 553c0eb4662..59a784c8a1c 100644
--- a/engines/vcruise/runtime.h
+++ b/engines/vcruise/runtime.h
@@ -271,6 +271,14 @@ struct InventoryItem {
bool highlighted;
};
+struct Fraction {
+ Fraction();
+ Fraction(uint pNumerator, uint pDenominator);
+
+ uint numerator;
+ uint denominator;
+};
+
class Runtime {
public:
enum LoadGameOutcome {
@@ -480,6 +488,7 @@ private:
void changeMusicTrack(int musicID);
void changeAnimation(const AnimationDef &animDef, bool consumeFPSOverride);
void changeAnimation(const AnimationDef &animDef, uint initialFrame, bool consumeFPSOverride);
+ void changeAnimation(const AnimationDef &animDef, uint initialFrame, bool consumeFPSOverride, const Fraction &defaultFrameRate);
void setSound3DParameters(uint soundID, int32 x, int32 y, const SoundParams3D &soundParams3D);
void triggerSound(bool looping, uint soundID, uint volume, int32 balance, bool is3D);
@@ -713,7 +722,7 @@ private:
uint _animFirstFrame;
uint _animLastFrame;
uint _animStopFrame;
- uint _animFrameRateLock;
+ Fraction _animFrameRateLock;
Common::Rect _animConstraintRect;
uint32 _animStartTime;
uint32 _animFramesDecoded;
@@ -767,6 +776,9 @@ private:
int32 _listenerY;
int32 _listenerAngle;
+ Fraction _animSpeedRotation;
+ Fraction _animSpeedWalk;
+
static const uint kAnimDefStackArgs = 8;
static const uint kCursorArrow = 0;
Commit: 5797eb17f0eb444ffa42aca3f3369e5bea5701ff
https://github.com/scummvm/scummvm/commit/5797eb17f0eb444ffa42aca3f3369e5bea5701ff
Author: elasota (ejlasota at gmail.com)
Date: 2023-03-27T00:08:48-04:00
Commit Message:
VCRUISE: Add fast animations option
Changed paths:
engines/vcruise/detection.h
engines/vcruise/metaengine.cpp
engines/vcruise/runtime.cpp
engines/vcruise/runtime.h
engines/vcruise/vcruise.cpp
diff --git a/engines/vcruise/detection.h b/engines/vcruise/detection.h
index 3afaf23afc1..073c0c37d27 100644
--- a/engines/vcruise/detection.h
+++ b/engines/vcruise/detection.h
@@ -47,6 +47,7 @@ struct VCruiseGameDescription {
#define GAMEOPTION_LAUNCH_DEBUG GUIO_GAMEOPTIONS1
+#define GAMEOPTION_FAST_ANIMATIONS GUIO_GAMEOPTIONS2
} // End of namespace VCruise
diff --git a/engines/vcruise/metaengine.cpp b/engines/vcruise/metaengine.cpp
index f19018985b0..ec86f6c4b13 100644
--- a/engines/vcruise/metaengine.cpp
+++ b/engines/vcruise/metaengine.cpp
@@ -44,6 +44,17 @@ static const ADExtraGuiOptionsMap optionsList[] = {
0
}
},
+ {
+ GAMEOPTION_LAUNCH_DEBUG,
+ {
+ _s("Faster animations"),
+ _s("Speeds up animations."),
+ "vcruise_fast_animations",
+ false,
+ 0,
+ 0
+ }
+ },
AD_EXTRA_GUI_OPTIONS_TERMINATOR
};
diff --git a/engines/vcruise/runtime.cpp b/engines/vcruise/runtime.cpp
index 0f9a7572175..8ff304f75ae 100644
--- a/engines/vcruise/runtime.cpp
+++ b/engines/vcruise/runtime.cpp
@@ -356,7 +356,7 @@ Fraction::Fraction(uint pNumerator, uint pDenominator) : numerator(pNumerator),
Runtime::Runtime(OSystem *system, Audio::Mixer *mixer, const Common::FSNode &rootFSNode, VCruiseGameID gameID)
: _system(system), _mixer(mixer), _roomNumber(1), _screenNumber(0), _direction(0), _haveHorizPanAnimations(false), _loadedRoomNumber(0), _activeScreenNumber(0),
_gameState(kGameStateBoot), _gameID(gameID), _havePendingScreenChange(false), _forceScreenChange(false), _havePendingReturnToIdleState(false), _havePendingCompletionCheck(false),
- _scriptNextInstruction(0), _escOn(false), _debugMode(false), _panoramaDirectionFlags(0),
+ _scriptNextInstruction(0), _escOn(false), _debugMode(false), _fastAnimationMode(false), _panoramaDirectionFlags(0),
_loadedAnimation(0), _animPendingDecodeFrame(0), _animDisplayingFrame(0), _animFirstFrame(0), _animLastFrame(0), _animStopFrame(0),
_animStartTime(0), _animFramesDecoded(0), _animDecoderState(kAnimDecoderStateStopped),
_animPlayWhileIdle(false), _idleIsOnInteraction(false), _idleHaveClickInteraction(false), _idleHaveDragInteraction(false), _idleInteractionID(0), _haveIdleStaticAnimation(false),
@@ -453,6 +453,10 @@ void Runtime::setDebugMode(bool debugMode) {
}
}
+void Runtime::setFastAnimationMode(bool fastAnimationMode) {
+ _fastAnimationMode = fastAnimationMode;
+}
+
bool Runtime::runFrame() {
bool moreActions = true;
while (moreActions) {
@@ -1835,7 +1839,7 @@ void Runtime::changeAnimation(const AnimationDef &animDef, uint initialFrame, bo
_animFrameRateLock = Fraction(_scriptEnv.fpsOverride, 1);
_scriptEnv.fpsOverride = 0;
} else {
- if (_animDecoder && _animDecoder->getAudioTrackCount() == 0)
+ if (!_fastAnimationMode && _animDecoder && _animDecoder->getAudioTrackCount() == 0)
_animFrameRateLock = defaultFrameRate;
}
diff --git a/engines/vcruise/runtime.h b/engines/vcruise/runtime.h
index 59a784c8a1c..1e7bb3eaa1b 100644
--- a/engines/vcruise/runtime.h
+++ b/engines/vcruise/runtime.h
@@ -299,6 +299,7 @@ public:
void loadCursors(const char *exeName);
void setDebugMode(bool debugMode);
+ void setFastAnimationMode(bool fastAnimationMode);
bool runFrame();
void drawFrame();
@@ -698,6 +699,7 @@ private:
bool _escOn;
bool _debugMode;
+ bool _fastAnimationMode;
VCruiseGameID _gameID;
diff --git a/engines/vcruise/vcruise.cpp b/engines/vcruise/vcruise.cpp
index e576e3ef8f1..da4df120a48 100644
--- a/engines/vcruise/vcruise.cpp
+++ b/engines/vcruise/vcruise.cpp
@@ -169,6 +169,10 @@ Common::Error VCruiseEngine::run() {
_runtime->setDebugMode(true);
}
+ if (ConfMan.getBool("vcruise_fast_animations")) {
+ _runtime->setFastAnimationMode(true);
+ }
+
if (ConfMan.hasKey("save_slot")) {
int saveSlot = ConfMan.getInt("save_slot");
if (saveSlot >= 0) {
Commit: 1720de02570d5efa2897f7b5a10ca0534bebf7d9
https://github.com/scummvm/scummvm/commit/1720de02570d5efa2897f7b5a10ca0534bebf7d9
Author: elasota (ejlasota at gmail.com)
Date: 2023-03-27T00:08:48-04:00
Commit Message:
VCRUISE: Adjust static animation default speed
Changed paths:
engines/vcruise/runtime.cpp
engines/vcruise/runtime.h
diff --git a/engines/vcruise/runtime.cpp b/engines/vcruise/runtime.cpp
index 8ff304f75ae..aaad43c5353 100644
--- a/engines/vcruise/runtime.cpp
+++ b/engines/vcruise/runtime.cpp
@@ -541,8 +541,9 @@ bool Runtime::bootGame(bool newGame) {
}
if (_gameID == GID_REAH) {
- _animSpeedRotation = Fraction(64, 3);
- _animSpeedWalk = Fraction(16, 1);
+ _animSpeedRotation = Fraction(21, 1); // Probably accurate
+ _animSpeedStaticAnim = Fraction(21, 1); // Probably accurate
+ _animSpeedWalk = Fraction(16, 1); // Possibly not accurate
}
return true;
@@ -585,7 +586,8 @@ bool Runtime::runIdle() {
// Try to re-trigger
StaticAnimation &sanim = _idleAnimations[_direction];
if (sanim.nextStartTime <= timestamp) {
- changeAnimation(sanim.animDefs[sanim.currentAlternation], false);
+ const AnimationDef &animDef = sanim.animDefs[sanim.currentAlternation];
+ changeAnimation(animDef, animDef.firstFrame, false, _animSpeedStaticAnim);
_animPlayWhileIdle = true;
}
}
@@ -2736,13 +2738,7 @@ void Runtime::scriptOpAnimR(ScriptArg_t arg) {
uint cursorID = 0;
if (_haveHorizPanAnimations) {
- uint panCursor = 0;
- if (_panoramaDirectionFlags & kPanoramaHorizFlags)
- panCursor |= kPanCursorDraggableHoriz;
- if (_panoramaDirectionFlags & kPanoramaUpFlag)
- panCursor |= kPanCursorDraggableUp;
- if (_panoramaDirectionFlags & kPanoramaUpFlag)
- panCursor |= kPanCursorDraggableDown;
+ uint panCursor = kPanCursorDraggableHoriz;
if (isRight)
panCursor |= kPanCursorDirectionRight;
@@ -2776,7 +2772,13 @@ void Runtime::scriptOpAnimF(ScriptArg_t arg) {
_direction = stackArgs[kAnimDefStackArgs + 1];
_havePendingScreenChange = true;
- changeToCursor(_cursors[kCursorArrow]);
+ uint cursorID = kCursorArrow;
+ if (_scriptEnv.panInteractionID == kPanUpInteraction)
+ cursorID = _panCursors[kPanCursorDraggableUp | kPanCursorDirectionUp];
+ else if (_scriptEnv.panInteractionID == kPanDownInteraction)
+ cursorID = _panCursors[kPanCursorDraggableDown | kPanCursorDirectionDown];
+
+ changeToCursor(_cursors[cursorID]);
}
void Runtime::scriptOpAnimN(ScriptArg_t arg) {
@@ -2865,7 +2867,7 @@ void Runtime::scriptOpStatic(ScriptArg_t arg) {
if (animDef.animName == _idleCurrentStaticAnimation)
return;
- changeAnimation(animDef, animDef.lastFrame, false);
+ changeAnimation(animDef, animDef.lastFrame, false, _animSpeedStaticAnim);
_havePendingReturnToIdleState = true;
_haveHorizPanAnimations = false;
diff --git a/engines/vcruise/runtime.h b/engines/vcruise/runtime.h
index 1e7bb3eaa1b..e60e4dc92c8 100644
--- a/engines/vcruise/runtime.h
+++ b/engines/vcruise/runtime.h
@@ -779,6 +779,7 @@ private:
int32 _listenerAngle;
Fraction _animSpeedRotation;
+ Fraction _animSpeedStaticAnim;
Fraction _animSpeedWalk;
static const uint kAnimDefStackArgs = 8;
Commit: 81e063dd68acb27d811e7b95908d5d000120bc13
https://github.com/scummvm/scummvm/commit/81e063dd68acb27d811e7b95908d5d000120bc13
Author: elasota (ejlasota at gmail.com)
Date: 2023-03-27T00:08:49-04:00
Commit Message:
VCRUISE: Adjust gyro default speed
Changed paths:
engines/vcruise/runtime.cpp
engines/vcruise/runtime.h
diff --git a/engines/vcruise/runtime.cpp b/engines/vcruise/runtime.cpp
index aaad43c5353..80f7681b956 100644
--- a/engines/vcruise/runtime.cpp
+++ b/engines/vcruise/runtime.cpp
@@ -543,7 +543,7 @@ bool Runtime::bootGame(bool newGame) {
if (_gameID == GID_REAH) {
_animSpeedRotation = Fraction(21, 1); // Probably accurate
_animSpeedStaticAnim = Fraction(21, 1); // Probably accurate
- _animSpeedWalk = Fraction(16, 1); // Possibly not accurate
+ _animSpeedDefault = Fraction(16, 1); // Possibly not accurate
}
return true;
@@ -732,7 +732,7 @@ bool Runtime::runWaitForFacingToAnim() {
continuePlayingAnimation(true, true, animEnded);
if (animEnded) {
- changeAnimation(_postFacingAnimDef, _postFacingAnimDef.firstFrame, true, _animSpeedWalk);
+ changeAnimation(_postFacingAnimDef, _postFacingAnimDef.firstFrame, true, _animSpeedDefault);
_gameState = kGameStateWaitingForAnimation;
return true;
}
@@ -806,7 +806,7 @@ bool Runtime::runGyroIdle() {
// firstFrame is left alone so playlists are based correctly.
animDef.lastFrame = initialFrame + _gyros.frameSeparation;
- changeAnimation(animDef, initialFrame, false);
+ changeAnimation(animDef, initialFrame, false, _animSpeedDefault);
gyro.logState();
gyro.currentState--;
@@ -827,7 +827,7 @@ bool Runtime::runGyroIdle() {
// firstFrame is left alone so playlists are based correctly.
animDef.lastFrame = initialFrame + _gyros.frameSeparation;
- changeAnimation(animDef, initialFrame, false);
+ changeAnimation(animDef, initialFrame, false, _animSpeedDefault);
gyro.logState();
gyro.currentState++;
@@ -2765,7 +2765,7 @@ void Runtime::scriptOpAnimF(ScriptArg_t arg) {
changeAnimation(*faceDirectionAnimDef, initialFrame, false, _animSpeedRotation);
_gameState = kGameStateWaitingForFacingToAnim;
} else {
- changeAnimation(animDef, animDef.firstFrame, true, _animSpeedWalk);
+ changeAnimation(animDef, animDef.firstFrame, true, _animSpeedDefault);
_gameState = kGameStateWaitingForAnimation;
}
_screenNumber = stackArgs[kAnimDefStackArgs + 0];
@@ -2838,7 +2838,7 @@ void Runtime::scriptOpAnim(ScriptArg_t arg) {
TAKE_STACK(kAnimDefStackArgs + 2);
AnimationDef animDef = stackArgsToAnimDef(stackArgs + 0);
- changeAnimation(animDef, animDef.firstFrame, true, _animSpeedWalk);
+ changeAnimation(animDef, animDef.firstFrame, true, _animSpeedDefault);
_gameState = kGameStateWaitingForAnimation;
_screenNumber = stackArgs[kAnimDefStackArgs + 0];
diff --git a/engines/vcruise/runtime.h b/engines/vcruise/runtime.h
index e60e4dc92c8..185030d1b1f 100644
--- a/engines/vcruise/runtime.h
+++ b/engines/vcruise/runtime.h
@@ -780,7 +780,7 @@ private:
Fraction _animSpeedRotation;
Fraction _animSpeedStaticAnim;
- Fraction _animSpeedWalk;
+ Fraction _animSpeedDefault;
static const uint kAnimDefStackArgs = 8;
More information about the Scummvm-git-logs
mailing list