#include "script\handlers\GameScriptEntity.h" #include "network/Debug/NetworkDebug.h" #include "network/Objects/Entities/NetObjPhysical.h" #include "Objects/object.h" #include "peds/ped.h" #include "scene/Physical.h" #include "script/handlers/GameScriptHandlerNetwork.h" #include "script/handlers/GameScriptResources.h" #include "script/script_channel.h" #include "script/script.h" #include "script/script_debug.h" SCRIPT_OPTIMISATIONS() NETWORK_OPTIMISATIONS() // ================================================================================================================ // CGameScriptHandlerObject - an base class used by all different types of script handler objects // ================================================================================================================ // Determine whether the object needs to be cleaned up properly. This is not done if the object is still required to persist as a script object // for the remaining participants, who may still be running the script normally. bool CGameScriptHandlerObject::IsCleanupRequired() const { netLoggingInterface* log = CTheScripts::GetScriptHandlerMgr().GetLog(); char logName[30]; char eventName[20]; logName[0] = 0; eventName[0] = 0; GetScriptInfo()->GetLogName(logName, 30); sprintf(eventName, "DETACH_%s", GetLogName()); scriptHandler* pHandler = GetScriptHandler(); CNetObjGame* pNetObject = static_cast(GetNetObject()); gnetAssert(GetScriptInfo()); gnetAssert(pHandler); if (!GetScriptInfo() || !pHandler || !pNetObject || !pHandler->GetNetworkComponent()) return true; bool bObjectIsClone = pNetObject->IsClone() || pNetObject->IsPendingOwnerChange(); bool bCloneBecomingAmbient = pNetObject->IsClone() && !pNetObject->IsScriptObject(); // this happens when we get an update for a clone, clearing its script state bool bScriptHasFinished = pHandler->GetNetworkComponent()->GetScriptHasFinished(); if (!bCloneBecomingAmbient) { if (GetScriptInfo()->IsScriptHostObject()) { bool bRemainingParticipants = false; // if the handler is terminating, the current participants can be out of date, if so use the remote script info instead if (pHandler->GetNetworkComponent()->GetState() == scriptHandlerNetComponent::NETSCRIPT_PLAYING) { bRemainingParticipants = pHandler->GetNetworkComponent()->GetNumParticipants()>1; } else { CGameScriptHandlerMgr::CRemoteScriptInfo* pInfo = CTheScripts::GetScriptHandlerMgr().GetRemoteScriptInfo(GetScriptInfo()->GetScriptId(), true); bRemainingParticipants = (pInfo != 0); } // leave the script state intact if the object is a clone or if the script is still running with active participants and the object has not been // marked as no longer needed. bool bLeaveScriptStateOnEntity = (bRemainingParticipants && !GetNoLongerNeeded() && !bScriptHasFinished); if (bObjectIsClone || bLeaveScriptStateOnEntity) { if (GetScriptInfo() && log) { NetworkLogUtils::WriteLogEvent(*log, eventName, "%s %s", GetScriptInfo()->GetScriptId().GetLogName(), logName); log->WriteDataValue("Script id", "%d", GetScriptInfo()->GetObjectId()); log->WriteDataValue("Net Object", "%s", pNetObject->GetLogName()); log->WriteDataValue("Clone", "%s", bObjectIsClone ? "true" : "false"); log->WriteDataValue("Other participants", "%s", bRemainingParticipants ? "true" : "false"); log->WriteDataValue("No longer needed", "%s", GetNoLongerNeeded() ? "true" : "false"); log->WriteDataValue("Script finished", "%s", bScriptHasFinished ? "true" : "false"); } // flag the object to be cleaned up when it becomes local again (it may have a pending owner but not migrate) if (bObjectIsClone && !bLeaveScriptStateOnEntity) { log->WriteDataValue("Flag for cleanup when local", "true"); pNetObject->SetLocalFlag(CNetObjGame::LOCALFLAG_NOLONGERNEEDED, true); } return false; } } else if (GetScriptInfo()->IsScriptClientObject()) { bool bParticipantStillActive = false; if (!GetNoLongerNeeded() && !bScriptHasFinished) { // if we have a client created object for an existing participant, we must leave it as a script entity int participantSlot = GET_SLOT_FROM_SCRIPT_OBJECT_ID(GetScriptInfo()->GetObjectId()); const netPlayer* pParticipant = pHandler->GetNetworkComponent()->GetParticipantUsingSlot(participantSlot); bParticipantStillActive = (pParticipant && !pParticipant->IsLocal() && !pParticipant->IsLeaving()); } if (bParticipantStillActive || bObjectIsClone) { if (GetScriptInfo() && log) { NetworkLogUtils::WriteLogEvent(*log, eventName, "%s %s", GetScriptInfo()->GetScriptId().GetLogName(), logName); log->WriteDataValue("Script id", "%d", GetScriptInfo()->GetObjectId()); log->WriteDataValue("Net Object", "%s", pNetObject->GetLogName()); log->WriteDataValue("Clone", "%s", bObjectIsClone ? "true" : "false"); log->WriteDataValue("Participant still active", "%s", bParticipantStillActive ? "true" : "false"); } return false; } } } return true; } // ================================================================================================================ // CScriptEntityExtension - an extension given to an entity that is a proper mission entity. // ================================================================================================================ AUTOID_IMPL(CScriptEntityExtension); FW_INSTANTIATE_CLASS_POOL(CScriptEntityExtension, CONFIGURED_FROM_FILE, atHashString("CScriptEntityExtension",0xef7129cb)); CScriptEntityExtension::CScriptEntityExtension(CPhysical& targetEntity) : m_pTargetEntity(&targetEntity) , m_pHandler(NULL) , m_bCleanupTargetEntity(true) , m_bNoLongerNeeded(false) , m_bIsNetworked(true) , m_bCanMigrate(true) , m_bScriptMigration(false) , m_bExistsOnAllMachines(false) , m_bStopCloning(false) , m_bScriptObjectWasGrabbedFromTheWorld(false) { // add this extension to the entity's extension list targetEntity.GetExtensionList().Add(*this); if (targetEntity.GetNetworkObject()) { static_cast(targetEntity.GetNetworkObject())->SetScriptExtension(this); } } CScriptEntityExtension::~CScriptEntityExtension() { if (m_pTargetEntity && m_pTargetEntity->GetNetworkObject()) { static_cast(m_pTargetEntity->GetNetworkObject())->SetScriptExtension(NULL); } } void CScriptEntityExtension::CreateScriptInfo(const scriptIdBase& scrId, const ScriptObjectId& objectId, const HostToken hostToken) { m_ScriptInfo = CGameScriptObjInfo(scrId, objectId, hostToken); Assert(m_ScriptInfo.IsValid()); } void CScriptEntityExtension::SetScriptInfo(const scriptObjInfoBase& info) { m_ScriptInfo = info; Assert(m_ScriptInfo.IsValid()); } scriptObjInfoBase* CScriptEntityExtension::GetScriptInfo() { if (m_ScriptInfo.IsValid()) return static_cast(&m_ScriptInfo); return NULL; } const scriptObjInfoBase* CScriptEntityExtension::GetScriptInfo() const { if (m_ScriptInfo.IsValid()) return static_cast(&m_ScriptInfo); return NULL; } void CScriptEntityExtension::SetScriptHandler(scriptHandler* handler) { FatalAssert(!handler || handler->GetScriptId() == m_ScriptInfo.GetScriptId()); m_pHandler = handler; } void CScriptEntityExtension::OnRegistration(bool newObject, bool BANK_ONLY(hostObject)) { if (AssertVerify(m_pTargetEntity)) { CNetObjGame* netObj = static_cast(GetNetObject()); #if __BANK scriptDisplayf("%s : Register %s script entity with model %s (Entity = 0x%p)", m_pHandler->GetLogName(), newObject ? "new" : "existing", m_pTargetEntity->GetModelName(), m_pTargetEntity); if (NetworkInterface::IsGameInProgress()) { netLoggingInterface *log = CTheScripts::GetScriptHandlerMgr().GetLog(); if(log) { char logName[30]; char typeName[20]; char header[100]; m_ScriptInfo.GetLogName(logName, 30); if (m_pTargetEntity->GetIsTypePed()) { formatf(typeName, "PED"); } else if (m_pTargetEntity->GetIsTypeVehicle()) { formatf(typeName, "VEHICLE"); } else if (m_pTargetEntity->GetIsTypeObject()) { if (((CObject*)m_pTargetEntity)->m_nObjectFlags.bIsPickUp) { formatf(typeName, "PICKUP"); } else { formatf(typeName, "OBJECT"); } } else { formatf(typeName, "ENTITY"); } formatf(header, "REGISTER_%s_%s_%s", newObject ? "NEW" : "EXISTING", hostObject ? "HOST" : "LOCAL", typeName); NetworkLogUtils::WriteLogEvent(*log, header, "%s %s", m_ScriptInfo.GetScriptId().GetLogName(), logName); log->WriteDataValue("Script id", "%d", m_ScriptInfo.GetObjectId()); log->WriteDataValue("Entity", "%p", m_pTargetEntity); if (netObj) { log->WriteDataValue("Net Object", "%s", netObj->GetLogName()); } } } #endif // __BANK if (newObject) { m_pTargetEntity->SetupMissionState(); if (m_pTargetEntity->GetNetworkObject()) { Assertf(m_pHandler->GetNetworkComponent(), "Script %s is not networked but is creating networked objects. This is not permitted.", m_pHandler->GetScriptId().GetLogName()); } } if (netObj) { netObj->OnConversionToScriptObject(); } } } void CScriptEntityExtension::OnUnregistration() { if (AssertVerify(m_pTargetEntity)) { scriptDisplayf("%s : Unregister script entity with model %s (Entity = 0x%p)", m_ScriptInfo.GetScriptId().GetLogName(), m_pTargetEntity->GetModelName(), m_pTargetEntity); #if __ASSERT //B*2222339 Temporary debug to trap places when a network player clone ped is removed and report the script stack if (NetworkInterface::IsGameInProgress() && m_pTargetEntity->GetIsTypePed()) { if(strstr(m_pTargetEntity->GetModelName(),"_freemode_")) { scriptDebugf1("%s UNREGISTER_ENTITY Network Freemode model: %s unregistered [ %p ]", m_ScriptInfo.GetScriptId().GetLogName(), m_pTargetEntity->GetModelName(), m_pTargetEntity); scrThread::PrePrintStackTrace(); } } #endif if (m_pTargetEntity->GetNetworkObject()) { NETWORK_DEBUG_BREAK_FOR_FOCUS(BREAK_TYPE_FOCUS_NO_LONGER_NEEDED, *m_pTargetEntity->GetNetworkObject()); } netLoggingInterface *log = CTheScripts::GetScriptHandlerMgr().GetLog(); char logName[30]; m_ScriptInfo.GetLogName(logName, 30); if (log) { NetworkLogUtils::WriteLogEvent(*log, "UNREGISTER_ENTITY", "%s %s", m_ScriptInfo.GetScriptId().GetLogName(), logName); log->WriteDataValue("Script id", "%d", m_ScriptInfo.GetObjectId()); CNetObjGame* pNetObj = m_pTargetEntity->GetNetworkObject(); if (pNetObj) { log->WriteDataValue("Net Object","%s", pNetObj ? pNetObj->GetLogName() : "-none-"); log->WriteDataValue("No longer needed", "%s", m_bNoLongerNeeded ? "true" : "false"); log->WriteDataValue("Cleanup", "%s", m_bCleanupTargetEntity ? "true" : "false"); } } } } void CScriptEntityExtension::Cleanup() { CPhysical* pPhysical = GetTargetEntity(); netLoggingInterface* log = CTheScripts::GetScriptHandlerMgr().GetLog(); if(log) { char logName[30]; logName[0] = 0; if (gnetVerify(GetScriptInfo())) { GetScriptInfo()->GetLogName(logName, 30); } NetworkLogUtils::WriteLogEvent(*log, "CLEANUP_ENTITY", "%s %s", GetScriptInfo()->GetScriptId().GetLogName(), logName); if (GetScriptInfo()) { log->WriteDataValue("Script id", "%d", GetScriptInfo()->GetObjectId()); } log->WriteDataValue("Net Object", "%s", (m_pTargetEntity && m_pTargetEntity->GetNetworkObject()) ? m_pTargetEntity->GetNetworkObject()->GetLogName() : "?"); } m_ScriptInfo.Invalidate(); m_pHandler = NULL; if (gnetVerify(pPhysical)) { // GetCleanupTargetEntity is false, the object is about to be deleted, so there is no need to cleanup the mission state if (GetCleanupTargetEntity()) { if(log) { log->WriteDataValue("Mission state cleaned up", "true"); } pPhysical->CleanupMissionState(); Assert(!pPhysical->PopTypeIsMission()); } else { if(log) { log->WriteDataValue("Mission state cleaned up", "false"); } } } } netObject* CScriptEntityExtension::GetNetObject() const { return m_pTargetEntity ? m_pTargetEntity->GetNetworkObject() : NULL; } fwEntity* CScriptEntityExtension::GetEntity() const { return static_cast(m_pTargetEntity); } strIndex CScriptEntityExtension::GetStreamingIndex() const { if (m_pTargetEntity) { strLocalIndex localIndex = CModelInfo::LookupLocalIndex(m_pTargetEntity->GetModelId()); strIndex streamingIndex = CModelInfo::GetStreamingModule()->GetStreamingIndex(localIndex); return(streamingIndex); } return strIndex(strIndex::INVALID_INDEX); } void CScriptEntityExtension::GetMemoryUsage(u32& virtualSize, u32& physicalSize, const strIndex* ignoreList, s32 numIgnores, u32 skipFlag) const { if (m_pTargetEntity) { // #if !__FINAL // if (CScriptDebug::GetCountModelUsage()) // #endif { strIndex streamingIndex = GetStreamingIndex(); if(!(strStreamingEngine::GetInfo().GetObjectFlags(streamingIndex) & skipFlag)) { strStreamingEngine::GetInfo().GetObjectAndDependenciesSizes(streamingIndex, virtualSize, physicalSize, ignoreList, numIgnores, true); } } } } #if __BANK void CScriptEntityExtension::DisplayDebugInfo(const char* scriptName) const { if (m_pTargetEntity) { const Vector3 vTargetEntityPosition = VEC3V_TO_VECTOR3(m_pTargetEntity->GetTransform().GetPosition()); if (scriptName) { grcDebugDraw::AddDebugOutput("%s: %s %s (%f, %f, %f)\n", scriptName, GetEntityTypeString(), CModelInfo::GetBaseModelInfoName(m_pTargetEntity->GetModelId()), vTargetEntityPosition.x, vTargetEntityPosition.y, vTargetEntityPosition.z); } else { grcDebugDraw::AddDebugOutput("%s %s (%f, %f, %f)\n", GetEntityTypeString(), CModelInfo::GetBaseModelInfoName(m_pTargetEntity->GetModelId()), vTargetEntityPosition.x, vTargetEntityPosition.y, vTargetEntityPosition.z); } } } void CScriptEntityExtension::SpewDebugInfo(const char* scriptName) const { if (m_pTargetEntity) { const Vector3 vTargetEntityPosition = VEC3V_TO_VECTOR3(m_pTargetEntity->GetTransform().GetPosition()); if (scriptName) { Displayf("%s: %s %s (%f, %f, %f)\n", scriptName, GetEntityTypeString(), CModelInfo::GetBaseModelInfoName(m_pTargetEntity->GetModelId()), vTargetEntityPosition.x, vTargetEntityPosition.y, vTargetEntityPosition.z); } else { Displayf("%s %s (%f, %f, %f)\n", GetEntityTypeString(), CModelInfo::GetBaseModelInfoName(m_pTargetEntity->GetModelId()), vTargetEntityPosition.x, vTargetEntityPosition.y, vTargetEntityPosition.z); } } } #endif // __BANK #if __SCRIPT_MEM_DISPLAY void CScriptEntityExtension::DisplayMemoryUsage(const char* scriptName, const strIndex* ignoreList, s32 numIgnores, u32 skipFlag) const { if (m_pTargetEntity) // && CScriptDebug::GetCountModelUsage()) { u32 virtualSize = 0; u32 physicalSize = 0; GetMemoryUsage(virtualSize, physicalSize, ignoreList, numIgnores, skipFlag); if (virtualSize > 0 || physicalSize > 0) { bool bDDFlagSet = false; if(strStreamingEngine::GetInfo().GetObjectFlags(GetStreamingIndex()) & STRFLAG_DONTDELETE) { bDDFlagSet = true; } grcDebugDraw::AddDebugOutput("%s: %s %s Model %dK %dK %s(for this entity, including deps)", scriptName, GetEntityTypeString(), CModelInfo::GetBaseModelInfoName(m_pTargetEntity->GetModelId()), virtualSize>>10, physicalSize>>10, bDDFlagSet?"(DD)":""); } } } char* CScriptEntityExtension::GetEntityTypeString() const { static char typeStr[10]; if (m_pTargetEntity) { switch (m_pTargetEntity->GetType()) { case ENTITY_TYPE_VEHICLE: sprintf(typeStr, "Vehicle"); break; case ENTITY_TYPE_PED: sprintf(typeStr, "Ped"); break; case ENTITY_TYPE_OBJECT: sprintf(typeStr, "Object"); break; default: Assert(0); } } else { sprintf(typeStr, ""); } return typeStr; } #endif // __SCRIPT_MEM_DISPLAY #if __DEV void CScriptEntityExtension::PoolFullCallback(void* pItem) { if (!pItem) { Printf("ERROR - CScriptEntityExtension Pool FULL!\n"); } else { CScriptEntityExtension* pScriptEntityExt = static_cast(pItem); const char *pDebugName = pScriptEntityExt->m_pTargetEntity->GetDebugName(); const char *pName = (pDebugName && pDebugName[0] != '\0') ? pDebugName : pScriptEntityExt->m_pTargetEntity->GetModelName(); const char *pScriptName = pScriptEntityExt->m_pHandler ? pScriptEntityExt->m_pHandler->GetLogName() : '\0'; Displayf("Entity: %s, Type: %s, Script: \"%s\", Model Index: %u, Memory Address: %p", pName, pScriptEntityExt->GetEntityTypeString(), pScriptName, pScriptEntityExt->m_pTargetEntity->GetModelId().GetModelIndex(), pScriptEntityExt); } } #endif // __DEV