private void LoadGameObjects(Resource resource) { if (!resource.Regions.TryGetValue("Templates", out Region templates)) { // TODO - log error return; } if (!templates.Children.TryGetValue("GameObjects", out List <LSLib.LS.Node> gameObjects)) { // TODO - log error return; } foreach (var gameObject in gameObjects) { if (gameObject.Attributes.TryGetValue("MapKey", out NodeAttribute objectGuid) && gameObject.Attributes.TryGetValue("Name", out NodeAttribute objectName) && gameObject.Attributes.TryGetValue("Type", out NodeAttribute objectType)) { LSLib.LS.Story.Compiler.ValueType type = null; switch ((string)objectType.Value) { case "item": type = Compiler.Context.LookupType("ITEMGUID"); break; case "character": type = Compiler.Context.LookupType("CHARACTERGUID"); break; case "trigger": type = Compiler.Context.LookupType("TRIGGERGUID"); break; default: // TODO - log unknown type break; } if (type != null) { var gameObjectInfo = new GameObjectInfo { Name = objectName.Value + "_" + objectGuid.Value, Type = type }; Compiler.Context.GameObjects[(string)objectGuid.Value] = gameObjectInfo; } } } }
private void VerifyIRBinaryCondition(IRRule rule, IRBinaryCondition condition, Int32 conditionIndex) { ValueType lhs = condition.LValue.Type, rhs = condition.RValue.Type; // Don't raise compiler errors if the untyped value is a variable, // as we already have a separate rule-level error for untyped variables. if ((lhs == null && condition.LValue is IRVariable) || (rhs == null && condition.RValue is IRVariable)) { return; } if (condition.LValue is IRVariable && condition.RValue is IRVariable && (condition.LValue as IRVariable).Index == (condition.RValue as IRVariable).Index // This bug was fixed in DOS2 DE && Game == TargetGame.DOS2 // There is a known bug in the main campaign that we have to ignore && rule.Goal.Name != "EndGame_PrisonersDilemma") { Context.Log.Error(condition.Location, DiagnosticCode.BinaryOperationSameRhsLhs, "Same variable used on both sides of a binary expression; this will result in an invalid compare in runtime"); return; } VerifyIRBinaryConditionValue(rule, condition.LValue, conditionIndex); VerifyIRBinaryConditionValue(rule, condition.RValue, conditionIndex); if (!AreIntrinsicTypesCompatible(lhs.IntrinsicTypeId, rhs.IntrinsicTypeId)) { Context.Log.Error(condition.Location, DiagnosticCode.LocalTypeMismatch, "Type of left expression ({0}) differs from type of right expression ({1})", TypeToName(lhs.IntrinsicTypeId), TypeToName(rhs.IntrinsicTypeId)); return; } if (IsRiskyComparison(lhs.IntrinsicTypeId, rhs.IntrinsicTypeId)) { Context.Log.Error(condition.Location, DiagnosticCode.RiskyComparison, "Comparison between {0} and {1} may trigger incorrect behavior", TypeToName(lhs.IntrinsicTypeId), TypeToName(rhs.IntrinsicTypeId)); return; } if (IsGuidAliasToAliasCast(lhs, rhs)) { Context.Log.Error(condition.Location, DiagnosticCode.GuidAliasMismatch, "GUID alias type of left expression ({0}) differs from type of right expression ({1})", TypeToName(lhs.TypeId), TypeToName(rhs.TypeId)); return; } // Using greater than/less than operators for strings and GUIDs is probably a mistake. if ((lhs.IntrinsicTypeId == Value.Type.String || lhs.IntrinsicTypeId == Value.Type.GuidString) && (condition.Op == RelOpType.Greater || condition.Op == RelOpType.GreaterOrEqual || condition.Op == RelOpType.Less || condition.Op == RelOpType.LessOrEqual)) { Context.Log.Warn(condition.Location, DiagnosticCode.StringLtGtComparison, "String comparison using operator {0} - probably a mistake?", condition.Op); return; } }
private void VerifyIRConstant(IRConstant constant) { if (constant.Type.IntrinsicTypeId == Value.Type.GuidString) { var nameWithoutType = constant.StringValue; ValueType type = null; // Check if the value is prefixed by any of the known GUID subtypes. // If a match is found, verify that the type of the constant matched the GUID subtype. var underscore = constant.StringValue.IndexOf('_'); if (underscore != -1) { var prefix = constant.StringValue.Substring(0, underscore); type = Context.LookupType(prefix); if (type != null) { nameWithoutType = constant.StringValue.Substring(underscore + 1); if (constant.Type.TypeId > CompilationContext.MaxIntrinsicTypeId && type.TypeId != constant.Type.TypeId) { Context.Log.Error(constant.Location, DiagnosticCode.GuidAliasMismatch, "GUID constant \"{0}\" has inferred type {1}", constant.StringValue, constant.Type.Name); } } else if (prefix.Contains("GUID")) { Context.Log.Warn(constant.Location, DiagnosticCode.GuidPrefixNotKnown, "GUID constant \"{0}\" is prefixed with unknown type {1}", constant.StringValue, prefix); } } var guid = constant.StringValue.Substring(constant.StringValue.Length - 36); if (!Context.GameObjects.TryGetValue(guid, out GameObjectInfo objectInfo)) { Context.Log.Warn(constant.Location, DiagnosticCode.UnresolvedGameObjectName, "Object \"{0}\" could not be resolved", constant.StringValue); } else { if (objectInfo.Name != nameWithoutType) { Context.Log.Warn(constant.Location, DiagnosticCode.GameObjectNameMismatch, "Constant \"{0}\" references game object with different name (\"{1}\")", nameWithoutType, objectInfo.Name); } if (constant.Type.TypeId != (uint)Value.Type.GuidString && objectInfo.Type.TypeId != (uint)Value.Type.GuidString && constant.Type.TypeId != objectInfo.Type.TypeId) { Context.Log.Warn(constant.Location, DiagnosticCode.GameObjectTypeMismatch, "Constant \"{0}\" of type {1} references game object of type {2}", constant.StringValue, constant.Type.Name, objectInfo.Type.Name); } } } }
private void VerifyIRFuncCondition(IRRule rule, IRFuncCondition condition, int conditionIndex) { // TODO - Merge FuncCondition and IRStatement base? // Base --> IRParameterizedCall --> FuncCond: has (NOT) field var func = Context.LookupSignature(condition.Func.Name); if (func == null) { Context.Log.Error(condition.Location, DiagnosticCode.UnresolvedSymbol, "Symbol \"{0}\" could not be resolved", condition.Func.Name); return; } if (!func.FullyTyped) { Context.Log.Error(condition.Location, DiagnosticCode.UnresolvedSignature, "Signature of \"{0}\" could not be determined", condition.Func.Name); return; } func.Read = true; if (conditionIndex == 0) { switch (rule.Type) { case RuleType.Proc: if (func.Type != FunctionType.Proc) { Context.Log.Error(condition.Location, DiagnosticCode.InvalidSymbolInInitialCondition, "Initial proc condition can only be a PROC name; \"{0}\" is a {1}", condition.Func.Name, func.Type); return; } break; case RuleType.Query: if (func.Type != FunctionType.UserQuery) { Context.Log.Error(condition.Location, DiagnosticCode.InvalidSymbolInInitialCondition, "Initial query condition can only be a user-defined QRY name; \"{0}\" is a {1}", condition.Func.Name, func.Type); return; } break; case RuleType.Rule: if (func.Type != FunctionType.Event && func.Type != FunctionType.Database) { Context.Log.Error(condition.Location, DiagnosticCode.InvalidSymbolInInitialCondition, "Initial rule condition can only be an event or a DB; \"{0}\" is a {1}", condition.Func.Name, func.Type); return; } break; default: throw new Exception("Unknown rule type"); } } else { if (func.Type != FunctionType.SysQuery && func.Type != FunctionType.Query && func.Type != FunctionType.Database && func.Type != FunctionType.UserQuery) { Context.Log.Error(condition.Location, DiagnosticCode.InvalidFunctionTypeInCondition, "Subsequent rule conditions can only be queries or DBs; \"{0}\" is a {1}", condition.Func.Name, func.Type); return; } } int index = 0; foreach (var param in func.Params) { var condParam = condition.Params[index]; ValueType type = condParam.Type; if (type == null) { Context.Log.Error(condParam.Location, DiagnosticCode.InternalError, "No type information available for func condition arg"); continue; } VerifyIRValue(rule, condParam); VerifyIRValueCall(rule, condParam, func, index, conditionIndex, condition.Not); VerifyParamCompatibility(func, index, param, condParam); index++; } }
private void VerifyIRStatement(IRRule rule, IRStatement statement) { if (statement.Func == null) { return; } var func = Context.LookupSignature(statement.Func.Name); if (func == null) { Context.Log.Error(statement.Location, DiagnosticCode.UnresolvedSymbol, "Symbol \"{0}\" could not be resolved", statement.Func.Name); return; } if (!func.FullyTyped) { Context.Log.Error(statement.Location, DiagnosticCode.UnresolvedSignature, "Signature of \"{0}\" could not be determined", statement.Func.Name); return; } if (func.Type != FunctionType.Database && func.Type != FunctionType.Call && func.Type != FunctionType.SysCall && func.Type != FunctionType.Proc) { Context.Log.Error(statement.Location, DiagnosticCode.InvalidSymbolInStatement, "KB rule actions can only reference databases, calls and PROCs; \"{0}\" is a {1}", statement.Func.Name, func.Type); return; } if (statement.Not && func.Type != FunctionType.Database) { Context.Log.Error(statement.Location, DiagnosticCode.CanOnlyDeleteFromDatabase, "KB rule NOT actions can only reference databases; \"{0}\" is a {1}", statement.Func.Name, func.Type); return; } if (statement.Not) { func.Deleted = true; } else { func.Inserted = true; } int index = 0; foreach (var param in func.Params) { var ele = statement.Params[index]; ValueType type = ele.Type; if (type == null) { Context.Log.Error(ele.Location, DiagnosticCode.InternalError, "No type information available for statement argument"); continue; } VerifyIRValue(rule, ele); VerifyIRValueCall(rule, ele, func, index, -1, statement.Not); VerifyParamCompatibility(func, index, param, ele); index++; } }