// // ToyActionParser.cpp // SteveMaggieCpp // // Created by Katarzyna Kalinowska-Górska on 26.05.2017. // // #include #include "ToyActionParser.h" #include "ToyJSONParseUtils.h" #include "ToyMathUtils.h" #include "ToyStringUtils.h" #include "ToySimpleValue.h" #include "ToySimpleActionParser.h" #include "ToyBlockingActionParser.h" #include "ToyValueStorage.h" // main parse function cocos2d::Action* ToyActionParser::parseJSONAction(const rapidjson::Value& jsonActionObject, ActionParseDelegate* parseDelegate, bool notifyDelegateWhenFinished) { cocos2d::Action* parsedAction = NULL; if(!ToyJSONParseUtils::hasMemberString(jsonActionObject, "actionType")){ cocos2d::log("ToyActionParser: parseJSONAction: jsonActionObject is not an action"); } else if(parseDelegate == NULL){ cocos2d::log("ToyActionParser: parseJSONAction: parseDelegate must not be null"); } else { if(!ToyJSONParseUtils::hasMemberString(jsonActionObject, "condition") || this->checkCondition(jsonActionObject["condition"].GetString(), parseDelegate)){ // cocos2d::log("ToyActionParser: parsing action of type %s",jsonActionObject["actionType"].GetString()); parsedAction = ToyRepeatedActionScheduler::getInstance().scheduleActionIfNeeded(jsonActionObject, parseDelegate, notifyDelegateWhenFinished); if(!parsedAction){ std::string actionType = jsonActionObject["actionType"].GetString(); if(actionType == "conditionalAction"){ parsedAction = this->parseConditionalAction(jsonActionObject, parseDelegate, notifyDelegateWhenFinished); } else if(actionType == "sequence"){ parsedAction = this->parseSequenceAction(jsonActionObject, parseDelegate, notifyDelegateWhenFinished); } else if(actionType == "spawn"){ parsedAction = this->parseSpawnAction(jsonActionObject, parseDelegate, notifyDelegateWhenFinished); } else if(actionType == "loop"){ parsedAction = this->parseLoopAction(jsonActionObject, parseDelegate, notifyDelegateWhenFinished); } else if(actionType == "randomAction"){ parsedAction = this->parseRandomAction(jsonActionObject, parseDelegate, notifyDelegateWhenFinished); } } if(!parsedAction){ parsedAction = ToySimpleActionParser::getInstance().parseJSONAction(jsonActionObject, parseDelegate, notifyDelegateWhenFinished); } if(!parsedAction){ parsedAction = ToyBlockingActionParser::getInstance().parseJSONAction(jsonActionObject, parseDelegate, notifyDelegateWhenFinished); } if(!parsedAction){ cocos2d::log("ToyActionParser: parseJSONAction: no support for this actionType. (Or runInstantly flag set)"); } } else if(notifyDelegateWhenFinished){ auto key = ToyValueStorage::getInstance().storeValue(jsonActionObject, parseDelegate->getToyValueStorageContainerName()); parsedAction = cocos2d::CallFunc::create(std::bind([&](std::string pStoredValueKey, ActionParseDelegate* pParseDelegate){ pParseDelegate->actionFinished(*ToyValueStorage::getInstance().getStoredValue(pStoredValueKey, pParseDelegate->getToyValueStorageContainerName())); ToyValueStorage::getInstance().removeStoredValue(pStoredValueKey, pParseDelegate->getToyValueStorageContainerName()); }, key, parseDelegate)); } } if(parsedAction != nullptr && ToyJSONParseUtils::hasMemberInt(jsonActionObject, "actionTag")){ parsedAction->setTag(jsonActionObject["actionTag"].GetInt()); } return parsedAction; } void ToyActionParser::cleanUp(ActionParseDelegate* delegate) { ToyValueStorage::getInstance().clearStoredData(delegate->getToyValueStorageContainerName()); } // functions for parsing differenct actions // "actionType" = "conditionalAction" // "conditionType" ["switch"] // "switchParameter" [string] - the name of the object on which to switch // "cases" [array of strings] - cases for the switch parameters // "actions" [array of action objects] - actions to perform for different cases cocos2d::Action* ToyActionParser::parseConditionalAction(const rapidjson::Value& jsonActionObject, ActionParseDelegate* parseDelegate, bool notifyDelegateWhenFinished) { auto storedValueKey = ToyValueStorage::getInstance().storeValue(jsonActionObject, parseDelegate->getToyValueStorageContainerName()); std::function actionFunction = std::bind([&](std::string pStoredValueKey, ActionParseDelegate* pParseDelegate, bool pNotifyDelegate){ auto storedJsonActionObject = ToyValueStorage::getInstance().getStoredValue(pStoredValueKey, pParseDelegate->getToyValueStorageContainerName()); cocos2d::Action* returnedAction = NULL; if(ToyJSONParseUtils::checkMemberString(*storedJsonActionObject, "conditionType", "switch")) { auto switchParameterString = (*storedJsonActionObject)["switchParameter"].GetString(); auto tokens = ToyStringUtils::splitString(switchParameterString, '.'); auto propertyNameToken = tokens[tokens.size()-1]; auto objectNameString = tokens[0]; for(int i = 1; i < tokens.size()-1; ++i){ objectNameString = objectNameString + "." + tokens[i]; } auto switchParameterObject = this->parseObject(objectNameString, pParseDelegate)->getPropertyAsString(propertyNameToken); auto cases = (*storedJsonActionObject)["cases"].GetArray(); auto actions = (*storedJsonActionObject)["actions"].GetArray(); for(int k = 0; k < cases.Size(); ++k){ if(switchParameterObject == cases[k].GetString()){ returnedAction = this->parseJSONAction(actions[k], pParseDelegate, pNotifyDelegate); break; } } } if(returnedAction == NULL){ cocos2d::log("ToyActionParser: parseConditionalAction invalid parameters"); if(pNotifyDelegate){ pParseDelegate->actionFinished(*storedJsonActionObject); } } else { pParseDelegate->runAction(returnedAction); } ToyValueStorage::getInstance().removeStoredValue(pStoredValueKey, pParseDelegate->getToyValueStorageContainerName()); }, storedValueKey, parseDelegate, notifyDelegateWhenFinished); return cocos2d::CallFunc::create(actionFunction); } cocos2d::Action* ToyActionParser::parseRandomAction(const rapidjson::Value& jsonActionObject, ActionParseDelegate* parseDelegate, bool notifyDelegateWhenFinished) { cocos2d::Action* parsedAction = NULL; if(ToyJSONParseUtils::hasMemberArray(jsonActionObject, "actions")){ rapidjson::Value actionsCopy; actionsCopy.CopyFrom(jsonActionObject, _valueStorage->GetAllocator()); //TODO: will memory be freed when actionsCopy goes out of scope? auto actions = actionsCopy["actions"].GetArray(); do { auto randIndex = ToyMathUtils::getRandomInt(0,actions.Size()-1); const auto& pickedAction = actions[randIndex]; if(ToyJSONParseUtils::hasMemberString(pickedAction, "randomActionCondition")){ if(ToyActionParser::getInstance().checkCondition(pickedAction["randomActionCondition"].GetString(), parseDelegate)){ parsedAction = this->parseJSONAction(pickedAction, parseDelegate, notifyDelegateWhenFinished); } else { actions.Erase(actions.begin() + randIndex); } } } while(!parsedAction && actions.Size() > 0); } return parsedAction; } // "actionType" = "sequence" // "actions" [array of action objects] cocos2d::Action* ToyActionParser::parseSequenceAction(const rapidjson::Value& jsonActionObject, ActionParseDelegate* parseDelegate, bool notifyDelegateWhenFinished) { cocos2d::Vector actionsForSeq; if(ToyJSONParseUtils::hasMemberArray(jsonActionObject, "actions")){ const auto& actions = jsonActionObject["actions"]; for(SizeType i = 0; i < actions.Size(); ++i){ auto parsedAction = this->parseJSONAction(actions[i], parseDelegate, false); if(parsedAction){ auto parsedFiniteAction = dynamic_cast(parsedAction); if(parsedFiniteAction){ actionsForSeq.pushBack(parsedFiniteAction); } } } } if(notifyDelegateWhenFinished){ auto key = ToyValueStorage::getInstance().storeValue(jsonActionObject, parseDelegate->getToyValueStorageContainerName()); auto sequenceFinishedAction = cocos2d::CallFunc::create(std::bind([&](std::string pStoredValueKey, ActionParseDelegate* pParseDelegate){ pParseDelegate->actionFinished(*ToyValueStorage::getInstance().getStoredValue(pStoredValueKey, pParseDelegate->getToyValueStorageContainerName())); ToyValueStorage::getInstance().removeStoredValue(pStoredValueKey, pParseDelegate->getToyValueStorageContainerName()); }, key, parseDelegate)); actionsForSeq.pushBack(sequenceFinishedAction); } return cocos2d::Sequence::create(actionsForSeq); } // "actionType" = "spawn" // "actions" [array of action objects] cocos2d::Action* ToyActionParser::parseSpawnAction(const rapidjson::Value& jsonActionObject, ActionParseDelegate* parseDelegate, bool notifyDelegateWhenFinished) { cocos2d::Vector actionsForSpawn; if(ToyJSONParseUtils::hasMemberArray(jsonActionObject, "actions")){ const auto& actions = jsonActionObject["actions"]; for(SizeType i = 0; i < actions.Size(); ++i){ auto parsedAction = this->parseJSONAction(actions[i], parseDelegate, false); if(parsedAction){ auto parsedFiniteAction = dynamic_cast(parsedAction); if(parsedFiniteAction){ actionsForSpawn.pushBack(parsedFiniteAction); } } } } if(notifyDelegateWhenFinished){ auto key = ToyValueStorage::getInstance().storeValue(jsonActionObject, parseDelegate->getToyValueStorageContainerName()); auto spawnFinishedAction = cocos2d::CallFunc::create(std::bind([&](std::string pStoredValueKey, ActionParseDelegate* pParseDelegate){ pParseDelegate->actionFinished(*ToyValueStorage::getInstance().getStoredValue(pStoredValueKey, pParseDelegate->getToyValueStorageContainerName())); ToyValueStorage::getInstance().removeStoredValue(pStoredValueKey, pParseDelegate->getToyValueStorageContainerName()); }, key, parseDelegate)); actionsForSpawn.pushBack(spawnFinishedAction); } return cocos2d::Spawn::create(actionsForSpawn); } //todo maybe not everything to parsedelegate, but to scenario parser, e.g. these loops cocos2d::Action* ToyActionParser::parseLoopAction(const rapidjson::Value& jsonActionObject, ActionParseDelegate* parseDelegate, bool notifyDelegateWhenFinished) { int existingLoopCounter = -1; if(ToyJSONParseUtils::hasMemberString(jsonActionObject, "loopId")){ auto loopId = jsonActionObject["loopId"].GetString(); existingLoopCounter = parseDelegate->getLoopActionCounter(loopId); if(existingLoopCounter == -1){ existingLoopCounter = parseDelegate->addNewLoopActionCounter(loopId, jsonActionObject["timesRepeated"].GetInt()); } else { existingLoopCounter = parseDelegate->decrementLoopActionCounter(loopId); if(existingLoopCounter == 0){ parseDelegate->deleteLoopActionCounter(loopId); existingLoopCounter = -1; } } } auto storedValueKey = ToyValueStorage::getInstance().storeValue(jsonActionObject, parseDelegate->getToyValueStorageContainerName()); std::function actionFunction = std::bind([&](std::string pStoredValueKey, ActionParseDelegate* pParseDelegate, int pExistingLoopCounter){ if(pExistingLoopCounter != -1){ auto pStoredValue = ToyValueStorage::getInstance().getStoredValue(pStoredValueKey, pParseDelegate->getToyValueStorageContainerName()); pParseDelegate->setLastActionIndex((*pStoredValue)["jumpActionIndex"].GetInt() - 1); } ToyValueStorage::getInstance().removeStoredValue(pStoredValueKey, pParseDelegate->getToyValueStorageContainerName()); }, storedValueKey, parseDelegate, existingLoopCounter); return this->embedFunctionInAction(actionFunction, jsonActionObject, parseDelegate, notifyDelegateWhenFinished); } // helper functions cocos2d::Action* ToyActionParser::embedFunctionInAction(std::function actionFunction, const rapidjson::Value& jsonActionObject, ActionParseDelegate* parseDelegate, bool notifyDelegateWhenFinished) { cocos2d::Action* parsedAction = NULL; auto functionKey = ToyValueStorage::getInstance().storeFunction(actionFunction, parseDelegate->getToyValueStorageContainerName()); auto valueKey = ToyValueStorage::getInstance().storeValue(jsonActionObject, parseDelegate->getToyValueStorageContainerName()); std::function callback = [&](std::string pFunctionKey, std::string pValueKey, ActionParseDelegate* pParseDelegate, bool pNotifyDelegate){ ToyValueStorage::getInstance().runStoredFunction(pFunctionKey, pParseDelegate->getToyValueStorageContainerName()); if(pNotifyDelegate){ pParseDelegate->actionFinished(*ToyValueStorage::getInstance().getStoredValue(pValueKey, pParseDelegate->getToyValueStorageContainerName())); } ToyValueStorage::getInstance().removeStoredValue(pValueKey, pParseDelegate->getToyValueStorageContainerName()); ToyValueStorage::getInstance().removeStoredFunction(pFunctionKey, pParseDelegate->getToyValueStorageContainerName()); }; std::function boundCallback = std::bind(callback, functionKey, valueKey, parseDelegate, notifyDelegateWhenFinished); parsedAction = cocos2d::CallFunc::create(boundCallback); return parsedAction; } // possible conditions // "true" | "false" // "objectProperty propertyName == value", e.g. "objectProperty autofilled == 0" -> default object, i.e. parentScene, will be chosen // "objectProperty(object) propertyName == value", e.g. "objectProperty(bigCar) colouringEnabled == 1" // conditions can be joint by simple "ands" && or conditions can be joint by simple "ors" || NEVER TOGETHER -- NOT SUPPORTED!!!! //TODO join these two possibilities!! brackets will be needed bool ToyActionParser::checkCondition(std::string condition, ActionParseDelegate* parseDelegate) { if(condition == "" || condition == "true"){ return true; } if(condition == "false"){ return false; } auto andConditions = ToyStringUtils::splitString(condition, " && "); auto orConditions = ToyStringUtils::splitString(condition, " || "); auto arrayToParse = andConditions.size() > orConditions.size() ? andConditions : orConditions; for(int i = 0; i < arrayToParse.size(); ++i){ auto tokens = ToyStringUtils::splitString(arrayToParse[i], " "); bool result = false; if(tokens[0].find("objectProperty") != std::string::npos){ cocos2d::Node* object = NULL; if(tokens[0] == "objectProperty"){ object = dynamic_cast(parseDelegate->getDefaultObjectForConditionCheck()); } else { auto substrBegin = tokens[0].find("(")+1; auto substrEnd = tokens[0].find(")"); auto objectName = tokens[0].substr(substrBegin, substrEnd - substrBegin); object = dynamic_cast(parseDelegate->getObjectByName(objectName)); } if(object != NULL){ auto scenarioObject = dynamic_cast(object); auto propertyName = tokens[1]; std::string leftSide = scenarioObject->getPropertyAsString(propertyName); auto rightSide = tokens[3]; auto operatorString = tokens[2]; result = this->checkExpression(leftSide, rightSide, operatorString); } } else if(tokens[0].find("currentLoopCountdownIndex") != std::string::npos){ auto substrBegin = tokens[0].find("(")+1; auto substrEnd = tokens[0].find(")"); auto loopId = tokens[0].substr(substrBegin, substrEnd - substrBegin); std::string loopCountDownIndex = std::to_string(parseDelegate->getLoopActionCounter(loopId)); auto rightSide = tokens[2]; auto operatorString = tokens[1]; result = this->checkExpression(loopCountDownIndex, rightSide, operatorString); } else if(tokens[0].find("objectInScreenBounds") != std::string::npos){ auto substrBegin = tokens[0].find("(")+1; auto substrEnd = tokens[0].find(")"); auto objectName = tokens[0].substr(substrBegin, substrEnd - substrBegin); auto object = dynamic_cast(parseDelegate->getObjectByName(objectName)); auto objectBB = ToyGeometryUtils::getBoundingBoxToWorld(object); auto screenBounds = cocos2d::Rect(cocos2d::Point(0,0), cocos2d::Director::getInstance()->getVisibleSize()); result = screenBounds.containsPoint(cocos2d::Point(objectBB.getMinX(), objectBB.getMinY())) && screenBounds.containsPoint(cocos2d::Point(objectBB.getMaxX(), objectBB.getMaxY())); } else if(tokens[0].find("objectXInScreenBounds") != std::string::npos){ auto substrBegin = tokens[0].find("(")+1; auto substrEnd = tokens[0].find(")"); auto objectName = tokens[0].substr(substrBegin, substrEnd - substrBegin); auto object = dynamic_cast(parseDelegate->getObjectByName(objectName)); auto objectBB = ToyGeometryUtils::getBoundingBoxToWorld(object); auto screenBounds = cocos2d::Rect(cocos2d::Point(0,0), cocos2d::Director::getInstance()->getVisibleSize()); result = screenBounds.containsPoint(cocos2d::Point(objectBB.getMinX(), 0)) && screenBounds.containsPoint(cocos2d::Point(objectBB.getMaxX(), 0)); } if(result == true){ if(arrayToParse == andConditions){ continue; } else { return true; } } else { if(arrayToParse == andConditions){ return false; } else { continue; } } } if(arrayToParse == andConditions){ return true; } else { return false; } } bool ToyActionParser::checkExpression(std::string leftValue, std::string rightValue, std::string operatorString) { // log("checking expr: "+leftValue + operator + rightValue); std::transform(leftValue.begin(), leftValue.end(), leftValue.begin(), ::toupper); std::transform(rightValue.begin(), rightValue.end(), rightValue.begin(), ::toupper); if(operatorString == "=="){ return leftValue == rightValue; } else if(operatorString == "!="){ return leftValue != rightValue; } else if(operatorString == "<"){ return leftValue < rightValue; } else if(operatorString == "<="){ return leftValue <= rightValue; } else if(operatorString == ">"){ return leftValue > rightValue; } else if(operatorString == ">="){ return leftValue >= rightValue; } return false; } ToyScenarioObject* ToyActionParser::parseObject(std::string objectName, ActionParseDelegate* parseDelegate) { auto tokens = ToyStringUtils::splitString(objectName, '.'); std::string firstObjectName = tokens[0]; ToyScenarioObject* object = parseDelegate->getObjectByName(firstObjectName); int k = 1; while(k < tokens.size() && object != NULL){ object = object->getToyScenarioObjectByName(tokens[k]); ++k; } return object; } std::vector ToyActionParser::parseObjects(const rapidjson::Value& objectJsonArray, ActionParseDelegate* parseDelegate) { std::vector objects; for(const auto& objectName : objectJsonArray.GetArray()){ if(objectName.IsString()){ objects.push_back(this->parseObject(objectName.GetString(), parseDelegate)); } } return objects; } std::vector ToyActionParser::parseObjectOrObjects(const rapidjson::Value& jsonObject, std::string objectKey, std::string objectsKey, ActionParseDelegate* parseDelegate) { std::vector objects; if(ToyJSONParseUtils::hasMemberString(jsonObject, objectKey.c_str())){ objects.push_back(this->parseObject(jsonObject[objectKey.c_str()].GetString(), parseDelegate)); } else if(ToyJSONParseUtils::hasMemberArray(jsonObject, objectsKey.c_str())){ objects = this->parseObjects(jsonObject[objectsKey.c_str()], parseDelegate); } return objects; } std::vector ToyActionParser::prepareObjectsForAction(const rapidjson::Value& jsonActionObject, ActionParseDelegate* parseDelegate) { std::vector objects = this->parseObjectOrObjects(jsonActionObject, "objectName", "objectNames", parseDelegate); if(objects.size() == 0){ auto object = parseDelegate->getDefaultObjectForAction(jsonActionObject["actionType"].GetString()); objects.push_back(dynamic_cast(object)); } return objects; } void ToyActionParser::parseAndRunAction(const rapidjson::Value& action, ActionParseDelegate* parseDelegate, bool notifyDelegateWhenFinished) { auto parsedAction = this->parseJSONAction(action, parseDelegate, notifyDelegateWhenFinished); if(parsedAction){ parseDelegate->runAction(parsedAction); } else { cocos2d::log("ToyActionParser::parseAndRunAction: ACTION NULL!"); } } void ToyActionParser::parseAndRunActions(const rapidjson::Value& actionsArray, ActionParseDelegate* parseDelegate, bool notifyDelegateWhenFinished) { auto array = actionsArray.GetArray(); for(SizeType i = 0; i < array.Size(); ++i){ this->parseAndRunAction(array[i], parseDelegate, notifyDelegateWhenFinished); } } bool ToyActionParser::checkLateCondition(const rapidjson::Value& jsonActionObject, ActionParseDelegate* parseDelegate) { if(ToyJSONParseUtils::hasMemberString(jsonActionObject, "lateCondition")){ if(ToyActionParser::getInstance().checkCondition(jsonActionObject["lateCondition"].GetString(), parseDelegate) == false){ return false; } } return true; } std::vector ToyActionParser::convertToNodeArray(std::vector objects){ std::vector nodes; for(auto object : objects){ auto objAsNode = dynamic_cast(object); if(objAsNode){ nodes.push_back(objAsNode); } } return nodes; } cocos2d::Node* ToyActionParser::convertToNode(ToyScenarioObject* object){ return dynamic_cast(object); }