diff --git a/binaries/data/mods/public/gui/common/functions_page_pregame.js b/binaries/data/mods/public/gui/common/functions_page_pregame.js index 492ad27c6c..d8b0f61afa 100644 --- a/binaries/data/mods/public/gui/common/functions_page_pregame.js +++ b/binaries/data/mods/public/gui/common/functions_page_pregame.js @@ -13,8 +13,9 @@ function startMainMenu() guiUnHide ("pg"); // Play main 0 A.D. theme when the main menu starts. - curr_music = newRandomSound("music", "menu"); - curr_music.loop(); + var curr_music = newRandomSound("music", "menu"); + if (curr_music) + curr_music.loop(); // Set starting volume (I'm using a value of zero here for no sound; feel free to comment out these two lines to use defaults). // curr_music.setGain (0.0); diff --git a/binaries/data/mods/public/simulation/components/UnitAI.js b/binaries/data/mods/public/simulation/components/UnitAI.js index 1da62e740c..7f78e1ce3a 100644 --- a/binaries/data/mods/public/simulation/components/UnitAI.js +++ b/binaries/data/mods/public/simulation/components/UnitAI.js @@ -55,7 +55,7 @@ UnitAI.prototype.Walk = function(x, z) if (!motion) return; - motion.MoveToPoint(x, z); + motion.MoveToPoint(x, z, 0, 0); this.state = STATE_WALKING; }; @@ -96,7 +96,7 @@ UnitAI.prototype.MoveToTarget = function(target) var cmpMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); var pos = cmpPositionTarget.GetPosition(); - cmpMotion.MoveToPoint(pos.x, pos.z); + cmpMotion.MoveToPoint(pos.x, pos.z, 0, 8); }; UnitAI.prototype.AttackTimeout = function(data) diff --git a/source/scriptinterface/ScriptInterface.h b/source/scriptinterface/ScriptInterface.h index 18e78251c6..1fcbb9ee97 100644 --- a/source/scriptinterface/ScriptInterface.h +++ b/source/scriptinterface/ScriptInterface.h @@ -32,7 +32,7 @@ // Set the maximum number of function arguments that can be handled // (This should be as small as possible (for compiler efficiency), // but as large as necessary for all wrapped functions) -#define SCRIPT_INTERFACE_MAX_ARGS 4 +#define SCRIPT_INTERFACE_MAX_ARGS 5 struct ScriptInterface_impl; class ScriptClass; diff --git a/source/simulation2/components/CCmpPathfinder.cpp b/source/simulation2/components/CCmpPathfinder.cpp index 64626dfcac..70a806f56f 100644 --- a/source/simulation2/components/CCmpPathfinder.cpp +++ b/source/simulation2/components/CCmpPathfinder.cpp @@ -227,15 +227,15 @@ public: virtual bool CanMoveStraight(entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1, entity_pos_t r, u32& cost); - virtual void ComputePath(entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1, Path& ret); + virtual void ComputePath(entity_pos_t x0, entity_pos_t z0, const Goal& goal, Path& ret); - virtual void SetDebugPath(entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1) + virtual void SetDebugPath(entity_pos_t x0, entity_pos_t z0, const Goal& goal) { delete m_DebugGrid; m_DebugGrid = NULL; delete m_DebugPath; m_DebugPath = new Path(); - ComputePath(x0, z0, x1, z1, *m_DebugPath); + ComputePath(x0, z0, goal, *m_DebugPath); } /** @@ -522,14 +522,24 @@ public: // Calculate heuristic cost from tile i,j to destination // (This ought to be an underestimate for correctness) -static u32 CalculateHeuristic(u16 i, u16 j, u16 iTarget, u16 jTarget) +static u32 CalculateHeuristic(u16 i, u16 j, u16 iGoal, u16 jGoal, u16 rGoal, bool aimingInwards) { #ifdef USE_DIAGONAL_MOVEMENT - return hypot(i-iTarget, j-jTarget)*g_CostPerTile; - // XXX: shouldn't use floats here - // Also, the heuristic should match the costs better + CFixedVector2D pos (CFixed_23_8::FromInt(i), CFixed_23_8::FromInt(j)); + CFixedVector2D goal (CFixed_23_8::FromInt(iGoal), CFixed_23_8::FromInt(jGoal)); + CFixed_23_8 dist = (pos - goal).Length(); + // TODO: the heuristic could match the costs better - it's not really Euclidean movement + + CFixed_23_8 rdist = dist - CFixed_23_8::FromInt(rGoal); + if (!aimingInwards) + rdist = -rdist; + + if (rdist < CFixed_23_8::FromInt(0)) + return 0; + return (rdist * g_CostPerTile).ToInt_RoundToZero(); + #else - return (abs((int)i - (int)iTarget) + abs((int)j - (int)jTarget)) * g_CostPerTile; + return (abs((int)i - (int)iGoal) + abs((int)j - (int)jGoal)) * g_CostPerTile; #endif } @@ -568,10 +578,12 @@ struct PathfinderState { u32 steps; // number of algorithm iterations - u16 iTarget, jTarget; // goal tile + u16 iGoal, jGoal; // goal tile + u16 rGoal; // radius of goal (around tile center) + bool aimingInwards; // whether we're moving towards the goal or away PriorityQueue open; - PriorityQueue closed; + // (there's no explicit closed list; it's encoded in PathfindTile::status) Grid* tiles; Grid* terrain; @@ -587,7 +599,7 @@ static void ProcessNeighbour(u16 pi, u16 pj, u16 i, u16 j, u32 pg, PathfinderSta if (state.terrain->get(i, j)) return; - u32 h = CalculateHeuristic(i, j, state.iTarget, state.jTarget); + u32 h = CalculateHeuristic(i, j, state.iGoal, state.jGoal, state.rGoal, state.aimingInwards); u32 dg = CalculateCostDelta(pi, pj, i, j, state.tiles); u32 g = pg + dg; // cost to this tile = cost to predecessor + delta from predecessor @@ -624,7 +636,6 @@ static void ProcessNeighbour(u16 pi, u16 pj, u16 i, u16 j, u32 pg, PathfinderSta // If this is a better path (possible when we use inadmissible heuristics), reopen it if (g < n.cost) { - state.closed.remove(i, j); // (don't return yet) } else @@ -643,7 +654,23 @@ static void ProcessNeighbour(u16 pi, u16 pj, u16 i, u16 j, u32 pg, PathfinderSta state.open.push(t); } -void CCmpPathfinder::ComputePath(entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1, Path& path) +static bool AtGoal(u16 i, u16 j, u16 iGoal, u16 jGoal, u16 rGoal, bool aimingInwards) +{ + // If we're aiming towards a point, stop when we get there + if (aimingInwards && rGoal == 0) + return (i == iGoal && j == jGoal); + + // Otherwise compute the distance and compare to desired radius + i32 dist2 = ((i32)i-iGoal)*((i32)i-iGoal) + ((i32)j-jGoal)*((i32)j-jGoal); + if (aimingInwards && (dist2 <= rGoal*rGoal)) + return true; + if (!aimingInwards && (dist2 >= rGoal*rGoal)) + return true; + + return false; +} + +void CCmpPathfinder::ComputePath(entity_pos_t x0, entity_pos_t z0, const Goal& goal, Path& path) { UpdateGrid(); @@ -651,9 +678,29 @@ void CCmpPathfinder::ComputePath(entity_pos_t x0, entity_pos_t z0, entity_pos_t PathfinderState state; + // Convert the start/end coordinates to tile indexes u16 i0, j0; NearestTile(x0, z0, i0, j0); - NearestTile(x1, z1, state.iTarget, state.jTarget); + NearestTile(goal.x, goal.z, state.iGoal, state.jGoal); + + // If we start closer than min radius, aim for the min radius + // If we start further than max radius, aim for the max radius + // Otherwise we're there already + CFixed_23_8 initialDist = (CFixedVector2D(x0, z0) - CFixedVector2D(goal.x, goal.z)).Length(); + if (initialDist < goal.minRadius) + { + state.aimingInwards = false; + state.rGoal = (goal.minRadius / CELL_SIZE).ToInt_RoundToZero(); // TODO: what rounding mode is appropriate? + } + else if (initialDist > goal.maxRadius) + { + state.aimingInwards = true; + state.rGoal = (goal.maxRadius / CELL_SIZE).ToInt_RoundToZero(); // TODO: what rounding mode is appropriate? + } + else + { + return; + } state.steps = 0; @@ -662,7 +709,7 @@ void CCmpPathfinder::ComputePath(entity_pos_t x0, entity_pos_t z0, entity_pos_t state.iBest = i0; state.jBest = j0; - state.hBest = CalculateHeuristic(i0, j0, state.iTarget, state.jTarget); + state.hBest = CalculateHeuristic(i0, j0, state.iGoal, state.jGoal, state.rGoal, state.aimingInwards); QueueItem start = { i0, j0, 0 }; state.open.push(start); @@ -687,11 +734,10 @@ void CCmpPathfinder::ComputePath(entity_pos_t x0, entity_pos_t z0, entity_pos_t // Move best tile from open to closed QueueItem curr = state.open.top(); state.open.pop(); - state.closed.push(curr); state.tiles->get(curr.i, curr.j).status = PathfindTile::STATUS_CLOSED; // If we've reached the destination, stop - if (curr.i == state.iTarget && curr.j == state.jTarget) + if (AtGoal(curr.i, curr.j, state.iGoal, state.jGoal, state.rGoal, state.aimingInwards)) { state.iBest = curr.i; state.jBest = curr.j; diff --git a/source/simulation2/components/CCmpUnitMotion.cpp b/source/simulation2/components/CCmpUnitMotion.cpp index 5dce1c2008..a15971d3d5 100644 --- a/source/simulation2/components/CCmpUnitMotion.cpp +++ b/source/simulation2/components/CCmpUnitMotion.cpp @@ -91,7 +91,7 @@ public: } } - virtual void MoveToPoint(entity_pos_t x, entity_pos_t z) + virtual void MoveToPoint(entity_pos_t x, entity_pos_t z, entity_pos_t minRadius, entity_pos_t maxRadius) { CmpPtr cmpPathfinder (*m_Context, SYSTEM_ENTITY); if (cmpPathfinder.null()) @@ -105,18 +105,23 @@ public: m_Path.m_Waypoints.clear(); - u32 cost; - entity_pos_t r = entity_pos_t::FromInt(0); // TODO: should get this from the entity's size - if (cmpPathfinder->CanMoveStraight(pos.X, pos.Z, x, z, r, cost)) +// u32 cost; +// entity_pos_t r = entity_pos_t::FromInt(0); // TODO: should get this from the entity's size +// if (cmpPathfinder->CanMoveStraight(pos.X, pos.Z, x, z, r, cost)) +// { +// m_TargetX = x; +// m_TargetZ = z; +// m_HasTarget = true; +// } +// else { - m_TargetX = x; - m_TargetZ = z; - m_HasTarget = true; - } - else - { - cmpPathfinder->SetDebugPath(pos.X, pos.Z, x, z); - cmpPathfinder->ComputePath(pos.X, pos.Z, x, z, m_Path); + ICmpPathfinder::Goal goal; + goal.x = x; + goal.z = z; + goal.minRadius = minRadius; + goal.maxRadius = maxRadius; + cmpPathfinder->SetDebugPath(pos.X, pos.Z, goal); + cmpPathfinder->ComputePath(pos.X, pos.Z, goal, m_Path); if (!m_Path.m_Waypoints.empty()) PickNextWaypoint(pos); } diff --git a/source/simulation2/components/ICmpPathfinder.h b/source/simulation2/components/ICmpPathfinder.h index 93c25c1875..0747340053 100644 --- a/source/simulation2/components/ICmpPathfinder.h +++ b/source/simulation2/components/ICmpPathfinder.h @@ -35,6 +35,12 @@ class ICmpPathfinder : public IComponent { public: + struct Goal + { + entity_pos_t x, z; + entity_pos_t minRadius, maxRadius; + }; + /** * Returned paths are currently represented as a series of waypoints. * These happen to correspond to the centers of horizontally/vertically adjacent tiles @@ -82,12 +88,12 @@ public: /** * Compute a path between the given points, and return the set of waypoints. */ - virtual void ComputePath(entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1, Path& ret) = 0; + virtual void ComputePath(entity_pos_t x0, entity_pos_t z0, const Goal& goal, Path& ret) = 0; /** * Compute a path between the given points, and draw the latest such path as a terrain overlay. */ - virtual void SetDebugPath(entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1) = 0; + virtual void SetDebugPath(entity_pos_t x0, entity_pos_t z0, const Goal& goal) = 0; DECLARE_INTERFACE_TYPE(Pathfinder) }; diff --git a/source/simulation2/components/ICmpUnitMotion.cpp b/source/simulation2/components/ICmpUnitMotion.cpp index 0adea33311..0d6dd4d01c 100644 --- a/source/simulation2/components/ICmpUnitMotion.cpp +++ b/source/simulation2/components/ICmpUnitMotion.cpp @@ -22,5 +22,5 @@ #include "simulation2/system/InterfaceScripted.h" BEGIN_INTERFACE_WRAPPER(UnitMotion) -DEFINE_INTERFACE_METHOD_2("MoveToPoint", void, ICmpUnitMotion, MoveToPoint, entity_pos_t, entity_pos_t) +DEFINE_INTERFACE_METHOD_4("MoveToPoint", void, ICmpUnitMotion, MoveToPoint, entity_pos_t, entity_pos_t, entity_pos_t, entity_pos_t) END_INTERFACE_WRAPPER(UnitMotion) diff --git a/source/simulation2/components/ICmpUnitMotion.h b/source/simulation2/components/ICmpUnitMotion.h index fccfe34748..7198033cef 100644 --- a/source/simulation2/components/ICmpUnitMotion.h +++ b/source/simulation2/components/ICmpUnitMotion.h @@ -34,7 +34,7 @@ class ICmpUnitMotion : public IComponent { public: - virtual void MoveToPoint(entity_pos_t x, entity_pos_t z) = 0; + virtual void MoveToPoint(entity_pos_t x, entity_pos_t z, entity_pos_t minRadius, entity_pos_t maxRadius) = 0; DECLARE_INTERFACE_TYPE(UnitMotion) };