Index: .arclint =================================================================== --- .arclint +++ .arclint @@ -27,7 +27,8 @@ "type": "eslint", "bin": ["eslint", "build\\arclint\\dummies\\eslint.bat", "build/arclint/dummies/eslint.php"], "include": "/\\.js$/", - "config": "build/arclint/configs/eslintrc.json" + "config": "build/arclint/configs/eslintrc.json", + "flags": ["--rulesdir=build/arclint/configs/eslintrules/"] }, "cppcheck": { "type": "cppcheck", Index: binaries/data/mods/public/simulation/components/Foundation.js =================================================================== --- binaries/data/mods/public/simulation/components/Foundation.js +++ binaries/data/mods/public/simulation/components/Foundation.js @@ -511,7 +511,7 @@ // Initially hide the preview underground let cmpPreviewPosition = Engine.QueryInterface(this.previewEntity, IID_Position); let cmpFoundationPosition = Engine.QueryInterface(this.entity, IID_Position); - if (cmpPreviewPosition && cmpFoundationPosition) + if (cmpPreviewPosition && cmpFoundationPosition && cmpPreviewPosition.IsInWorld()) { let rot = cmpFoundationPosition.GetRotation(); cmpPreviewPosition.SetYRotation(rot.y); Index: binaries/data/mods/public/simulation/components/GarrisonHolder.js =================================================================== --- binaries/data/mods/public/simulation/components/GarrisonHolder.js +++ binaries/data/mods/public/simulation/components/GarrisonHolder.js @@ -226,7 +226,7 @@ // If ejection is forced, we need to continue, so use center of the building let cmpPosition = Engine.QueryInterface(this.entity, IID_Position); - pos = cmpPosition.GetPosition(); + pos = cmpPosition.IsInWorld() ? cmpPosition.GetPosition() : null; } return pos; }; Index: binaries/data/mods/public/simulation/components/Garrisonable.js =================================================================== --- binaries/data/mods/public/simulation/components/Garrisonable.js +++ binaries/data/mods/public/simulation/components/Garrisonable.js @@ -132,7 +132,7 @@ } let cmpHolderPosition = Engine.QueryInterface(this.holder, IID_Position); - if (cmpHolderPosition) + if (cmpHolderPosition && cmpHolderPosition.IsInWorld()) cmpPosition.SetYRotation(cmpHolderPosition.GetPosition().horizAngleTo(pos)); let cmpUnitAI = Engine.QueryInterface(this.entity, IID_UnitAI); Index: build/arclint/configs/eslintrc.json =================================================================== --- build/arclint/configs/eslintrc.json +++ build/arclint/configs/eslintrc.json @@ -6,6 +6,7 @@ "brace-rules" ], "rules": { + "no-in-world-check": 1, "no-caller": 1, "no-cond-assign": 1, "no-constant-condition": ["error", { "checkLoops": false }], Index: build/arclint/configs/eslintrules/no-in-world-check.js =================================================================== --- /dev/null +++ build/arclint/configs/eslintrules/no-in-world-check.js @@ -0,0 +1,50 @@ +/** + * @fileoverview Rule to check Mirage interface usage. + */ + +"use strict"; + +module.exports = { + "meta": { + "type": "suggestion", + + "docs": { + "description": "disallow unnecessary semicolons", + "category": "Possible Errors", + "recommended": true, + "url": "https://eslint.org/docs/rules/no-extra-semi" + }, + "fixable": "code", + "schema": [] // no options + }, + "create": function(context) { + return { + "CallExpression[callee.property.name='QueryInterface']": function(node) { + // Find all usage of this variable. + if (node.arguments[0].name === "SYSTEM_ENTITY" || + node.arguments[1].name !== "IID_Position") + return; + let variable = context.getDeclaredVariables(node.parent)[0]; + if (!variable) + return; + + let usages = variable.references.filter(x => x.identifier.parent.type === "MemberExpression"); + if (!usages.length) + return; + let checkBefore = usages.findIndex(x => [ + "GetPosition", "GetPosition2D", + "GetPreviousPosition", "GetPreviousPosition2D" + ].indexOf(x.identifier.parent.property.name) !== -1); + if (checkBefore === -1) + return; + let checkAt = usages.findIndex(x => x.identifier.parent.property.name === "IsInWorld"); + if (checkAt !== -1 && checkAt < checkBefore) + return; + context.report({ + "node": usages[checkBefore].identifier, + "message": "Call to a position-related method without checking IsInWorld." + }); + } + }; + } +};