public function __construct(TES5ObjectPropertyFactory $objectPropertyFactory)
 {
     $this->objectPropertyFactory = $objectPropertyFactory;
     //Those are used to hook in the internal Skyblivion systems.
     $special_conversions = ['TES4AttrStrength' => TES5BasicType::T_GLOBALVARIABLE(), 'TES4AttrIntelligence' => TES5BasicType::T_GLOBALVARIABLE(), 'TES4AttrWillpower' => TES5BasicType::T_GLOBALVARIABLE(), 'TES4AttrAgility' => TES5BasicType::T_GLOBALVARIABLE(), 'TES4AttrSpeed' => TES5BasicType::T_GLOBALVARIABLE(), 'TES4AttrEndurance' => TES5BasicType::T_GLOBALVARIABLE(), 'TES4AttrPersonality' => TES5BasicType::T_GLOBALVARIABLE(), 'TES4AttrLuck' => TES5BasicType::T_GLOBALVARIABLE(), 'tContainer' => TES5TypeFactory::memberByValue("TES4Container", TES5BasicType::T_QUEST()), 'tTimer' => TES5TypeFactory::memberByValue("TES4TimerHelper", TES5BasicType::T_QUEST()), 'tGSPLocalTimer' => TES5BasicType::T_FLOAT(), 'TES4CyrodiilCrimeFaction' => TES5BasicType::T_FACTION(), self::MESSAGEBOX_VARIABLE_CONST => TES5BasicType::T_INT()];
     $this->special_conversions = $special_conversions;
 }
 public function __construct($scriptName, $edid, TES5Type $scriptType, $scriptNamePrefix, $isHidden = false)
 {
     $this->scriptName = $scriptName;
     $this->edid = $edid;
     $this->scriptNamePrefix = $scriptNamePrefix;
     $this->scriptType = TES5TypeFactory::memberByValue($scriptName, $scriptType);
     $this->basicScriptType = $scriptType;
     $this->isHidden = $isHidden;
     $this->inheritanceAnalyzer = new TES5InheritanceGraphAnalyzer();
 }
 /**
  * @param TES5Variable $variable
  * @return \Ormin\OBSLexicalParser\TES5\Types\TES5CustomType
  * @throws ConversionException
  */
 public function resolveInferenceTypeByReferenceEdid(TES5Variable $variable)
 {
     $base = $variable->getReferenceEdid();
     $tryAs = [$base, $base . 'Script'];
     if (strtolower(substr($base, -3, 3)) == "ref") {
         $tryAsRef = substr($base, 0, -3);
         $tryAs[] = $tryAsRef;
         $tryAs[] = $tryAsRef . 'Script';
     }
     $tryAs = array_unique($tryAs);
     foreach ($tryAs as $try) {
         if (in_array(strtolower($try), $this->otherScripts)) {
             return TES5TypeFactory::memberByValue($try);
         }
     }
     //If it's not found, we're forced to scan the ESM to see, how to resolve the ref name to script type
     return $this->esmAnalyzer->resolveScriptTypeByItsAttachedName($variable->getReferenceEdid());
 }
 /**
  * @param $attachedName
  * @return \Eloquent\Enumeration\ValueMultitonInterface|\Ormin\OBSLexicalParser\TES5\Types\TES5CustomType|\Ormin\OBSLexicalParser\TES5\Types\TES5VoidType
  * @todo REFACTOR, it's really ugly!
  * @throws ConversionException
  */
 public function resolveScriptTypeByItsAttachedName($attachedName)
 {
     if (!isset($this->attachedNameCache[strtolower($attachedName)])) {
         if (preg_match("#REFR................EDID..(?i)" . $attachedName . "(?-i)\\x{00}NAME..(....)#s", $this->esm, $refrFormidMatches) || preg_match("#ACRE................EDID..(?i)" . $attachedName . "(?-i)\\x{00}NAME..(....)#s", $this->esm, $acreFormidMatches) || preg_match("#ACHR................EDID..(?i)" . $attachedName . "(?-i)\\x{00}NAME..(....)#s", $this->esm, $achrFormidMatches)) {
             if (!empty($refrFormidMatches)) {
                 $formidMatches = $refrFormidMatches;
                 $searchedFormType = "(?!SCPT)[A-Z]{4}";
                 //TODO - this can be a specific list of objects, perhaps do a search of this list?
             } else {
                 if (!empty($acreFormidMatches)) {
                     $formidMatches = $acreFormidMatches;
                     $searchedFormType = "CREA";
                 } else {
                     $formidMatches = $achrFormidMatches;
                     $searchedFormType = "NPC_";
                 }
             }
             //We have a REFR, we have to unpack it and match the formid
             $targetFormid = $formidMatches[1];
             $targetFormidString = "";
             for ($i = 0; $i < 4; ++$i) {
                 $hexCharacter = dechex(ord(substr($targetFormid, $i, 1)));
                 if (strlen($hexCharacter) == 1) {
                     $hexCharacter = '0' . $hexCharacter;
                 }
                 $targetFormidString .= "\\x{" . $hexCharacter . "}";
             }
             if ($searchedFormType != "NPC_") {
                 preg_match("#" . $searchedFormType . "........" . $targetFormidString . ".*?SCRI..(....)#s", $this->esm, $matches);
             } else {
                 if (preg_match("#NPC_(....)...." . $targetFormidString . "#s", $this->esm, $failoverMatches, PREG_OFFSET_CAPTURE)) {
                     $baseDataOffset = $failoverMatches[0][1] + 24;
                     $dataLengthMatch = $failoverMatches[1][0];
                     $dataLength = 0;
                     for ($i = 0; $i < 4; ++$i) {
                         $dataLength += ord($dataLengthMatch[$i]) * pow(256, $i);
                     }
                     $dataLength -= 4;
                     $gzippedData = substr($this->esm, $baseDataOffset, $dataLength);
                     $ungzippedData = gzuncompress($gzippedData);
                     preg_match("#SCRI..(....)#si", $ungzippedData, $matches);
                 }
             }
         } else {
             //Just go with usual matching via EDID
             preg_match("#EDID..(?i)" . $attachedName . "(?-i)\\x{00}.*?SCRI..(....)#s", $this->esm, $matches);
         }
         if (empty($matches)) {
             throw new ConversionException("Cannot resolve script type by searching its base form edid " . $attachedName);
         }
         $hex = $matches[1];
         $hexString = "";
         $hexFormid = "";
         for ($i = 0; $i < 4; ++$i) {
             $hexCharacter = dechex(ord(substr($hex, $i, 1)));
             if (strlen($hexCharacter) == 1) {
                 $hexCharacter = '0' . $hexCharacter;
             }
             $hexString .= "\\x{" . $hexCharacter . "}";
             $hexFormid .= $hexCharacter;
         }
         preg_match("#SCPT........" . $hexString . "....EDID..([a-zA-Z0-9]+)#si", $this->esm, $dataMatches);
         if (empty($dataMatches)) {
             throw new ConversionException("For EDID " . $attachedName . " and script formid " . $hexFormid . " we couldn't find any scripts in ESM.");
         }
         $customType = TES5TypeFactory::memberByValue($dataMatches[1]);
         $this->attachedNameCache[strtolower($attachedName)] = $customType;
     }
     return $this->attachedNameCache[strtolower($attachedName)];
 }
 public static function map($type)
 {
     switch ($type) {
         case "AACT":
             $propertyType = 'Action';
             break;
         case "ACTI":
             $propertyType = 'Activator';
             break;
         case "ACHR":
         case "ACRE":
         case "NPC_":
             $propertyType = 'Actor';
             break;
         case "AMMO":
             $propertyType = 'Ammo';
             break;
         case "APPA":
             $propertyType = 'Apparatus';
             break;
         case "CREA":
             $propertyType = "Actor";
             break;
         case "CLMT":
             $propertyType = "Climate";
             break;
         case "CLOT":
         case "ARMO":
             $propertyType = 'Armor';
             break;
         case "ASTP":
             $propertyType = 'AssociationType';
             break;
         case "BOOK":
             $propertyType = 'Book';
             break;
         case "CELL":
             $propertyType = 'Cell';
             break;
         case "CLAS":
             $propertyType = 'Class';
             break;
         case "COBJ":
             $propertyType = 'ConstructibleObject';
             break;
         case "CONT":
             $propertyType = 'Container';
             break;
         case "DOOR":
             $propertyType = 'Door';
             break;
         case "EFSH":
             $propertyType = 'EffectShader';
             break;
         case "ENCH":
             $propertyType = 'Enchantment';
             break;
         case "ECNZ":
             $propertyType = 'EncounterZone';
             break;
         case "EXPL":
             $propertyType = 'Explosion';
             break;
         case "FACT":
             $propertyType = 'Faction';
             break;
         case "FLOR":
             $propertyType = 'Flora';
             break;
         case "FLST":
             $propertyType = 'FormList';
             break;
         case "FURN":
             $propertyType = 'Furniture';
             break;
         case "GLOB":
             $propertyType = 'GlobalVariable';
             break;
         case "HAZD":
             $propertyType = 'Hazard';
             break;
         case "IMAD":
             $propertyType = 'ImageSpaceModifier';
             break;
         case "IPDS":
             $propertyType = 'ImpactDataSet';
             break;
         case "INGR":
             $propertyType = 'Ingredient';
             break;
         case "KEYM":
             $propertyType = 'Key';
             break;
         case "KYWD":
             $propertyType = 'Keyword';
             break;
         case "LVLN":
             $propertyType = 'LeveledActor';
             break;
         case "LVLI":
             $propertyType = 'LeveledItem';
             break;
         case "LVSP":
             $propertyType = 'LeveledSpell';
             break;
         case "LIGH":
             $propertyType = 'Light';
             break;
         case "LCTN":
             $propertyType = 'Location';
             break;
         case "LCRT":
             $propertyType = 'LocationRefType';
             break;
         case "MGEF":
             $propertyType = 'MagicEffect';
             break;
         case "MATH":
             $propertyType = 'Math';
             break;
         case "MESG":
             $propertyType = 'Message';
             break;
         case "MISC":
             $propertyType = 'MiscObject';
             break;
         case "MUSC":
             $propertyType = 'MusicType';
             break;
         case "REFR":
             $propertyType = 'ObjectReference';
             break;
         case "OTFT":
             $propertyType = 'Outfit';
             break;
         case "PACK":
             $propertyType = 'Package';
             break;
         case "PERK":
             $propertyType = 'Perk';
             break;
         case "ALCH":
             $propertyType = 'Potion';
             break;
         case "PROJ":
             $propertyType = 'Projectile';
             break;
         case "QUST":
             $propertyType = 'Quest';
             break;
         case "RACE":
             $propertyType = 'Race';
             break;
         case "SCEN":
             $propertyType = 'Scene';
             break;
         case "SCRL":
             $propertyType = 'Scroll';
             break;
         case "SHOU":
             $propertyType = 'Shout';
             break;
         case "SLGM":
             $propertyType = 'SoulGem';
             break;
         case "SOUN":
             $propertyType = 'Sound';
             break;
         case "SNCT":
             $propertyType = 'SoundCategory';
             break;
         case "SPEL":
             $propertyType = 'Spell';
             break;
         case "STAT":
             $propertyType = 'Static';
             break;
         case "TACT":
             $propertyType = 'TalkingActivator';
             break;
         case "DIAL":
             $propertyType = 'Topic';
             break;
         case "INFO":
             $propertyType = 'TopicInfo';
             break;
         case "RFCT":
             $propertyType = 'VisualEffect';
             break;
         case "VTYP":
             $propertyType = 'VoiceType';
             break;
         case "WEAP":
             $propertyType = 'Weapon';
             break;
         case "WTHR":
             $propertyType = 'Weather';
             break;
         case "WOOP":
             $propertyType = 'WordOfPower';
             break;
         case "WRLD":
             $propertyType = 'WorldSpace';
             break;
         default:
             throw new ConversionException("Unknown " . $type . " formid reference.");
     }
     return TES5TypeFactory::memberByValue($propertyType);
 }
 public function getType()
 {
     return TES5TypeFactory::memberByValue($this->getName());
 }
 /**
  * @param TES5ObjectCall $objectCall
  * @return mixed|TES5Type
  * @throws \Ormin\OBSLexicalParser\TES5\Exception\ConversionException
  *
  */
 public static function findTypeByMethod(TES5ObjectCall $objectCall)
 {
     $methodName = $objectCall->getFunctionName();
     $possibleMatches = [];
     foreach (self::$callReturns as $type => $methods) {
         foreach ($methods as $method => $functionData) {
             if (strtolower($method) == strtolower($methodName)) {
                 $possibleMatches[] = TES5TypeFactory::memberByValue($type);
             }
         }
     }
     $calledOn = $objectCall->getAccessedObject()->getReferencesTo();
     $extendingMatches = [];
     $actualType = $calledOn->getPropertyType()->getNativeType();
     /**
      * @var TES5Type[] $possibleMatches
      */
     foreach ($possibleMatches as $possibleMatch) {
         if ($possibleMatch == $actualType) {
             return $possibleMatch;
             //if the possible match matches the actual basic type, it means that it surely IS one of those.
         }
         //Ok, so are those matches somehow connected at all?
         if (TES5InheritanceGraphAnalyzer::isExtending($possibleMatch, $actualType) || TES5InheritanceGraphAnalyzer::isExtending($actualType, $possibleMatch)) {
             $extendingMatches[] = $possibleMatch;
         }
     }
     switch (count($extendingMatches)) {
         case 0:
             $concatTypes = [];
             foreach ($possibleMatches as $possibleMatch) {
                 $concatTypes[] = $possibleMatch->value();
             }
             throw new ConversionException("Cannot find any possible type for method " . $methodName . ", trying to extend " . $actualType->value() . " with following types: " . implode(', ', $concatTypes));
         case 1:
             return current($extendingMatches);
         default:
             //We analyze the property name and check inside the ESM analyzer.
             $formType = ESMAnalyzer::instance()->getFormTypeByEDID($calledOn->getReferenceEdid());
             if (!in_array($formType, $extendingMatches)) {
                 throw new ConversionException("ESM <-> Inheritance Graph conflict, ESM returned " . $formType->value() . ", which is not present in possible matches from inheritance graph");
             }
             return $formType;
     }
 }
 private function convertArithmeticExpression(TES4Expression $expression, TES5CodeScope $codeScope, TES5GlobalScope $globalScope, TES5MultipleScriptsScope $multipleScriptsScope)
 {
     $sets = [[$expression->getLeftValue(), $expression->getRightValue()], [$expression->getRightValue(), $expression->getLeftValue()]];
     /**
      * Scenario 1 - Special functions converted on expression level
      * @var TES4Value[] $set
      */
     foreach ($sets as $set) {
         if (!$set[0] instanceof TES4Callable) {
             continue;
         }
         $function = $set[0]->getFunction();
         switch (strtolower($function->getFunctionCall()->getFunctionName())) {
             case "getweaponanimtype":
                 $calledOn = $this->createCalledOnReferenceOfCalledFunction($set[0], $codeScope, $globalScope, $multipleScriptsScope);
                 $leftValue = $this->createObjectCall($this->createObjectCall($calledOn, "GetEquippedWeapon", $multipleScriptsScope), "GetWeaponType", $multipleScriptsScope);
                 switch ((int) $set[1]->getData()) {
                     case 0:
                         $targetedWeaponTypes = [0];
                         break;
                     case 1:
                         $targetedWeaponTypes = [1, 2, 3, 4];
                         break;
                     case 2:
                         $targetedWeaponTypes = [5, 6, 8];
                         break;
                     case 3:
                         $targetedWeaponTypes = [7, 9];
                         break;
                     default:
                         throw new ConversionException("GetWeaponAnimType() - Unknown weapon type in expression");
                 }
                 $expressions = [];
                 foreach ($targetedWeaponTypes as $targetedWeaponType) {
                     $expressions[] = $this->expressionFactory->createArithmeticExpression($leftValue, TES5ArithmeticExpressionOperator::OPERATOR_EQUAL(), new TES5Integer($targetedWeaponType));
                 }
                 $resultExpression = $expressions[0];
                 unset($expressions[0]);
                 while (!empty($expressions)) {
                     $resultExpression = $this->expressionFactory->createLogicalExpression($resultExpression, TES5LogicalExpressionOperator::OPERATOR_OR(), array_pop($expressions));
                 }
                 return $resultExpression;
             case "getdetected":
                 $inversedArgument = new TES5ObjectCallArguments();
                 $inversedArgument->add($this->createCalledOnReferenceOfCalledFunction($set[0], $codeScope, $globalScope, $multipleScriptsScope));
                 $leftValue = $this->createObjectCall($this->createReadReference($function->getArguments()->getValue(0)->getData(), $globalScope, $multipleScriptsScope, $codeScope->getLocalScope()), "isDetectedBy", $multipleScriptsScope, $inversedArgument);
                 $rightValue = new TES5Integer((int) $set[1]->getData() == 0 ? 0 : 1);
                 $expression = $this->expressionFactory->createArithmeticExpression($leftValue, TES5ArithmeticExpressionOperator::OPERATOR_EQUAL(), $rightValue);
                 return $expression;
             case "getdetectionlevel":
                 if (!$set[1]->hasFixedValue()) {
                     throw new ConversionException("Cannot convert getDetectionLevel calls with dynamic comparision");
                 }
                 $boolean = new TES5Bool($set[1]->getData() == 3);
                 //true only if the compared value was 3
                 $inversedArgument = new TES5ObjectCallArguments();
                 $inversedArgument->add($this->createCalledOnReferenceOfCalledFunction($set[0], $codeScope, $globalScope, $multipleScriptsScope));
                 $expression = $this->expressionFactory->createArithmeticExpression($this->createObjectCall($this->createReadReference($function->getArguments()->getValue(0)->getData(), $globalScope, $multipleScriptsScope, $codeScope->getLocalScope()), "isDetectedBy", $multipleScriptsScope, $inversedArgument), TES5ArithmeticExpressionOperator::OPERATOR_EQUAL(), $boolean);
                 return $expression;
             case "getcurrentaiprocedure":
                 if (!$set[1]->hasFixedValue()) {
                     throw new ConversionException("Cannot convert getCurrentAIProcedure() calls with dynamic comparision");
                 }
                 switch ((int) $set[1]->getData()) {
                     case 4:
                         //ref.getSleepState() == 3
                         $expression = $this->expressionFactory->createArithmeticExpression($this->createObjectCall($this->createCalledOnReferenceOfCalledFunction($set[0], $codeScope, $globalScope, $multipleScriptsScope), "IsInDialogueWithPlayer", $multipleScriptsScope), TES5ArithmeticExpressionOperator::OPERATOR_EQUAL(), new TES5Bool((bool) $expression->getOperator() == TES4ArithmeticExpressionOperator::OPERATOR_EQUAL()));
                         return $expression;
                     case 8:
                         //ref.getSleepState() == 3
                         $expression = $this->expressionFactory->createArithmeticExpression($this->createObjectCall($this->createCalledOnReferenceOfCalledFunction($set[0], $codeScope, $globalScope, $multipleScriptsScope), "getSleepState", $multipleScriptsScope), TES5ArithmeticExpressionOperator::OPERATOR_EQUAL(), new TES5Integer(3));
                         return $expression;
                     case 13:
                         //ref.getSleepState() == 3
                         $expression = $this->expressionFactory->createArithmeticExpression($this->createObjectCall($this->createCalledOnReferenceOfCalledFunction($set[0], $codeScope, $globalScope, $multipleScriptsScope), "IsInCombat", $multipleScriptsScope), TES5ArithmeticExpressionOperator::OPERATOR_EQUAL(), new TES5Bool((bool) $expression->getOperator() == TES4ArithmeticExpressionOperator::OPERATOR_EQUAL()));
                         return $expression;
                     case 0:
                     case 7:
                     case 15:
                     case 17:
                         //@INCONSISTENCE Wander.. idk how to check it tbh. We return always true. Think about better representation
                         return new TES5Bool((bool) $expression->getOperator() == TES4ArithmeticExpressionOperator::OPERATOR_EQUAL());
                     default:
                         throw new ConversionException("Cannot convert GetCurrentAiProcedure - unknown TES4 procedure number arg " . (int) $set[1]->getData());
                 }
                 break;
             case "isidleplaying":
             case "getknockedstate":
             case "gettalkedtopc":
                 return new TES5Bool(true);
                 //This is so unimportant that i think it's not worth to find a good alternative and waste time.
             case "getsitting":
                 //WARNING: Needs to implement Horse sittings, too.
                 //SEE: http://www.gameskyrim.com/papyrus-isridinghorse-function-t255012.html
                 switch ((int) $set[1]->getData()) {
                     case 0:
                         $goTo = 0;
                         break;
                     case 1:
                     case 2:
                     case 11:
                     case 12:
                         $goTo = 2;
                         break;
                     case 3:
                     case 13:
                         $goTo = 3;
                         break;
                     case 4:
                     case 14:
                         $goTo = 4;
                         break;
                     default:
                         throw new ConversionException("GetSitting - unknown state found");
                 }
                 //ref.getSleepState() == 3
                 $expression = $this->expressionFactory->createArithmeticExpression($this->createObjectCall($this->createCalledOnReferenceOfCalledFunction($set[0], $codeScope, $globalScope, $multipleScriptsScope), "GetSitState", $multipleScriptsScope), TES5ArithmeticExpressionOperator::memberByValue($expression->getOperator()->value()), new TES5Integer($goTo));
                 return $expression;
         }
     }
     $leftValue = $this->createValue($expression->getLeftValue(), $codeScope, $globalScope, $multipleScriptsScope);
     $rightValue = $this->createValue($expression->getRightValue(), $codeScope, $globalScope, $multipleScriptsScope);
     $tes5sets = [[$leftValue, $rightValue], [$rightValue, $leftValue]];
     $objectReferenceType = TES5BasicType::T_FORM();
     //used just to make sure.
     $operator = TES5ArithmeticExpressionOperator::memberByValueWithDefault($expression->getOperator()->value(), null);
     /**
      * @var TES5Value[] $tes5set
      * Scenario 2: Comparision of ObjectReferences to integers ( quick formid check )
      */
     foreach ($tes5sets as $tes5set) {
         if ($tes5set[0]->getType() == $objectReferenceType || TES5InheritanceGraphAnalyzer::isExtending($tes5set[0]->getType(), $objectReferenceType)) {
             if ($tes5set[1]->getType() == TES5BasicType::T_INT()) {
                 $tes5set[0] = $this->createObjectCall($tes5set[0], "GetFormID", $multipleScriptsScope);
                 return $this->expressionFactory->createArithmeticExpression($tes5set[0], $operator, $tes5set[1]);
             }
         } else {
             if ($tes5set[0]->getType() == TES5TypeFactory::void()) {
                 if ($tes5set[1] instanceof TES5Integer || $tes5set[1] instanceof TES5Float) {
                     if ($tes5set[1]->getValue() == 0) {
                         return $this->expressionFactory->createArithmeticExpression($tes5set[0], $operator, new TES5None());
                     }
                 }
             }
         }
     }
     return $this->expressionFactory->createArithmeticExpression($leftValue, $operator, $rightValue);
 }