public virtual IDictionary BuildNonGenericMapInstance(MapObjectInfo mapObjectInfo, Type t) { System.Collections.Generic.IDictionary <AbstractObjectInfo, AbstractObjectInfo> map = mapObjectInfo.GetMap(); IDictionary newMap; try { newMap = (IDictionary)System.Activator.CreateInstance(t); } catch (System.Exception e) { throw new ODBRuntimeException(NeoDatisError.MapInstanciationError.AddParameter(map.GetType().FullName), e); } int i = 0; System.Collections.Generic.IEnumerator <AbstractObjectInfo> iterator = map.Keys.GetEnumerator(); AbstractObjectInfo key = null; while (iterator.MoveNext()) { key = iterator.Current; object realKey = BuildOneInstance(key); object realValue = BuildOneInstance(map[key]); newMap[realKey] = realValue; } return(newMap); }
public virtual object BuildGenericMapInstance(MapObjectInfo mapObjectInfo, Type t) { System.Collections.Generic.IDictionary <AbstractObjectInfo, AbstractObjectInfo> map = mapObjectInfo.GetMap(); Type genericType = t.GetGenericTypeDefinition(); object newMap = null; try { newMap = System.Activator.CreateInstance(t); } catch (System.Exception e) { Console.WriteLine(e); throw new ODBRuntimeException(NeoDatisError.MapInstanciationError.AddParameter(map.GetType().FullName), e); } int i = 0; System.Collections.Generic.IEnumerator <AbstractObjectInfo> iterator = map.Keys.GetEnumerator(); AbstractObjectInfo key = null; MethodInfo method = t.GetMethod("Add", t.GetGenericArguments()); while (iterator.MoveNext()) { key = iterator.Current; object realKey = BuildOneInstance(key); object realValue = BuildOneInstance(map[key]); method.Invoke(newMap, new object[] { realKey, realValue }); } return(newMap); }
/// <summary>Checks if something in the Map has changed, if yes, stores the change</summary> /// <param name="nnoi1"> /// The first Object meta representation (nnoi = /// NonNativeObjectInfo) /// </param> /// <param name="nnoi2">The second object meta representation</param> /// <param name="fieldIndex">The field index that this map represents</param> /// <param name="moi1">The Meta representation of the map 1 (moi = MapObjectInfo)</param> /// <param name="moi2">The Meta representation of the map 2</param> /// <param name="objectRecursionLevel"></param> /// <returns>true if the 2 map representations are different</returns> private bool ManageMapChanges(NonNativeObjectInfo nnoi1, NonNativeObjectInfo nnoi2, int fieldId , MapObjectInfo moi1, MapObjectInfo moi2, int objectRecursionLevel) { if (true) { return(true); } IDictionary <AbstractObjectInfo , AbstractObjectInfo> map1 = moi1.GetMap(); IDictionary <AbstractObjectInfo , AbstractObjectInfo> map2 = moi2.GetMap(); if (map1.Count != map2.Count) { System.Text.StringBuilder buffer = new System.Text.StringBuilder(); buffer.Append("Map size has changed oldsize=").Append(map1.Count).Append("/newsize=" ).Append(map2.Count); StoreChangedObject(nnoi1, nnoi2, fieldId, moi1, moi2, buffer.ToString(), objectRecursionLevel ); return(true); } IEnumerator <AbstractObjectInfo > keys1 = map1.Keys.GetEnumerator(); IEnumerator <AbstractObjectInfo > keys2 = map2.Keys.GetEnumerator(); AbstractObjectInfo key1 = null; AbstractObjectInfo key2 = null; AbstractObjectInfo value1 = null; AbstractObjectInfo value2 = null; int index = 0; while (keys1.MoveNext()) { keys2.MoveNext(); key1 = keys1.Current; key2 = keys2.Current; bool keysHaveChanged = this.HasChanged(key1, key2, objectRecursionLevel); if (keysHaveChanged) { StoreChangedObject(nnoi1, nnoi2, fieldId, key1, key2, "Map key index " + index + " has changed", objectRecursionLevel); return(true); } value1 = map1[key1]; value2 = map2[key2]; bool valuesHaveChanged = this.HasChanged(value1, value2, objectRecursionLevel); if (valuesHaveChanged) { StoreChangedObject(nnoi1, nnoi2, fieldId, value1, value2, "Map value index " + index + " has changed", objectRecursionLevel); return(true); } index++; } return(false); }
/// <summary> /// 0 = touch /// 1 = stay near /// 2 = neutral, basically ignore it (this is 0 to 1) /// 3 = avoid /// 4 = attack /// </summary> /// <remarks> /// This default implementation is very simplistic. It is meant to be overridden with ItemOptions.CameraHardCoded_ClassifyObject /// </remarks> public static double[] ClassifyObject(MapObjectInfo mapObject) { return(new[] { 0d, 0d, mapObject == null ? 0d : 1d, 0d, 0d, }); }
public virtual object BuildMapInstance(MapObjectInfo moi) { Type t = classPool.GetClass(moi.GetRealMapClassName()); if (t.IsGenericType) { return(BuildGenericMapInstance(moi, t)); } else { return(BuildNonGenericMapInstance(moi, t)); } }
/// <summary> /// Place an object on a grid tile /// </summary> /// <param name="objectTrans"></param> /// <param name="xCoord"></param> /// <param name="zCoord"></param> public static void PlaceObject(Transform objectTrans, int xCoord, int zCoord) { MapObjectInfo movedObject = objectTrans.GetComponent <MapObjectInfo>(); if (movedObject.currentOccupyingTile != null) { movedObject.currentOccupyingTile.containingObject = null; // Erase it from the previous tile } sMapManager.currentMap[xCoord, zCoord].containingObject = objectTrans.gameObject; // Assign the object to tile movedObject.currentOccupyingTile = sMapManager.currentMap[xCoord, zCoord]; // Update the tile this moved object is occuping objectTrans.position = sMapManager.currentMap[xCoord, zCoord].transform.position + Vector3.up * 0.5f; // Move the object }
private static double BuildField_NodeSprtAncestorMass(MapOctree[] ancestors, Point3D minRange, Point3D maxRange) { if (ancestors == null) { return(0d); } double retVal = 0d; for (int ancestorCntr = 0; ancestorCntr < ancestors.Length; ancestorCntr++) { if (ancestors[ancestorCntr].Items == null) { continue; } for (int itemCntr = 0; itemCntr < ancestors[ancestorCntr].Items.Length; itemCntr++) { MapObjectInfo item = ancestors[ancestorCntr].Items[itemCntr]; // Make sure the item is inside this cell if (!Math3D.IsIntersecting_AABB_AABB(item.AABBMin, item.AABBMax, minRange, maxRange)) { continue; } // Get the amount of the ancestor's shape that is inside this cell double fromX = minRange.X < item.AABBMin.X ? item.AABBMin.X : minRange.X; double toX = maxRange.X > item.AABBMax.X ? item.AABBMax.X : maxRange.X; double fromY = minRange.Y < item.AABBMin.Y ? item.AABBMin.Y : minRange.Y; double toY = maxRange.Y > item.AABBMax.Y ? item.AABBMax.Y : maxRange.Y; double fromZ = minRange.Z < item.AABBMin.Z ? item.AABBMin.Z : minRange.Z; double toZ = maxRange.Z > item.AABBMax.Z ? item.AABBMax.Z : maxRange.Z; // Get the percent that's inside compared to the whoe shape double subVolume = (toX - fromX) * (toY - fromY) * (toZ - fromZ); double wholeVolume = (item.AABBMax.X - item.AABBMin.X) * (item.AABBMax.Y - item.AABBMin.Y) * (item.AABBMax.Z - item.AABBMin.Z); double percent = subVolume / wholeVolume; // Take that percent of the shape's mass retVal += item.Mass * percent; } } return(retVal); }
private static Vector3D GetMatchVelocityForce(MapObjectInfo mapObject, Vector3D velocity) { //Vector3D dif = mapObject.Velocity - velocity; // this just causes the whole blob to have an average of zero Vector3D dif = mapObject.Velocity; //TODO: Have a gradient of velocity match based on distance. So if this is really far away from the item, then the item //should have no influence return dif; }
//private Tuple<Vector3D?, Vector3D?> GetSeekForce_1() //{ // #region velocity // double maxSpeed = this.MaxSpeed; // double maxAngSpeed = this.MaxAngularSpeed; // Vector3D velocity = this.VelocityWorld; // Vector3D angVelocity = this.PhysicsBody.AngularVelocity; // Vector3D desiredVelocity = velocity + Math3D.GetRandomVector_Spherical(maxSpeed / 10); // Vector3D desiredAngVelocity = angVelocity + Math3D.GetRandomVector_Spherical(maxAngSpeed / 10); // desiredVelocity = CapVector(desiredVelocity, maxSpeed); // desiredAngVelocity = CapVector(desiredAngVelocity, maxAngSpeed); // #endregion // #region acceleration // double maxAccel = this.MaxAccel; // double maxAngAccel = this.MaxAngularAccel; // //TODO: Come up with better units for converting velocity to acceleration // Vector3D accel = desiredAngVelocity - velocity; // Vector3D angAccel = desiredAngVelocity - angVelocity; // accel = CapVector(accel, maxAccel); // angAccel = CapVector(angAccel, maxAngAccel); // #endregion // #region force // // Multiply by mass to turn accel into force // accel *= this.PhysicsBody.Mass; // MassMatrix inertia = this.PhysicsBody.MassMatrix; // double dot = Math.Abs(Vector3D.DotProduct(angAccel.ToUnit(), inertia.Inertia.ToUnit())); // //TODO: Make sure this is right // angAccel *= dot * inertia.Inertia.Length; // #endregion // return new Tuple<Vector3D?, Vector3D?>(accel, angAccel); //} //private Vector3D? GetStrokeForce_FIRST(SwarmObjectiveStrokes.Stroke objectiveStroke, Point3D position, Vector3D velocity) //{ // if (objectiveStroke == null) // { // return null; // } // // For now, just aim straight for the first point // return (objectiveStroke.Points[0].Item1 - position).ToUnit(false) * this.MaxAccel; //} //private Vector3D? GetStrokeForce_HASREPULSE(SwarmObjectiveStrokes.Stroke objectiveStroke, Point3D position, Vector3D velocity) //{ // //const double CLOSEENOUGHRADIUSMULT = 2; // //const double VELOCITYINFLUENCERADIUSMULT = 5; // const double CLOSEENOUGHRADIUSMULT = 8; // const double VELOCITYINFLUENCERADIUSMULT = 15; // // Null stroke // if (objectiveStroke == null) // { // lock (_currentStrokeLock) _currentlyChasingStroke = null; // return null; // } // #region current stroke // // Get currently chasing stroke // CurrentChasingStroke currentStroke = null; // lock (_currentStrokeLock) // { // currentStroke = _currentlyChasingStroke; // if (currentStroke == null || currentStroke.Stroke.Token != objectiveStroke.Token) // { // currentStroke = new CurrentChasingStroke(objectiveStroke, ACCEL * 3); // _currentlyChasingStroke = currentStroke; // } // } // #endregion // #region current index of stroke // int currentIndex = currentStroke.CurrentIndex; // Vector3D towardCurrentPoint; // double towardCurrentPointLenSqr; // while (true) // { // if (currentIndex >= currentStroke.Stroke.Points.Length) // { // // Already went through all the points // return null; // } // // If too close to the current point, then advance to the next // towardCurrentPoint = currentStroke.Stroke.Points[currentIndex].Item1 - position; // towardCurrentPointLenSqr = towardCurrentPoint.LengthSquared; // double closeEnoughDist = this.Radius * CLOSEENOUGHRADIUSMULT; // if (towardCurrentPointLenSqr < closeEnoughDist * closeEnoughDist) // { // currentIndex++; // currentStroke.CurrentIndex = currentIndex; // } // else // { // break; // } // } // #endregion // // Start with attraction to the current point // Vector3D retVal = towardCurrentPoint.ToUnit(false) * this.MaxAccel; // #region influence velocity // // Add the desired velocity // double velocityInfluenceDist = this.Radius * VELOCITYINFLUENCERADIUSMULT; // if (towardCurrentPointLenSqr < velocityInfluenceDist * velocityInfluenceDist) // { // double percent = UtilityCore.GetScaledValue(1d, 0d, 0d, velocityInfluenceDist, Math.Sqrt(towardCurrentPointLenSqr)); // retVal += currentStroke.Stroke.Points[currentIndex].Item2 * percent; // } // #endregion // #region repulse // foreach (var repulse in currentStroke.UpcomingPoints[currentIndex]) // { // Vector3D dirToPoint = position - repulse.Position; // double lenSqr = dirToPoint.LengthSquared; // if (lenSqr > repulse.EffectRadius * repulse.EffectRadius) // { // // Too far away to be influenced by this point // continue; // } // if (lenSqr.IsNearZero()) // { // retVal += Math3D.GetRandomVector_Spherical_Shell(repulse.Strength); // } // else // { // retVal += dirToPoint.ToUnit(false) * repulse.Strength; // } // } // #endregion // return retVal; //} //private Vector3D? GetStrokeForce_LATEST(SwarmObjectiveStrokes.Stroke objectiveStroke, Point3D position, Vector3D velocity) //{ // //const double CLOSEENOUGHRADIUSMULT = 2; // //const double VELOCITYINFLUENCERADIUSMULT = 5; // const double CLOSEENOUGHRADIUSMULT = 4; // const double VELOCITYINFLUENCERADIUSMULT = 15; // // Null stroke // if (objectiveStroke == null) // { // lock (_currentStrokeLock) _currentlyChasingStroke = null; // return null; // } // #region current stroke // // Get currently chasing stroke // CurrentChasingStroke currentStroke = null; // lock (_currentStrokeLock) // { // currentStroke = _currentlyChasingStroke; // if (currentStroke == null || currentStroke.Stroke.Token != objectiveStroke.Token) // { // currentStroke = new CurrentChasingStroke(objectiveStroke, ACCEL * 3); // _currentlyChasingStroke = currentStroke; // } // } // #endregion // #region current index of stroke // int currentIndex = currentStroke.CurrentIndex; // Vector3D towardCurrentPoint; // double towardCurrentPointLenSqr; // while (true) // { // //if (currentIndex > 0) // //{ // // return null; // //} // if (currentIndex >= currentStroke.Stroke.Points.Length) // { // // Already went through all the points // return null; // } // // If too close to the current point, then advance to the next // towardCurrentPoint = currentStroke.Stroke.Points[currentIndex].Item1 - position; // towardCurrentPointLenSqr = towardCurrentPoint.LengthSquared; // double closeEnoughDist = this.Radius * CLOSEENOUGHRADIUSMULT; // if (towardCurrentPointLenSqr < closeEnoughDist * closeEnoughDist) // { // currentIndex++; // currentStroke.CurrentIndex = currentIndex; // } // else // { // break; // } // } // #endregion // // Start with attraction to the current point // Vector3D retVal = towardCurrentPoint.ToUnit(false) * this.MaxAccel; // #region influence velocity // // Add the desired velocity // //double velocityInfluenceDist = this.Radius * VELOCITYINFLUENCERADIUSMULT; // //if (towardCurrentPointLenSqr < velocityInfluenceDist * velocityInfluenceDist) // //{ // // double percent = UtilityCore.GetScaledValue(1d, 0d, 0d, velocityInfluenceDist, Math.Sqrt(towardCurrentPointLenSqr)); // // retVal += currentStroke.Stroke.Points[currentIndex].Item2 * percent; // //} // #endregion // #region repulse // //foreach (var repulse in currentStroke.UpcomingPoints[currentIndex]) // //{ // // Vector3D dirToPoint = position - repulse.Position; // // double lenSqr = dirToPoint.LengthSquared; // // if (lenSqr > repulse.EffectRadius * repulse.EffectRadius) // // { // // // Too far away to be influenced by this point // // continue; // // } // // if (lenSqr.IsNearZero()) // // { // // retVal += Math3D.GetRandomVector_Spherical_Shell(repulse.Strength); // // } // // else // // { // // retVal += dirToPoint.ToUnit(false) * repulse.Strength; // // } // //} // #endregion // return retVal; //} //private Vector3D? GetStrokeForce_MULTINOVELOCITY(SwarmObjectiveStrokes.Stroke objectiveStroke, Point3D position, Vector3D velocity) //{ // const double CLOSEENOUGHRADIUSMULT = 3; // if (objectiveStroke == null) // { // return null; // } // Tuple<long, int> stroke_index = _stroke_index; // if (stroke_index == null || stroke_index.Item1 != objectiveStroke.Token) // { // stroke_index = Tuple.Create(objectiveStroke.Token, 0); // _stroke_index = stroke_index; // } // Vector3D toObjective; // while (true) // { // if (stroke_index.Item2 >= objectiveStroke.Points.Length) // { // return null; // } // toObjective = objectiveStroke.Points[stroke_index.Item2].Item1 - position; // if (toObjective.LengthSquared > (this.Radius * CLOSEENOUGHRADIUSMULT) * (this.Radius * CLOSEENOUGHRADIUSMULT)) // { // break; // } // stroke_index = Tuple.Create(stroke_index.Item1, stroke_index.Item2 + 1); // _stroke_index = stroke_index; // } // // For now, just aim straight for the first point // return (toObjective).ToUnit(false) * this.MaxAccel; //} //private Vector3D? GetStrokeForce_MULTIWITHVELOCITY(SwarmObjectiveStrokes.Stroke objectiveStroke, Point3D position, Vector3D velocity) //{ // const double CLOSEENOUGHRADIUSMULT = 3; // const double VELOCITYINFLUENCERADIUSMULT = 7; // // This fails when they are on or very near the stroke and try to swim up stream. The point they are trying to // // go to is pushing them along. // // // // So need to just move on to the next point (or maybe just closest point). Need to take dot product of direction // // and point's velocity into account // if (objectiveStroke == null) // { // return null; // } // Tuple<long, int> stroke_index = _stroke_index; // if (stroke_index == null || stroke_index.Item1 != objectiveStroke.Token) // { // stroke_index = Tuple.Create(objectiveStroke.Token, 0); // _stroke_index = stroke_index; // } // #region get the next point // Vector3D toObjective; // while (true) // { // if (stroke_index.Item2 >= objectiveStroke.Points.Length) // { // return null; // } // toObjective = objectiveStroke.Points[stroke_index.Item2].Item1 - position; // if (toObjective.LengthSquared > (this.Radius * CLOSEENOUGHRADIUSMULT) * (this.Radius * CLOSEENOUGHRADIUSMULT)) // { // break; // } // stroke_index = Tuple.Create(stroke_index.Item1, stroke_index.Item2 + 1); // _stroke_index = stroke_index; // } // #endregion // // For now, just aim straight for the first point // Vector3D retVal = (toObjective).ToUnit(false) * this.MaxAccel; // #region influence velocity // // Add the desired velocity // double velocityInfluenceDist = this.Radius * VELOCITYINFLUENCERADIUSMULT; // if (toObjective.LengthSquared < velocityInfluenceDist * velocityInfluenceDist) // { // double percent = UtilityCore.GetScaledValue(2d, 0d, 0d, velocityInfluenceDist, toObjective.Length); // retVal += objectiveStroke.Points[stroke_index.Item2].Item2 * percent; // } // #endregion // return retVal; //} //private Vector3D? GetStrokeForce_BETTER(SwarmObjectiveStrokes.Stroke objectiveStroke, Point3D position, Vector3D velocity) //{ // if (objectiveStroke == null) // { // return null; // } // double maxAccel = this.MaxAccel; // double radius = this.Radius; // //TODO: Don't just use the return length, have the function also return a weighted distance to the point (based on dot) // //TODO: Remember which points are objectives, and never choose any index less than that (once null, ignore the stroke) // int startIndex = 0; // var strokeIndex = _stroke_index; // if (strokeIndex != null && strokeIndex.Item1 == objectiveStroke.Token) // { // startIndex = strokeIndex.Item2; // } // if (startIndex >= objectiveStroke.Points.Length) // { // return null; // } // var candidates = Enumerable.Range(startIndex, objectiveStroke.Points.Length - startIndex). // Select(o => new { Index = o, Item = objectiveStroke.Points[o], Force = GetStrokePointForce_BETTER(objectiveStroke.Points[o], position, velocity, maxAccel, radius) }). // Where(o => o.Force != null). // OrderBy(o => o.Force.Value.LengthSquared). // FirstOrDefault(); // if (candidates == null) // { // _stroke_index = Tuple.Create(objectiveStroke.Token, objectiveStroke.Points.Length); // return null; // } // _stroke_index = Tuple.Create(objectiveStroke.Token, candidates.Index); // return candidates.Force; //} //private static Vector3D? GetStrokePointForce_BETTER(Tuple<Point3D, Vector3D> strokePoint, Point3D position, Vector3D velocity, double maxAccel, double radius) //{ // const double VELOCITYINFLUENCERADIUSMULT = 7; // const double MINDOT = -.7; // Vector3D toPoint = strokePoint.Item1 - position; // double toPointLength = toPoint.Length; // if (toPointLength.IsNearZero()) // { // // Sitting on top of the point. The only force felt is the point's velocity // return strokePoint.Item2; // } // Vector3D toPointUnit = toPoint / toPointLength; // double dot = Vector3D.DotProduct(toPointUnit, strokePoint.Item2.ToUnit()); // if (dot < MINDOT) // { // // It would have to fly too directly up stream to get to the point // return null; // } // // Toward Point // Vector3D retVal = toPointUnit * maxAccel; // //if (dot < 0) // The larger the dot, the larger the force (because when the dot is small, this point is in the wrong direction) // //{ // // double percent = UtilityCore.GetScaledValue(0d, 1d, MINDOT, 0d, dot); // // retVal *= percent; // //} // // Influence velocity // double velocityInfluenceDist = radius * VELOCITYINFLUENCERADIUSMULT; // if (toPointLength < velocityInfluenceDist) // { // double percent = UtilityCore.GetScaledValue(2d, 0d, 0d, velocityInfluenceDist, toPointLength); // retVal += strokePoint.Item2 * percent; // } // //TODO: Compare a dot with the return force and location/velocity of point to see if it's even possible // //to get to that point // return retVal; //} #endregion #endregion #region Private Methods - initial forces private ForceSettings_Initial GetForceSetting_Initial(ref int chaseBotCount, MapObjectInfo item, int maxBotCount) { // Convert into a lookup vector, then do a fuzzy lookup (this will allow the bot to learn about objects instead // of a hardcoded switch statement) if (item.MapObject is Asteroid) { return _settings_Asteroid; } else if (item.MapObject is SwarmBot1a) { chaseBotCount++; if (chaseBotCount <= maxBotCount) { return _settings_OtherBot_Chase; } else { return _settings_OtherBot_Passive; } } else { return null; } }
protected virtual NeoDatis.Odb.Core.Layers.Layer2.Meta.AbstractObjectInfo GetNativeObjectInfoInternal (NeoDatis.Odb.Core.Layers.Layer2.Meta.ODBType type, object o, bool recursive , System.Collections.Generic.IDictionary <object, NeoDatis.Odb.Core.Layers.Layer2.Meta.NonNativeObjectInfo > alreadyReadObjects, NeoDatis.Odb.Core.Layers.Layer1.Introspector.IIntrospectionCallback callback) { NeoDatis.Odb.Core.Layers.Layer2.Meta.AbstractObjectInfo aoi = null; if (type.IsAtomicNative()) { if (o == null) { aoi = new NeoDatis.Odb.Core.Layers.Layer2.Meta.NullNativeObjectInfo(type.GetId()); } else { aoi = new NeoDatis.Odb.Core.Layers.Layer2.Meta.AtomicNativeObjectInfo(o, type .GetId()); } } else { if (type.IsCollection()) { aoi = IntrospectCollection((System.Collections.ICollection)o, recursive, alreadyReadObjects , type, callback); } else { if (type.IsArray()) { if (o == null) { aoi = new NeoDatis.Odb.Core.Layers.Layer2.Meta.ArrayObjectInfo(null); } else { // Gets the type of the elements of the array string realArrayClassName = OdbClassUtil.GetFullName(o.GetType().GetElementType()); NeoDatis.Odb.Core.Layers.Layer2.Meta.ArrayObjectInfo aroi = null; if (recursive) { aroi = IntrospectArray(o, recursive, alreadyReadObjects, type, callback); } else { aroi = new NeoDatis.Odb.Core.Layers.Layer2.Meta.ArrayObjectInfo((object[])o ); } aroi.SetRealArrayComponentClassName(realArrayClassName); aoi = aroi; } } else { if (type.IsMap()) { if (o == null) { aoi = new NeoDatis.Odb.Core.Layers.Layer2.Meta.MapObjectInfo(null, type, type.GetDefaultInstanciationClass ().FullName); } else { MapObjectInfo moi = null; string realMapClassName = OdbClassUtil.GetFullName(o.GetType()); bool isGeneric = o.GetType().IsGenericType; if (isGeneric) { moi = new MapObjectInfo(IntrospectGenericMap((System.Collections.Generic.IDictionary <object, object>)o, recursive, alreadyReadObjects, callback), type, realMapClassName); } else { moi = new MapObjectInfo(IntrospectNonGenericMap((System.Collections.IDictionary)o, recursive, alreadyReadObjects, callback), type, realMapClassName); } if (realMapClassName.IndexOf("$") != -1) { moi.SetRealMapClassName(OdbClassUtil.GetFullName(type.GetDefaultInstanciationClass())); } aoi = moi; } } else { if (type.IsEnum()) { System.Enum enumObject = (System.Enum)o; if (enumObject == null) { aoi = new NeoDatis.Odb.Core.Layers.Layer2.Meta.NullNativeObjectInfo(type.GetSize( )); } else { Type t = enumObject.GetType(); string enumClassName = enumObject == null ? null : OdbClassUtil.GetFullName(enumObject.GetType()); // Here we must check if the enum is already in the meta model. Enum must be stored in the meta // model to optimize its storing as we need to keep track of the enum class // for each enum stored. So instead of storing the enum class name, we can store enum class id, a long // instead of the full enum class name string NeoDatis.Odb.Core.Layers.Layer2.Meta.ClassInfo ci = GetClassInfo(enumClassName); string enumValue = enumObject == null ? null : enumObject.ToString(); aoi = new NeoDatis.Odb.Core.Layers.Layer2.Meta.EnumNativeObjectInfo(ci, enumValue ); } } } } } } return(aoi); }
private static Vector3D GetMatchVelocityForce(MapObjectInfo mapObject, Vector3D velocity, double minSpeed) { //Vector3D dif = mapObject.Velocity - velocity; // this just causes the whole blob to have an average of zero Vector3D dif = mapObject.Velocity; if (minSpeed > 0 && dif.LengthSquared < minSpeed * minSpeed) { dif = dif.ToUnit(false); if (dif.LengthSquared.IsNearZero()) { dif = Math3D.GetRandomVector_Spherical(minSpeed); } else { dif *= minSpeed; } } //TODO: Have a gradient of velocity match based on distance. So if this is really far away from the item, then the item //should have no influence return dif; }
private Tuple<MapObjectInfo, double, ForceSettings_Initial>[] GetNeighbors(MapOctree snapshot, Point3D position) { // Get nearby items, sort by distance double searchRadius = this.SearchRadius; var initial = snapshot.GetItems(position, searchRadius). Where(o => o.Token != this.Token). Select(o => Tuple.Create(o, (o.Position - position).LengthSquared)). OrderBy(o => o.Item2). ToArray(); // Get chase settings for each item //NOTE: This needs to be done after sorting on distance, because bots will have different settings if too many are actively chased var retVal = new List<Tuple<MapObjectInfo, double, ForceSettings_Initial>>(); bool sawParent = false; int chaseNeighborCount = this.ChaseNeighborCount; int currentNeighborCount = 0; foreach (var item in initial) { ForceSettings_Initial chaseProps = GetForceSetting_Initial(ref currentNeighborCount, ref sawParent, item.Item1, chaseNeighborCount); if (chaseProps != null) { retVal.Add(Tuple.Create(item.Item1, item.Item2, chaseProps)); } } if (!sawParent && _parent != null && !_parent.IsDisposed) { MapObjectInfo parent = new MapObjectInfo(_parent, _parentType); retVal.Add(Tuple.Create(parent, (parent.Position - position).LengthSquared, _settings_Parent)); } return retVal.ToArray(); }
//private Tuple<Vector3D?, Vector3D?> GetSeekForce_1() //{ // #region velocity // double maxSpeed = this.MaxSpeed; // double maxAngSpeed = this.MaxAngularSpeed; // Vector3D velocity = this.VelocityWorld; // Vector3D angVelocity = this.PhysicsBody.AngularVelocity; // Vector3D desiredVelocity = velocity + Math3D.GetRandomVector_Spherical(maxSpeed / 10); // Vector3D desiredAngVelocity = angVelocity + Math3D.GetRandomVector_Spherical(maxAngSpeed / 10); // desiredVelocity = CapVector(desiredVelocity, maxSpeed); // desiredAngVelocity = CapVector(desiredAngVelocity, maxAngSpeed); // #endregion // #region acceleration // double maxAccel = this.MaxAccel; // double maxAngAccel = this.MaxAngularAccel; // //TODO: Come up with better units for converting velocity to acceleration // Vector3D accel = desiredAngVelocity - velocity; // Vector3D angAccel = desiredAngVelocity - angVelocity; // accel = CapVector(accel, maxAccel); // angAccel = CapVector(angAccel, maxAngAccel); // #endregion // #region force // // Multiply by mass to turn accel into force // accel *= this.PhysicsBody.Mass; // MassMatrix inertia = this.PhysicsBody.MassMatrix; // double dot = Math.Abs(Vector3D.DotProduct(angAccel.ToUnit(), inertia.Inertia.ToUnit())); // //TODO: Make sure this is right // angAccel *= dot * inertia.Inertia.Length; // #endregion // return new Tuple<Vector3D?, Vector3D?>(accel, angAccel); //} //private Vector3D? GetStrokeForce_FIRST(SwarmObjectiveStrokes.Stroke objectiveStroke, Point3D position, Vector3D velocity) //{ // if (objectiveStroke == null) // { // return null; // } // // For now, just aim straight for the first point // return (objectiveStroke.Points[0].Item1 - position).ToUnit(false) * this.MaxAccel; //} //private Vector3D? GetStrokeForce_HASREPULSE(SwarmObjectiveStrokes.Stroke objectiveStroke, Point3D position, Vector3D velocity) //{ // //const double CLOSEENOUGHRADIUSMULT = 2; // //const double VELOCITYINFLUENCERADIUSMULT = 5; // const double CLOSEENOUGHRADIUSMULT = 8; // const double VELOCITYINFLUENCERADIUSMULT = 15; // // Null stroke // if (objectiveStroke == null) // { // lock (_currentStrokeLock) _currentlyChasingStroke = null; // return null; // } // #region current stroke // // Get currently chasing stroke // CurrentChasingStroke currentStroke = null; // lock (_currentStrokeLock) // { // currentStroke = _currentlyChasingStroke; // if (currentStroke == null || currentStroke.Stroke.Token != objectiveStroke.Token) // { // currentStroke = new CurrentChasingStroke(objectiveStroke, ACCEL * 3); // _currentlyChasingStroke = currentStroke; // } // } // #endregion // #region current index of stroke // int currentIndex = currentStroke.CurrentIndex; // Vector3D towardCurrentPoint; // double towardCurrentPointLenSqr; // while (true) // { // if (currentIndex >= currentStroke.Stroke.Points.Length) // { // // Already went through all the points // return null; // } // // If too close to the current point, then advance to the next // towardCurrentPoint = currentStroke.Stroke.Points[currentIndex].Item1 - position; // towardCurrentPointLenSqr = towardCurrentPoint.LengthSquared; // double closeEnoughDist = this.Radius * CLOSEENOUGHRADIUSMULT; // if (towardCurrentPointLenSqr < closeEnoughDist * closeEnoughDist) // { // currentIndex++; // currentStroke.CurrentIndex = currentIndex; // } // else // { // break; // } // } // #endregion // // Start with attraction to the current point // Vector3D retVal = towardCurrentPoint.ToUnit(false) * this.MaxAccel; // #region influence velocity // // Add the desired velocity // double velocityInfluenceDist = this.Radius * VELOCITYINFLUENCERADIUSMULT; // if (towardCurrentPointLenSqr < velocityInfluenceDist * velocityInfluenceDist) // { // double percent = UtilityCore.GetScaledValue(1d, 0d, 0d, velocityInfluenceDist, Math.Sqrt(towardCurrentPointLenSqr)); // retVal += currentStroke.Stroke.Points[currentIndex].Item2 * percent; // } // #endregion // #region repulse // foreach (var repulse in currentStroke.UpcomingPoints[currentIndex]) // { // Vector3D dirToPoint = position - repulse.Position; // double lenSqr = dirToPoint.LengthSquared; // if (lenSqr > repulse.EffectRadius * repulse.EffectRadius) // { // // Too far away to be influenced by this point // continue; // } // if (lenSqr.IsNearZero()) // { // retVal += Math3D.GetRandomVector_Spherical_Shell(repulse.Strength); // } // else // { // retVal += dirToPoint.ToUnit(false) * repulse.Strength; // } // } // #endregion // return retVal; //} //private Vector3D? GetStrokeForce_LATEST(SwarmObjectiveStrokes.Stroke objectiveStroke, Point3D position, Vector3D velocity) //{ // //const double CLOSEENOUGHRADIUSMULT = 2; // //const double VELOCITYINFLUENCERADIUSMULT = 5; // const double CLOSEENOUGHRADIUSMULT = 4; // const double VELOCITYINFLUENCERADIUSMULT = 15; // // Null stroke // if (objectiveStroke == null) // { // lock (_currentStrokeLock) _currentlyChasingStroke = null; // return null; // } // #region current stroke // // Get currently chasing stroke // CurrentChasingStroke currentStroke = null; // lock (_currentStrokeLock) // { // currentStroke = _currentlyChasingStroke; // if (currentStroke == null || currentStroke.Stroke.Token != objectiveStroke.Token) // { // currentStroke = new CurrentChasingStroke(objectiveStroke, ACCEL * 3); // _currentlyChasingStroke = currentStroke; // } // } // #endregion // #region current index of stroke // int currentIndex = currentStroke.CurrentIndex; // Vector3D towardCurrentPoint; // double towardCurrentPointLenSqr; // while (true) // { // //if (currentIndex > 0) // //{ // // return null; // //} // if (currentIndex >= currentStroke.Stroke.Points.Length) // { // // Already went through all the points // return null; // } // // If too close to the current point, then advance to the next // towardCurrentPoint = currentStroke.Stroke.Points[currentIndex].Item1 - position; // towardCurrentPointLenSqr = towardCurrentPoint.LengthSquared; // double closeEnoughDist = this.Radius * CLOSEENOUGHRADIUSMULT; // if (towardCurrentPointLenSqr < closeEnoughDist * closeEnoughDist) // { // currentIndex++; // currentStroke.CurrentIndex = currentIndex; // } // else // { // break; // } // } // #endregion // // Start with attraction to the current point // Vector3D retVal = towardCurrentPoint.ToUnit(false) * this.MaxAccel; // #region influence velocity // // Add the desired velocity // //double velocityInfluenceDist = this.Radius * VELOCITYINFLUENCERADIUSMULT; // //if (towardCurrentPointLenSqr < velocityInfluenceDist * velocityInfluenceDist) // //{ // // double percent = UtilityCore.GetScaledValue(1d, 0d, 0d, velocityInfluenceDist, Math.Sqrt(towardCurrentPointLenSqr)); // // retVal += currentStroke.Stroke.Points[currentIndex].Item2 * percent; // //} // #endregion // #region repulse // //foreach (var repulse in currentStroke.UpcomingPoints[currentIndex]) // //{ // // Vector3D dirToPoint = position - repulse.Position; // // double lenSqr = dirToPoint.LengthSquared; // // if (lenSqr > repulse.EffectRadius * repulse.EffectRadius) // // { // // // Too far away to be influenced by this point // // continue; // // } // // if (lenSqr.IsNearZero()) // // { // // retVal += Math3D.GetRandomVector_Spherical_Shell(repulse.Strength); // // } // // else // // { // // retVal += dirToPoint.ToUnit(false) * repulse.Strength; // // } // //} // #endregion // return retVal; //} //private Vector3D? GetStrokeForce_MULTINOVELOCITY(SwarmObjectiveStrokes.Stroke objectiveStroke, Point3D position, Vector3D velocity) //{ // const double CLOSEENOUGHRADIUSMULT = 3; // if (objectiveStroke == null) // { // return null; // } // Tuple<long, int> stroke_index = _stroke_index; // if (stroke_index == null || stroke_index.Item1 != objectiveStroke.Token) // { // stroke_index = Tuple.Create(objectiveStroke.Token, 0); // _stroke_index = stroke_index; // } // Vector3D toObjective; // while (true) // { // if (stroke_index.Item2 >= objectiveStroke.Points.Length) // { // return null; // } // toObjective = objectiveStroke.Points[stroke_index.Item2].Item1 - position; // if (toObjective.LengthSquared > (this.Radius * CLOSEENOUGHRADIUSMULT) * (this.Radius * CLOSEENOUGHRADIUSMULT)) // { // break; // } // stroke_index = Tuple.Create(stroke_index.Item1, stroke_index.Item2 + 1); // _stroke_index = stroke_index; // } // // For now, just aim straight for the first point // return (toObjective).ToUnit(false) * this.MaxAccel; //} //private Vector3D? GetStrokeForce_MULTIWITHVELOCITY(SwarmObjectiveStrokes.Stroke objectiveStroke, Point3D position, Vector3D velocity) //{ // const double CLOSEENOUGHRADIUSMULT = 3; // const double VELOCITYINFLUENCERADIUSMULT = 7; // // This fails when they are on or very near the stroke and try to swim up stream. The point they are trying to // // go to is pushing them along. // // // // So need to just move on to the next point (or maybe just closest point). Need to take dot product of direction // // and point's velocity into account // if (objectiveStroke == null) // { // return null; // } // Tuple<long, int> stroke_index = _stroke_index; // if (stroke_index == null || stroke_index.Item1 != objectiveStroke.Token) // { // stroke_index = Tuple.Create(objectiveStroke.Token, 0); // _stroke_index = stroke_index; // } // #region get the next point // Vector3D toObjective; // while (true) // { // if (stroke_index.Item2 >= objectiveStroke.Points.Length) // { // return null; // } // toObjective = objectiveStroke.Points[stroke_index.Item2].Item1 - position; // if (toObjective.LengthSquared > (this.Radius * CLOSEENOUGHRADIUSMULT) * (this.Radius * CLOSEENOUGHRADIUSMULT)) // { // break; // } // stroke_index = Tuple.Create(stroke_index.Item1, stroke_index.Item2 + 1); // _stroke_index = stroke_index; // } // #endregion // // For now, just aim straight for the first point // Vector3D retVal = (toObjective).ToUnit(false) * this.MaxAccel; // #region influence velocity // // Add the desired velocity // double velocityInfluenceDist = this.Radius * VELOCITYINFLUENCERADIUSMULT; // if (toObjective.LengthSquared < velocityInfluenceDist * velocityInfluenceDist) // { // double percent = UtilityCore.GetScaledValue(2d, 0d, 0d, velocityInfluenceDist, toObjective.Length); // retVal += objectiveStroke.Points[stroke_index.Item2].Item2 * percent; // } // #endregion // return retVal; //} //private Vector3D? GetStrokeForce_BETTER(SwarmObjectiveStrokes.Stroke objectiveStroke, Point3D position, Vector3D velocity) //{ // if (objectiveStroke == null) // { // return null; // } // double maxAccel = this.MaxAccel; // double radius = this.Radius; // //TODO: Don't just use the return length, have the function also return a weighted distance to the point (based on dot) // //TODO: Remember which points are objectives, and never choose any index less than that (once null, ignore the stroke) // int startIndex = 0; // var strokeIndex = _stroke_index; // if (strokeIndex != null && strokeIndex.Item1 == objectiveStroke.Token) // { // startIndex = strokeIndex.Item2; // } // if (startIndex >= objectiveStroke.Points.Length) // { // return null; // } // var candidates = Enumerable.Range(startIndex, objectiveStroke.Points.Length - startIndex). // Select(o => new { Index = o, Item = objectiveStroke.Points[o], Force = GetStrokePointForce_BETTER(objectiveStroke.Points[o], position, velocity, maxAccel, radius) }). // Where(o => o.Force != null). // OrderBy(o => o.Force.Value.LengthSquared). // FirstOrDefault(); // if (candidates == null) // { // _stroke_index = Tuple.Create(objectiveStroke.Token, objectiveStroke.Points.Length); // return null; // } // _stroke_index = Tuple.Create(objectiveStroke.Token, candidates.Index); // return candidates.Force; //} //private static Vector3D? GetStrokePointForce_BETTER(Tuple<Point3D, Vector3D> strokePoint, Point3D position, Vector3D velocity, double maxAccel, double radius) //{ // const double VELOCITYINFLUENCERADIUSMULT = 7; // const double MINDOT = -.7; // Vector3D toPoint = strokePoint.Item1 - position; // double toPointLength = toPoint.Length; // if (toPointLength.IsNearZero()) // { // // Sitting on top of the point. The only force felt is the point's velocity // return strokePoint.Item2; // } // Vector3D toPointUnit = toPoint / toPointLength; // double dot = Vector3D.DotProduct(toPointUnit, strokePoint.Item2.ToUnit()); // if (dot < MINDOT) // { // // It would have to fly too directly up stream to get to the point // return null; // } // // Toward Point // Vector3D retVal = toPointUnit * maxAccel; // //if (dot < 0) // The larger the dot, the larger the force (because when the dot is small, this point is in the wrong direction) // //{ // // double percent = UtilityCore.GetScaledValue(0d, 1d, MINDOT, 0d, dot); // // retVal *= percent; // //} // // Influence velocity // double velocityInfluenceDist = radius * VELOCITYINFLUENCERADIUSMULT; // if (toPointLength < velocityInfluenceDist) // { // double percent = UtilityCore.GetScaledValue(2d, 0d, 0d, velocityInfluenceDist, toPointLength); // retVal += strokePoint.Item2 * percent; // } // //TODO: Compare a dot with the return force and location/velocity of point to see if it's even possible // //to get to that point // return retVal; //} #endregion #endregion #region Private Methods - initial forces private ForceSettings_Initial GetForceSetting_Initial(ref int chaseBotCount, ref bool sawParent, MapObjectInfo item, int maxBotCount) { //TODO: Convert into a lookup vector, then do a fuzzy lookup (this will allow the bot to learn about objects instead //of a hardcoded switch statement) if (_parent != null && !_parent.IsDisposed && item.Token == _parent.Token) { sawParent = true; return _settings_Parent; } else if (item.MapObject is Asteroid) { return _settings_Asteroid; } else if (item.MapObject is SwarmBot1b) { chaseBotCount++; if (chaseBotCount <= maxBotCount) { return _settings_OtherBot_Chase; } else { return _settings_OtherBot_Passive; } } else { return null; } }
private static ChangeInstruction[] ExamineMinerals_Remove1(MapObjectInfo[] minerals, int count) { return minerals. Select(o => (Mineral)o.MapObject). OrderBy(o => o.Credits). Take(count). Select(o => new ChangeInstruction(new[] { o })). ToArray(); }
private bool HasChanged(NonNativeObjectInfo nnoi1, NonNativeObjectInfo nnoi2, int objectRecursionLevel) { AbstractObjectInfo value1 = null; AbstractObjectInfo value2 = null; bool hasChanged = false; // If the object is already being checked, return false, this second // check will not affect the check int n = 0; alreadyCheckingObjects.TryGetValue(nnoi2, out n); if (n != 0) { return(false); } // Put the object in the temporary cache alreadyCheckingObjects[nnoi1] = 1; alreadyCheckingObjects[nnoi2] = 1; // Warning ID Start with 1 and not 0 for (int id = 1; id <= nnoi1.GetMaxNbattributes(); id++) { value1 = nnoi1.GetAttributeValueFromId(id); // Gets the value by the attribute id to be sure // Problem because a new object info may not have the right ids ? // Check if // the new oiD is ok. value2 = nnoi2.GetAttributeValueFromId(id); if (value2 == null) { // this means the object to have attribute id StoreChangedObject(nnoi1, nnoi2, id, objectRecursionLevel); hasChanged = true; continue; } if (value1 == null) { //throw new ODBRuntimeException("ObjectInfoComparator.hasChanged:attribute with id "+id+" does not exist on "+nnoi2); // This happens when this object was created with an version of ClassInfo (which has been refactored). // In this case,we simply tell that in place update is not supported so that the object will be rewritten with // new metamodel supportInPlaceUpdate = false; continue; } // If both are null, no effect if (value1.IsNull() && value2.IsNull()) { continue; } if (value1.IsNull() || value2.IsNull()) { supportInPlaceUpdate = false; hasChanged = true; StoreActionSetAttributetoNull(nnoi1, id, objectRecursionLevel); continue; } if (!ClassAreCompatible(value1, value2)) { if (value2 is NativeObjectInfo) { StoreChangedObject(nnoi1, nnoi2, id, objectRecursionLevel); StoreChangedAttributeAction(new ChangedNativeAttributeAction (nnoi1, nnoi2, nnoi1.GetHeader().GetAttributeIdentificationFromId(id), (NativeObjectInfo )value2, objectRecursionLevel, false, nnoi1.GetClassInfo().GetAttributeInfoFromId (id).GetName())); } if (value2 is ObjectReference) { NonNativeObjectInfo nnoi = (NonNativeObjectInfo )value1; ObjectReference oref = (ObjectReference )value2; if (!nnoi.GetOid().Equals(oref.GetOid())) { StoreChangedObject(nnoi1, nnoi2, id, objectRecursionLevel); int attributeIdThatHasChanged = id; // this is the exact position where the object reference // definition is stored long attributeDefinitionPosition = nnoi2.GetAttributeDefinitionPosition(attributeIdThatHasChanged ); StoreChangedAttributeAction(new ChangedObjectReferenceAttributeAction (attributeDefinitionPosition, (ObjectReference )value2, objectRecursionLevel)); } else { continue; } } hasChanged = true; continue; } if (value1.IsAtomicNativeObject()) { if (!value1.Equals(value2)) { // storeChangedObject(nnoi1, nnoi2, id, // objectRecursionLevel); StoreChangedAttributeAction(new ChangedNativeAttributeAction (nnoi1, nnoi2, nnoi1.GetHeader().GetAttributeIdentificationFromId(id), (NativeObjectInfo )value2, objectRecursionLevel, false, nnoi1.GetClassInfo().GetAttributeInfoFromId (id).GetName())); hasChanged = true; continue; } continue; } if (value1.IsCollectionObject()) { CollectionObjectInfo coi1 = (CollectionObjectInfo)value1; CollectionObjectInfo coi2 = (CollectionObjectInfo)value2; bool collectionHasChanged = ManageCollectionChanges(nnoi1, nnoi2, id, coi1, coi2, objectRecursionLevel); hasChanged = hasChanged || collectionHasChanged; continue; } if (value1.IsArrayObject()) { ArrayObjectInfo aoi1 = (ArrayObjectInfo)value1; ArrayObjectInfo aoi2 = (ArrayObjectInfo)value2; bool arrayHasChanged = ManageArrayChanges(nnoi1, nnoi2, id, aoi1, aoi2, objectRecursionLevel ); hasChanged = hasChanged || arrayHasChanged; continue; } if (value1.IsMapObject()) { MapObjectInfo moi1 = (MapObjectInfo)value1; MapObjectInfo moi2 = (MapObjectInfo)value2; bool mapHasChanged = ManageMapChanges(nnoi1, nnoi2, id, moi1, moi2, objectRecursionLevel ); hasChanged = hasChanged || mapHasChanged; continue; } if (value1.IsEnumObject()) { EnumNativeObjectInfo enoi1 = (EnumNativeObjectInfo)value1; EnumNativeObjectInfo enoi2 = (EnumNativeObjectInfo)value2; bool enumHasChanged = !enoi1.GetEnumClassInfo().GetId().Equals(enoi2.GetEnumClassInfo ().GetId()) || !enoi1.GetEnumName().Equals(enoi2.GetEnumName()); hasChanged = hasChanged || enumHasChanged; continue; } if (value1.IsNonNativeObject()) { NonNativeObjectInfo oi1 = (NonNativeObjectInfo)value1; NonNativeObjectInfo oi2 = (NonNativeObjectInfo)value2; // If oids are equal, they are the same objects if (oi1.GetOid() != null && oi1.GetOid().Equals(oi2.GetOid())) { hasChanged = HasChanged(value1, value2, objectRecursionLevel + 1) || hasChanged; } else { // This means that an object reference has changed. hasChanged = true; // keep track of the position where the reference must be // updated long positionToUpdateReference = nnoi1.GetAttributeDefinitionPosition(id); StoreNewObjectReference(positionToUpdateReference, oi2, objectRecursionLevel, nnoi1 .GetClassInfo().GetAttributeInfoFromId(id).GetName()); objectRecursionLevel++; // Value2 may have change too AddPendingVerification(value2); } continue; } } int i1 = (int)alreadyCheckingObjects[nnoi1]; int i2 = (int)alreadyCheckingObjects[nnoi2]; if (i1 != null) { i1 = i1 - 1; } if (i2 != null) { i2 = i2 - 1; } if (i1 == 0) { alreadyCheckingObjects.Remove(nnoi1); } else { alreadyCheckingObjects.Add(nnoi1, i1); } if (i2 == 0) { alreadyCheckingObjects.Remove(nnoi2); } else { alreadyCheckingObjects.Add(nnoi2, i2); } return(hasChanged); }