AGS: Added "sound volume" param to all Animate() functions

From upstream 926220f9706baafa80d05ff6264f7fa13296dc16
This commit is contained in:
Paul Gilbert 2022-05-09 19:36:39 -07:00
parent 898650ebbd
commit d39c6cb31f
14 changed files with 122 additions and 71 deletions

View File

@ -55,7 +55,8 @@ void UpdateButtonState(const AnimatingGUIButton &abtn) {
_GP(guibuts)[abtn.buttonid].MouseOverImage = 0;
}
void Button_AnimateEx(GUIButton *butt, int view, int loop, int speed, int repeat, int blocking, int direction, int sframe) {
void Button_AnimateEx(GUIButton *butt, int view, int loop, int speed,
int repeat, int blocking, int direction, int sframe, int volume = -1) {
int guin = butt->ParentId;
int objn = butt->Id;
@ -82,6 +83,8 @@ void Button_AnimateEx(GUIButton *butt, int view, int loop, int speed, int repeat
if ((direction < 0) || (direction > 1))
quit("!AnimateButton: invalid direction");
volume = std::min(volume, 100); // NOTE: negative volume means use defaults
// if it's already animating, stop it
FindAndRemoveButtonAnimation(guin, objn);
@ -104,6 +107,7 @@ void Button_AnimateEx(GUIButton *butt, int view, int loop, int speed, int repeat
abtn.direction = direction;
abtn.frame = sframe;
abtn.wait = abtn.speed + _GP(views)[abtn.view].loops[abtn.loop].frames[abtn.frame].speed;
abtn.volume = volume;
_GP(animbuts).push_back(abtn);
// launch into the first frame, and play the first frame's sound
UpdateButtonState(abtn);
@ -115,7 +119,7 @@ void Button_AnimateEx(GUIButton *butt, int view, int loop, int speed, int repeat
}
void Button_Animate(GUIButton *butt, int view, int loop, int speed, int repeat) {
Button_AnimateEx(butt, view, loop, speed, repeat, IN_BACKGROUND, FORWARDS, 0);
Button_AnimateEx(butt, view, loop, speed, repeat, IN_BACKGROUND, FORWARDS, 0, -1);
}
const char *Button_GetText_New(GUIButton *butt) {
@ -260,7 +264,7 @@ bool UpdateAnimatingButton(int bu) {
if (!CycleViewAnim(abtn.view, abtn.loop, abtn.frame, !abtn.direction,
abtn.repeat != 0 ? ANIM_REPEAT : ANIM_ONCE))
return false;
CheckViewFrame(abtn.view, abtn.loop, abtn.frame);
CheckViewFrame(abtn.view, abtn.loop, abtn.frame, abtn.volume);
abtn.wait = abtn.speed + _GP(views)[abtn.view].loops[abtn.loop].frames[abtn.frame].speed;
UpdateButtonState(abtn);
return true;
@ -341,7 +345,7 @@ RuntimeScriptValue Sc_Button_Animate(void *self, const RuntimeScriptValue *param
}
RuntimeScriptValue Sc_Button_AnimateEx(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID_PINT7(GUIButton, Button_AnimateEx);
API_OBJCALL_VOID_PINT8(GUIButton, Button_AnimateEx);
}
// const char* | GUIButton *butt
@ -454,7 +458,7 @@ RuntimeScriptValue Sc_Button_GetView(void *self, const RuntimeScriptValue *param
void RegisterButtonAPI() {
ccAddExternalObjectFunction("Button::Animate^4", Sc_Button_Animate);
ccAddExternalObjectFunction("Button::Animate^7", Sc_Button_AnimateEx);
ccAddExternalObjectFunction("Button::Animate^8", Sc_Button_AnimateEx);
ccAddExternalObjectFunction("Button::Click^1", Sc_Button_Click);
ccAddExternalObjectFunction("Button::GetText^1", Sc_Button_GetText);
ccAddExternalObjectFunction("Button::SetText^1", Sc_Button_SetText);

View File

@ -153,25 +153,32 @@ void Character_AddWaypoint(CharacterInfo *chaa, int x, int y) {
}
void Character_AnimateFrom(CharacterInfo *chaa, int loop, int delay, int repeat, int blocking, int direction, int sframe) {
void Character_AnimateEx(CharacterInfo *chaa, int loop, int delay, int repeat,
int blocking, int direction, int sframe, int volume = -1) {
if (direction == FORWARDS)
direction = 0;
else if (direction == BACKWARDS)
direction = 1;
else
quit("!Character.Animate: Invalid DIRECTION parameter");
if (blocking == BLOCKING)
blocking = 1;
else if (blocking == IN_BACKGROUND)
blocking = 0;
animate_character(chaa, loop, delay, repeat, 0, direction, sframe);
if ((repeat < 0) || (repeat > 1))
quit("!Character.Animate: invalid repeat value");
if ((blocking < 0) || (blocking > 1))
quit("!Character.Animate: invalid blocking value");
if ((direction < 0) || (direction > 1))
quit("!Character.Animate: invalid direction");
if ((blocking == BLOCKING) || (blocking == 1))
animate_character(chaa, loop, delay, repeat, 0, direction, sframe, volume);
if (blocking != 0)
GameLoopUntilValueIsZero(&chaa->animating);
else if ((blocking != IN_BACKGROUND) && (blocking != 0))
quit("!Character.Animate: Invalid BLOCKING parameter");
}
void Character_Animate(CharacterInfo *chaa, int loop, int delay, int repeat, int blocking, int direction) {
Character_AnimateFrom(chaa, loop, delay, repeat, blocking, direction, 0);
Character_AnimateEx(chaa, loop, delay, repeat, blocking, direction, 0, -1);
}
void Character_ChangeRoomAutoPosition(CharacterInfo *chaa, int room, int newPos) {
@ -259,7 +266,7 @@ void Character_ChangeView(CharacterInfo *chap, int vii) {
debug_script_log("%s: Change view to %d", chap->scrname, vii + 1);
chap->defview = vii;
chap->view = vii;
chap->animating = 0;
stop_character_anim(chap);
chap->frame = 0;
chap->wait = 0;
chap->walkwait = 0;
@ -548,7 +555,7 @@ void Character_LockViewEx(CharacterInfo *chap, int vii, int stopMoving) {
Character_StopMoving(chap);
}
chap->view = vii;
chap->animating = 0;
stop_character_anim(chap);
FindReasonableLoopForCharacter(chap);
chap->frame = 0;
chap->wait = 0;
@ -932,7 +939,7 @@ void Character_UnlockViewEx(CharacterInfo *chaa, int stopMoving) {
maxloop = 4;
FindReasonableLoopForCharacter(chaa);
}
chaa->animating = 0;
stop_character_anim(chaa);
chaa->idleleft = chaa->idletime;
chaa->pic_xoffs = 0;
chaa->pic_yoffs = 0;
@ -1585,7 +1592,7 @@ void walk_character(int chac, int tox, int toy, int ignwal, bool autoWalkAnims)
}
if ((chin->animating) && (autoWalkAnims))
chin->animating = 0;
stop_character_anim(chin);
if (chin->idleleft < 0) {
ReleaseCharacterView(chac);
@ -2013,13 +2020,14 @@ void setup_player_character(int charid) {
}
}
void animate_character(CharacterInfo *chap, int loopn, int sppd, int rept, int noidleoverride, int direction, int sframe) {
void animate_character(CharacterInfo *chap, int loopn, int sppd, int rept,
int noidleoverride, int direction, int sframe, int volume) {
if ((chap->view < 0) || (chap->view > _GP(game).numviews)) {
quitprintf("!AnimateCharacter: you need to set the view number first\n"
"(trying to animate '%s' using loop %d. View is currently %d).", chap->name, loopn, chap->view + 1);
"(trying to animate '%s' using loop %d. View is currently %d).", chap->name, loopn, chap->view + 1);
}
debug_script_log("%s: Start anim view %d loop %d, spd %d, repeat %d, frame: %d", chap->scrname, chap->view + 1, loopn, sppd, rept, sframe);
debug_script_log("%s: Start anim view %d loop %d, spd %d, repeat %d, frame: %d",
chap->scrname, chap->view + 1, loopn, sppd, rept, sframe);
if ((chap->idleleft < 0) && (noidleoverride == 0)) {
// if idle view in progress for the character (and this is not the
// "start idle animation" animate_character call), stop the idle anim
@ -2046,30 +2054,39 @@ void animate_character(CharacterInfo *chap, int loopn, int sppd, int rept, int n
sframe = _GP(views)[chap->view].loops[loopn].numFrames - (-sframe);
}
chap->frame = sframe;
chap->wait = sppd + _GP(views)[chap->view].loops[loopn].frames[chap->frame].speed;
_GP(charextra)[chap->index_id].cur_anim_volume = std::min(100, volume);
CheckViewFrameForCharacter(chap);
}
void stop_character_anim(CharacterInfo *chap) { // TODO: may expand with resetting more properties,
// but have to be careful to not break logic somewhere
chap->animating = 0;
_GP(charextra)[chap->index_id].cur_anim_volume = -1;
}
void CheckViewFrameForCharacter(CharacterInfo *chi) {
int soundVolume = SCR_NO_VALUE;
int frame_vol = -1;
// If there's an explicit animation volume - use that first
if (_GP(charextra)[chi->index_id].cur_anim_volume >= 0) {
frame_vol = _GP(charextra)[chi->index_id].cur_anim_volume;
}
// Adjust the sound volume using the character's zoom level
// NOTE: historically scales only in 0-100 range :/
if (chi->flags & CHF_SCALEVOLUME) {
// adjust the sound volume using the character's zoom level
int zoom_level = _GP(charextra)[chi->index_id].zoom;
if (zoom_level == 0)
if (zoom_level <= 0)
zoom_level = 100;
soundVolume = zoom_level;
if (soundVolume < 0)
soundVolume = 0;
if (soundVolume > 100)
soundVolume = 100;
else
zoom_level = std::min(zoom_level, 100);
if (frame_vol < 0)
frame_vol = zoom_level;
else
frame_vol = frame_vol * zoom_level / 100;
}
CheckViewFrame(chi->view, chi->loop, chi->frame, soundVolume);
CheckViewFrame(chi->view, chi->loop, chi->frame, frame_vol);
}
Bitmap *GetCharacterImage(int charid, int *isFlipped) {
@ -2696,7 +2713,7 @@ void _displayspeech(const char *texx, int aschar, int xx, int yy, int widd, int
if (_G(loaded_game_file_version) > kGameVersion_272)
speakingChar->loop = oldloop;
speakingChar->animating = 0;
stop_character_anim(speakingChar);
speakingChar->frame = charFrameWas;
speakingChar->wait = 0;
speakingChar->idleleft = speakingChar->idletime;
@ -2834,8 +2851,12 @@ RuntimeScriptValue Sc_Character_Animate(void *self, const RuntimeScriptValue *pa
API_OBJCALL_VOID_PINT5(CharacterInfo, Character_Animate);
}
RuntimeScriptValue Sc_Character_AnimateFrom(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID_PINT6(CharacterInfo, Character_AnimateFrom);
RuntimeScriptValue Sc_Character_Animate6(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID_PINT6(CharacterInfo, Character_AnimateEx);
}
RuntimeScriptValue Sc_Character_Animate7(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID_PINT7(CharacterInfo, Character_AnimateEx);
}
// void | CharacterInfo *chaa, int room, int x, int y
@ -3545,7 +3566,8 @@ void RegisterCharacterAPI(ScriptAPIVersion base_api, ScriptAPIVersion /* compat_
ccAddExternalObjectFunction("Character::AddInventory^2", Sc_Character_AddInventory);
ccAddExternalObjectFunction("Character::AddWaypoint^2", Sc_Character_AddWaypoint);
ccAddExternalObjectFunction("Character::Animate^5", Sc_Character_Animate);
ccAddExternalObjectFunction("Character::Animate^6", Sc_Character_AnimateFrom);
ccAddExternalObjectFunction("Character::Animate^6", Sc_Character_Animate6);
ccAddExternalObjectFunction("Character::Animate^7", Sc_Character_Animate7);
ccAddExternalObjectFunction("Character::ChangeRoom^3", Sc_Character_ChangeRoom);
ccAddExternalObjectFunction("Character::ChangeRoom^4", Sc_Character_ChangeRoomSetLoop);
ccAddExternalObjectFunction("Character::ChangeRoomAutoPosition^2", Sc_Character_ChangeRoomAutoPosition);

View File

@ -176,7 +176,10 @@ class Bitmap;
}
using namespace AGS; // FIXME later
void animate_character(CharacterInfo *chap, int loopn, int sppd, int rept, int noidleoverride = 0, int direction = 0, int sframe = 0);
void animate_character(CharacterInfo *chap, int loopn, int sppd, int rept,
int noidleoverride = 0, int direction = 0, int sframe = 0, int volume = -1);
// Clears up animation parameters
void stop_character_anim(CharacterInfo *chap);
void walk_character(int chac, int tox, int toy, int ignwal, bool autoWalkAnims);
int find_looporder_index(int curloop);
// returns 0 to use diagonal, 1 to not

View File

@ -54,6 +54,7 @@ struct CharacterExtras {
int8 process_idle_this_time = 0;
int8 slow_move_counter = 0;
short animwait = 0;
int cur_anim_volume = -1; // current animation sound volume (-1 = default)
void ReadFromFile(Shared::Stream *in);
void WriteToFile(Shared::Stream *out);

View File

@ -294,18 +294,19 @@ int CharacterInfo::update_character_animating(int &aa, int &doing_nothing) {
// Normal view animation
const int oldframe = frame;
bool done_anim = false;
if ((aa == _G(char_speaking)) &&
(_GP(play).speech_in_post_state ||
((!_GP(play).speech_has_voice) &&
(_GP(play).close_mouth_speech_time > 0) &&
(_GP(play).messagetime < _GP(play).close_mouth_speech_time)))) {
// finished talking - stop animation
animating = 0;
done_anim = true;
frame = 0;
} else {
if (!CycleViewAnim(view, loop, frame, (animating & CHANIM_BACKWARDS) == 0,
(animating & CHANIM_REPEAT) ? ANIM_REPEAT : ANIM_ONCE)) {
animating = 0; // finished animating
done_anim = true; // finished animating
// end of idle anim
if (idleleft < 0) {
// constant anim, reset (need this cos animating==0)
@ -329,6 +330,9 @@ int CharacterInfo::update_character_animating(int &aa, int &doing_nothing) {
if (frame != oldframe)
CheckViewFrameForCharacter(this);
if (done_anim)
stop_character_anim(this);
}
}

View File

@ -19,6 +19,7 @@
*
*/
#include "ags/lib/std/algorithm.h"
#include "ags/engine/ac/global_object.h"
#include "ags/shared/ac/common.h"
#include "ags/engine/ac/object.h"
@ -213,7 +214,7 @@ int GetObjectBaseline(int obn) {
return _G(objs)[obn].baseline;
}
void AnimateObjectImpl(int obn, int loopn, int spdd, int rept, int direction, int blocking, int sframe) {
void AnimateObjectImpl(int obn, int loopn, int spdd, int rept, int direction, int blocking, int sframe, int volume) {
if (obn >= MANOBJNUM) {
scAnimateCharacter(obn - 100, loopn, spdd, rept);
return;
@ -257,7 +258,8 @@ void AnimateObjectImpl(int obn, int loopn, int spdd, int rept, int direction, in
_G(objs)[obn].num = Math::InRangeOrDef<uint16_t>(pic, 0);
if (pic > UINT16_MAX)
debug_script_warn("Warning: object's (id %d) sprite %d is outside of internal range (%d), reset to 0", obn, pic, UINT16_MAX);
CheckViewFrame(_G(objs)[obn].view, loopn, _G(objs)[obn].frame);
_G(objs)[obn].anim_volume = std::min(volume, 100); // NOTE: negative volume means use defaults
CheckViewFrame(_G(objs)[obn].view, loopn, _G(objs)[obn].frame, _G(objs)[obn].anim_volume);
if (blocking)
GameLoopUntilValueIsZero(&_G(objs)[obn].cycling);

View File

@ -50,7 +50,7 @@ void SetObjectBaseline(int obn, int basel);
int GetObjectBaseline(int obn);
void AnimateObjectEx(int obn, int loopn, int spdd, int rept, int direction, int blocking);
void AnimateObject(int obn, int loopn, int spdd, int rept);
void AnimateObjectImpl(int obn, int loopn, int spdd, int rept, int direction, int blocking, int sframe);
void AnimateObjectImpl(int obn, int loopn, int spdd, int rept, int direction, int blocking, int sframe, int volume = -1);
void MergeObject(int obn);
void StopObjectMoving(int objj);
void ObjectOff(int obn);

View File

@ -120,26 +120,29 @@ int Object_GetBaseline(ScriptObject *objj) {
return GetObjectBaseline(objj->id);
}
void Object_AnimateFrom(ScriptObject *objj, int loop, int delay, int repeat, int blocking, int direction, int sframe) {
void Object_AnimateEx(ScriptObject *objj, int loop, int delay, int repeat,
int blocking, int direction, int sframe, int volume = -1) {
if (direction == FORWARDS)
direction = 0;
else if (direction == BACKWARDS)
direction = 1;
else
quit("!Object.Animate: Invalid DIRECTION parameter");
if ((blocking == BLOCKING) || (blocking == 1))
if (blocking == BLOCKING)
blocking = 1;
else if ((blocking == IN_BACKGROUND) || (blocking == 0))
else if (blocking == IN_BACKGROUND)
blocking = 0;
else
quit("!Object.Animate: Invalid BLOCKING parameter");
AnimateObjectImpl(objj->id, loop, delay, repeat, direction, blocking, sframe);
if ((repeat < 0) || (repeat > 1))
quit("!Object.Animate: invalid repeat value");
if ((blocking < 0) || (blocking > 1))
quit("!Object.Animate: invalid blocking value");
if ((direction < 0) || (direction > 1))
quit("!Object.Animate: invalid direction");
AnimateObjectImpl(objj->id, loop, delay, repeat, direction, blocking, sframe, volume);
}
void Object_Animate(ScriptObject *objj, int loop, int delay, int repeat, int blocking, int direction) {
Object_AnimateFrom(objj, loop, delay, repeat, blocking, direction, 0);
Object_AnimateEx(objj, loop, delay, repeat, blocking, direction, 0, -1);
}
void Object_StopAnimating(ScriptObject *objj) {
@ -629,8 +632,12 @@ RuntimeScriptValue Sc_Object_Animate(void *self, const RuntimeScriptValue *param
API_OBJCALL_VOID_PINT5(ScriptObject, Object_Animate);
}
RuntimeScriptValue Sc_Object_AnimateFrom(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID_PINT6(ScriptObject, Object_AnimateFrom);
RuntimeScriptValue Sc_Object_Animate6(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID_PINT6(ScriptObject, Object_AnimateEx);
}
RuntimeScriptValue Sc_Object_Animate7(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID_PINT7(ScriptObject, Object_AnimateEx);
}
// int (ScriptObject *objj, ScriptObject *obj2)
@ -935,7 +942,8 @@ RuntimeScriptValue Sc_Object_SetY(void *self, const RuntimeScriptValue *params,
void RegisterObjectAPI() {
ccAddExternalObjectFunction("Object::Animate^5", Sc_Object_Animate);
ccAddExternalObjectFunction("Object::Animate^6", Sc_Object_AnimateFrom);
ccAddExternalObjectFunction("Object::Animate^6", Sc_Object_Animate6);
ccAddExternalObjectFunction("Object::Animate^7", Sc_Object_Animate7);
ccAddExternalObjectFunction("Object::IsCollidingWithObject^1", Sc_Object_IsCollidingWithObject);
ccAddExternalObjectFunction("Object::GetName^1", Sc_Object_GetName);
ccAddExternalObjectFunction("Object::GetProperty^1", Sc_Object_GetProperty);

View File

@ -96,7 +96,7 @@ void RoomObject::UpdateCyclingView(int ref_id) {
return;
wait = vfptr->speed + overall_speed;
CheckViewFrame(view, loop, frame);
CheckViewFrame(view, loop, frame, anim_volume);
}
void RoomObject::ReadFromSavegame(Stream *in, int save_ver) {

View File

@ -65,6 +65,7 @@ struct RoomObject {
int8 flags;
// Down to here is a part of the plugin API
short blocking_width, blocking_height;
int anim_volume = -1; // current animation volume
Shared::String name;
RoomObject();

View File

@ -117,15 +117,15 @@ void precache_view(int view) {
}
}
// the specified frame has just appeared, see if we need
// to play a sound or whatever
// Handle the new animation frame (play linked sounds, etc)
void CheckViewFrame(int view, int loop, int frame, int sound_volume) {
ScriptAudioChannel *channel = nullptr;
// Play a sound, if one is linked to this frame
if (_GP(game).IsLegacyAudioSystem()) {
// sound field contains legacy sound num, so we also need an actual clip index
const int sound = _GP(views)[view].loops[loop].frames[frame].sound;
int &clip_id = _GP(views)[view].loops[loop].frames[frame].audioclip;
if (sound > 0) {
int &clip_id = _GP(views)[view].loops[loop].frames[frame].audioclip;
if (clip_id < 0) {
ScriptAudioClip *clip = GetAudioClipForOldStyleNumber(_GP(game), false, sound);
if (!clip)
@ -136,13 +136,12 @@ void CheckViewFrame(int view, int loop, int frame, int sound_volume) {
}
} else {
if (_GP(views)[view].loops[loop].frames[frame].sound >= 0) {
// play this sound (eg. footstep)
channel = play_audio_clip_by_index(_GP(views)[view].loops[loop].frames[frame].sound);
}
}
if (sound_volume != SCR_NO_VALUE && channel != nullptr) {
if (channel && (sound_volume >= 0)) {
sound_volume = std::min(sound_volume, 100);
auto *ch = AudioChans::GetChannel(channel->id);
if (ch)
ch->set_volume100(ch->get_volume100() * sound_volume / 100);
}

View File

@ -51,7 +51,8 @@ int ViewFrame_GetLoop(ScriptViewFrame *svf);
int ViewFrame_GetFrame(ScriptViewFrame *svf);
void precache_view(int view);
void CheckViewFrame(int view, int loop, int frame, int sound_volume = SCR_NO_VALUE);
// Handle the new animation frame (play linked sounds, etc)
void CheckViewFrame(int view, int loop, int frame, int sound_volume = -1);
// draws a view frame, flipped if appropriate
void DrawViewFrame(Shared::Bitmap *ds, const ViewFrame *vframe, int x, int y, bool alpha_blend = false);

View File

@ -39,12 +39,13 @@ class Stream;
using namespace AGS; // FIXME later
struct AnimatingGUIButton {
// index into _GP(guibuts) array, GUI, button
short buttonid = 0, ongui = 0, onguibut = 0;
// index into guibuts array, GUI, button
short buttonid = -1, ongui = -1, onguibut = -1;
// current animation status
uint16_t view = 0, loop = 0, frame = 0;
short speed = 0, repeat = 0, blocking = 0,
direction = 0, wait = 0;
short speed = 0, repeat = 0, blocking = 0, direction = 0, wait = 0;
// relative volume of the frame sounds
int volume = -1;
void ReadFromFile(Shared::Stream *in, int cmp_ver);
void WriteToFile(Shared::Stream *out);

View File

@ -443,6 +443,11 @@ inline const char *ScriptVSprintf(char *buffer, size_t buf_length, const char *f
METHOD((CLASS*)self, params[0].IValue, params[1].IValue, params[2].IValue, params[3].IValue, params[4].IValue, params[5].IValue, params[6].IValue); \
return RuntimeScriptValue((int32_t)0)
#define API_OBJCALL_VOID_PINT8(CLASS, METHOD) \
ASSERT_OBJ_PARAM_COUNT(METHOD, 8); \
METHOD((CLASS*)self, params[0].IValue, params[1].IValue, params[2].IValue, params[3].IValue, params[4].IValue, params[5].IValue, params[6].IValue, params[7].IValue); \
return RuntimeScriptValue((int32_t)0)
#define API_OBJCALL_VOID_PFLOAT(CLASS, METHOD) \
ASSERT_OBJ_PARAM_COUNT(METHOD, 1); \
METHOD((CLASS*)self, params[0].FValue); \