// // ToyScenarioHandler.cpp // SteveMaggieCpp // // Created by Katarzyna Kalinowska-Górska on 23.05.2017. // // #include #include "ToyScenarioHandler.h" #include "ToySoundsRepo.h" #include "ToyJSONParseUtils.h" #include "ToyActionParser.h" #include "ToyParentScene.h" #include "ToyRepeatedActionScheduler.h" #include "ToyMathUtils.h" #include "ToyResourceUtilities.h" #include "ToyValueStorage.h" #include "ToyTimeIndicatorInterface.h" #include "ToyRatePromptHandler.h" const std::string ToyScenarioHandler::DEMO_STATE_UNINITIALIZED = "UNINITIALIZED"; const std::string ToyScenarioHandler::DEMO_STATE_PLAYING = "PLAYING"; const std::string ToyScenarioHandler::DEMO_STATE_FINISHED = "FINISHED"; const std::string ToyScenarioHandler::DEMO_STATE_NEVER_PLAYED = "NEVER_PLAYED"; ToyScenarioHandler::ToyScenarioHandler(ToyParentScene* scene) { _scene = scene; _soundsResFolder = ""; _altSoundsResFolder = ""; _cachedJSONData = NULL; _cachedJSONString = ""; static int valueStorageKeyNumber = 0; valueStorageKeyNumber = (valueStorageKeyNumber + 1)%100; _valueStorageContainerName = "ScenarioHandlerValues" + std::to_string(valueStorageKeyNumber); _shouldSkipLayoutReloadOnFastForward = false; this->clearSetup(); } ToyScenarioHandler::~ToyScenarioHandler() { this->end(); } void ToyScenarioHandler::clearSetup() { _scene->clearTouchHandlers(); _scene->resetLastUserTouchTimes(); _lastActionIndex = -1; _currentCompletingActionTags.clear(); _scenarioPaused = false; _scenarioBarred = false; _scenarioFinished = false; _predefinedActions.clear(); _loopActionCounters.clear(); _storedSoundIds.clear(); _demoState = DEMO_STATE_UNINITIALIZED; this->clearAllScheduledActions(); for(auto pair : _predefinedActions){ delete pair.second; } _predefinedActions.clear(); for(auto action : _actionsSequence){ delete action; } _actionsSequence.clear(); //TODO --> cleanup } void ToyScenarioHandler::end() //TODO: clear steup? { this->clearSetup(); if(_cachedJSONData){ delete _cachedJSONData; _cachedJSONData = nullptr; } _cachedJSONString = ""; } void ToyScenarioHandler::loadScenarioFromJSONString(std::string jsonString, bool skipDemoActions) { _cachedJSONString = jsonString; rapidjson::Document doc; doc.Parse(jsonString.c_str()); this->loadScenarioFromJSONObject(doc, skipDemoActions); } // handling scenario and actions void ToyScenarioHandler::loadScenarioFromJSONObject(const rapidjson::Value& jsonObject, bool skipDemoActions, bool isReplay) { rapidjson::Document* copy = new rapidjson::Document(); //in case we pass _cachedJSONData as jsonObject copy->CopyFrom(jsonObject, copy->GetAllocator()); if(_cachedJSONData){ delete _cachedJSONData; } _cachedJSONData = copy; for(auto pair : _predefinedActions){ delete pair.second; } _predefinedActions.clear(); _soundsResFolder = (*_cachedJSONData)["soundResFolder"]["path"].GetString(); _soundsResFolder = ToyResourceUtilities::getInstance().getFullPathForDownloadedFile(_soundsResFolder, false); _altSoundsResFolder = ""; if(ToyJSONParseUtils::hasMemberString(((*_cachedJSONData)["soundResFolder"]), "alternativePath")){ _altSoundsResFolder = (*_cachedJSONData)["soundResFolder"]["alternativePath"].GetString(); _altSoundsResFolder = ToyResourceUtilities::getInstance().getFullPathForDownloadedFile(_altSoundsResFolder, false); } if(ToyJSONParseUtils::hasMemberObject((*_cachedJSONData), "predefinedActions")){ const auto& predefinedActionsData = (*_cachedJSONData)["predefinedActions"]; for(const auto& pair : predefinedActionsData.GetObject()){ rapidjson::Value* val = new rapidjson::Value(); val->CopyFrom(pair.value, _cachedJSONData->GetAllocator()); //!!!TODO crashes? should crash on using predefined aciton probably? std::string name = pair.name.GetString(); _predefinedActions.insert({name, val}); } } for(auto action : _actionsSequence){ delete action; } _actionsSequence.clear(); int nDemoActions = 0; auto actionsArrayJSON = (*_cachedJSONData)["actions"].GetArray(); for(int i = 0; i < actionsArrayJSON.Size(); ++i){ if(ToyJSONParseUtils::checkMemberBool(actionsArrayJSON[i], "demoAction", true)){ ++nDemoActions; if(skipDemoActions){ continue; } } else if(!skipDemoActions && ToyJSONParseUtils::checkMemberBool(actionsArrayJSON[i], "doNotPlayAfterDemo", true)){ continue; } else if(!isReplay && ToyJSONParseUtils::checkMemberBool(actionsArrayJSON[i],"onReplayOnly", true)){ continue; } if(actionsArrayJSON[i]["actionType"] == "randomizedOrderActionSets"){ auto& actionSets = actionsArrayJSON[i]["actionSets"]; std::vector actionSetsVector; // for(const auto& actionSet : actionSets.GetArray()){ // actionSetsVector.push_back(&actionSet); // } // ToyMathUtils::shuffleArray(actionSetsVector); // crashes randomly when this is run. Probably because of the behaviour of the copy constructor of rapidjson::Value objects. std::vector shuffleHelperArray; // an int array, on the other hand, can be shuffled shuffleHelperArray.reserve(actionSets.Size()); for(int i = 0; i < actionSets.Size(); ++i){ shuffleHelperArray.push_back(i); } ToyMathUtils::shuffleArray(shuffleHelperArray); auto actionSetsArray = actionSets.GetArray(); for(int i = 0; i < shuffleHelperArray.size(); ++i){ int j = shuffleHelperArray[i]; actionSetsVector.push_back(&actionSetsArray[j]); } for(int j = 0; j < actionSetsVector.size(); ++j){ auto actions = actionSetsVector[j]->GetArray(); for(int k = 0; k < actions.Size(); ++k){ this->prepareAction(&actions[k]); } } } else { this->prepareAction(&actionsArrayJSON[i]); } } if(nDemoActions == 0){ _scene->disableFastForwardButton(); } this->trimActions(_actionsSequence); this->mergeSequences(_actionsSequence); this->preloadSoundEffects(_actionsSequence); } void ToyScenarioHandler::prepareAction(const rapidjson::Value* action) { std::string actionType = (*action)["actionType"].GetString(); if(actionType == "callPredefinedAction"){ rapidjson::Value* predefinedAction = new rapidjson::Value(); predefinedAction->CopyFrom(*_predefinedActions[(*action)["actionId"].GetString()], _cachedJSONData->GetAllocator()); _actionsSequence.push_back(predefinedAction); } else if(actionType == "callRandomPredefinedAction"){ auto actionIdsJSON = (*action)["actionIds"].GetArray(); auto randIndex = ToyMathUtils::getRandomInt(0,actionIdsJSON.Size() - 1); rapidjson::Value* predefinedAction = new rapidjson::Value(); predefinedAction->CopyFrom(*_predefinedActions[actionIdsJSON[randIndex].GetString()], _cachedJSONData->GetAllocator()); _actionsSequence.push_back(predefinedAction); } else { rapidjson::Value* actionCopy = new rapidjson::Value(); actionCopy->CopyFrom(*action, _cachedJSONData->GetAllocator()); _actionsSequence.push_back(actionCopy); } }; void ToyScenarioHandler::trimActions(std::vector& actions) { int i = 0; std::vector actionsToTrim; while(i < actions.size()){ if(ToyJSONParseUtils::hasMemberInt(*actions[i], "trimPreviousActions")){ auto trimBy = (*actions[i])["trimPreviousActions"].GetInt(); for(int j = MAX(0, i - trimBy); j < i; ++j){ if(std::find(actionsToTrim.begin(), actionsToTrim.end(), j) == actionsToTrim.end()){ actionsToTrim.push_back(j); } } } ++i; } for(int k = (int)actionsToTrim.size()-1; k >= 0; --k){ actions.erase(actions.begin() + actionsToTrim[k], actions.begin() + actionsToTrim[k] + 1); } } void ToyScenarioHandler::mergeSequences(std::vector& actions) { int i = (int)actions.size()-1; while(i > 0){ if(ToyJSONParseUtils::checkMemberString(*actions[i], "actionType", "sequence") && ToyJSONParseUtils::checkMemberString(*actions[i-1], "actionType", "sequence")){ if(ToyJSONParseUtils::checkMemberInt(*actions[i], "trimPreviousSequence", 1)){ actions.erase(actions.begin() + i - 1, actions.begin() + i); } else { //TODO powinno dzialac ale jak nie to to tez trzeba odkomentowac i poprawic // rapidjson::Value newVal; // newVal.CopyFrom(*actions[i-1], _cachedJSONData->GetAllocator()); // actions[i-1] = &newVal; // auto arr = (*actions[i])["actions"].GetArray(); // for(int j = 0; j < arr.Size(); ++j){ // arr.PushBack((*actions[i])["actions"].GetArray()[j]); // } // // actions.erase(actions.begin() + i, actions.begin() + i + 1); } } --i; } } void ToyScenarioHandler::reloadScenario(bool skipDemo, bool isReplay) { if(_cachedJSONData){ // if(_cachedJSONString != ""){ // this->loadScenarioFromJSONString(_cachedJSONString, skipDemo); this->loadScenarioFromJSONObject(*_cachedJSONData, skipDemo, isReplay); } } // scenario void ToyScenarioHandler::runScenario() { if(_scenarioPaused){ return; } if(_scenarioFinished || (_lastActionIndex >= (int)_actionsSequence.size()-1)){ ToyActionParser::getInstance().cleanUp(this); return; } auto i = _lastActionIndex+1; auto currentAction = _actionsSequence[i]; // std::string actionType = (*currentAction)["actionType"].GetString(); // if(actionType == "wait"){ // if(i < _actionsSequence.size()-1){ // auto nextAction = _actionsSequence[i+1]; // if(ToyJSONParseUtils::hasMemberBool(*nextAction, "ignoreWaitBefore") && (*nextAction)["ignoreWaitBefore"].GetBool() == true){ // // skip action // _lastActionIndex = i; // if(i >= _actionsSequence.size()-1){ // _scenarioFinished = true; // } // this->runScenario(); // } // } // } // log("action index: "+i+" action type: "+currentAction.actionType); auto parsedAction = ToyActionParser::getInstance().parseJSONAction(*currentAction, this); _lastActionIndex = i; if(i >= _actionsSequence.size()-1){ _scenarioFinished = true; } if(parsedAction == NULL){ this->runScenario(); } else { this->runAction(parsedAction); } } void ToyScenarioHandler::pauseScenario() { _scenarioPaused = true; } void ToyScenarioHandler::resumeScenario() { _scenarioPaused = false; } void ToyScenarioHandler::resetScenario(bool skipDemo, bool reloadLayout) { this->pauseScenario(); this->stopAllActions(); this->clearAllScheduledActions(); _scene->unscheduleAllCallbacks(); ToySoundsRepo::stopAllSounds(); ToyActionParser::getInstance().cleanUp(this); this->clearSetup(); if(reloadLayout){ _scene->reloadLayoutClean(); } // _scene->removeAllChildren(); //change all references to scene to more liberal i.e. e.g. here leave these concrete lines to the scene and just put "onResetScenario" or something // _scene->loadLayout(true); //TODO memory leaks? if(skipDemo){ _scene->disableFastForwardButton(); } else { skipDemo = _scene->_alwaysSkipDemo; } this->reloadScenario(skipDemo, skipDemo && reloadLayout); // we hit replay if we skip the demo and do not reload layuot. TODO: make it more flexible. the detcetion, that is./ maybe 3rd param in the resetScenario func. this->runScenario(); } // sound void ToyScenarioHandler::preloadSoundEffects(std::vector actions) { for (auto actionObject : actions) { this->preloadSoundEffects(*actionObject); } } void ToyScenarioHandler::preloadSoundEffects(const rapidjson::Value& actionObject) { if(actionObject.IsObject()){ if(ToyJSONParseUtils::hasMemberArray(actionObject, "actions")){ for(const auto& value : actionObject["actions"].GetArray()){ this->preloadSoundEffects(value); } } else if(ToyJSONParseUtils::checkMemberString(actionObject, "actionType", "playSound") && ToyJSONParseUtils::checkMemberInt(actionObject, "needsPreload", 1)){ if(ToyJSONParseUtils::hasMemberString(actionObject, "sound")){ auto soundFilename = actionObject["sound"].GetString(); auto soundPath = ToyJSONParseUtils::checkMemberBool(actionObject, "useAlternativePath", true) ? _altSoundsResFolder + soundFilename : _soundsResFolder + soundFilename; ToySoundsRepo::preloadSoundEffect(soundPath); // CocosDenshion::SimpleAudioEngine::getInstance()->preloadEffect(soundPath.c_str()); } } } } void ToyScenarioHandler::stopAllSoundEffects() { ToySoundsRepo::stopAllSounds(); // CocosDenshion::SimpleAudioEngine::getInstance()->stopAllEffects(); } //debug //void ToyScenarioHandler::jumpToLastAction : function(){ // // this.lastActionIndex = this.actionsSequence.length - 2; // this.runScenario(); // // }, // ActionParseDelegate ToyScenarioObject* ToyScenarioHandler::getObjectByName (std::string objectName) { return _scene->getObjectByName(objectName); } ToyScenarioObject* ToyScenarioHandler::getDefaultObjectForAction(std::string actionType) { return _scene; } ToyScenarioObject* ToyScenarioHandler::getDefaultObjectForConditionCheck() { return _scene; } void ToyScenarioHandler::addNewObject(std::string objectName, ToyScenarioObject* newObject) { _scene->addNewObject(objectName, newObject); } std::string ToyScenarioHandler::getDefaultSoundsPath() { return _soundsResFolder; } std::string ToyScenarioHandler::getAlternativeSoundsPath() { return _altSoundsResFolder; } std::string ToyScenarioHandler::getToyValueStorageContainerName() { return _valueStorageContainerName; } void ToyScenarioHandler::runAction(cocos2d::Action* action) { _scene->runAction(action); } void ToyScenarioHandler::actionFinished(const rapidjson::Value& jsonActionObject) { this->runScenario(); } void ToyScenarioHandler::runInstantly(std::function actionFunction) { actionFunction(); } void ToyScenarioHandler::runCompletingAction(cocos2d::Action* action) { _currentCompletingActionTags.push_back(action->getTag()); _scene->runAction(action); } void ToyScenarioHandler::cancelAllCompletingActions() { for(const auto& actionTag : _currentCompletingActionTags){ _scene->stopActionByTag(actionTag); } _currentCompletingActionTags.clear(); } void ToyScenarioHandler::schedule(std::function callback, std::string key, float delay) { _scene->schedule(callback, delay, key); } void ToyScenarioHandler::scheduleOnce(const std::function callback, float delay, std::string key) { _scene->scheduleOnce(callback, delay, key); } void ToyScenarioHandler::unschedule(std::string key) { _scene->unschedule(key); } void ToyScenarioHandler::newTouchHandler(std::string key, TouchHandlerFunction touchHandler, TouchHandlerType touchType) { _scene->addTouchHandler(key, touchHandler, touchType); } void ToyScenarioHandler::removeTouchHandler(std::string key, TouchHandlerType touchHandlerType) { _scene->removeTouchHandler(key, touchHandlerType); } long long ToyScenarioHandler::getLastScreenTouchTime() { return _scene->getLastScreenTouchTime(); } int ToyScenarioHandler::getLoopActionCounter(std::string loopId) { if(_loopActionCounters.find(loopId) == _loopActionCounters.end()){ return -1; } return _loopActionCounters[loopId]; } int ToyScenarioHandler::addNewLoopActionCounter(std::string loopId, int timesRepeated) { _loopActionCounters[loopId] = timesRepeated; return _loopActionCounters[loopId]; } int ToyScenarioHandler::decrementLoopActionCounter(std::string loopId) { _loopActionCounters[loopId] = _loopActionCounters[loopId] - 1; return _loopActionCounters[loopId]; } void ToyScenarioHandler::deleteLoopActionCounter(std::string loopId) { _loopActionCounters.erase(loopId); } void ToyScenarioHandler::setLastActionIndex(int index) { _lastActionIndex = index; } void ToyScenarioHandler::storeSoundId(std::string soundPath, unsigned int newSoundId) { _storedSoundIds[soundPath] = newSoundId; } unsigned int ToyScenarioHandler::removeStoredSoundId(std::string soundPath) { if(_storedSoundIds.find(soundPath) != _storedSoundIds.end()){ auto soundId = _storedSoundIds[soundPath]; _storedSoundIds.erase(soundPath); return soundId; } return -1; } //addiitonal convenience methods void ToyScenarioHandler::stopAllActions() { _scene->stopAllActions(); } void ToyScenarioHandler::clearAllScheduledActions() { ToyRepeatedActionScheduler::getInstance().clearAllScheduledActions(this); } // ToyScenarioObject // ToyScenarioObject void ToyScenarioHandler::setProperty(std::string propertyName, const rapidjson::Value& newValue, ActionParseDelegate* parseDelegate) { if (propertyName == "demoState") { auto value = newValue.GetString(); if(DEMO_STATE_PLAYING == value){ _demoState = DEMO_STATE_PLAYING; } else if(DEMO_STATE_FINISHED == value){ _demoState = DEMO_STATE_FINISHED; } else if(DEMO_STATE_UNINITIALIZED == value){ _demoState = DEMO_STATE_UNINITIALIZED; } else if(DEMO_STATE_NEVER_PLAYED == value){ _demoState = DEMO_STATE_NEVER_PLAYED; } } else if(propertyName == "shouldSkipLayoutReloadOnFastForward"){ _shouldSkipLayoutReloadOnFastForward = newValue.GetBool(); } } void ToyScenarioHandler::callFunctionByName(std::string methodName, const rapidjson::Value* arguments, ActionParseDelegate* parseDelegate, std::function callback) { if(methodName == "clearAllScheduledActions"){ this->clearAllScheduledActions(); } else if(methodName == "stopActionByTag"){ if((*arguments).IsArray()) { const auto ¶ms = (*arguments).GetArray(); const auto &tag = params[0].GetInt(); _scene->stopActionByTag(tag); } } else if(methodName == "dismiss"){ cocos2d::Director::getInstance()->popScene(); } else if(methodName == "startTimer"){ if((*arguments).IsArray()){ const auto& params = (*arguments).GetArray(); const auto& totalTime = params[0].GetFloat(); const auto& step = params[1].GetFloat(); const auto& actionsOnProgressStep = &(params[2]); //params[1];//.GetArray(); const auto& actionsOnTimeUp = &(params[3]); const auto& finishScenarioOnTimeUp = params[4].GetBool(); const auto& timeIndicatorName = params[5].GetString(); startTimer(totalTime, step, actionsOnProgressStep, actionsOnTimeUp, finishScenarioOnTimeUp, timeIndicatorName); } } else if(methodName == "stopTimer"){ stopTimer(); } else if(methodName == "pauseTimer"){ pauseTimer(); } else if(methodName == "resumeTimer"){ resumeTimer(); } else if(methodName == "countUpRateMePromptCounter"){ countUpRateMePromptCounter(); } } void ToyScenarioHandler::startTimer(float pTimeToFinish, float step, const rapidjson::Value* actionsOnProgressStep, const rapidjson::Value* actionsOnTimeUp, bool finishScenarioAfterTimeUp, std::string timeIndicatorObjectName){ _timerRunning = true; _timerStep = step; _totalTimerTime = pTimeToFinish; _timePassed = 0; _timeSlider = dynamic_cast(_scene->getObjectByName(timeIndicatorObjectName)); if (_timeSlider != nullptr) { _timeSlider->setProgress(0); _timeSlider->startTimeAnimation(pTimeToFinish); } _actionsOnTimerUp.clear(); auto array = actionsOnTimeUp->GetArray(); for(int i = 0; i < array.Size(); ++i){ const rapidjson::Value& action = array[i]; rapidjson::Value* actionCopy = new rapidjson::Value(); actionCopy->CopyFrom(action, _cachedJSONData->GetAllocator()); _actionsOnTimerUp.push_back(actionCopy); } this->schedule([&](float timeElapsed){ _timePassed += timeElapsed; // if (_timeSlider != nullptr) { // _timeSlider->setProgressFrac(_timePassed / _totalTimerTime); // } if(_timePassed >= _totalTimerTime){ stopTimer(); pauseScenario(); stopAllActions(); clearAllScheduledActions(); _scene->unscheduleAllCallbacks(); //TODO: this doesn't work here. We still have one waitingFunction trying to be scheduled after this in the ToyRepeatedActionScheduler!!! ToySoundsRepo::stopAllSounds(); // if (finishScenarioAfterTimeUp) { clearSetup(); // } simpleCopyActionsToMainSequence(_actionsOnTimerUp); runScenario(); } /*else { //TODO execute on timer progress actions }*/ }, "timerStep", step); } void ToyScenarioHandler::stopTimer(){ if (_timeSlider != nullptr) { _timeSlider->stopTimeAnimation(); } _timerRunning = false; unschedule("timerStep"); //// if(timeout){ // simpleCopyActionsToMainSequence(_actionsOnTimerUp); //// } /*else { // simpleCopyActionsToMainSequence(_actionsOnTimerStopped); // }*/ } void ToyScenarioHandler::pauseTimer(){ _timerRunning = false; if (_timeSlider != nullptr) { _timeSlider->stopTimeAnimation(); } unschedule("timerStep"); } void ToyScenarioHandler::resumeTimer(){ if(_timerRunning) return; if (_timeSlider != nullptr) { _timeSlider->startTimeAnimation(_totalTimerTime - _timePassed); } this->schedule([&](float timeElapsed){ _timePassed += timeElapsed; if(_timePassed >= _totalTimerTime){ stopTimer(); clearSetup(); simpleCopyActionsToMainSequence(_actionsOnTimerUp); runScenario(); } }, "timerStep", _timerStep); } // review prompt void ToyScenarioHandler::countUpRateMePromptCounter(){ ToyRatePromptHandler::countUp(); } void ToyScenarioHandler::simpleCopyActionsToMainSequence(std::vector actions){ for(int i = 0; i < actions.size(); ++i){ this->prepareAction(actions[i]); } }