Index: ps/trunk/binaries/data/mods/public/simulation/components/UnitAI.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/UnitAI.js +++ ps/trunk/binaries/data/mods/public/simulation/components/UnitAI.js @@ -271,6 +271,8 @@ } this.SetHeldPosition(this.order.data.x, this.order.data.z); + // It's not too bad if we don't arrive at exactly the right position. + this.order.data.relaxed = true; if (this.IsAnimal()) this.SetNextState("ANIMAL.WALKING"); else @@ -295,6 +297,8 @@ } this.SetHeldPosition(this.order.data.x, this.order.data.z); + // It's not too bad if we don't arrive at exactly the right position. + this.order.data.relaxed = true; if (this.IsAnimal()) this.SetNextState("ANIMAL.WALKING"); // WalkAndFight not applicable for animals else @@ -327,6 +331,9 @@ return true; } + // It's not too bad if we don't arrive at exactly the right position. + this.order.data.relaxed = true; + if (this.IsAnimal()) this.SetNextState("ANIMAL.WALKING"); else @@ -458,6 +465,9 @@ return; } + // It's not too bad if we don't arrive at exactly the right position. + this.order.data.relaxed = true; + this.SetNextState("INDIVIDUAL.PATROL"); }, @@ -540,6 +550,7 @@ "Order.GatherNearPosition": function(msg) { this.SetNextState("INDIVIDUAL.GATHER.WALKING"); this.order.data.initPos = { 'x': this.order.data.x, 'z': this.order.data.z }; + this.order.data.relaxed = true; }, "Order.ReturnResource": function(msg) { @@ -904,8 +915,11 @@ }, "MovementUpdate": function(msg) { - if (this.CheckRange(this.order.data) && this.FinishOrder()) + if (msg.likelyFailure || this.CheckRange(this.order.data)) + { + this.FinishOrder(); this.CallMemberFunction("ResetFinishOrder", []); + } }, }, @@ -933,8 +947,11 @@ }, "MovementUpdate": function(msg) { - if (this.CheckRange(this.order.data) && this.FinishOrder()) + if (msg.likelyFailure || this.CheckRange(this.order.data)) + { + this.FinishOrder(); this.CallMemberFunction("ResetFinishOrder", []); + } }, }, @@ -978,7 +995,7 @@ }, "MovementUpdate": function(msg) { - if (!msg.error && !this.CheckRange(this.order.data)) + if (!msg.likelyFailure && !this.CheckRange(this.order.data)) return; /** * A-B-A-B-..: @@ -1036,7 +1053,8 @@ }, "MovementUpdate": function(msg) { - this.SetNextState("GARRISONING"); + if (msg.likelyFailure || msg.likelySuccess) + this.SetNextState("GARRISONING"); }, }, @@ -1071,7 +1089,7 @@ }, "MovementUpdate": function(msg) { - if (!msg.error && !this.CheckRange(this.order.data)) + if (!msg.likelyFailure && !this.CheckRange(this.order.data)) return; if (this.FinishOrder()) @@ -1290,7 +1308,7 @@ let pos = cmpPosition.GetPosition2D(); atDestination = this.CheckPointRangeExplicit(pos.X + this.order.data.x, pos.Y + this.order.data.z, 0, 1); } - if (!atDestination && !msg.error) + if (!atDestination && !msg.likelyFailure) return; if (this.FinishOrder()) @@ -1487,12 +1505,15 @@ } }, - "leave": function () { + "leave": function() { this.StopMoving(); }, "MovementUpdate": function(msg) { - if (msg.error || this.CheckRange(this.order.data)) + // If it looks like the path is failing, and we are close enough (3 tiles) + // stop anyways. This avoids pathing for an unreachable goal and reduces lag considerably. + if (msg.likelyFailure || msg.obstructed && this.RelaxedMaxRangeCheck(this.order.data, 12) || + this.CheckRange(this.order.data)) this.FinishOrder(); }, }, @@ -1521,7 +1542,10 @@ }, "MovementUpdate": function(msg) { - if (msg.error || this.CheckRange(this.order.data)) + // If it looks like the path is failing, and we are close enough (3 tiles) + // stop anyways. This avoids pathing for an unreachable goal and reduces lag considerably. + if (msg.likelyFailure || msg.obstructed && this.RelaxedMaxRangeCheck(this.order.data, 12) || + this.CheckRange(this.order.data)) this.FinishOrder(); }, }, @@ -1560,8 +1584,9 @@ }, "MovementUpdate": function(msg) { - if (!msg.error && !this.CheckRange(this.order.data)) - return; + if (msg.likelyFailure || msg.obstructed && this.RelaxedMaxRangeCheck(this.order.data, 12) || + this.CheckRange(this.order.data)) + this.FinishOrder(); if (this.orderQueue.length == 1) this.PushOrder("Patrol", this.patrolStartPosOrder); @@ -1625,7 +1650,7 @@ }, "MovementUpdate": function(msg) { - if (msg.error || this.CheckTargetRangeExplicit(this.isGuardOf, 0, this.guardRange)) + if (msg.likelyFailure || this.CheckTargetRangeExplicit(this.isGuardOf, 0, this.guardRange)) this.SetNextState("GUARDING"); }, }, @@ -1710,7 +1735,7 @@ "MovementUpdate": function(msg) { // When we've run far enough, stop fleeing - if (msg.error || this.CheckTargetRangeExplicit(this.order.data.target, this.order.data.distanceToFlee, -1)) + if (msg.likelyFailure || this.CheckTargetRangeExplicit(this.order.data.target, this.order.data.distanceToFlee, -1)) this.FinishOrder(); }, @@ -1766,7 +1791,7 @@ }, "MovementUpdate": function(msg) { - if (msg.error) + if (msg.likelyFailure) { // This also handles hunting. if (this.orderQueue.length > 1) @@ -1786,22 +1811,21 @@ return; } - if (!this.CheckTargetAttackRange(this.order.data.target, this.order.data.attackType)) + if (this.CheckTargetAttackRange(this.order.data.target, this.order.data.attackType)) { + // If the unit needs to unpack, do so + if (this.CanUnpack()) + { + this.PushOrderFront("Unpack", { "force": true }); + return; + } + this.SetNextState("ATTACKING"); + } + else if (msg.likelySuccess) // Try moving again, // attack range uses a height-related formula and our actual max range might have changed. if (!this.MoveToTargetAttackRange(this.order.data.target, this.order.data.attackType)) this.FinishOrder(); - return; - } - - // If the unit needs to unpack, do so - if (this.CanUnpack()) - { - this.PushOrderFront("Unpack", { "force": true }); - return; - } - this.SetNextState("ATTACKING"); }, }, @@ -2026,8 +2050,22 @@ } }, - "MovementUpdate": function() { - this.SetNextState("ATTACKING"); + "MovementUpdate": function(msg) { + if (this.CheckTargetAttackRange(this.order.data.target, this.order.data.attackType)) + { + // If the unit needs to unpack, do so + if (this.CanUnpack()) + { + this.PushOrderFront("Unpack", { "force": true }); + return; + } + this.SetNextState("ATTACKING"); + } + else if (msg.likelySuccess) + // Try moving again, + // attack range uses a height-related formula and our actual max range might have changed. + if (!this.MoveToTargetAttackRange(this.order.data.target, this.order.data.attackType)) + this.FinishOrder(); }, }, }, @@ -2054,7 +2092,8 @@ "MovementUpdate": function(msg) { // The GATHERING timer will handle finding a valid resource. - this.SetNextState("GATHERING"); + if (msg.likelyFailure || this.CheckRange(this.order.data, IID_ResourceGatherer)) + this.SetNextState("GATHERING"); }, "leave": function() { @@ -2088,7 +2127,8 @@ "MovementUpdate": function(msg) { // If we failed, the GATHERING timer will handle finding a valid resource. - if (msg.error || this.CheckRange(this.order.data)) + if (msg.likelyFailure || msg.obstructed && this.RelaxedMaxRangeCheck(this.order.data, 8) || + this.CheckRange(this.order.data)) this.SetNextState("GATHERING"); }, }, @@ -2329,7 +2369,7 @@ "APPROACHING": { "enter": function() { - if (this.CheckTargetRange(this.order.data.target, IID_Heal)) + if (this.CheckRange(this.order.data, IID_Heal)) { this.SetNextState("HEALING"); return true; @@ -2337,7 +2377,7 @@ if (!this.MoveTo(this.order.data, IID_Heal)) { - this.FinishOrder(); + this.SetNextState("FINDINGNEWTARGET"); return true; } @@ -2351,21 +2391,31 @@ "Timer": function(msg) { if (this.ShouldAbandonChase(this.order.data.target, this.order.data.force, IID_Heal, null)) - { - // Return to our original position unless we have a better order. - if (!this.FinishOrder() && this.GetStance().respondHoldGround) - this.WalkToHeldPosition(); - } + this.SetNextState("FINDINGNEWTARGET"); }, - "MovementUpdate": function() { - this.SetNextState("HEALING"); + "MovementUpdate": function(msg) { + if (msg.likelyFailure || this.CheckRange(this.order.data, IID_Heal)) + this.SetNextState("HEALING"); }, }, "HEALING": { "enter": function() { - var cmpHeal = Engine.QueryInterface(this.entity, IID_Heal); + if (!this.CheckRange(this.order.data, IID_Heal)) + { + this.SetNextState("APPROACHING"); + return true; + } + + if (!this.TargetIsAlive(this.order.data.target) || + !this.CanHeal(this.order.data.target)) + { + this.SetNextState("FINDINGNEWTARGET"); + return true; + } + + let cmpHeal = Engine.QueryInterface(this.entity, IID_Heal); this.healTimers = cmpHeal.GetTimers(); // If the repeat time since the last heal hasn't elapsed, @@ -2378,14 +2428,12 @@ prepare = Math.max(prepare, repeatLeft); } - this.StopMoving(); - this.SelectAnimation("heal"); this.SetAnimationSync(prepare, this.healTimers.repeat); this.StartTimer(prepare, this.healTimers.repeat); // If using a non-default prepare time, re-sync the animation when the timer runs. - this.resyncAnimation = (prepare != this.healTimers.prepare) ? true : false; + this.resyncAnimation = prepare != this.healTimers.prepare; this.FaceTowardsTarget(this.order.data.target); }, @@ -2396,30 +2444,19 @@ }, "Timer": function(msg) { - var target = this.order.data.target; + let target = this.order.data.target; // Check the target is still alive and healable - if (this.TargetIsAlive(target) && this.CanHeal(target)) + if (!this.TargetIsAlive(target) || !this.CanHeal(target)) + { + this.SetNextState("FINDINGNEWTARGET"); + return; + } + // Check if we can still reach the target + if (!this.CheckRange(this.order.data, IID_Heal)) { - // Check if we can still reach the target - if (this.CheckTargetRange(target, IID_Heal)) - { - var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); - this.lastHealed = cmpTimer.GetTime() - msg.lateness; - - this.FaceTowardsTarget(target); - var cmpHeal = Engine.QueryInterface(this.entity, IID_Heal); - cmpHeal.PerformHeal(target); - - if (this.resyncAnimation) - { - this.SetAnimationSync(this.healTimers.repeat, this.healTimers.repeat); - this.resyncAnimation = false; - } - return; - } - // Can't reach it - try to chase after it if (this.ShouldChaseTargetedEntity(target, this.order.data.force)) { + // Can't reach it - try to chase after it if (this.CanPack()) { this.PushOrderFront("Pack", { "force": true }); @@ -2427,18 +2464,42 @@ } this.SetNextState("HEAL.APPROACHING"); } + else + this.SetNextState("FINDINGNEWTARGET"); + return; + } + + let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); + this.lastHealed = cmpTimer.GetTime() - msg.lateness; + + this.FaceTowardsTarget(target); + let cmpHeal = Engine.QueryInterface(this.entity, IID_Heal); + cmpHeal.PerformHeal(target); + + if (this.resyncAnimation) + { + this.SetAnimationSync(this.healTimers.repeat, this.healTimers.repeat); + this.resyncAnimation = false; } - // Can't reach it, healed to max hp or doesn't exist any more - give up + }, + }, + + "FINDINGNEWTARGET": { + "enter": function() { + // If we have another order, do that instead. if (this.FinishOrder()) - return; + return true; // Heal another one if (this.FindNewHealTargets()) - return; + return true; // Return to our original position if (this.GetStance().respondHoldGround) this.WalkToHeldPosition(); + + // We quit this state right away. + return true; }, }, }, @@ -2458,18 +2519,18 @@ this.StopMoving(); }, - "MovementUpdate": function() { + "MovementUpdate": function(msg) { // Check the dropsite is in range and we can return our resource there // (we didn't get stopped before reaching it) if (this.CheckTargetRange(this.order.data.target, IID_ResourceGatherer) && this.CanReturnResource(this.order.data.target, true)) { - var cmpResourceDropsite = Engine.QueryInterface(this.order.data.target, IID_ResourceDropsite); + let cmpResourceDropsite = Engine.QueryInterface(this.order.data.target, IID_ResourceDropsite); if (cmpResourceDropsite) { // Dump any resources we can - var dropsiteTypes = cmpResourceDropsite.GetTypes(); + let dropsiteTypes = cmpResourceDropsite.GetTypes(); - var cmpResourceGatherer = Engine.QueryInterface(this.entity, IID_ResourceGatherer); + let cmpResourceGatherer = Engine.QueryInterface(this.entity, IID_ResourceGatherer); cmpResourceGatherer.CommitResources(dropsiteTypes); // Stop showing the carried resource animation. @@ -2482,12 +2543,16 @@ } } - // The dropsite was destroyed, or we couldn't reach it, or ownership changed + if (msg.obstructed) + return; + + // If we are here: we are in range but not carrying the right resources (or resources at all), + // the dropsite was destroyed, or we couldn't reach it, or ownership changed. // Look for a new one. - var cmpResourceGatherer = Engine.QueryInterface(this.entity, IID_ResourceGatherer); - var genericType = cmpResourceGatherer.GetMainCarryingType(); - var nearby = this.FindNearestDropsite(genericType); + let cmpResourceGatherer = Engine.QueryInterface(this.entity, IID_ResourceGatherer); + let genericType = cmpResourceGatherer.GetMainCarryingType(); + let nearby = this.FindNearestDropsite(genericType); if (nearby) { this.FinishOrder(); @@ -2521,7 +2586,7 @@ }, "MovementUpdate": function(msg) { - if (!msg.error && !this.CheckTargetRange(this.order.data.target, IID_Trader)) + if (!msg.likelyFailure && !this.CheckTargetRange(this.order.data.target, IID_Trader)) return; if (this.waypoints && this.waypoints.length) @@ -2559,8 +2624,9 @@ this.StopMoving(); }, - "MovementUpdate": function() { - this.SetNextState("REPAIRING"); + "MovementUpdate": function(msg) { + if (msg.likelyFailure || msg.likelySuccess) + this.SetNextState("REPAIRING"); }, }, @@ -2769,8 +2835,9 @@ this.StopMoving(); }, - "MovementUpdate": function() { - this.SetNextState("GARRISONED"); + "MovementUpdate": function(msg) { + if (msg.likelyFailure || msg.likelySuccess) + this.SetNextState("GARRISONED"); }, }, @@ -2949,8 +3016,9 @@ this.StopMoving(); }, - "MovementUpdate": function() { - this.SetNextState("LOADING"); + "MovementUpdate": function(msg) { + if (msg.likelyFailure || msg.likelySuccess) + this.SetNextState("LOADING"); }, "PickupCanceled": function() { @@ -3793,11 +3861,9 @@ this.timer = undefined; }; -//// Message handlers ///// - -UnitAI.prototype.OnMotionChanged = function(msg) +UnitAI.prototype.OnMotionUpdate = function(msg) { - this.UnitFsm.ProcessMessage(this, { "type": "MovementUpdate", "error": msg.error }); + this.UnitFsm.ProcessMessage(this, Object.assign({ "type": "MovementUpdate" }, msg)); }; UnitAI.prototype.OnGlobalConstructionFinished = function(msg) @@ -4400,6 +4466,20 @@ }; /** + * @returns true if the unit is in the relaxed-range from the target. + */ +UnitAI.prototype.RelaxedMaxRangeCheck = function(data, relaxedRange) +{ + if (!data.relaxed) + return false; + + let ndata = data; + ndata.min = 0; + ndata.max = relaxedRange; + return this.CheckRange(ndata); +}; + +/** * Let an entity face its target. * @param {number} target - The entity-ID of the target. */ Index: ps/trunk/source/simulation2/MessageTypes.h =================================================================== --- ps/trunk/source/simulation2/MessageTypes.h +++ ps/trunk/source/simulation2/MessageTypes.h @@ -319,16 +319,25 @@ /** * Sent by CCmpUnitMotion during Update if an event happened that might interest other components. */ -class CMessageMotionChanged : public CMessage +class CMessageMotionUpdate : public CMessage { public: - DEFAULT_MESSAGE_IMPL(MotionChanged) + DEFAULT_MESSAGE_IMPL(MotionUpdate) - CMessageMotionChanged(bool error) : error(error) + enum UpdateType { + LIKELY_SUCCESS, // UnitMotion considers it is arrived at destination. + LIKELY_FAILURE, // UnitMotion says it cannot reach the destination. + OBSTRUCTED, // UitMotion was obstructed. This does not mean stuck, but can be a hint to run range checks. + LENGTH + }; + + static const std::array UpdateTypeStr; + + CMessageMotionUpdate(UpdateType ut) : updateType(ut) { } - bool error; // whether we failed to start moving (couldn't find any path) + UpdateType updateType; }; /** Index: ps/trunk/source/simulation2/TypeList.h =================================================================== --- ps/trunk/source/simulation2/TypeList.h +++ ps/trunk/source/simulation2/TypeList.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2018 Wildfire Games. +/* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -45,7 +45,7 @@ MESSAGE(PositionChanged) MESSAGE(InterpolatedPositionChanged) MESSAGE(TerritoryPositionChanged) -MESSAGE(MotionChanged) +MESSAGE(MotionUpdate) MESSAGE(RangeUpdate) MESSAGE(TerrainChanged) MESSAGE(VisibilityChanged) Index: ps/trunk/source/simulation2/components/CCmpUnitMotion.cpp =================================================================== --- ps/trunk/source/simulation2/components/CCmpUnitMotion.cpp +++ ps/trunk/source/simulation2/components/CCmpUnitMotion.cpp @@ -53,10 +53,17 @@ /** * Min/Max range to restrict short path queries to. (Larger ranges are slower, + * Min/Max range to restrict short path queries to. (Larger ranges are (much) slower, * smaller ranges might miss some legitimate routes around large obstacles.) */ -static const entity_pos_t SHORT_PATH_MIN_SEARCH_RANGE = entity_pos_t::FromInt(TERRAIN_TILE_SIZE*2); -static const entity_pos_t SHORT_PATH_MAX_SEARCH_RANGE = entity_pos_t::FromInt(TERRAIN_TILE_SIZE*10); +static const entity_pos_t SHORT_PATH_MIN_SEARCH_RANGE = entity_pos_t::FromInt(TERRAIN_TILE_SIZE*3)/2; +static const entity_pos_t SHORT_PATH_MAX_SEARCH_RANGE = entity_pos_t::FromInt(TERRAIN_TILE_SIZE*6); +static const entity_pos_t SHORT_PATH_SEARCH_RANGE_INCREMENT = entity_pos_t::FromInt(TERRAIN_TILE_SIZE*1); + +/** + * When using the short-pathfinder to rejoin a long-path waypoint, aim for a circle of this radius around the waypoint. + */ +static const entity_pos_t SHORT_PATH_LONG_WAYPOINT_RANGE = entity_pos_t::FromInt(TERRAIN_TILE_SIZE*1); /** * Minimum distance to goal for a long path request @@ -461,13 +468,13 @@ void MoveFailed() { - CMessageMotionChanged msg(true); + CMessageMotionUpdate msg(CMessageMotionUpdate::LIKELY_FAILURE); GetSimContext().GetComponentManager().PostMessage(GetEntityId(), msg); } void MoveSucceeded() { - CMessageMotionChanged msg(false); + CMessageMotionUpdate msg(CMessageMotionUpdate::LIKELY_SUCCESS); GetSimContext().GetComponentManager().PostMessage(GetEntityId(), msg); } @@ -689,6 +696,13 @@ if (!m_ShortPath.m_Waypoints.empty()) return; + if (m_FailedPathComputations >= 1) + { + // Inform other components - we might be ordered to stop, and computeGoal will then fail and return early. + CMessageMotionUpdate msg(CMessageMotionUpdate::OBSTRUCTED); + GetSimContext().GetComponentManager().PostMessage(GetEntityId(), msg); + } + // Don't notify if we are a formation member - we can occasionally be stuck for a long time // if our current offset is unreachable. if (!IsFormationMember()) @@ -702,7 +716,9 @@ m_LongPath.m_Waypoints.pop_back(); if (!m_LongPath.m_Waypoints.empty()) { - PathGoal goal = { PathGoal::POINT, m_LongPath.m_Waypoints.back().x, m_LongPath.m_Waypoints.back().z }; + // Get close enough - this will likely help the short path efficiency, and if we end up taking a wrong way + // we'll easily be able to revert it using a long path. + PathGoal goal = { PathGoal::CIRCLE, m_LongPath.m_Waypoints.back().x, m_LongPath.m_Waypoints.back().z, SHORT_PATH_LONG_WAYPOINT_RANGE }; RequestShortPath(pos, goal, true); return; } @@ -908,6 +924,13 @@ if (!cmpPosition || !cmpPosition->IsInWorld()) return false; + if (m_FailedPathComputations >= 1) + { + // Inform other components - we might be ordered to stop, and computeGoal will then fail and return early. + CMessageMotionUpdate msg(CMessageMotionUpdate::OBSTRUCTED); + GetSimContext().GetComponentManager().PostMessage(GetEntityId(), msg); + } + CFixedVector2D pos = cmpPosition->GetPosition2D(); // Oops, we hit something (very likely another unit). @@ -930,7 +953,9 @@ m_LongPath.m_Waypoints.pop_back(); if (!m_LongPath.m_Waypoints.empty()) { - PathGoal goal = { PathGoal::POINT, m_LongPath.m_Waypoints.back().x, m_LongPath.m_Waypoints.back().z }; + // Get close enough - this will likely help the short path efficiency, and if we end up taking a wrong way + // we'll easily be able to revert it using a long path. + PathGoal goal = { PathGoal::CIRCLE, m_LongPath.m_Waypoints.back().x, m_LongPath.m_Waypoints.back().z, SHORT_PATH_LONG_WAYPOINT_RANGE }; RequestShortPath(pos, goal, true); return true; } @@ -1322,7 +1347,7 @@ if (!cmpPathfinder) return; - fixed searchRange = std::max(SHORT_PATH_MIN_SEARCH_RANGE * (m_FailedPathComputations + 1), goal.DistanceToPoint(from)); + fixed searchRange = SHORT_PATH_MIN_SEARCH_RANGE + SHORT_PATH_SEARCH_RANGE_INCREMENT * m_FailedPathComputations; if (searchRange > SHORT_PATH_MAX_SEARCH_RANGE) searchRange = SHORT_PATH_MAX_SEARCH_RANGE; Index: ps/trunk/source/simulation2/helpers/VertexPathfinder.cpp =================================================================== --- ps/trunk/source/simulation2/helpers/VertexPathfinder.cpp +++ ps/trunk/source/simulation2/helpers/VertexPathfinder.cpp @@ -518,11 +518,25 @@ // Create impassable edges at the max-range boundary, so we can't escape the region // where we're meant to be searching + fixed rangeXMin = request.x0 - request.range; fixed rangeXMax = request.x0 + request.range; fixed rangeZMin = request.z0 - request.range; fixed rangeZMax = request.z0 + request.range; + // If useful, move the center of the search-space so that it's slightly towards the goal, + // as the vertex pathfinder tends to be used to get around entities in front of us. + CFixedVector2D toGoal = CFixedVector2D(request.goal.x, request.goal.z) - CFixedVector2D(request.x0, request.z0); + if (toGoal.CompareLength(request.range) >= 0) + { + fixed toGoalLength = toGoal.Length(); + fixed inv = fixed::FromInt(1) / toGoalLength; + rangeXMin += (toGoal.Multiply(std::min(toGoalLength / 2, request.range * 3 / 5)).Multiply(inv)).X; + rangeXMax += (toGoal.Multiply(std::min(toGoalLength / 2, request.range * 3 / 5)).Multiply(inv)).X; + rangeZMin += (toGoal.Multiply(std::min(toGoalLength / 2, request.range * 3 / 5)).Multiply(inv)).Y; + rangeZMax += (toGoal.Multiply(std::min(toGoalLength / 2, request.range * 3 / 5)).Multiply(inv)).Y; + } + // Add domain edges // (Inside-out square, so edges are in reverse from the usual direction.) m_Edges.emplace_back(Edge{ CFixedVector2D(rangeXMin, rangeZMin), CFixedVector2D(rangeXMin, rangeZMax) }); Index: ps/trunk/source/simulation2/scripting/MessageTypeConversions.cpp =================================================================== --- ps/trunk/source/simulation2/scripting/MessageTypeConversions.cpp +++ ps/trunk/source/simulation2/scripting/MessageTypeConversions.cpp @@ -266,18 +266,25 @@ //////////////////////////////// -JS::Value CMessageMotionChanged::ToJSVal(const ScriptInterface& scriptInterface) const +const std::array CMessageMotionUpdate::UpdateTypeStr = { { + "likelySuccess", "likelyFailure", "obstructed" +} }; + +JS::Value CMessageMotionUpdate::ToJSVal(const ScriptInterface& scriptInterface) const { TOJSVAL_SETUP(); - SET_MSG_PROPERTY(error); + JS::RootedValue prop(cx); + + if (!JS_SetProperty(cx, obj, UpdateTypeStr[updateType], JS::TrueHandleValue)) + return JS::UndefinedValue(); + return JS::ObjectValue(*obj); } -CMessage* CMessageMotionChanged::FromJSVal(const ScriptInterface& scriptInterface, JS::HandleValue val) +CMessage* CMessageMotionUpdate::FromJSVal(const ScriptInterface&, JS::HandleValue) { - FROMJSVAL_SETUP(); - GET_MSG_PROPERTY(bool, error); - return new CMessageMotionChanged(error); + LOGWARNING("CMessageMotionUpdate::FromJSVal not implemented"); + return NULL; } ////////////////////////////////