diff --git a/source/graphics/MapReader.cpp b/source/graphics/MapReader.cpp index b3ea9567b7..e1679612b8 100644 --- a/source/graphics/MapReader.cpp +++ b/source/graphics/MapReader.cpp @@ -3,6 +3,7 @@ #include "MapReader.h" #include "graphics/Camera.h" +#include "graphics/CinemaTrack.h" #include "graphics/GameView.h" #include "graphics/Model.h" #include "graphics/ObjectManager.h" diff --git a/source/graphics/MapReader.h b/source/graphics/MapReader.h index 5cfc0110ec..9c9c81d447 100644 --- a/source/graphics/MapReader.h +++ b/source/graphics/MapReader.h @@ -5,7 +5,6 @@ #include "lib/res/handle.h" #include "ps/CStr.h" #include "LightEnv.h" -#include "CinemaTrack.h" #include "ps/FileUnpacker.h" class CObjectEntry; diff --git a/source/graphics/Model.cpp b/source/graphics/Model.cpp index dcb04b6c24..54f98e600f 100644 --- a/source/graphics/Model.cpp +++ b/source/graphics/Model.cpp @@ -225,20 +225,35 @@ void CModel::Update(float time) if (m_Anim && m_Anim->m_AnimDef && m_BoneMatrices) { // adjust for animation speed - float animtime = time*m_AnimSpeed; + float animTimeDelta = time*m_AnimSpeed; + + float oldAnimTime = m_AnimTime; // update animation time, but don't calculate bone matrices - do that (lazily) when // something requests them; that saves some calculation work for offscreen models, // and also assures the world space, inverted bone matrices (required for normal // skinning) are up to date with respect to m_Transform - m_AnimTime += animtime; + m_AnimTime += animTimeDelta; - float duration = m_Anim->m_AnimDef->GetDuration(); if (m_AnimTime > duration) { if (m_Flags & MODELFLAG_NOLOOPANIMATION) - SetAnimation(m_NextAnim); + { + if (m_NextAnim) + SetAnimation(m_NextAnim); + else + { + // Changing to no animation - probably becoming a corpse. + // Make sure the last displayed frame is the final frame + // of the animation. + float nearlyEnd = duration - 1.f; // 1 msec + if (abs(oldAnimTime - nearlyEnd) < 1.f) + SetAnimation(NULL); + else + m_AnimTime = nearlyEnd; + } + } else m_AnimTime = fmod(m_AnimTime, duration); } @@ -255,6 +270,24 @@ void CModel::Update(float time) m_Props[i].m_Model->Update(time); } +///////////////////////////////////////////////////////////////////////////////////////////////////////////// +bool CModel::NeedsNewAnim(float time) const +{ + // TODO: fix UnitAnimation so it correctly loops animated props + + if (m_Anim && m_Anim->m_AnimDef && m_BoneMatrices) + { + // adjust for animation speed + float animtime = time * m_AnimSpeed; + + float duration = m_Anim->m_AnimDef->GetDuration(); + if (m_AnimTime + animtime > duration) + return true; + } + + return false; +} + ///////////////////////////////////////////////////////////////////////////////////////////////////////////// // InvalidatePosition void CModel::InvalidatePosition() diff --git a/source/graphics/Model.h b/source/graphics/Model.h index 31f3a1bad6..c9f932e3f4 100644 --- a/source/graphics/Model.h +++ b/source/graphics/Model.h @@ -29,6 +29,10 @@ class CSkeletonAnimDef; // information for a model in game class CModel : public CRenderableObject { + friend class CUnitAnimation; + // HACK - we should probably move the rest of this class's animation state + // into the animation class, then it wouldn't need friend access + public: struct Prop { Prop() : m_Point(0), m_Model(0), m_ObjectEntry(0) {} @@ -50,6 +54,9 @@ public: void CalcBounds(); // update this model's state; 'time' is the time since the last update, in MS void Update(float time); + // returns true if Update(time) will require a new animation (due to the + // current one ending) + bool NeedsNewAnim(float time) const; // get the model's geometry data CModelDefPtr GetModelDef() { return m_pModelDef; } @@ -76,7 +83,7 @@ public: bool SetAnimation(CSkeletonAnim* anim, bool once = false, float speed = 1000.0f, CSkeletonAnim* next = NULL); // get the currently playing animation, if any - CSkeletonAnim* GetAnimation() { return m_Anim; } + CSkeletonAnim* GetAnimation() const { return m_Anim; } // set the animation state to be the same as from another; both models should // be compatible types (same type of skeleton) diff --git a/source/graphics/ObjectBase.cpp b/source/graphics/ObjectBase.cpp index 87a03cd787..8c141e51af 100644 --- a/source/graphics/ObjectBase.cpp +++ b/source/graphics/ObjectBase.cpp @@ -9,6 +9,7 @@ #include "ps/XML/Xeromyces.h" #include "ps/CLogger.h" #include "lib/timer.h" +#include "maths/MathUtil.h" #define LOG_CATEGORY "graphics" @@ -153,17 +154,13 @@ bool CObjectBase::Load(const char* filename) } else if (ae.Name == at_event) { - anim.m_ActionPos = CStr(ae.Value).ToDouble(); - if (anim.m_ActionPos < 0.0) anim.m_ActionPos = 0.0; - else if (anim.m_ActionPos > 100.0) anim.m_ActionPos = 1.0; - else if (anim.m_ActionPos > 1.0) anim.m_ActionPos /= 100.0; + float pos = CStr(ae.Value).ToFloat(); + anim.m_ActionPos = clamp(pos, 0.f, 1.f); } else if (ae.Name == at_load) { - anim.m_ActionPos2 = CStr(ae.Value).ToDouble(); - if (anim.m_ActionPos2 < 0.0) anim.m_ActionPos2 = 0.0; - else if (anim.m_ActionPos2 > 100.0) anim.m_ActionPos2 = 1.0; - else if (anim.m_ActionPos2 > 1.0) anim.m_ActionPos2 /= 100.0; + float pos = CStr(ae.Value).ToFloat(); + anim.m_ActionPos2 = clamp(pos, 0.f, 1.f); } else ; // unrecognised element diff --git a/source/graphics/ObjectBase.h b/source/graphics/ObjectBase.h index 6b7fcb8ab2..77a646cf0f 100644 --- a/source/graphics/ObjectBase.h +++ b/source/graphics/ObjectBase.h @@ -26,8 +26,8 @@ public: float m_Speed; // fraction of the way through the animation that the interesting bit(s) // happens - double m_ActionPos; - double m_ActionPos2; + float m_ActionPos; + float m_ActionPos2; }; struct Prop { diff --git a/source/graphics/SkeletonAnim.h b/source/graphics/SkeletonAnim.h index f323133779..4946807102 100644 --- a/source/graphics/SkeletonAnim.h +++ b/source/graphics/SkeletonAnim.h @@ -25,7 +25,10 @@ public: CSkeletonAnimDef* m_AnimDef; // speed at which this animation runs float m_Speed; - // time(s) during the animation at which the interesting bit(s) happens (fractional) + // Times during the animation at which the interesting bits happen. Measured + // as fractions (0..1) of the total animation length. + // ActionPos is used for melee hits, projectile launches, etc. + // ActionPos2 is used for loading projectile ammunition. float m_ActionPos; float m_ActionPos2; // object space bounds of the model when this animation is applied to it diff --git a/source/graphics/SkeletonAnimDef.cpp b/source/graphics/SkeletonAnimDef.cpp index 0f29389d8c..41507e53a9 100644 --- a/source/graphics/SkeletonAnimDef.cpp +++ b/source/graphics/SkeletonAnimDef.cpp @@ -9,6 +9,7 @@ #include "precompiled.h" #include "SkeletonAnimDef.h" +#include "maths/MathUtil.h" #include "ps/FilePacker.h" #include "ps/FileUnpacker.h" @@ -31,27 +32,46 @@ CSkeletonAnimDef::~CSkeletonAnimDef() // animation void CSkeletonAnimDef::BuildBoneMatrices(float time,CMatrix3D* matrices) const { - float fstartframe=time/m_FrameTime; - u32 startframe=u32(time/m_FrameTime); - float deltatime=fstartframe-startframe; + float fstartframe = time/m_FrameTime; + u32 startframe = u32(time/m_FrameTime); + float deltatime = fstartframe-startframe; - startframe%=m_NumFrames; + startframe %= m_NumFrames; - u32 endframe=startframe+1; - endframe%=m_NumFrames; + u32 endframe = startframe + 1; + endframe %= m_NumFrames; - u32 i; - for (i=0;iGetRandomAnimation(name) != NULL); +} + bool CUnit::IsPlayingAnimation(const CStr& name) { return (m_Model->GetAnimation() && m_Model->GetAnimation()->m_Name == name); } +void CUnit::SetAnimationState(const CStr& name, bool once, float speed, bool keepSelection) +{ + m_Animation->SetAnimationState(name, once, speed, keepSelection); +} + +void CUnit::SetAnimationSync(float timeUntilActionPos) +{ + m_Animation->SetAnimationSync(timeUntilActionPos); +} + +void CUnit::UpdateModel(float frameTime) +{ + m_Animation->Update(frameTime); +} + void CUnit::SetPlayerID(int id) { diff --git a/source/graphics/Unit.h b/source/graphics/Unit.h index 6fbcec3280..58300a895b 100644 --- a/source/graphics/Unit.h +++ b/source/graphics/Unit.h @@ -10,7 +10,7 @@ class CObjectEntry; class CObjectManager; class CEntity; class CSkeletonAnim; -class CStrW; +class CUnitAnimation; ///////////////////////////////////////////////////////////////////////////////////////////// // CUnit: simple "actor" definition - defines a sole object within the world @@ -47,10 +47,16 @@ public: // SetEntitySelection(name) should typically be used before this. bool SetRandomAnimation(const CStr& name, bool once = false, float speed = 0.0f); + void SetAnimationState(const CStr& name, bool once = false, float speed = 0.0f, bool keepSelection = false); + void SetAnimationSync(float timeUntilActionPos); + void UpdateModel(float frameTime); + // Returns a random animation matching 'name'. If none is found, // returns idle instead. CSkeletonAnim* GetRandomAnimation(const CStr& name); + bool HasAnimation(const CStr& name); + // Sets the entity-selection, and updates the unit to use the new // actor variation. void SetEntitySelection(const CStr& selection); @@ -85,6 +91,8 @@ private: // player id of this unit (only read for graphical effects), or -1 if unspecified int m_PlayerID; + CUnitAnimation* m_Animation; + // unique (per map) ID number for units created in the editor, as a // permanent way of referencing them. -1 for non-editor units. int m_ID; diff --git a/source/graphics/UnitAnimation.cpp b/source/graphics/UnitAnimation.cpp new file mode 100644 index 0000000000..4575e7e57a --- /dev/null +++ b/source/graphics/UnitAnimation.cpp @@ -0,0 +1,95 @@ +#include "precompiled.h" + +#include "UnitAnimation.h" + +#include "graphics/Model.h" +#include "graphics/SkeletonAnim.h" +#include "graphics/SkeletonAnimDef.h" +#include "graphics/Unit.h" +#include "ps/CStr.h" + +namespace +{ + // Randomly modify the speed, so that units won't stay perfectly + // synchronised if they're playing animations of the same length + float DesyncSpeed(float speed) + { +// const float var = 0.05f; // max fractional variation from default +// return speed * (1.f - var + 2.f*var*(rand(0, 256)/255.f)); + // TODO: enable this desyncing for cases where we don't care about + // accurate looping, and just don't do it for e.g. projectile-launchers + // where we do care + return speed; + } +} + +CUnitAnimation::CUnitAnimation(CUnit& unit) +: m_Unit(unit), m_State("idle"), m_Looping(true), m_Speed(0.f), m_OriginalSpeed(0.f), m_TimeToNextSync(0.f) +{ +} + +void CUnitAnimation::SetAnimationState(const CStr& name, bool once, float speed, bool keepSelection) +{ + if (name == m_State) + return; + + m_State = name; + m_Looping = !once; + m_Speed = m_OriginalSpeed = speed; + m_TimeToNextSync = 0.f; + + if (! keepSelection) + m_Unit.SetEntitySelection(name); + + m_Unit.SetRandomAnimation(m_State, !m_Looping, DesyncSpeed(m_Speed)); +} + +void CUnitAnimation::SetAnimationSync(float timeUntilActionPos) +{ + // We need to finish looping our animation at the specified time from now. + // Assume it's playing at nearly the right speed, and we just need to perhaps + // shift it a little bit to stay in sync. + + m_TimeToNextSync = timeUntilActionPos; + + CModel* model = m_Unit.GetModel(); + + // Calculate the required playback speed so ActionPos coincides with timeUntilActionPos + float currentPos = model->m_AnimTime / model->m_Anim->m_AnimDef->GetDuration(); + float length = (model->m_Anim->m_ActionPos - currentPos); + if (length < 0.f) + length += 1.f; + float requiredSpeed = length / m_TimeToNextSync; + + // Shift in the right direction + if (requiredSpeed > m_OriginalSpeed) + m_Speed = std::min(requiredSpeed, m_OriginalSpeed*1.1f); + else if (requiredSpeed < m_OriginalSpeed) + m_Speed = std::max(requiredSpeed, m_OriginalSpeed*0.9f); + + model->m_AnimSpeed = m_Speed * model->m_Anim->m_AnimDef->GetDuration() * model->m_Anim->m_Speed; + + // TODO: this should use the ActionPos2, instead of totally ignoring it + m_Unit.ShowAmmunition(); +} + +void CUnitAnimation::Update(float time) +{ + CModel* model = m_Unit.GetModel(); + + // Choose a new random animation if we're going to loop + if (m_Looping && model->NeedsNewAnim(time)) + { + m_Unit.SetRandomAnimation(m_State, !m_Looping, DesyncSpeed(m_Speed)); + } + + if (m_TimeToNextSync >= 0.0 && m_TimeToNextSync-time < 0.0) + m_Unit.HideAmmunition(); + + m_TimeToNextSync -= time; + + // TODO: props should get a new random animation once they loop, independent + // of the object they're propped onto + + model->Update(time); +} diff --git a/source/graphics/UnitAnimation.h b/source/graphics/UnitAnimation.h new file mode 100644 index 0000000000..da1c96511c --- /dev/null +++ b/source/graphics/UnitAnimation.h @@ -0,0 +1,28 @@ +#ifndef UNITANIMATION_H__ +#define UNITANIMATION_H__ + +#include "ps/CStr.h" + +class CUnit; + +class CUnitAnimation : boost::noncopyable +{ +public: + CUnitAnimation(CUnit& unit); + + // (All times are measured in seconds) + + void SetAnimationState(const CStr& name, bool once, float speed, bool keepSelection); + void SetAnimationSync(float timeUntilActionPos); + void Update(float time); + +private: + CUnit& m_Unit; + CStr m_State; + bool m_Looping; + float m_Speed; + float m_OriginalSpeed; + float m_TimeToNextSync; +}; + +#endif // UNITANIMATION_H__ diff --git a/source/lib/res/sound/snd_mgr.cpp b/source/lib/res/sound/snd_mgr.cpp index 4d5c121c00..a60d569d7c 100644 --- a/source/lib/res/sound/snd_mgr.cpp +++ b/source/lib/res/sound/snd_mgr.cpp @@ -1143,31 +1143,31 @@ static LibError SndData_reload(SndData * sd, const char * fn, Handle hsd) ALsizei al_size = (ALsizei)file_size; #ifdef OGG_HACK -std::vector data; -data.reserve(500000); -if(file_type == FT_OGG) -{ - sd->o = ogg_create(); - ogg_give_raw(sd->o, (void*)file, file_size); - ogg_open(sd->o, sd->al_fmt, sd->al_freq); - size_t datasize=0; - size_t bytes_read; - do - { - const size_t bufsize = 32*KiB; - char buf[bufsize]; - bytes_read = ogg_read(sd->o, buf, bufsize); - data.insert(data.end(), &buf[0], &buf[bytes_read]); - datasize += bytes_read; - } - while(bytes_read > 0); - al_data = &data[0]; - al_size = (ALsizei)datasize; -} -else -{ - sd->o = NULL; -} + std::vector data; + data.reserve(500000); + if(file_type == FT_OGG) + { + sd->o = ogg_create(); + ogg_give_raw(sd->o, (void*)file, file_size); + ogg_open(sd->o, sd->al_fmt, sd->al_freq); + size_t datasize=0; + size_t bytes_read; + do + { + const size_t bufsize = 32*KiB; + char buf[bufsize]; + bytes_read = ogg_read(sd->o, buf, bufsize); + data.insert(data.end(), &buf[0], &buf[bytes_read]); + datasize += bytes_read; + } + while(bytes_read > 0); + al_data = &data[0]; + al_size = (ALsizei)datasize; + } + else + { + sd->o = NULL; + } #endif (void)file_buf_free(file); diff --git a/source/ps/FileUnpacker.h b/source/ps/FileUnpacker.h index 3f05bbd544..9490f06c29 100644 --- a/source/ps/FileUnpacker.h +++ b/source/ps/FileUnpacker.h @@ -10,8 +10,9 @@ #define _FILEUNPACKER_H #include -#include "lib/res/file/file.h" -#include "CStr.h" +#include "lib/res/file/file_io.h" + +class CStr8; #include "ps/Errors.h" #ifndef ERROR_GROUP_FILE_DEFINED @@ -47,7 +48,7 @@ public: // the given number of bytes have been read void UnpackRaw(void* rawdata, u32 rawdatalen); // UnpackString: unpack a string from the raw data stream - void UnpackString(CStr& result); + void UnpackString(CStr8& result); private: // the data read from file and used during unpack operations diff --git a/source/ps/GameSetup/GameSetup.cpp b/source/ps/GameSetup/GameSetup.cpp index b96acaee84..f55c80a741 100644 --- a/source/ps/GameSetup/GameSetup.cpp +++ b/source/ps/GameSetup/GameSetup.cpp @@ -30,6 +30,7 @@ #include "ps/Util.h" #include "ps/i18n.h" +#include "graphics/CinemaTrack.h" #include "graphics/GameView.h" #include "graphics/LightEnv.h" #include "graphics/MapReader.h" diff --git a/source/scripting/ScriptGlue.cpp b/source/scripting/ScriptGlue.cpp index 7c2bdfe408..f964e1d73d 100644 --- a/source/scripting/ScriptGlue.cpp +++ b/source/scripting/ScriptGlue.cpp @@ -9,24 +9,28 @@ #include "ScriptGlue.h" #include "JSConversions.h" #include "GameEvents.h" + #include "graphics/GameView.h" #include "graphics/LightEnv.h" #include "graphics/MapWriter.h" +#include "graphics/Unit.h" +#include "graphics/UnitManager.h" #include "graphics/scripting/JSInterface_Camera.h" #include "graphics/scripting/JSInterface_LightEnv.h" #include "gui/CGUI.h" #include "lib/timer.h" #include "maths/scripting/JSInterface_Vector3D.h" +#include "network/Client.h" +#include "network/Server.h" #include "ps/CConsole.h" #include "ps/CLogger.h" #include "ps/CStr.h" #include "ps/Game.h" #include "ps/GameSetup/GameSetup.h" -#include "ps/Interact.h" -#include "network/Client.h" -#include "network/Server.h" -#include "ps/i18n.h" #include "ps/Hotkey.h" +#include "ps/Interact.h" +#include "ps/ProfileViewer.h" +#include "ps/i18n.h" #include "ps/scripting/JSCollection.h" #include "ps/scripting/JSInterface_Console.h" #include "ps/scripting/JSInterface_Selection.h" @@ -34,20 +38,18 @@ #include "renderer/Renderer.h" #include "renderer/SkyManager.h" #include "renderer/WaterManager.h" -#include "graphics/Unit.h" -#include "graphics/UnitManager.h" -#include "simulation/EntityTemplateCollection.h" -#include "simulation/TechnologyCollection.h" #include "simulation/Entity.h" #include "simulation/EntityFormation.h" #include "simulation/EntityHandles.h" #include "simulation/EntityManager.h" #include "simulation/EntityTemplate.h" +#include "simulation/EntityTemplateCollection.h" #include "simulation/FormationManager.h" -#include "simulation/TriggerManager.h" #include "simulation/LOSManager.h" #include "simulation/Scheduler.h" #include "simulation/Simulation.h" +#include "simulation/TechnologyCollection.h" +#include "simulation/TriggerManager.h" #ifndef NO_GUI # include "gui/scripting/JSInterface_IGUIObject.h" @@ -1058,6 +1060,14 @@ JSBool getGlobal( JSContext* cx, JSObject* globalObject, uint argc, jsval* argv, return( JS_TRUE ); } +// Saves the current profiling data to the logs/profile.txt file +JSBool saveProfileData( JSContext* cx, JSObject* UNUSED(globalObject), uint argc, jsval* argv, jsval* rval ) +{ + JSU_REQUIRE_NO_PARAMS(); + g_ProfileViewer.SaveToFile(); + return( JS_TRUE ); +} + // Activates the building placement cursor for placing a building. The currently selected units // are then ordered to construct the building if it is placed. // params: templateName - the name of the entity to place. @@ -1474,6 +1484,7 @@ JSFunctionSpec ScriptFunctionTable[] = JS_FUNC(v3dist, v3dist, 2) JS_FUNC(buildTime, buildTime, 0) JS_FUNC(getGlobal, getGlobal, 0) + JS_FUNC(saveProfileData, saveProfileData, 0) // end of table marker {0, 0, 0, 0, 0} diff --git a/source/simulation/Entity.cpp b/source/simulation/Entity.cpp index b31acdb403..d29784d65f 100644 --- a/source/simulation/Entity.cpp +++ b/source/simulation/Entity.cpp @@ -257,11 +257,12 @@ void CEntity::kill(bool keepActor) g_Selection.removeAll( me ); entf_set(ENTF_DESTROYED); - g_EntityManager.SetDeath(true); // remember that a unit died this frame + g_EntityManager.m_refd[me.m_handle] = false; // refd must be made false when DESTROYED is set + g_EntityManager.SetDeath(true); // remember that a unit died this frame // If we have a death animation and want to keep the actor, play that animation if( keepActor && m_actor && - m_actor->GetRandomAnimation( "death" ) != m_actor->GetRandomAnimation( "idle" ) ) + m_actor->HasAnimation( "death" ) ) { // Prevent "wiggling" as we try to interpolate between here and our death position (if we were moving) m_graphics_position = m_position; @@ -274,16 +275,13 @@ void CEntity::kill(bool keepActor) // Play death animation and keep the actor in the game in a dead state // (TODO: remove the actor after some time through some kind of fading mechanism) - m_actor->SetEntitySelection( "death" ); - m_actor->SetRandomAnimation( "death", true ); + m_actor->SetAnimationState( "death", true ); } else { - g_Game->GetWorld()->GetUnitManager().RemoveUnit( m_actor ); - delete( m_actor ); + g_Game->GetWorld()->GetUnitManager().DeleteUnit( m_actor ); m_actor = NULL; - g_EntityManager.m_refd[me.m_handle] = false; me = HEntity(); // Will deallocate the entity, assuming nobody else has a reference to it } } @@ -526,8 +524,7 @@ void CEntity::update( size_t timestep ) { if( ( m_lastState != -1 ) || !m_actor->GetModel()->GetAnimation() ) { - m_actor->SetEntitySelection( "idle" ); - m_actor->SetRandomAnimation( "idle" ); + m_actor->SetAnimationState( "idle" ); } } } diff --git a/source/simulation/Entity.h b/source/simulation/Entity.h index 6a15f251c0..1e99c31c88 100644 --- a/source/simulation/Entity.h +++ b/source/simulation/Entity.h @@ -210,9 +210,6 @@ public: // Position in the current state's cycle static const size_t NOT_IN_CYCLE = (size_t)-1; size_t m_fsm_cyclepos; // -cycle_length....cycle_length - CSkeletonAnim* m_fsm_animation; // the animation we're about to play this cycle, - size_t m_fsm_anipos; // the time at which we should start playing it. - size_t m_fsm_anipos2; // for when there are two animation-related events we need to take care of. CEntityOrders m_orderQueue; std::deque m_listeners; diff --git a/source/simulation/EntityStateProcessing.cpp b/source/simulation/EntityStateProcessing.cpp index cb09f971a6..84c1fa2000 100644 --- a/source/simulation/EntityStateProcessing.cpp +++ b/source/simulation/EntityStateProcessing.cpp @@ -36,14 +36,14 @@ enum EGotoSituation bool CEntity::shouldRun(float distance) { - if (!entf_get(ENTF_SHOULD_RUN)) + if( !entf_get(ENTF_SHOULD_RUN) ) return false; // tired - if(m_staminaCurr <= 0) + if( m_staminaCurr <= 0 ) return false; - if(distance >= m_runMaxRange) + if( distance >= m_runMaxRange ) return false; // don't start running if less than minimum @@ -65,22 +65,16 @@ float CEntity::chooseMovementSpeed( float distance ) int sector = rintf( angle / (PI/2) * m_base->m_pitchDivs ); speed -= sector * m_base->m_pitchValue; - // TODO: the animation code requires unicode for now. will be changed to - // 8bit later (for consistency; note that filenames etc. need not be - // unicode), so remove this then. - const CStrW u_anim_name(anim_name); + entf_set_to(ENTF_IS_RUNNING, should_run); if ( m_actor ) { if ( !m_actor->IsPlayingAnimation( anim_name ) ) { - m_actor->SetEntitySelection( u_anim_name ); - m_actor->SetRandomAnimation( anim_name, false, speed ); + m_actor->SetAnimationState( anim_name, false, speed ); // Animation desync m_actor->GetModel()->Update( rand( 0, 1000 ) / 1000.0f ); - - entf_set_to(ENTF_IS_RUNNING, should_run); } } @@ -409,6 +403,7 @@ bool CEntity::processContactAction( CEntityOrder* current, size_t UNUSED(timeste return true; } } + bool CEntity::processContactActionNoPathing( CEntityOrder* current, size_t timestep_millis, const CStr& animation, CScriptEvent* contactEvent, SEntityAction* action ) { HEntity target = current->m_target_entity; @@ -416,31 +411,9 @@ bool CEntity::processContactActionNoPathing( CEntityOrder* current, size_t times if( m_fsm_cyclepos != NOT_IN_CYCLE ) { size_t nextpos = m_fsm_cyclepos + timestep_millis * 2; - if( ( m_fsm_cyclepos <= m_fsm_anipos ) && - ( nextpos > m_fsm_anipos ) ) - { - // Start playing. - // Start the animation. Actual damage/gather will be done in a - // few hundred ms, at the 'action point' of the animation we're - // now setting. - entf_clear(ENTF_IS_RUNNING); - // TODO: this is set to be looping, because apparently it otherwise - // plays one frame of 'idle' after e.g. attacks. But this way means - // animations sometimes play ~1.5 times then repeat, which looks - // broken too. - //m_actor->GetModel()->SetAnimation( m_fsm_animation, true, 1000.0f * m_fsm_animation->m_AnimDef->GetDuration() / (float)action->m_Speed, m_actor->GetRandomAnimation( "idle" ) ); - m_actor->GetModel()->SetAnimation( m_fsm_animation, false, 1000.0f * m_fsm_animation->m_AnimDef->GetDuration() / (float)action->m_Speed ); - } - if( ( m_fsm_cyclepos <= m_fsm_anipos2 ) && - ( nextpos > m_fsm_anipos2 ) ) - { - // Load the ammunition. - m_actor->ShowAmmunition(); - } + if( ( m_fsm_cyclepos <= action->m_Speed ) && ( nextpos > action->m_Speed ) ) { - m_actor->HideAmmunition(); - // TODO: Play a sound here. Use m_base->m_SoundGroupTable[animation] to get the // name of the soundgroup XML file to play. @@ -449,8 +422,7 @@ bool CEntity::processContactActionNoPathing( CEntityOrder* current, size_t times // Cancel current order entf_clear(ENTF_IS_RUNNING); entf_clear(ENTF_SHOULD_RUN); - m_actor->SetEntitySelection( "idle" ); - m_actor->SetRandomAnimation( "idle" ); + m_actor->SetAnimationState( "idle" ); popOrder(); if( m_orderQueue.empty() && target.isValid() ) { @@ -503,7 +475,7 @@ bool CEntity::processContactActionNoPathing( CEntityOrder* current, size_t times if( current->m_source == CEntityOrder::SOURCE_UNIT_AI && !m_stance->allowsMovement() ) { popOrder(); - m_actor->SetRandomAnimation( "idle" ); + m_actor->SetAnimationState( "idle" ); return false; // We're not allowed to move at all by the current stance } @@ -513,7 +485,7 @@ bool CEntity::processContactActionNoPathing( CEntityOrder* current, size_t times // The pathfinder will push its result in front of the current order if( !g_Pathfinder.requestAvoidPath( me, current, action->m_MinRange + 2.0f ) ) { - m_actor->SetRandomAnimation( "idle" ); // Nothing we can do.. maybe we'll find a better target + m_actor->SetAnimationState( "idle" ); // Nothing we can do.. maybe we'll find a better target popOrder(); } @@ -596,44 +568,8 @@ bool CEntity::processContactActionNoPathing( CEntityOrder* current, size_t times entf_clear(ENTF_IS_RUNNING); } - // Pick our animation, calculate the time to play it, and start the timer. - m_actor->SetEntitySelection( animation ); - m_fsm_animation = m_actor->GetRandomAnimation( animation ); - - // Here's the idea - we want to be at that animation's event point - // when the timer reaches action->m_Speed. The timer increments by 2 every millisecond. - // animation->m_actionpos is the time offset into that animation that event - // should happen. So... - m_fsm_anipos = (size_t)( action->m_Speed * ( 1.0f - 2 * m_fsm_animation->m_ActionPos ) ); - // But... - if( m_fsm_anipos < 0 ) // (FIXME: m_fsm_anipos is unsigned, so this will never be true...) - { - // We ought to have started it in the past. Oh well. - // Here's what we'll do: play it now, and advance it to - // the point it should be by now. - - m_actor->GetModel()->SetAnimation( m_fsm_animation, true, 1000.0f * m_fsm_animation->m_AnimDef->GetDuration() / (float)action->m_Speed, m_actor->GetRandomAnimation( "idle" ) ); - m_actor->GetModel()->Update( action->m_Speed * ( m_fsm_animation->m_ActionPos / 1000.0f - 0.0005f ) ); - } - else - { - // If we've just transitioned, play idle. Otherwise, let the previous animation complete, if it - // hasn't already. - if( entf_get(ENTF_TRANSITION) ) - { - // (don't change actor's entity-selection) - m_actor->SetRandomAnimation( "idle" ); - } - } - - // Load time needs to be animation->m_ActionPos2 ms after the start of the animation. - - m_fsm_anipos2 = m_fsm_anipos + (size_t)( action->m_Speed * m_fsm_animation->m_ActionPos2 * 2 ); - if( m_fsm_anipos2 < 0 ) // (FIXME: m_fsm_anipos2 is unsigned, so this will never be true...) - { - // Load now. - m_actor->ShowAmmunition(); - } + m_actor->SetAnimationState( animation, false, 1000.f / (float)action->m_Speed ); + m_actor->SetAnimationSync( (float)( action->m_Speed / 2) / 1000.f ); m_fsm_cyclepos = 0; diff --git a/source/simulation/Simulation.cpp b/source/simulation/Simulation.cpp index 726426367a..9403a67938 100644 --- a/source/simulation/Simulation.cpp +++ b/source/simulation/Simulation.cpp @@ -101,6 +101,12 @@ bool CSimulation::Update(double frameTime) return ok; } +void CSimulation::DiscardMissedUpdates() +{ + if (m_DeltaTime > 0.0) + m_DeltaTime = 0.0; +} + void CSimulation::Interpolate(double frameTime) { double turnLength = m_pTurnManager->GetTurnLength()/1000.0; @@ -110,10 +116,9 @@ void CSimulation::Interpolate(double frameTime) // m_DeltaTime/turnLength will usually be between -1 and 0, indicating // the time until the next frame, so we can use that easily. // If the simulation is going too slowly and hasn't been giving a chance - // to catch up before Interpolate is called, then m_DeltaTime > 0, and - // we'll just end up being clamped to offset=1 inside CEntity::interpolate, - // which is alright. - Interpolate(frameTime, m_DeltaTime / turnLength + 1.0); + // to catch up before Interpolate is called, then m_DeltaTime > 0, so we'll + // just clamp it to offset=1, which is alright. + Interpolate(frameTime, clamp(m_DeltaTime / turnLength + 1.0, 0.0, 1.0)); } void CSimulation::Interpolate(double frameTime, double offset) @@ -122,25 +127,27 @@ void CSimulation::Interpolate(double frameTime, double offset) const std::vector& units = m_pWorld->GetUnitManager().GetUnits(); for (size_t i = 0; i < units.size(); ++i) - units[i]->GetModel()->Update((float)frameTime); + units[i]->UpdateModel((float)frameTime); g_EntityManager.interpolateAll(offset); - m_pWorld->GetProjectileManager().InterpolateAll(frameTime); + m_pWorld->GetProjectileManager().InterpolateAll(offset); g_Renderer.GetWaterManager()->m_WaterTexTimer += frameTime; } void CSimulation::Simulate() { + float time = m_pTurnManager->GetTurnLength(); + PROFILE_START( "scheduler tick" ); - g_Scheduler.update(m_pTurnManager->GetTurnLength()); + g_Scheduler.update(time); PROFILE_END( "scheduler tick" ); PROFILE_START( "entity updates" ); - g_EntityManager.updateAll( m_pTurnManager->GetTurnLength() ); + g_EntityManager.updateAll(time); PROFILE_END( "entity updates" ); PROFILE_START( "projectile updates" ); - m_pWorld->GetProjectileManager().UpdateAll( m_pTurnManager->GetTurnLength() ); + m_pWorld->GetProjectileManager().UpdateAll(time); PROFILE_END( "projectile updates" ); PROFILE_START( "los update" ); @@ -148,7 +155,7 @@ void CSimulation::Simulate() PROFILE_END( "los update" ); PROFILE_START("trigger update"); - g_TriggerManager.Update( m_pTurnManager->GetTurnLength() ); + g_TriggerManager.Update(time); PROFILE_END("trigger udpate"); PROFILE_START( "turn manager update" ); diff --git a/source/simulation/Simulation.h b/source/simulation/Simulation.h index 30273fe9a0..e75e62baa9 100644 --- a/source/simulation/Simulation.h +++ b/source/simulation/Simulation.h @@ -44,6 +44,11 @@ public: // Returns false if it can't keep up with the desired simulation rate. bool Update(double frameTime); + // If the last Update couldn't keep up with the desired rate, ignore that + // and don't try to catch up when Update is called again. Will completely break + // synchronisation of sim time vs real time. + void DiscardMissedUpdates(); + // Update the graphical representations of the simulation by the given time. void Interpolate(double frameTime); diff --git a/source/tools/atlas/GameInterface/GameLoop.cpp b/source/tools/atlas/GameInterface/GameLoop.cpp index d587716609..99fd9c465c 100644 --- a/source/tools/atlas/GameInterface/GameLoop.cpp +++ b/source/tools/atlas/GameInterface/GameLoop.cpp @@ -17,6 +17,7 @@ #include "lib/res/file/vfs.h" #include "ps/CLogger.h" #include "ps/DllLoader.h" +#include "ps/Profile.h" using namespace AtlasMessage; @@ -201,6 +202,10 @@ bool BeginAtlas(const CmdLineArgs& args, const DllLoader& dll) state.view->Render(); + if (CProfileManager::IsInitialised()) + g_Profiler.Frame(); + + double time = get_time(); if (recent_activity) last_activity = time; diff --git a/source/tools/atlas/GameInterface/View.cpp b/source/tools/atlas/GameInterface/View.cpp index da61c15c03..a55095086f 100644 --- a/source/tools/atlas/GameInterface/View.cpp +++ b/source/tools/atlas/GameInterface/View.cpp @@ -197,9 +197,14 @@ void ViewGame::Update(float frameLength) ok = g_Game->Update(0.0, false); // don't add on any extra sim time } } - // Interpolate the graphics - we only want to do it once per visual frame, + + // Interpolate the graphics - we only want to do this once per visual frame, // not in every call to g_Game->Update g_Game->GetSimulation()->Interpolate(actualFrameLength); + + // If we still couldn't keep up, just drop the sim rate instead of building + // up a (potentially very large) backlog of required updates + g_Game->GetSimulation()->DiscardMissedUpdates(); } // Cinematic motion should be independent of simulation update, so we can