AGS: Share animation cycling algo between Buttons and RoomObjects

From upstream fd28a2548e39c85478d98e26b51713cd687acada
This commit is contained in:
Paul Gilbert 2022-04-22 20:05:04 -07:00
parent 3a01a16e12
commit 09d4069428
9 changed files with 110 additions and 129 deletions

View File

@ -25,6 +25,7 @@
#include "ags/shared/ac/view.h"
#include "ags/shared/ac/game_setup_struct.h"
#include "ags/engine/ac/global_translation.h"
#include "ags/engine/ac/object.h"
#include "ags/engine/ac/string.h"
#include "ags/engine/ac/view_frame.h"
#include "ags/engine/debugging/debug_log.h"
@ -43,6 +44,15 @@ using namespace AGS::Shared;
// *** BUTTON FUNCTIONS
// Update the actual button's image from the current animation frame
void UpdateButtonState(const AnimatingGUIButton &abtn) {
_GP(guibuts)[abtn.buttonid].Image = _GP(views)[abtn.view].loops[abtn.loop].frames[abtn.frame].pic;
_GP(guibuts)[abtn.buttonid].CurrentImage = _GP(guibuts)[abtn.buttonid].Image;
_GP(guibuts)[abtn.buttonid].PushedImage = 0;
_GP(guibuts)[abtn.buttonid].MouseOverImage = 0;
_GP(guibuts)[abtn.buttonid].NotifyParentChanged();
}
void Button_AnimateEx(GUIButton *butt, int view, int loop, int speed, int repeat, int blocking, int direction, int sframe) {
int guin = butt->ParentId;
int objn = butt->Id;
@ -73,24 +83,17 @@ void Button_AnimateEx(GUIButton *butt, int view, int loop, int speed, int repeat
// if it's already animating, stop it
FindAndRemoveButtonAnimation(guin, objn);
// Prepare button
int buttonId = _GP(guis)[guin].GetControlID(objn);
_GP(guibuts)[buttonId].PushedImage = 0;
_GP(guibuts)[buttonId].MouseOverImage = 0;
// reverse animation starts at the *previous frame*
if (direction) {
if (--sframe < 0)
sframe = _GP(views)[view].loops[loop].numFrames - (-sframe);
sframe++; // set on next frame, first call to Update will decrement
} else {
sframe--; // set on prev frame, first call to Update will increment
}
int but_id = _GP(guis)[guin].GetControlID(objn);
AnimatingGUIButton abtn;
abtn.ongui = guin;
abtn.onguibut = objn;
abtn.buttonid = buttonId;
abtn.buttonid = but_id;
abtn.view = view;
abtn.loop = loop;
abtn.speed = speed;
@ -98,14 +101,11 @@ void Button_AnimateEx(GUIButton *butt, int view, int loop, int speed, int repeat
abtn.blocking = blocking;
abtn.direction = direction;
abtn.frame = sframe;
abtn.wait = 0;
abtn.wait = abtn.speed + _GP(views)[abtn.view].loops[abtn.loop].frames[abtn.frame].speed;
_GP(animbuts).push_back(abtn);
// launch into the first frame
if (UpdateAnimatingButton(_GP(animbuts).size() - 1)) {
debug_script_warn("AnimateButton: no frames to animate (button: %s, view: %d, loop: %d)",
butt->GetScriptName().GetCStr(), view, loop);
StopButtonAnimation(_GP(animbuts).size() - 1);
}
// launch into the first frame, and play the first frame's sound
UpdateButtonState(abtn);
CheckViewFrame(abtn.view, abtn.loop, abtn.frame);
// Blocking animate
if (blocking)
@ -243,59 +243,19 @@ void AddButtonAnimation(const AnimatingGUIButton &abtn) {
}
// returns 1 if animation finished
int UpdateAnimatingButton(int bu) {
bool UpdateAnimatingButton(int bu) {
AnimatingGUIButton &abtn = _GP(animbuts)[bu];
if (abtn.wait > 0) {
abtn.wait--;
return 0;
return true;
}
ViewStruct *tview = &_GP(views)[abtn.view];
if (abtn.direction) { // backwards
abtn.frame--;
if (abtn.frame < 0) {
if ((abtn.loop > 0) && tview->loops[abtn.loop - 1].RunNextLoop()) {
// go to next loop
abtn.loop--;
abtn.frame = tview->loops[abtn.loop].numFrames - 1;
} else if (abtn.repeat) {
// multi-loop anim, go back
while (tview->loops[abtn.loop].RunNextLoop())
abtn.loop++;
abtn.frame = tview->loops[abtn.loop].numFrames - 1;
} else
return 1;
}
} else { // forwards
abtn.frame++;
if (abtn.frame >= tview->loops[abtn.loop].numFrames) {
if (tview->loops[abtn.loop].RunNextLoop()) {
// go to next loop
abtn.loop++;
abtn.frame = 0;
} else if (abtn.repeat) {
abtn.frame = 0;
// multi-loop anim, go back
while ((abtn.loop > 0) &&
(tview->loops[abtn.loop - 1].RunNextLoop()))
abtn.loop--;
} else
return 1;
}
}
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);
// update the button's image
_GP(guibuts)[abtn.buttonid].Image = tview->loops[abtn.loop].frames[abtn.frame].pic;
_GP(guibuts)[abtn.buttonid].CurrentImage = _GP(guibuts)[abtn.buttonid].Image;
_GP(guibuts)[abtn.buttonid].PushedImage = 0;
_GP(guibuts)[abtn.buttonid].MouseOverImage = 0;
_GP(guibuts)[abtn.buttonid].NotifyParentChanged();
abtn.wait = abtn.speed + tview->loops[abtn.loop].frames[abtn.frame].speed;
return 0;
abtn.wait = abtn.speed + _GP(views)[abtn.view].loops[abtn.loop].frames[abtn.frame].speed;
UpdateButtonState(abtn);
return true;
}
void StopButtonAnimation(int idxn) {

View File

@ -48,7 +48,8 @@ void Button_SetPushedGraphic(GUIButton *guil, int slotn);
int Button_GetTextColor(GUIButton *butt);
void Button_SetTextColor(GUIButton *butt, int newcol);
int UpdateAnimatingButton(int bu);
// Update button's animation, returns whether the animation continues
bool UpdateAnimatingButton(int bu);
size_t GetAnimatingButtonCount();
AnimatingGUIButton *GetAnimatingButtonByIndex(int idxn);
void AddButtonAnimation(const AnimatingGUIButton &abtn);

View File

@ -40,6 +40,7 @@
#include "ags/engine/ac/route_finder.h"
#include "ags/engine/gfx/graphics_driver.h"
#include "ags/shared/ac/view.h"
#include "ags/engine/ac/view_frame.h"
#include "ags/shared/gfx/bitmap.h"
#include "ags/shared/gfx/gfx_def.h"
#include "ags/shared/gui/gui_main.h"
@ -552,6 +553,72 @@ int check_click_on_object(int roomx, int roomy, int mood) {
return 1;
}
// General view animation algorithm: find next loop and frame, depending on anim settings
bool CycleViewAnim(int view, uint16_t &o_loop, uint16_t &o_frame, bool forwards, int repeat) {
// Allow multi-loop repeat: idk why, but original engine behavior
// was to only check this for forward animation, not backward
const bool multi_loop_repeat = !forwards || (_GP(play).no_multiloop_repeat == 0);
ViewStruct *aview = &_GP(views)[view];
uint16_t loop = o_loop;
uint16_t frame = o_frame;
bool done = false;
if (forwards) {
if (frame + 1 >= aview->loops[loop].numFrames) { // Reached the last frame in the loop, find out what to do next
if (aview->loops[loop].RunNextLoop()) {
// go to next loop
loop++;
frame = 0;
} else {
// If either ANIM_REPEAT or ANIM_ONCERESET:
// reset to the beginning of a multiloop animation
if (repeat != ANIM_ONCE) {
frame = 0;
if (multi_loop_repeat)
while ((loop > 0) && (aview->loops[loop - 1].RunNextLoop()))
loop--;
} else { // if ANIM_ONCE, stop at the last frame
frame = aview->loops[loop].numFrames - 1;
}
if (repeat != ANIM_REPEAT) // either ANIM_ONCE or ANIM_ONCERESET
done = true; // finished animation
}
} else
frame++;
} else // backwards
{
if (frame == 0) { // Reached the first frame in the loop, find out what to do next
if ((loop > 0) && aview->loops[loop - 1].RunNextLoop()) {
// go to next loop
loop--;
frame = aview->loops[loop].numFrames - 1;
} else {
// If either ANIM_REPEAT or ANIM_ONCERESET:
// reset to the beginning of a multiloop animation
if (repeat != ANIM_ONCE) {
if (multi_loop_repeat)
while (aview->loops[loop].RunNextLoop())
loop++;
frame = aview->loops[loop].numFrames - 1;
} else { // if ANIM_ONCE, stop at the first frame
frame = 0;
}
if (repeat != ANIM_REPEAT) // either ANIM_ONCE or ANIM_ONCERESET
done = true; // finished animation
}
} else
frame--;
}
// Update object values
o_loop = loop;
o_frame = frame;
return !done; // have we finished animating?
}
//=============================================================================
//
// Script API Functions

View File

@ -102,6 +102,10 @@ int is_pos_in_sprite(int xx, int yy, int arx, int ary, Shared::Bitmap *sprit
// X and Y co-ordinates must be in native format
// X and Y are ROOM coordinates
int check_click_on_object(int roomx, int roomy, int mood);
// General view animation algorithm: find next loop and frame, depending on anim settings;
// loop and frame values are passed by reference and will be updated;
// returns whether the animation should continue.
bool CycleViewAnim(int view, uint16_t &loop, uint16_t &frame, bool forwards, int repeat);
} // namespace AGS3

View File

@ -24,6 +24,7 @@
#include "ags/shared/ac/common_defines.h"
#include "ags/shared/ac/game_setup_struct.h"
#include "ags/engine/ac/game_state.h"
#include "ags/engine/ac/object.h"
#include "ags/engine/ac/runtime_defines.h"
#include "ags/engine/ac/view_frame.h"
#include "ags/engine/debugging/debug_log.h"
@ -79,24 +80,15 @@ void RoomObject::UpdateCyclingView(int ref_id) {
if (cycling == 0) return;
if (view == (uint16_t)-1) return;
if (wait > 0) {
wait--;
return;
wait--; return;
}
if (cycling >= ANIM_BACKWARDS) {
update_cycle_view_backwards();
} else { // Animate forwards
update_cycle_view_forwards();
} // end if forwards
cycling = CycleViewAnim(view, loop, frame, cycling < ANIM_BACKWARDS, cycling % ANIM_BACKWARDS);
ViewFrame *vfptr = &_GP(views)[view].loops[loop].frames[frame];
if (vfptr->pic > UINT16_MAX)
debug_script_warn("Warning: object's (id %d) sprite %d is outside of internal range (%d), reset to 0",
ref_id, vfptr->pic, UINT16_MAX);
ref_id, vfptr->pic, UINT16_MAX);
num = Math::InRangeOrDef<uint16_t>(vfptr->pic, 0);
if (cycling == 0)
@ -106,54 +98,6 @@ void RoomObject::UpdateCyclingView(int ref_id) {
CheckViewFrame(view, loop, frame);
}
void RoomObject::update_cycle_view_forwards() {
frame++;
if (frame >= _GP(views)[view].loops[loop].numFrames) {
// go to next loop thing
if (_GP(views)[view].loops[loop].RunNextLoop()) {
if (loop + 1 >= _GP(views)[view].numLoops)
quit("!Last loop in a view requested to move to next loop");
loop++;
frame = 0;
} else if (cycling % ANIM_BACKWARDS == ANIM_ONCE) {
// leave it on the last frame
cycling = 0;
frame--;
} else {
if (_GP(play).no_multiloop_repeat == 0) {
// multi-loop anims, go back to start of it
while ((loop > 0) &&
(_GP(views)[view].loops[loop - 1].RunNextLoop()))
loop--;
}
if (cycling % ANIM_BACKWARDS == ANIM_ONCERESET)
cycling = 0;
frame = 0;
}
}
}
void RoomObject::update_cycle_view_backwards() {
// animate backwards
if (frame > 0) {
frame--;
} else {
if ((loop > 0) &&
(_GP(views)[view].loops[loop - 1].RunNextLoop())) {
// If it's a Go-to-next-loop on the previous one, then go back
loop--;
frame = _GP(views)[view].loops[loop].numFrames - 1;
} else if (cycling % ANIM_BACKWARDS == ANIM_ONCE) {
// leave it on the first frame
cycling = 0;
frame = 0;
} else { // repeating animation
frame = _GP(views)[view].loops[loop].numFrames - 1;
}
}
}
void RoomObject::ReadFromSavegame(Stream *in, int save_ver) {
x = in->ReadInt32();
y = in->ReadInt32();

View File

@ -79,8 +79,6 @@ struct RoomObject {
}
void UpdateCyclingView(int ref_id);
void update_cycle_view_forwards();
void update_cycle_view_backwards();
void ReadFromSavegame(Shared::Stream *in, int save_ver);
void WriteToSavegame(Shared::Stream *out) const;

View File

@ -90,8 +90,11 @@ const int LegacyRoomVolumeFactor = 30;
#define CHANIM_REPEAT 2
#define CHANIM_BACKWARDS 4
#define ANIM_BACKWARDS 10
// Animates once and stops at the *last* frame
#define ANIM_ONCE 1
// Animates infinitely until stopped by command
#define ANIM_REPEAT 2
// Animates once and stops, resetting to the very first frame
#define ANIM_ONCERESET 3
#define FONT_STATUSBAR 0
#define FONT_NORMAL _GP(play).normal_font

View File

@ -19,9 +19,13 @@
*
*/
// Description of a button animation; stored separately from the GUI button.
//
#ifndef AGS_ENGINE_GUI_ANIMATING_GUI_BUTTON_H
#define AGS_ENGINE_GUI_ANIMATING_GUI_BUTTON_H
#include "ags/shared/core/types.h"
#include "ags/engine/ac/runtime_defines.h"
namespace AGS3 {
@ -38,7 +42,7 @@ struct AnimatingGUIButton {
// index into _GP(guibuts) array, GUI, button
short buttonid = 0, ongui = 0, onguibut = 0;
// current animation status
short view = 0, loop = 0, frame = 0;
uint16_t view = 0, loop = 0, frame = 0;
short speed = 0, repeat = 0, blocking = 0,
direction = 0, wait = 0;

View File

@ -594,7 +594,7 @@ static void game_loop_update_animated_buttons() {
// this bit isn't in update_stuff because it always needs to
// happen, even when the game is paused
for (size_t i = 0; i < GetAnimatingButtonCount(); ++i) {
if (UpdateAnimatingButton(i)) {
if (!UpdateAnimatingButton(i)) {
StopButtonAnimation(i);
i--;
}