Ticket #4420: unitMotionRewrite.patch
File unitMotionRewrite.patch, 89.3 KB (added by , 7 years ago) |
---|
-
binaries/data/mods/public/simulation/components/Formation.js
diff --git a/binaries/data/mods/public/simulation/components/Formation.js b/binaries/data/mods/public/simulation/components/Formation.js index 4533497..b70a59f 100644
a b Formation.prototype.ComputeMotionParameters = function() 865 865 { 866 866 var cmpUnitMotion = Engine.QueryInterface(ent, IID_UnitMotion); 867 867 if (cmpUnitMotion) 868 minSpeed = Math.min(minSpeed, cmpUnitMotion.Get WalkSpeed());868 minSpeed = Math.min(minSpeed, cmpUnitMotion.GetSpeed()); 869 869 } 870 870 minSpeed *= this.GetSpeedMultiplier(); 871 871 -
binaries/data/mods/public/simulation/components/GuiInterface.js
diff --git a/binaries/data/mods/public/simulation/components/GuiInterface.js b/binaries/data/mods/public/simulation/components/GuiInterface.js index 932c8cd..eb24ed0 100644
a b GuiInterface.prototype.GetExtendedEntityState = function(player, ent) 596 596 let cmpUnitMotion = Engine.QueryInterface(ent, IID_UnitMotion); 597 597 if (cmpUnitMotion) 598 598 ret.speed = { 599 "walk": cmpUnitMotion.Get WalkSpeed(),600 "run": cmpUnitMotion.Get RunSpeed()599 "walk": cmpUnitMotion.GetSpeed(), 600 "run": cmpUnitMotion.GetSpeed() * cmpUnitMotion.GetTopSpeedRatio() 601 601 }; 602 602 603 603 return ret; -
binaries/data/mods/public/simulation/components/UnitAI.js
diff --git a/binaries/data/mods/public/simulation/components/UnitAI.js b/binaries/data/mods/public/simulation/components/UnitAI.js index cbb0428..764f619 100644
a b 1 const WALKING_SPEED = 1.0 2 1 3 function UnitAI() {} 2 4 3 5 UnitAI.prototype.Schema = … … UnitAI.prototype.UnitFsmSpec = { 1391 1393 "enter": function () { 1392 1394 var cmpFormation = Engine.QueryInterface(this.formationController, IID_Formation); 1393 1395 var cmpVisual = Engine.QueryInterface(this.entity, IID_Visual); 1394 if (cmpFormation && cmpVisual)1396 /* TOREPLACE if (cmpFormation && cmpVisual) 1395 1397 { 1396 1398 cmpVisual.ReplaceMoveAnimation("walk", cmpFormation.GetFormationAnimation(this.entity, "walk")); 1397 1399 cmpVisual.ReplaceMoveAnimation("run", cmpFormation.GetFormationAnimation(this.entity, "run")); 1398 1400 } 1401 */ 1399 1402 this.SelectAnimation("move"); 1400 1403 }, 1401 1404 … … UnitAI.prototype.UnitFsmSpec = { 1405 1408 // We can only finish this order if the move was really completed. 1406 1409 if (!msg.data.error && this.FinishOrder()) 1407 1410 return; 1408 var cmpVisual = Engine.QueryInterface(this.entity, IID_Visual);1411 /* TOREPLACE var cmpVisual = Engine.QueryInterface(this.entity, IID_Visual); 1409 1412 if (cmpVisual) 1410 1413 { 1411 1414 cmpVisual.ResetMoveAnimation("walk"); 1412 1415 cmpVisual.ResetMoveAnimation("run"); 1413 1416 } 1414 1417 */ 1415 1418 var cmpFormation = Engine.QueryInterface(this.formationController, IID_Formation); 1416 1419 if (cmpFormation) 1417 1420 cmpFormation.SetInPosition(this.entity); … … UnitAI.prototype.UnitFsmSpec = { 1699 1702 }, 1700 1703 1701 1704 "leave": function(msg) { 1702 this.SetMoveSpeed( this.GetWalkSpeed());1705 this.SetMoveSpeed(WALKING_SPEED); 1703 1706 this.StopTimer(); 1704 1707 }, 1705 1708 1706 1709 "MoveStarted": function(msg) { 1707 1710 // Adapt the speed to the one of the target if needed 1708 varcmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);1711 let cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); 1709 1712 if (cmpUnitMotion.IsInTargetRange(this.isGuardOf, 0, 3*this.guardRange)) 1710 1713 { 1711 var cmp UnitAI = Engine.QueryInterface(this.isGuardOf, IID_UnitAI);1712 if (cmp UnitAI)1714 var cmpOtherMotion = Engine.QueryInterface(this.isGuardOf, IID_UnitMotion); 1715 if (cmpOtherMotion) 1713 1716 { 1714 var speed = cmpUnitAI.GetWalkSpeed(); 1715 if (speed < this.GetWalkSpeed()) 1717 let otherSpeed = cmpOtherMotion.GetSpeed(); 1718 let mySpeed = cmpUnitMotion.GetSpeed(); 1719 let speed = otherSpeed / mySpeed; 1720 if (speed < WALKING_SPEED) 1716 1721 this.SetMoveSpeed(speed); 1717 1722 } 1718 1723 } 1719 1724 }, 1720 1725 1721 1726 "MoveCompleted": function() { 1722 this.SetMoveSpeed( this.GetWalkSpeed());1727 this.SetMoveSpeed(WALKING_SPEED); 1723 1728 if (!this.MoveToTargetRangeExplicit(this.isGuardOf, 0, this.guardRange)) 1724 1729 this.SetNextState("GUARDING"); 1725 1730 }, … … UnitAI.prototype.UnitFsmSpec = { 1774 1779 this.PlaySound("panic"); 1775 1780 1776 1781 // Run quickly 1777 var speed = this.GetRunSpeed(); 1778 this.SelectAnimation("move"); 1779 this.SetMoveSpeed(speed); 1780 }, 1781 1782 "HealthChanged": function() { 1783 var speed = this.GetRunSpeed(); 1784 this.SetMoveSpeed(speed); 1782 this.SetMoveSpeed(this.GetRunSpeed()); 1785 1783 }, 1786 1784 1787 1785 "leave": function() { 1788 1786 // Reset normal speed 1789 this.SetMoveSpeed( this.GetWalkSpeed());1787 this.SetMoveSpeed(WALKING_SPEED); 1790 1788 }, 1791 1789 1792 1790 "MoveCompleted": function() { … … UnitAI.prototype.UnitFsmSpec = { 2100 2098 2101 2099 this.SelectAnimation("move"); 2102 2100 var cmpUnitAI = Engine.QueryInterface(this.order.data.target, IID_UnitAI); 2101 // Run after a fleeing target 2103 2102 if (cmpUnitAI && cmpUnitAI.IsFleeing()) 2104 { 2105 // Run after a fleeing target 2106 var speed = this.GetRunSpeed(); 2107 this.SetMoveSpeed(speed); 2108 } 2103 this.SetMoveSpeed(this.GetRunSpeed()); 2109 2104 this.StartTimer(1000, 1000); 2110 2105 }, 2111 2106 … … UnitAI.prototype.UnitFsmSpec = { 2113 2108 var cmpUnitAI = Engine.QueryInterface(this.order.data.target, IID_UnitAI); 2114 2109 if (!cmpUnitAI || !cmpUnitAI.IsFleeing()) 2115 2110 return; 2116 var speed = this.GetRunSpeed(); 2117 this.SetMoveSpeed(speed); 2111 // TODO: figure out what to do with fleeing 2112 //var speed = this.GetRunSpeed(); 2113 //this.SetMoveSpeed(speed); 2118 2114 }, 2119 2115 2120 2116 "leave": function() { 2121 2117 // Reset normal speed in case it was changed 2122 this.SetMoveSpeed( this.GetWalkSpeed());2118 this.SetMoveSpeed(WALKING_SPEED); 2123 2119 // Show carried resources when walking. 2124 2120 this.SetGathererAnimationOverride(); 2125 2121 … … UnitAI.prototype.UnitFsmSpec = { 3227 3223 "ROAMING": { 3228 3224 "enter": function() { 3229 3225 // Walk in a random direction 3230 this.SelectAnimation("walk", false, this.GetWalkSpeed());3231 3226 this.MoveRandomly(+this.template.RoamDistance); 3232 3227 // Set a random timer to switch to feeding state 3233 3228 this.StartTimer(RandomInt(+this.template.RoamTimeMin, +this.template.RoamTimeMax)); … … UnitAI.prototype.StopTimer = function() 4010 4005 4011 4006 //// Message handlers ///// 4012 4007 4013 UnitAI.prototype.OnMotionChanged = function(msg) 4008 UnitAI.prototype.OnBeginMove = function(msg) 4009 { 4010 this.UnitFsm.ProcessMessage(this, {"type": "MoveStarted", "data": msg}); 4011 }; 4012 4013 UnitAI.prototype.OnFinishedMove = function(msg) 4014 4014 { 4015 if (msg.starting && !msg.error) 4016 this.UnitFsm.ProcessMessage(this, {"type": "MoveStarted", "data": msg}); 4017 else if (!msg.starting || msg.error) 4018 this.UnitFsm.ProcessMessage(this, {"type": "MoveCompleted", "data": msg}); 4015 this.UnitFsm.ProcessMessage(this, {"type": "MoveCompleted", "data": msg}); 4019 4016 }; 4020 4017 4021 4018 UnitAI.prototype.OnGlobalConstructionFinished = function(msg) … … UnitAI.prototype.OnPackFinished = function(msg) 4076 4073 4077 4074 //// Helper functions to be called by the FSM //// 4078 4075 4079 UnitAI.prototype.GetWalkSpeed = function()4080 {4081 var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);4082 return cmpUnitMotion.GetWalkSpeed();4083 };4084 4085 4076 UnitAI.prototype.GetRunSpeed = function() 4086 4077 { 4087 4078 var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); 4088 var runSpeed = cmpUnitMotion.GetRunSpeed(); 4089 var walkSpeed = cmpUnitMotion.GetWalkSpeed(); 4090 if (runSpeed <= walkSpeed) 4091 return runSpeed; 4092 var cmpHealth = Engine.QueryInterface(this.entity, IID_Health); 4093 var health = cmpHealth.GetHitpoints()/cmpHealth.GetMaxHitpoints(); 4094 return (health*runSpeed + (1-health)*walkSpeed); 4079 return cmpUnitMotion.GetTopSpeedRatio(); 4095 4080 }; 4096 4081 4097 4082 /** … … UnitAI.prototype.SetGathererAnimationOverride = function(disable) 4290 4275 // Remove the animation override, so that weapons are shown again. 4291 4276 if (disable) 4292 4277 { 4293 cmpVisual.ResetMoveAnimation("walk");4278 //TOREPLACE cmpVisual.ResetMoveAnimation("walk"); 4294 4279 return; 4295 4280 } 4296 4281 4297 4282 // Work out what we're carrying, in order to select an appropriate animation 4283 /* 4298 4284 var type = cmpResourceGatherer.GetLastCarriedType(); 4299 4285 if (type) 4300 4286 { … … UnitAI.prototype.SetGathererAnimationOverride = function(disable) 4304 4290 if (type.specific == "meat") 4305 4291 typename = "carry_" + type.specific; 4306 4292 4307 4293 // TOREPLACE cmpVisual.ReplaceMoveAnimation("walk", typename); 4308 4294 } 4309 4295 else 4310 cmpVisual.ResetMoveAnimation("walk"); 4296 cmpVisual.ResetMoveAnimation("idle"); 4297 */ 4311 4298 }; 4312 4299 4313 4300 UnitAI.prototype.SelectAnimation = function(name, once, speed, sound) … … UnitAI.prototype.SelectAnimation = function(name, once, speed, sound) 4316 4303 if (!cmpVisual) 4317 4304 return; 4318 4305 4319 // Special case: the "move" animation gets turned into a special4320 // movement mode that deals with speeds and walk/run automatically4321 if (name == "move")4322 {4323 // Speed to switch from walking to running animations4324 var runThreshold = (this.GetWalkSpeed() + this.GetRunSpeed()) / 2;4325 4326 cmpVisual.SelectMovementAnimation(runThreshold);4327 return;4328 }4329 4330 4306 var soundgroup; 4331 4307 if (sound) 4332 4308 { … … UnitAI.prototype.SetAnimationSync = function(actiontime, repeattime) 4359 4335 UnitAI.prototype.StopMoving = function() 4360 4336 { 4361 4337 var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); 4362 cmpUnitMotion. StopMoving();4338 cmpUnitMotion.DiscardMove(); 4363 4339 }; 4364 4340 4365 4341 UnitAI.prototype.MoveToPoint = function(x, z) 4366 4342 { 4367 4343 var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); 4344 cmpUnitMotion.SetAbortIfStuck(3); 4368 4345 return cmpUnitMotion.MoveToPointRange(x, z, 0, 0); 4369 4346 }; 4370 4347 4371 4348 UnitAI.prototype.MoveToPointRange = function(x, z, rangeMin, rangeMax) 4372 4349 { 4373 4350 var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); 4351 cmpUnitMotion.SetAbortIfStuck(3); 4374 4352 return cmpUnitMotion.MoveToPointRange(x, z, rangeMin, rangeMax); 4375 4353 }; 4376 4354 … … UnitAI.prototype.MoveToTarget = function(target) 4380 4358 return false; 4381 4359 4382 4360 var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); 4361 cmpUnitMotion.SetAbortIfStuck(5); 4383 4362 return cmpUnitMotion.MoveToTargetRange(target, 0, 0); 4384 4363 }; 4385 4364 … … UnitAI.prototype.MoveToTargetRange = function(target, iid, type) 4394 4373 var range = cmpRanged.GetRange(type); 4395 4374 4396 4375 var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); 4376 cmpUnitMotion.SetAbortIfStuck(5); 4397 4377 return cmpUnitMotion.MoveToTargetRange(target, range.min, range.max); 4398 4378 }; 4399 4379 … … UnitAI.prototype.MoveToTargetAttackRange = function(target, type) 4450 4430 var guessedMaxRange = (range.max + parabolicMaxRange)/2; 4451 4431 4452 4432 var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); 4433 cmpUnitMotion.SetAbortIfStuck(9); 4453 4434 if (cmpUnitMotion.MoveToTargetRange(target, range.min, guessedMaxRange)) 4454 4435 return true; 4455 4436 … … UnitAI.prototype.MoveToTargetRangeExplicit = function(target, min, max) 4463 4444 return false; 4464 4445 4465 4446 var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); 4447 cmpUnitMotion.SetAbortIfStuck(5); 4466 4448 return cmpUnitMotion.MoveToTargetRange(target, min, max); 4467 4449 }; 4468 4450 … … UnitAI.prototype.MoveToGarrisonRange = function(target) 4477 4459 var range = cmpGarrisonHolder.GetLoadingRange(); 4478 4460 4479 4461 var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); 4462 cmpUnitMotion.SetAbortIfStuck(5); 4480 4463 return cmpUnitMotion.MoveToTargetRange(target, range.min, range.max); 4481 4464 }; 4482 4465 … … UnitAI.prototype.GetStanceName = function() 5694 5677 UnitAI.prototype.SetMoveSpeed = function(speed) 5695 5678 { 5696 5679 var cmpMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); 5697 cmpMotion.SetSpeed( speed);5680 cmpMotion.SetSpeed(1.0); 5698 5681 }; 5699 5682 5700 5683 UnitAI.prototype.SetHeldPosition = function(x, z) -
binaries/data/mods/public/simulation/components/UnitMotionFlying.js
diff --git a/binaries/data/mods/public/simulation/components/UnitMotionFlying.js b/binaries/data/mods/public/simulation/components/UnitMotionFlying.js index a03c3b5..3c42cf6 100644
a b UnitMotionFlying.prototype.IsInTargetRange = function(target, minRange, maxRange 295 295 return this.IsInPointRange(targetPos.x, targetPos.y, minRange, maxRange); 296 296 }; 297 297 298 UnitMotionFlying.prototype.Get WalkSpeed = function()298 UnitMotionFlying.prototype.GetSpeed = function() 299 299 { 300 300 return +this.template.MaxSpeed; 301 301 }; -
binaries/data/mods/public/simulation/data/pathfinder.xml
diff --git a/binaries/data/mods/public/simulation/data/pathfinder.xml b/binaries/data/mods/public/simulation/data/pathfinder.xml index eb912f1..aeed241 100644
a b 11 11 <Obstructions>pathfinding</Obstructions> 12 12 <MaxWaterDepth>2</MaxWaterDepth> 13 13 <MaxTerrainSlope>1.0</MaxTerrainSlope> 14 <Clearance>0. 8</Clearance>14 <Clearance>0.7</Clearance> 15 15 </default> 16 16 <large> 17 17 <Obstructions>pathfinding</Obstructions> -
source/simulation2/MessageTypes.h
diff --git a/source/simulation2/MessageTypes.h b/source/simulation2/MessageTypes.h index af194d6..36984a3 100644
a b public: 317 317 }; 318 318 319 319 /** 320 * Sent by CCmpUnitMotion during Update, whenever the motion status has changed 321 * since the previous update. 320 * Sent by CCmpUnitMotion during Update, 321 * whenever we have started actually moving and were not moving before. 322 * We may or may not already have been trying to move 322 323 */ 323 class CMessage MotionChanged: public CMessage324 class CMessageBeginMove : public CMessage 324 325 { 325 326 public: 326 DEFAULT_MESSAGE_IMPL( MotionChanged)327 DEFAULT_MESSAGE_IMPL(BeginMove) 327 328 328 CMessageMotionChanged(bool starting, bool error) : 329 starting(starting), error(error) 329 CMessageBeginMove() 330 { 331 } 332 }; 333 334 /** 335 * Sent by CCmpUnitMotion during Update, 336 * whenever we were actually moving before, and cannot continue 337 * this can be because we've arrived (failed=false) or we failed moving (failed=true) 338 * After this message is sent, the unit won't remove/repath without orders. 339 * Will never be sent on the same turn as MT_BeginMove. 340 */ 341 class CMessageFinishedMove : public CMessage 342 { 343 public: 344 DEFAULT_MESSAGE_IMPL(FinishedMove) 345 346 CMessageFinishedMove(bool fail) : failed(fail) 330 347 { 331 348 } 332 349 333 bool starting; // whether this is a start or end of movement 334 bool error; // whether we failed to start moving (couldn't find any path) 350 bool failed; // move failed 351 }; 352 353 /** 354 * Sent by CCmpUnitMotion during Update, 355 * whenever we were actually moving before, and now stopped 356 * In this case, we will retry moving/pathing in the future on our own 357 * Unless ordered otherwise. 358 * We are just possibly stuck short-term, or must repath. 359 * Will never be sent on the same turn as MT_BeginMove. 360 */ 361 class CMessagePausedMove : public CMessage 362 { 363 public: 364 DEFAULT_MESSAGE_IMPL(PausedMove) 365 366 CMessagePausedMove() 367 { 368 } 335 369 }; 336 370 337 371 /** -
source/simulation2/TypeList.h
diff --git a/source/simulation2/TypeList.h b/source/simulation2/TypeList.h index 773c39b..c1f6473 100644
a b MESSAGE(OwnershipChanged) 45 45 MESSAGE(PositionChanged) 46 46 MESSAGE(InterpolatedPositionChanged) 47 47 MESSAGE(TerritoryPositionChanged) 48 MESSAGE(MotionChanged) 48 MESSAGE(BeginMove) 49 MESSAGE(FinishedMove) 50 MESSAGE(PausedMove) 49 51 MESSAGE(RangeUpdate) 50 52 MESSAGE(TerrainChanged) 51 53 MESSAGE(VisibilityChanged) -
source/simulation2/components/CCmpObstructionManager.cpp
diff --git a/source/simulation2/components/CCmpObstructionManager.cpp b/source/simulation2/components/CCmpObstructionManager.cpp index 553ad91..8dc6829 100644
a b void CCmpObstructionManager::RenderSubmit(SceneCollector& collector) 1084 1084 m_DebugOverlayLines.push_back(SOverlayLine()); 1085 1085 m_DebugOverlayLines.back().m_Color = defaultColor; 1086 1086 float a = atan2f(it->second.v.X.ToFloat(), it->second.v.Y.ToFloat()); 1087 SimRender::ConstructSquareOnGround(GetSimContext(), it->second.x.ToFloat(), it->second.z.ToFloat(), it->second.hw.ToFloat()*2 , it->second.hh.ToFloat()*2, a, m_DebugOverlayLines.back(), true);1087 SimRender::ConstructSquareOnGround(GetSimContext(), it->second.x.ToFloat(), it->second.z.ToFloat(), it->second.hw.ToFloat()*2 + 1, it->second.hh.ToFloat()*2 + 1, a, m_DebugOverlayLines.back(), true); 1088 1088 } 1089 1089 1090 1090 m_DebugOverlayDirty = false; -
source/simulation2/components/CCmpPathfinder_Vertex.cpp
diff --git a/source/simulation2/components/CCmpPathfinder_Vertex.cpp b/source/simulation2/components/CCmpPathfinder_Vertex.cpp index a6f2a43..6442db0 100644
a b void CCmpPathfinder::ComputeShortPath(const IObstructionTestFilter& filter, 610 610 // Check whether this is an axis-aligned square 611 611 bool aa = (u.X == fixed::FromInt(1) && u.Y == fixed::Zero() && v.X == fixed::Zero() && v.Y == fixed::FromInt(1)); 612 612 613 bool add = true; 613 614 Vertex vert; 614 615 vert.status = Vertex::UNEXPLORED; 615 616 vert.quadInward = QUADRANT_NONE; 616 617 vert.quadOutward = QUADRANT_ALL; 617 618 vert.p.X = center.X - hd0.Dot(u); vert.p.Y = center.Y + hd0.Dot(v); 618 if (vert.p.X < rangeXMin) continue; 619 if (vert.p.Y < rangeZMin) continue; 620 if (vert.p.X > rangeXMax) continue; 621 if (vert.p.Y > rangeZMax) continue; 622 if (aa) vert.quadInward = QUADRANT_BR; vertexes.push_back(vert); 619 if (vert.p.X <= rangeXMin) add = false; 620 else if (vert.p.X >= rangeXMax) add = false; 621 else if (vert.p.Y <= rangeZMin) add = false; 622 else if (vert.p.Y >= rangeZMax) add = false; 623 if (aa) vert.quadInward = QUADRANT_BR; 624 if (add) vertexes.push_back(vert); 625 add = true; 623 626 vert.p.X = center.X - hd1.Dot(u); vert.p.Y = center.Y + hd1.Dot(v); 624 if (vert.p.X < rangeXMin) continue; 625 if (vert.p.Y < rangeZMin) continue; 626 if (vert.p.X > rangeXMax) continue; 627 if (vert.p.Y > rangeZMax) continue; 628 if (aa) vert.quadInward = QUADRANT_TR; vertexes.push_back(vert); 627 if (vert.p.X <= rangeXMin) add = false; 628 else if (vert.p.X >= rangeXMax) add = false; 629 else if (vert.p.Y <= rangeZMin) add = false; 630 else if (vert.p.Y >= rangeZMax) add = false; 631 if (aa) vert.quadInward = QUADRANT_TR; 632 if (add) vertexes.push_back(vert); 633 add = true; 629 634 vert.p.X = center.X + hd0.Dot(u); vert.p.Y = center.Y - hd0.Dot(v); 630 if (vert.p.X < rangeXMin) continue; 631 if (vert.p.Y < rangeZMin) continue; 632 if (vert.p.X > rangeXMax) continue; 633 if (vert.p.Y > rangeZMax) continue; 634 if (aa) vert.quadInward = QUADRANT_TL; vertexes.push_back(vert); 635 if (vert.p.X <= rangeXMin) add = false; 636 else if (vert.p.X >= rangeXMax) add = false; 637 else if (vert.p.Y <= rangeZMin) add = false; 638 else if (vert.p.Y >= rangeZMax) add = false; 639 if (aa) vert.quadInward = QUADRANT_TL; 640 if (add) vertexes.push_back(vert); 641 add = true; 635 642 vert.p.X = center.X + hd1.Dot(u); vert.p.Y = center.Y - hd1.Dot(v); 636 if (vert.p.X < rangeXMin) continue; 637 if (vert.p.Y < rangeZMin) continue; 638 if (vert.p.X > rangeXMax) continue; 639 if (vert.p.Y > rangeZMax) continue; 640 if (aa) vert.quadInward = QUADRANT_BL; vertexes.push_back(vert); 643 if (vert.p.X <= rangeXMin) add = false; 644 else if (vert.p.X >= rangeXMax) add = false; 645 else if (vert.p.Y <= rangeZMin) add = false; 646 else if (vert.p.Y >= rangeZMax) add = false; 647 if (aa) vert.quadInward = QUADRANT_BL; 648 if (add) vertexes.push_back(vert); 641 649 642 650 // Add the edges: 643 651 … … void CCmpPathfinder::ComputeShortPath(const IObstructionTestFilter& filter, 658 666 edges.emplace_back(Edge{ ev3, ev0 }); 659 667 } 660 668 661 // TODO: should clip out vertexes and edges that are outside the range,662 // to reduce the search space663 669 } 664 670 665 671 // Add terrain obstructions -
source/simulation2/components/CCmpUnitMotion.cpp
diff --git a/source/simulation2/components/CCmpUnitMotion.cpp b/source/simulation2/components/CCmpUnitMotion.cpp index 04a7a00..b66c726 100644
a b 27 27 #include "simulation2/components/ICmpPathfinder.h" 28 28 #include "simulation2/components/ICmpRangeManager.h" 29 29 #include "simulation2/components/ICmpValueModificationManager.h" 30 #include "simulation2/components/ICmpVisual.h" 30 31 #include "simulation2/helpers/Geometry.h" 31 32 #include "simulation2/helpers/Render.h" 32 33 #include "simulation2/MessageTypes.h" … … static const entity_pos_t CHECK_TARGET_MOVEMENT_AT_MAX_DIST = entity_pos_t::From 106 107 */ 107 108 static const fixed CHECK_TARGET_MOVEMENT_MIN_COS = fixed::FromInt(866)/1000; 108 109 110 /** 111 * See unitmotion logic for details. Higher means units will retry more often before potentially failing. 112 */ 113 static const size_t MAX_PATH_REATTEMPS = 8; 114 109 115 static const CColor OVERLAY_COLOR_LONG_PATH(1, 1, 1, 1); 110 116 static const CColor OVERLAY_COLOR_SHORT_PATH(1, 0, 0, 1); 111 117 112 118 class CCmpUnitMotion : public ICmpUnitMotion 113 119 { 120 private: 121 struct SMotionGoal 122 { 123 private: 124 bool m_Valid = false; 125 126 entity_pos_t m_TargetMinRange; 127 entity_pos_t m_TargetMaxRange; 128 129 entity_id_t m_TargetEntity; 130 // pathfinder-compliant goal. 131 PathGoal m_Goal; 132 public: 133 SMotionGoal() : m_Valid(false) {}; 134 135 SMotionGoal(PathGoal& goal, entity_pos_t minRange, entity_pos_t maxRange) 136 { 137 m_TargetEntity = INVALID_ENTITY; 138 139 m_TargetMinRange = minRange; 140 m_TargetMaxRange = maxRange; 141 142 m_Goal = goal; 143 m_Valid = true; 144 } 145 146 SMotionGoal(const CSimContext& context, entity_id_t target, PathGoal& goal, entity_pos_t minRange, entity_pos_t maxRange) 147 { 148 m_TargetEntity = target; 149 m_TargetMinRange = minRange; 150 m_TargetMaxRange = maxRange; 151 152 m_Goal = goal; 153 m_Valid = true; 154 155 UpdateTargetPosition(context); 156 } 157 158 const PathGoal& Goal() const { return m_Goal; }; 159 160 bool TargetIsEntity() const { return m_TargetEntity != INVALID_ENTITY; } 161 entity_id_t GetEntity() const { return m_TargetEntity; } 162 163 bool Valid() const { return m_Valid; } 164 void Clear() { m_Valid = false; } 165 166 entity_pos_t MinRange() const { return m_TargetMinRange; }; 167 entity_pos_t MaxRange() const { return m_TargetMaxRange; }; 168 169 CFixedVector2D Pos() const { return CFixedVector2D(m_Goal.x, m_Goal.z); } 170 entity_pos_t X() const { return m_Goal.x; } 171 entity_pos_t Z() const { return m_Goal.z; } 172 173 void UpdateTargetPosition(const CSimContext& context) 174 { 175 if (!TargetIsEntity()) 176 return; 177 178 CmpPtr<ICmpPosition> cmpPosition(context, m_TargetEntity); 179 if (!cmpPosition || !cmpPosition->IsInWorld()) 180 return; 181 182 m_Goal.x = cmpPosition->GetPosition2D().X; 183 m_Goal.z = cmpPosition->GetPosition2D().Y; 184 } 185 186 bool IsAPoint() const 187 { 188 return m_Goal.type == PathGoal::POINT && !TargetIsEntity(); 189 } 190 }; 191 114 192 public: 115 193 static void ClassInit(CComponentManager& componentManager) 116 194 { 117 componentManager.SubscribeToMessageType(MT_Update_MotionFormation);118 195 componentManager.SubscribeToMessageType(MT_Update_MotionUnit); 119 196 componentManager.SubscribeToMessageType(MT_PathResult); 120 197 componentManager.SubscribeToMessageType(MT_OwnershipChanged); … … public: 128 205 std::vector<SOverlayLine> m_DebugOverlayLongPathLines; 129 206 std::vector<SOverlayLine> m_DebugOverlayShortPathLines; 130 207 131 // Template state: 132 133 bool m_FormationController; 134 fixed m_WalkSpeed, m_OriginalWalkSpeed; // in metres per second 135 fixed m_RunSpeed, m_OriginalRunSpeed; 208 // Template state, never changed after init. 209 fixed m_TemplateSpeed, m_TopSpeedRatio; 136 210 pass_class_t m_PassClass; 137 211 std::string m_PassClassName; 138 139 // Dynamic state:140 141 212 entity_pos_t m_Clearance; 142 bool m_Moving;143 bool m_FacePointAfterMove;144 145 enum State146 {147 /*148 * Not moving at all.149 */150 STATE_IDLE,151 152 /*153 * Not moving at all. Will go to IDLE next turn.154 * (This one-turn delay is a hack to fix animation timings.)155 */156 STATE_STOPPING,157 158 /*159 * Member of a formation.160 * Pathing to the target (depending on m_PathState).161 * Target is m_TargetEntity plus m_TargetOffset.162 */163 STATE_FORMATIONMEMBER_PATH,164 165 /*166 * Individual unit or formation controller.167 * Pathing to the target (depending on m_PathState).168 * Target is m_TargetPos, m_TargetMinRange, m_TargetMaxRange;169 * if m_TargetEntity is not INVALID_ENTITY then m_TargetPos is tracking it.170 */171 STATE_INDIVIDUAL_PATH,172 173 STATE_MAX174 };175 u8 m_State;176 177 enum PathState178 {179 /*180 * There is no path.181 * (This should only happen in IDLE and STOPPING.)182 */183 PATHSTATE_NONE,184 185 /*186 * We have an outstanding long path request.187 * No paths are usable yet, so we can't move anywhere.188 */189 PATHSTATE_WAITING_REQUESTING_LONG,190 191 /*192 * We have an outstanding short path request.193 * m_LongPath is valid.194 * m_ShortPath is not yet valid, so we can't move anywhere.195 */196 PATHSTATE_WAITING_REQUESTING_SHORT,197 198 /*199 * We are following our path, and have no path requests.200 * m_LongPath and m_ShortPath are valid.201 */202 PATHSTATE_FOLLOWING,203 204 /*205 * We are following our path, and have an outstanding long path request.206 * (This is because our target moved a long way and we need to recompute207 * the whole path).208 * m_LongPath and m_ShortPath are valid.209 */210 PATHSTATE_FOLLOWING_REQUESTING_LONG,211 212 /*213 * We are following our path, and have an outstanding short path request.214 * (This is because our target moved and we've got a new long path215 * which we need to follow).216 * m_LongPath is valid; m_ShortPath is valid but obsolete.217 */218 PATHSTATE_FOLLOWING_REQUESTING_SHORT,219 220 PATHSTATE_MAX221 };222 u8 m_PathState;223 224 u32 m_ExpectedPathTicket; // asynchronous request ID we're waiting for, or 0 if none225 226 entity_id_t m_TargetEntity;227 CFixedVector2D m_TargetPos;228 CFixedVector2D m_TargetOffset;229 entity_pos_t m_TargetMinRange;230 entity_pos_t m_TargetMaxRange;231 213 214 // TARGET 215 // As long as we have a valid target, the unit is considered "on the move". 216 // It may not be actually moving for a variety of reasons (no path, blocked path)… but it will shortly. 217 SMotionGoal m_FinalGoal; 218 219 // MOTION PLANNING 220 // We will abort if we are stuck after X tries. 221 u8 m_AbortIfStuck; 222 // turn towards our target at the end 223 bool m_FacePointAfterMove; 224 // actual unit speed, after technology and ratio 232 225 fixed m_Speed; 226 // cached for convenience 227 fixed m_SpeedRatio; 233 228 234 // Current mean speed (over the last turn).235 fixed m_CurSpeed;229 // asynchronous request ID we're waiting for, or 0 if none 230 u32 m_ExpectedPathTicket; 236 231 237 232 // Currently active paths (storing waypoints in reverse order). 238 233 // The last item in each path is the point we're currently heading towards. 239 WaypointPath m_LongPath; 240 WaypointPath m_ShortPath; 241 242 // Motion planning 243 u8 m_Tries; // how many tries we've done to get to our current Final Goal. 244 245 PathGoal m_FinalGoal; 234 WaypointPath m_Path; 235 // used for the short pathfinder, incremented on each unsuccessful try. 236 u8 m_Tries; 237 // Turns to wait before a certain action. 238 u8 m_WaitingTurns; 239 // if we actually started moving at some point. 240 bool m_StartedMoving; 241 242 // Speed over the last turn 243 // cached for components that want it 244 fixed m_ActualSpeed; 246 245 247 246 static std::string GetSchema() 248 247 { … … public: 276 275 277 276 virtual void Init(const CParamNode& paramNode) 278 277 { 279 m_FormationController = paramNode.GetChild("FormationController").ToBool();280 281 m_Moving = false;282 278 m_FacePointAfterMove = true; 283 279 284 m_ WalkSpeed = m_OriginalWalkSpeed = paramNode.GetChild("WalkSpeed").ToFixed();285 m_ Speed = m_WalkSpeed;286 m_ CurSpeed = fixed::Zero();280 m_TemplateSpeed = m_Speed = paramNode.GetChild("WalkSpeed").ToFixed(); 281 m_ActualSpeed = fixed::Zero(); 282 m_SpeedRatio = fixed::FromInt(1); 287 283 288 if (paramNode.GetChild("Run").IsOk()) 289 m_RunSpeed = m_OriginalRunSpeed = paramNode.GetChild("Run").GetChild("Speed").ToFixed(); 290 else 291 m_RunSpeed = m_OriginalRunSpeed = m_WalkSpeed; 284 m_TopSpeedRatio = fixed::FromInt(1); 285 if (paramNode.GetChild("RunMultiplier").IsOk()) 286 m_TopSpeedRatio = paramNode.GetChild("WalkSpeed").ToFixed(); 292 287 293 288 CmpPtr<ICmpPathfinder> cmpPathfinder(GetSystemEntity()); 294 289 if (cmpPathfinder) … … public: 302 297 cmpObstruction->SetUnitClearance(m_Clearance); 303 298 } 304 299 305 m_State = STATE_IDLE;306 m_PathState = PATHSTATE_NONE;307 308 300 m_ExpectedPathTicket = 0; 309 301 310 302 m_Tries = 0; 311 312 m_TargetEntity = INVALID_ENTITY; 313 314 m_FinalGoal.type = PathGoal::POINT; 303 m_WaitingTurns = 0; 315 304 316 305 m_DebugOverlayEnabled = false; 306 m_AbortIfStuck = 0; 317 307 } 318 308 319 309 virtual void Deinit() … … public: 323 313 template<typename S> 324 314 void SerializeCommon(S& serialize) 325 315 { 326 serialize.NumberU8("state", m_State, 0, STATE_MAX-1);316 /*serialize.NumberU8("state", m_State, 0, STATE_MAX-1); 327 317 serialize.NumberU8("path state", m_PathState, 0, PATHSTATE_MAX-1); 328 318 329 319 serialize.StringASCII("pass class", m_PassClassName, 0, 64); … … public: 339 329 serialize.NumberFixed_Unbounded("target max range", m_TargetMaxRange); 340 330 341 331 serialize.NumberFixed_Unbounded("speed", m_Speed); 342 serialize.NumberFixed_Unbounded("current speed", m_ CurSpeed);332 serialize.NumberFixed_Unbounded("current speed", m_ActualSpeed); 343 333 344 334 serialize.Bool("moving", m_Moving); 345 335 serialize.Bool("facePointAfterMove", m_FacePointAfterMove); … … public: 349 339 SerializeVector<SerializeWaypoint>()(serialize, "long path", m_LongPath.m_Waypoints); 350 340 SerializeVector<SerializeWaypoint>()(serialize, "short path", m_ShortPath.m_Waypoints); 351 341 352 SerializeGoal()(serialize, "goal", m_FinalGoal); 342 SerializeGoal()(serialize, "goal", m_FinalGoal);*/ 353 343 } 354 344 355 345 virtual void Serialize(ISerializer& serialize) … … public: 359 349 360 350 virtual void Deserialize(const CParamNode& paramNode, IDeserializer& deserialize) 361 351 { 362 Init(paramNode);352 /*Init(paramNode); 363 353 364 354 SerializeCommon(deserialize); 365 355 366 356 CmpPtr<ICmpPathfinder> cmpPathfinder(GetSystemEntity()); 367 357 if (cmpPathfinder) 368 m_PassClass = cmpPathfinder->GetPassabilityClass(m_PassClassName); 358 m_PassClass = cmpPathfinder->GetPassabilityClass(m_PassClassName);*/ 369 359 } 370 360 371 361 virtual void HandleMessage(const CMessage& msg, bool UNUSED(global)) 372 362 { 373 363 switch (msg.GetType()) 374 364 { 375 case MT_Update_MotionFormation:376 {377 if (m_FormationController)378 {379 fixed dt = static_cast<const CMessageUpdate_MotionFormation&> (msg).turnLength;380 Move(dt);381 }382 break;383 }384 365 case MT_Update_MotionUnit: 385 366 { 386 if (!m_FormationController) 387 { 388 fixed dt = static_cast<const CMessageUpdate_MotionUnit&> (msg).turnLength; 389 Move(dt); 390 } 367 fixed dt = static_cast<const CMessageUpdate_MotionUnit&> (msg).turnLength; 368 Move(dt); 391 369 break; 392 370 } 393 371 case MT_RenderSubmit: … … public: 417 395 if (!cmpValueModificationManager) 418 396 break; 419 397 420 fixed newWalkSpeed = cmpValueModificationManager->ApplyModifications(L"UnitMotion/WalkSpeed", m_OriginalWalkSpeed, GetEntityId()); 421 fixed newRunSpeed = cmpValueModificationManager->ApplyModifications(L"UnitMotion/Run/Speed", m_OriginalRunSpeed, GetEntityId()); 422 423 // update m_Speed (the actual speed) if set to one of the variables 424 if (m_Speed == m_WalkSpeed) 425 m_Speed = newWalkSpeed; 426 else if (m_Speed == m_RunSpeed) 427 m_Speed = newRunSpeed; 398 m_Speed = m_SpeedRatio.Multiply(cmpValueModificationManager->ApplyModifications(L"UnitMotion/WalkSpeed", m_TemplateSpeed, GetEntityId())); 428 399 429 m_WalkSpeed = newWalkSpeed;430 m_RunSpeed = newRunSpeed;431 400 break; 432 401 } 433 402 } … … public: 439 408 GetSimContext().GetComponentManager().DynamicSubscriptionNonsync(MT_RenderSubmit, this, needRender); 440 409 } 441 410 442 virtual bool IsMoving() 411 virtual bool IsActuallyMoving() 412 { 413 return m_StartedMoving; 414 } 415 416 virtual bool IsTryingToMove() 417 { 418 // speed check as sanity check to avoid infinite loops. 419 return m_FinalGoal.Valid() && m_Speed > fixed::Zero(); 420 } 421 422 virtual fixed GetTemplateSpeed() 423 { 424 return m_TemplateSpeed; 425 } 426 427 virtual fixed GetSpeed() 443 428 { 444 return m_ Moving;429 return m_Speed; 445 430 } 446 431 447 virtual fixed Get WalkSpeed()432 virtual fixed GetActualSpeed() 448 433 { 449 return m_ WalkSpeed;434 return m_ActualSpeed; 450 435 } 451 436 452 virtual fixed Get RunSpeed()437 virtual fixed GetSpeedRatio() 453 438 { 454 return m_RunSpeed; 439 return m_SpeedRatio; 440 } 441 442 virtual fixed GetTopSpeedRatio() 443 { 444 return m_TopSpeedRatio; 445 } 446 447 // don't call this all the time 448 // it's voluntarily too slow, because you shouldn't be doing this. 449 virtual void SetSpeed(fixed ratio) 450 { 451 m_SpeedRatio = ratio; 452 CmpPtr<ICmpValueModificationManager> cmpValueModificationManager(GetSystemEntity()); 453 if (cmpValueModificationManager) 454 { 455 m_Speed = m_SpeedRatio.Multiply(cmpValueModificationManager->ApplyModifications(L"UnitMotion/WalkSpeed", m_TemplateSpeed, GetEntityId())); 456 return; 457 } 458 459 m_Speed = m_SpeedRatio.Multiply(m_TemplateSpeed); 455 460 } 456 461 457 462 virtual pass_class_t GetPassabilityClass() … … public: 472 477 m_PassClass = cmpPathfinder->GetPassabilityClass(passClassName); 473 478 } 474 479 475 virtual fixed GetCurrentSpeed()476 {477 return m_CurSpeed;478 }479 480 virtual void SetSpeed(fixed speed)481 {482 m_Speed = speed;483 }484 485 480 virtual void SetFacePointAfterMove(bool facePointAfterMove) 486 481 { 487 482 m_FacePointAfterMove = facePointAfterMove; … … public: 493 488 UpdateMessageSubscriptions(); 494 489 } 495 490 491 virtual entity_pos_t GetUnitClearance() 492 { 493 return m_Clearance; 494 } 495 496 496 virtual bool MoveToPointRange(entity_pos_t x, entity_pos_t z, entity_pos_t minRange, entity_pos_t maxRange); 497 497 virtual bool IsInPointRange(entity_pos_t x, entity_pos_t z, entity_pos_t minRange, entity_pos_t maxRange); 498 498 virtual bool MoveToTargetRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange); 499 499 virtual bool IsInTargetRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange); 500 virtual void MoveToFormationOffset(entity_id_t target, entity_pos_t x, entity_pos_t z);501 500 502 501 virtual void FaceTowardsPoint(entity_pos_t x, entity_pos_t z); 503 502 504 virtual void S topMoving()503 virtual void SetAbortIfStuck(u8 shouldAbort) 505 504 { 506 m_Moving = false; 507 m_ExpectedPathTicket = 0; 508 m_State = STATE_STOPPING; 509 m_PathState = PATHSTATE_NONE; 510 m_LongPath.m_Waypoints.clear(); 511 m_ShortPath.m_Waypoints.clear(); 505 m_AbortIfStuck = shouldAbort; 512 506 } 513 507 514 virtual entity_pos_t GetUnitClearance()508 virtual void DiscardMove() 515 509 { 516 return m_Clearance;510 StopMovingQuietly(); 517 511 } 518 512 519 private: 520 bool ShouldAvoidMovingUnits() const513 // stop moving and send message 514 virtual void CompleteMove() 521 515 { 522 return !m_FormationController; 516 // highlight bugs. 517 if (!IsTryingToMove()) 518 { 519 LOGERROR("Entity %i trying to stop moving but has not actually started", GetEntityId()); 520 return; 521 } 522 523 StopMovingQuietly(); 524 525 if (m_FacePointAfterMove) 526 { 527 CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle()); 528 if (cmpPosition && cmpPosition->IsInWorld()) 529 FaceTowardsPointFromPos(cmpPosition->GetPosition2D(), m_FinalGoal.X(), m_FinalGoal.Z()); 530 } 531 532 CMessageFinishedMove msg(false); 533 GetSimContext().GetComponentManager().PostMessage(GetEntityId(), msg); 523 534 } 524 535 536 private: 537 /* 538 TODO: reimplement 525 539 bool IsFormationMember() const 526 540 { 527 541 return m_State == STATE_FORMATIONMEMBER_PATH; 528 542 } 529 543 */ 530 544 entity_id_t GetGroup() const 531 545 { 532 return IsFormationMember() ? m_TargetEntity : GetEntityId(); 546 //return IsFormationMember() ? m_TargetEntity : GetEntityId(); 547 return GetEntityId(); 533 548 } 534 549 535 550 bool HasValidPath() const 536 551 { 537 return m_PathState == PATHSTATE_FOLLOWING 538 || m_PathState == PATHSTATE_FOLLOWING_REQUESTING_LONG 539 || m_PathState == PATHSTATE_FOLLOWING_REQUESTING_SHORT; 552 return !m_Path.m_Waypoints.empty(); 540 553 } 541 554 542 void St artFailed()555 void StopMovingQuietly() 543 556 { 544 StopMoving(); 545 m_State = STATE_IDLE; // don't go through the STOPPING state since we never even started 557 // sanity 558 m_Tries = 0; 559 m_WaitingTurns = 0; 560 m_StartedMoving = false; 561 562 // reset state. 563 m_ExpectedPathTicket = 0; 564 m_FinalGoal.Clear(); 565 m_Path.m_Waypoints.clear(); 546 566 547 567 CmpPtr<ICmpObstruction> cmpObstruction(GetEntityHandle()); 548 568 if (cmpObstruction) 549 569 cmpObstruction->SetMovingFlag(false); 550 570 551 CMessageMotionChanged msg(true, true); 552 GetSimContext().GetComponentManager().PostMessage(GetEntityId(), msg); 571 CmpPtr<ICmpVisual> cmpVisualActor(GetEntityHandle()); 572 if (cmpVisualActor) 573 cmpVisualActor->SetMoving(true); 553 574 } 554 575 555 576 void MoveFailed() 556 577 { 557 StopMoving ();578 StopMovingQuietly(); 558 579 559 CmpPtr<ICmpObstruction> cmpObstruction(GetEntityHandle()); 560 if (cmpObstruction) 561 cmpObstruction->SetMovingFlag(false); 562 563 CMessageMotionChanged msg(false, true); 580 CMessageFinishedMove msg(true); 564 581 GetSimContext().GetComponentManager().PostMessage(GetEntityId(), msg); 582 CmpPtr<ICmpVisual> cmpVisualActor(GetEntityHandle()); 583 if (cmpVisualActor) 584 cmpVisualActor->SetMoving(false); 565 585 } 566 586 567 void StartSucceeded()587 void MovePaused() 568 588 { 569 CmpPtr<ICmpObstruction> cmpObstruction(GetEntityHandle()); 570 if (cmpObstruction) 571 cmpObstruction->SetMovingFlag(true); 589 m_StartedMoving = false; 572 590 573 m_Moving = true; 574 575 CMessageMotionChanged msg(true, false); 591 CMessagePausedMove msg; 576 592 GetSimContext().GetComponentManager().PostMessage(GetEntityId(), msg); 593 CmpPtr<ICmpVisual> cmpVisualActor(GetEntityHandle()); 594 if (cmpVisualActor) 595 cmpVisualActor->SetMoving(false); 577 596 } 578 597 579 void MoveS ucceeded()598 void MoveStarted() 580 599 { 581 m_Moving = false; 582 583 CmpPtr<ICmpObstruction> cmpObstruction(GetEntityHandle()); 584 if (cmpObstruction) 585 cmpObstruction->SetMovingFlag(false); 600 m_StartedMoving = true; 586 601 587 // No longer moving, so speed is 0. 588 m_CurSpeed = fixed::Zero(); 589 590 CMessageMotionChanged msg(false, false); 602 CMessageBeginMove msg; 591 603 GetSimContext().GetComponentManager().PostMessage(GetEntityId(), msg); 604 CmpPtr<ICmpVisual> cmpVisualActor(GetEntityHandle()); 605 if (cmpVisualActor) 606 cmpVisualActor->SetMoving(true); 592 607 } 593 608 594 609 bool MoveToPointRange(entity_pos_t x, entity_pos_t z, entity_pos_t minRange, entity_pos_t maxRange, entity_id_t target); … … private: 634 649 bool CheckTargetMovement(const CFixedVector2D& from, entity_pos_t minDelta); 635 650 636 651 /** 637 * Update goal position if moving target638 */639 void UpdateFinalGoal();640 641 /**642 652 * Returns whether we are close enough to the target to assume it's a good enough 643 653 * position to stop. 644 654 */ 645 bool ShouldConsiderOurselvesAtDestination( const CFixedVector2D& from);655 bool ShouldConsiderOurselvesAtDestination(); 646 656 647 657 /** 648 658 * Returns whether the length of the given path, plus the distance from … … private: 663 673 ControlGroupMovementObstructionFilter GetObstructionFilter(bool noTarget = false) const; 664 674 665 675 /** 666 * Start moving to the given goal, from our current position 'from'.676 * Dump current paths and request a new one. 667 677 * Might go in a straight line immediately, or might start an asynchronous 668 678 * path request. 669 679 */ 670 void BeginPathing(const CFixedVector2D& from, const PathGoal& goal);680 void RequestNewPath(); 671 681 672 682 /** 673 683 * Start an asynchronous long path query. … … REGISTER_COMPONENT_TYPE(UnitMotion) 691 701 692 702 void CCmpUnitMotion::PathResult(u32 ticket, const WaypointPath& path) 693 703 { 694 // reset our state for sanity.695 CmpPtr<ICmpObstruction> cmpObstruction(GetEntityHandle());696 if (cmpObstruction)697 cmpObstruction->SetMovingFlag(false);698 699 m_Moving = false;700 701 704 // Ignore obsolete path requests 702 705 if (ticket != m_ExpectedPathTicket) 703 706 return; 704 707 705 708 m_ExpectedPathTicket = 0; // we don't expect to get this result again 706 709 707 // Check that we are still able to do something with that path 708 CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle()); 709 if (!cmpPosition || !cmpPosition->IsInWorld()) 710 { 711 if (m_PathState == PATHSTATE_WAITING_REQUESTING_LONG || m_PathState == PATHSTATE_WAITING_REQUESTING_SHORT) 712 StartFailed(); 713 else if (m_PathState == PATHSTATE_FOLLOWING_REQUESTING_LONG || m_PathState == PATHSTATE_FOLLOWING_REQUESTING_SHORT) 714 StopMoving(); 710 if (!m_FinalGoal.Valid()) 715 711 return; 716 }717 712 718 if ( m_PathState == PATHSTATE_WAITING_REQUESTING_LONG || m_PathState == PATHSTATE_FOLLOWING_REQUESTING_LONG)713 if (path.m_Waypoints.empty()) 719 714 { 720 m_LongPath = path; 715 // no waypoints, path failed. 716 // if we have some room, pop waypoint 717 // TODO: this isn't particularly bright. 718 if (!m_Path.m_Waypoints.empty()) 719 m_Path.m_Waypoints.pop_back(); 721 720 722 // If we are following a path, leave the old m_ShortPath so we can carry on following it 723 // until a new short path has been computed 724 if (m_PathState == PATHSTATE_WAITING_REQUESTING_LONG) 725 m_ShortPath.m_Waypoints.clear(); 726 727 // If there's no waypoints then we couldn't get near the target. 728 // Sort of hack: Just try going directly to the goal point instead 729 // (via the short pathfinder), so if we're stuck and the user clicks 730 // close enough to the unit then we can probably get unstuck 731 if (m_LongPath.m_Waypoints.empty()) 732 m_LongPath.m_Waypoints.emplace_back(Waypoint{ m_FinalGoal.x, m_FinalGoal.z }); 733 734 if (!HasValidPath()) 735 StartSucceeded(); 736 737 m_PathState = PATHSTATE_FOLLOWING; 738 739 if (cmpObstruction) 740 cmpObstruction->SetMovingFlag(true); 741 742 m_Moving = true; 721 // we will then deal with this on the next Move() call. 722 return; 743 723 } 744 else if (m_PathState == PATHSTATE_WAITING_REQUESTING_SHORT || m_PathState == PATHSTATE_FOLLOWING_REQUESTING_SHORT)745 {746 m_ShortPath = path;747 748 // If there's no waypoints then we couldn't get near the target749 if (m_ShortPath.m_Waypoints.empty())750 {751 // If we're globally following a long path, try to remove the next waypoint, it might be obstructed752 // If not, and we are not in a formation, retry753 // unless we are close to our target and we don't have a target entity.754 // This makes sure that units don't clump too much when they are not in a formation and tasked to move.755 if (m_LongPath.m_Waypoints.size() > 1)756 m_LongPath.m_Waypoints.pop_back();757 else if (IsFormationMember())758 {759 m_Moving = false;760 CMessageMotionChanged msg(true, true);761 GetSimContext().GetComponentManager().PostMessage(GetEntityId(), msg);762 return;763 }764 765 CMessageMotionChanged msg(false, false);766 GetSimContext().GetComponentManager().PostMessage(GetEntityId(), msg);767 768 CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle());769 if (!cmpPosition || !cmpPosition->IsInWorld())770 return;771 724 772 CFixedVector2D pos = cmpPosition->GetPosition2D(); 773 774 if (ShouldConsiderOurselvesAtDestination(pos)) 775 return; 776 777 UpdateFinalGoal(); 778 RequestLongPath(pos, m_FinalGoal); 779 m_PathState = PATHSTATE_WAITING_REQUESTING_LONG; 780 return; 781 } 782 783 // else we could, so reset our number of tries. 784 m_Tries = 0; 785 786 // Now we've got a short path that we can follow 787 if (!HasValidPath()) 788 StartSucceeded(); 789 790 m_PathState = PATHSTATE_FOLLOWING; 791 792 if (cmpObstruction) 793 cmpObstruction->SetMovingFlag(true); 794 795 m_Moving = true; 796 } 797 else 798 LOGWARNING("unexpected PathResult (%u %d %d)", GetEntityId(), m_State, m_PathState); 725 // add to the top of our current waypoints 726 m_Path.m_Waypoints.insert(m_Path.m_Waypoints.end(), path.m_Waypoints.begin(), path.m_Waypoints.end()); 799 727 } 800 728 801 729 void CCmpUnitMotion::Move(fixed dt) 802 730 { 803 731 PROFILE("Move"); 804 732 805 if (m_State == STATE_STOPPING) 733 /** 734 * TODO: the visual actor doesn't interpolate, it merely changes things on update 735 * This means if a unit wants to change animation between turns (because it stops…) 736 * It will look slightly glitchy for a very short while 737 */ 738 739 if (!IsTryingToMove()) 806 740 { 807 m_State = STATE_IDLE; 808 MoveSucceeded(); 741 m_ActualSpeed = fixed::Zero(); 809 742 return; 810 743 } 811 744 812 if (m_State == STATE_IDLE) 745 m_FinalGoal.UpdateTargetPosition(GetSimContext()); 746 747 // TODO: units will look at each other's position in an arbitrary order that must be the same for any simulation 748 // In particular this means no threading. Maybe we should update this someday if it's possible. 749 750 CmpPtr<ICmpPathfinder> cmpPathfinder(GetSystemEntity()); 751 if (!cmpPathfinder) 813 752 return; 814 753 815 switch (m_PathState) 816 { 817 case PATHSTATE_NONE: 818 { 819 // If we're not pathing, do nothing 754 CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle()); 755 if (!cmpPosition || !cmpPosition->IsInWorld()) 820 756 return; 821 }822 757 823 case PATHSTATE_WAITING_REQUESTING_LONG: 824 case PATHSTATE_WAITING_REQUESTING_SHORT: 758 CFixedVector2D initialPos = cmpPosition->GetPosition2D(); 759 760 // Preliminary check: our target may be an entity and may have moved before us 761 if (ShouldConsiderOurselvesAtDestination()) 825 762 { 826 // If we're waiting for a path and don't have one yet, do nothing763 CompleteMove(); 827 764 return; 828 765 } 829 766 830 case PATHSTATE_FOLLOWING: 831 case PATHSTATE_FOLLOWING_REQUESTING_SHORT: 832 case PATHSTATE_FOLLOWING_REQUESTING_LONG: 833 { 834 // TODO: there's some asymmetry here when units look at other 835 // units' positions - the result will depend on the order of execution. 836 // Maybe we should split the updates into multiple phases to minimise 837 // that problem. 767 // TODO: here should go things such as: 768 // - has our target moved enough that we should re-path? 769 // end TODO 838 770 839 CmpPtr<ICmpPathfinder> cmpPathfinder(GetSystemEntity()); 840 if (!cmpPathfinder) 841 return; 771 // Keep track of the current unit's position during the update 772 CFixedVector2D pos = initialPos; 842 773 843 CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle()); 844 if (!cmpPosition || !cmpPosition->IsInWorld()) 845 return; 774 // Find the speed factor of the underlying terrain 775 // (We only care about the tile we start on - it doesn't matter if we're moving 776 // partially onto a much slower/faster tile) 777 // TODO: Terrain-dependent speeds are not currently supported 778 // TODO: note that this is also linked to pathfinding so maybe never supported 779 // fixed terrainSpeed = fixed::FromInt(1); 846 780 847 CFixedVector2D initialPos = cmpPosition->GetPosition2D();781 bool wasObstructed = false; 848 782 849 // If we're chasing a potentially-moving unit and are currently close 850 // enough to its current position, and we can head in a straight line 851 // to it, then throw away our current path and go straight to it 852 if (m_PathState == PATHSTATE_FOLLOWING) 853 TryGoingStraightToTargetEntity(initialPos); 783 // We want to move (at most) m_Speed*dt units from pos towards the next waypoint 854 784 855 // Keep track of the current unit's position during the update 856 CFixedVector2D pos = initialPos; 785 fixed timeLeft = dt; 857 786 858 // If in formation, run to keep up; otherwise just walk 859 fixed basicSpeed; 860 if (IsFormationMember()) 861 basicSpeed = GetRunSpeed(); 862 else 863 basicSpeed = m_Speed; // (typically but not always WalkSpeed) 787 while (timeLeft > fixed::Zero()) 788 { 789 // If we ran out of path, we have to stop 790 if (!HasValidPath()) 791 break; 864 792 865 // Find the speed factor of the underlying terrain 866 // (We only care about the tile we start on - it doesn't matter if we're moving 867 // partially onto a much slower/faster tile) 868 // TODO: Terrain-dependent speeds are not currently supported 869 fixed terrainSpeed = fixed::FromInt(1); 793 CFixedVector2D target; 794 target = CFixedVector2D(m_Path.m_Waypoints.back().x, m_Path.m_Waypoints.back().z); 870 795 871 fixed maxSpeed = basicSpeed.Multiply(terrainSpeed); 796 CFixedVector2D offset = target - pos; 797 fixed offsetLength = offset.Length(); 798 // Work out how far we can travel in timeLeft 799 fixed maxdist = m_Speed.Multiply(timeLeft); 872 800 873 bool wasObstructed = false; 801 CFixedVector2D destination; 802 if (offsetLength <= maxdist) 803 destination = target; 804 else 805 { 806 offset.Normalize(maxdist); 807 destination = pos + offset; 808 } 874 809 875 // We want to move (at most) maxSpeed*dt units from pos towards the next waypoint 810 // TODO: try moving as much as we can still? 811 // TODO: get more information about what blocked us. 812 if (cmpPathfinder->CheckMovement(GetObstructionFilter(), pos.X, pos.Y, target.X, target.Y, m_Clearance, m_PassClass)) 813 { 814 pos = destination; 876 815 877 fixed timeLeft = dt; 878 fixed zero = fixed::Zero(); 816 timeLeft = (timeLeft.Multiply(m_Speed) - offsetLength) / m_Speed; 879 817 880 while (timeLeft > zero) 818 if (destination == target) 819 m_Path.m_Waypoints.pop_back(); 820 continue; 821 } 822 else 881 823 { 882 // If we ran out of path, we have to stop 883 if (m_ShortPath.m_Waypoints.empty() && m_LongPath.m_Waypoints.empty()) 884 break; 885 886 CFixedVector2D target; 887 if (m_ShortPath.m_Waypoints.empty()) 888 target = CFixedVector2D(m_LongPath.m_Waypoints.back().x, m_LongPath.m_Waypoints.back().z); 889 else 890 target = CFixedVector2D(m_ShortPath.m_Waypoints.back().x, m_ShortPath.m_Waypoints.back().z); 824 // Error - path was obstructed 825 wasObstructed = true; 826 break; 827 } 828 } 891 829 892 CFixedVector2D offset = target - pos; 830 if (!m_StartedMoving && wasObstructed) 831 // If this is the turn we start moving, and we're already obstructed, 832 // fail the move entirely to avoid weirdness. 833 // (we would need to send a "move started" and a "move failed" message in the same turn) 834 pos = initialPos; 893 835 894 // Work out how far we can travel in timeLeft 895 fixed maxdist = maxSpeed.Multiply(timeLeft); 836 // Update the Position component after our movement (if we actually moved anywhere) 837 if (pos != initialPos) 838 { 839 CFixedVector2D offset = pos - initialPos; 896 840 897 // If the target is close, we can move there directly 898 fixed offsetLength = offset.Length(); 899 if (offsetLength <= maxdist) 900 { 901 if (cmpPathfinder->CheckMovement(GetObstructionFilter(), pos.X, pos.Y, target.X, target.Y, m_Clearance, m_PassClass)) 902 { 903 pos = target; 904 905 // Spend the rest of the time heading towards the next waypoint 906 timeLeft = timeLeft - (offsetLength / maxSpeed); 907 908 if (m_ShortPath.m_Waypoints.empty()) 909 m_LongPath.m_Waypoints.pop_back(); 910 else 911 m_ShortPath.m_Waypoints.pop_back(); 912 913 continue; 914 } 915 else 916 { 917 // Error - path was obstructed 918 wasObstructed = true; 919 break; 920 } 921 } 922 else 923 { 924 // Not close enough, so just move in the right direction 925 offset.Normalize(maxdist); 926 target = pos + offset; 841 // Face towards the target 842 entity_angle_t angle = atan2_approx(offset.X, offset.Y); 843 cmpPosition->MoveAndTurnTo(pos.X,pos.Y, angle); 927 844 928 if (cmpPathfinder->CheckMovement(GetObstructionFilter(), pos.X, pos.Y, target.X, target.Y, m_Clearance, m_PassClass)) 929 pos = target; 930 else 931 wasObstructed = true; // Error - path was obstructed 845 // Calculate the mean speed over this past turn. 846 m_ActualSpeed = cmpPosition->GetDistanceTravelled() / dt; 932 847 933 break;934 }935 }848 // tell other components and visual actor we are moving. 849 if (!m_StartedMoving) 850 MoveStarted(); 936 851 937 // Update the Position component after our movement (if we actually moved anywhere) 938 if (pos != initialPos) 852 // Check if we are at our destination 853 // since we're already checking in the general case at the beginning of this function, 854 // no need to do this outside this if block. 855 if (ShouldConsiderOurselvesAtDestination()) 939 856 { 940 CFixedVector2D offset = pos - initialPos; 941 942 // Face towards the target 943 entity_angle_t angle = atan2_approx(offset.X, offset.Y); 944 cmpPosition->MoveAndTurnTo(pos.X,pos.Y, angle); 945 946 // Calculate the mean speed over this past turn. 947 m_CurSpeed = cmpPosition->GetDistanceTravelled() / dt; 857 CompleteMove(); 858 return; 948 859 } 949 860 950 if ( wasObstructed)861 if (!wasObstructed) 951 862 { 952 // Oops, we hit something (very likely another unit). 953 // This is when we might easily get stuck wrongly. 954 955 // check if we've arrived. 956 if (ShouldConsiderOurselvesAtDestination(pos)) 957 return; 958 959 // If we still have long waypoints, try and compute a short path 960 // This will get us around units, amongst others. 961 // However in some cases a long waypoint will be in located in the obstruction of 962 // an idle unit. In that case, we need to scrap that waypoint or we might never be able to reach it. 963 // I am not sure why this happens but the following code seems to work. 964 if (!m_LongPath.m_Waypoints.empty()) 965 { 966 CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSystemEntity()); 967 if (cmpObstructionManager) 968 { 969 // create a fake obstruction to represent our waypoint. 970 ICmpObstructionManager::ObstructionSquare square; 971 square.hh = m_Clearance; 972 square.hw = m_Clearance; 973 square.u = CFixedVector2D(entity_pos_t::FromInt(1),entity_pos_t::FromInt(0)); 974 square.v = CFixedVector2D(entity_pos_t::FromInt(0),entity_pos_t::FromInt(1)); 975 square.x = m_LongPath.m_Waypoints.back().x; 976 square.z = m_LongPath.m_Waypoints.back().z; 977 std::vector<entity_id_t> unitOnGoal; 978 // don't ignore moving units as those might be units like us, ie not really moving. 979 cmpObstructionManager->GetUnitsOnObstruction(square, unitOnGoal, GetObstructionFilter(), true); 980 if (!unitOnGoal.empty()) 981 m_LongPath.m_Waypoints.pop_back(); 982 } 983 if (!m_LongPath.m_Waypoints.empty()) 984 { 985 PathGoal goal; 986 if (m_LongPath.m_Waypoints.size() > 1 || m_FinalGoal.DistanceToPoint(pos) > LONG_PATH_MIN_DIST) 987 goal = { PathGoal::POINT, m_LongPath.m_Waypoints.back().x, m_LongPath.m_Waypoints.back().z }; 988 else 989 { 990 UpdateFinalGoal(); 991 goal = m_FinalGoal; 992 m_LongPath.m_Waypoints.clear(); 993 CFixedVector2D target = goal.NearestPointOnGoal(pos); 994 m_LongPath.m_Waypoints.emplace_back(Waypoint{ target.X, target.Y }); 995 } 996 RequestShortPath(pos, goal, true); 997 m_PathState = PATHSTATE_WAITING_REQUESTING_SHORT; 998 return; 999 } 1000 } 1001 // Else, just entirely recompute 1002 UpdateFinalGoal(); 1003 BeginPathing(pos, m_FinalGoal); 1004 1005 // potential TODO: We could switch the short-range pathfinder for something else entirely. 863 // everything is going smoothly, return. 864 m_Tries = 0; 865 m_WaitingTurns = 0; 1006 866 return; 1007 867 } 868 } 1008 869 1009 // We successfully moved along our path, until running out of 1010 // waypoints or time. 870 // tell relevant components we have paused if necessary 871 if (m_StartedMoving) 872 MovePaused(); 1011 873 1012 if (m_PathState == PATHSTATE_FOLLOWING) 1013 { 1014 // If we're not currently computing any new paths: 1015 if (m_LongPath.m_Waypoints.empty() && m_ShortPath.m_Waypoints.empty()) 1016 { 1017 if (IsFormationMember()) 1018 { 1019 // We've reached our assigned position. If the controller 1020 // is idle, send a notification in case it should disband, 1021 // otherwise continue following the formation next turn. 1022 CmpPtr<ICmpUnitMotion> cmpUnitMotion(GetSimContext(), m_TargetEntity); 1023 if (cmpUnitMotion && !cmpUnitMotion->IsMoving()) 1024 { 1025 CmpPtr<ICmpObstruction> cmpObstruction(GetEntityHandle()); 1026 if (cmpObstruction) 1027 cmpObstruction->SetMovingFlag(false); 1028 1029 m_Moving = false; 1030 CMessageMotionChanged msg(false, false); 1031 GetSimContext().GetComponentManager().PostMessage(GetEntityId(), msg); 1032 } 1033 } 1034 else 1035 { 1036 // check if target was reached in case of a moving target 1037 CmpPtr<ICmpUnitMotion> cmpUnitMotion(GetSimContext(), m_TargetEntity); 1038 if (cmpUnitMotion && cmpUnitMotion->IsMoving() && 1039 MoveToTargetRange(m_TargetEntity, m_TargetMinRange, m_TargetMaxRange)) 1040 return; 1041 1042 // Not in formation, so just finish moving 1043 StopMoving(); 1044 m_State = STATE_IDLE; 1045 MoveSucceeded(); 1046 1047 if (m_FacePointAfterMove) 1048 FaceTowardsPointFromPos(pos, m_FinalGoal.x, m_FinalGoal.z); 1049 // TODO: if the goal was a square building, we ought to point towards the 1050 // nearest point on the square, not towards its center 1051 } 1052 } 874 // Oops, we've had a problem. Either we were obstructed, or we ran out of path (but still have a goal). 875 // Handle it. 876 // Failure to handle it will result in stuckness and players complaining. 1053 877 1054 // If we have a target entity, and we're not miles away from the end of 1055 // our current path, and the target moved enough, then recompute our 1056 // whole path 1057 if (IsFormationMember()) 1058 CheckTargetMovement(pos, CHECK_TARGET_MOVEMENT_MIN_DELTA_FORMATION); 1059 else 1060 CheckTargetMovement(pos, CHECK_TARGET_MOVEMENT_MIN_DELTA); 1061 } 1062 } 878 if (m_ExpectedPathTicket != 0) 879 // wait until we get our path to see where that leads us. 880 return; 881 882 // give us some turns to recover. 883 // TODO: only do this if we ran into a moving unit and not something else, because something else won't move 884 if (m_WaitingTurns == 0) 885 { 886 if (HasValidPath()) 887 m_WaitingTurns = MAX_PATH_REATTEMPS + 1; 888 else 889 m_WaitingTurns = 3; 1063 890 } 1064 }1065 891 1066 bool CCmpUnitMotion::ComputeTargetPosition(CFixedVector2D& out) 1067 { 1068 if (m_TargetEntity == INVALID_ENTITY) 1069 return false; 892 --m_WaitingTurns; 1070 893 1071 CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), m_TargetEntity);1072 if ( !cmpPosition || !cmpPosition->IsInWorld())1073 return false;894 // Try again next turn, no changes 895 if (m_WaitingTurns >= MAX_PATH_REATTEMPS) 896 return; 1074 897 1075 if (m_TargetOffset.IsZero()) 898 // already waited one turn, no changes, so try computing a short path. 899 if (m_WaitingTurns >= 3) 1076 900 { 1077 // No offset, just return the position directly 1078 out = cmpPosition->GetPosition2D(); 901 PathGoal goal; 902 if (m_Path.m_Waypoints.empty()) 903 goal = { PathGoal::POINT, m_FinalGoal.X(), m_FinalGoal.Z() }; 904 else 905 { 906 goal = { PathGoal::POINT, m_Path.m_Waypoints.back().x, m_Path.m_Waypoints.back().z }; 907 m_Path.m_Waypoints.pop_back(); 908 } 909 RequestShortPath(pos, goal, true); 910 return; 1079 911 } 1080 else 912 913 // Last resort, compute a long path 914 if (m_WaitingTurns == 2) 1081 915 { 1082 // There is an offset, so compute it relative to orientation 1083 entity_angle_t angle = cmpPosition->GetRotation().Y; 1084 CFixedVector2D offset = m_TargetOffset.Rotate(angle); 1085 out = cmpPosition->GetPosition2D() + offset; 916 PathGoal goal; 917 if (m_Path.m_Waypoints.empty()) 918 goal = { PathGoal::POINT, m_FinalGoal.X(), m_FinalGoal.Z() }; 919 else 920 { 921 goal = { PathGoal::POINT, m_Path.m_Waypoints.back().x, m_Path.m_Waypoints.back().z }; 922 m_Path.m_Waypoints.pop_back(); 923 } 924 RequestLongPath(pos, goal); 925 return; 1086 926 } 1087 return true;1088 }1089 927 1090 bool CCmpUnitMotion::TryGoingStraightToGoalPoint(const CFixedVector2D& from)1091 {1092 // Make sure the goal is a point (and not a point-like target like a formation controller)1093 if (m_FinalGoal.type != PathGoal::POINT || m_TargetEntity != INVALID_ENTITY)1094 return false;1095 928 1096 // Fail if the goal is too far away 1097 CFixedVector2D goalPos(m_FinalGoal.x, m_FinalGoal.z); 1098 if ((goalPos - from).CompareLength(DIRECT_PATH_RANGE) > 0) 1099 return false; 929 // m_waitingTurns == 1 here 1100 930 1101 CmpPtr<ICmpPathfinder> cmpPathfinder(GetSystemEntity()); 1102 if (!cmpPathfinder) 1103 return false; 931 // we tried getting a renewed path and still got stuck 932 if (m_AbortIfStuck == 0) 933 { 934 MoveFailed(); 935 return; 936 } 1104 937 1105 // Check if there's any collisions on that route 1106 if (!cmpPathfinder->CheckMovement(GetObstructionFilter(), from.X, from.Y, goalPos.X, goalPos.Y, m_Clearance, m_PassClass)) 1107 return false; 938 --m_AbortIfStuck; 1108 939 1109 // That route is okay, so update our path 1110 m_LongPath.m_Waypoints.clear(); 1111 m_ShortPath.m_Waypoints.clear(); 1112 m_ShortPath.m_Waypoints.emplace_back(Waypoint{ goalPos.X, goalPos.Y }); 940 // Recompute a new path, but wait a dozen turns. 941 m_WaitingTurns = 12 + MAX_PATH_REATTEMPS; 1113 942 1114 return true;943 return; 1115 944 } 1116 945 1117 bool CCmpUnitMotion::TryGoingStraightToTargetEntity(const CFixedVector2D& from) 946 // TODO: this should care about target movement 947 bool CCmpUnitMotion::CheckTargetMovement(const CFixedVector2D& from, entity_pos_t minDelta) 1118 948 { 1119 CFixedVector2D targetPos; 1120 if (!ComputeTargetPosition(targetPos)) 949 if (!m_FinalGoal.TargetIsEntity()) 1121 950 return false; 1122 951 1123 // Fail if the target is too far away 1124 if ((targetPos - from).CompareLength(DIRECT_PATH_RANGE) > 0) 1125 return false; 952 if (!HasValidPath()) 953 return true; 1126 954 1127 CmpPtr<ICmpPathfinder> cmpPathfinder(GetSystemEntity()); 1128 if (!cmpPathfinder) 1129 return false; 1130 1131 // Move the goal to match the target entity's new position 1132 PathGoal goal = m_FinalGoal; 1133 goal.x = targetPos.X; 1134 goal.z = targetPos.Y; 1135 // (we ignore changes to the target's rotation, since only buildings are 1136 // square and buildings don't move) 1137 1138 // Find the point on the goal shape that we should head towards 1139 CFixedVector2D goalPos = goal.NearestPointOnGoal(from); 1140 1141 // Check if there's any collisions on that route 1142 if (!cmpPathfinder->CheckMovement(GetObstructionFilter(true), from.X, from.Y, goalPos.X, goalPos.Y, m_Clearance, m_PassClass)) 1143 return false; 1144 1145 // That route is okay, so update our path 1146 m_FinalGoal = goal; 1147 m_LongPath.m_Waypoints.clear(); 1148 m_ShortPath.m_Waypoints.clear(); 1149 m_ShortPath.m_Waypoints.emplace_back(Waypoint{ goalPos.X, goalPos.Y }); 1150 1151 return true; 1152 } 955 // Fail unless the target has moved enough 956 CFixedVector2D oldTargetPos = CFixedVector2D(m_Path.m_Waypoints[0].x,m_Path.m_Waypoints[0].z); 1153 957 1154 bool CCmpUnitMotion::CheckTargetMovement(const CFixedVector2D& from, entity_pos_t minDelta) 1155 { 1156 CFixedVector2D targetPos; 1157 if (!ComputeTargetPosition(targetPos)) 958 if ((m_FinalGoal.Pos() - oldTargetPos).CompareLength(minDelta) < 0) 1158 959 return false; 1159 960 1160 // Fail unless the target has moved enough1161 CFixedVector2D oldTargetPos(m_FinalGoal.x, m_FinalGoal.z);1162 if ((targetPos - oldTargetPos).CompareLength(minDelta) < 0)1163 return false;1164 961 1165 962 CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle()); 1166 963 if (!cmpPosition || !cmpPosition->IsInWorld()) 1167 964 return false; 1168 965 CFixedVector2D pos = cmpPosition->GetPosition2D(); 1169 966 CFixedVector2D oldDir = (oldTargetPos - pos); 1170 CFixedVector2D newDir = ( targetPos- pos);967 CFixedVector2D newDir = (m_FinalGoal.Pos() - pos); 1171 968 oldDir.Normalize(); 1172 969 newDir.Normalize(); 1173 970 1174 971 // Fail unless we're close enough to the target to care about its movement 1175 972 // and the angle between the (straight-line) directions of the previous and new target positions is small 1176 if (oldDir.Dot(newDir) > CHECK_TARGET_MOVEMENT_MIN_COS && !PathIsShort(m_ LongPath, from, CHECK_TARGET_MOVEMENT_AT_MAX_DIST))973 if (oldDir.Dot(newDir) > CHECK_TARGET_MOVEMENT_MIN_COS && !PathIsShort(m_Path, from, CHECK_TARGET_MOVEMENT_AT_MAX_DIST)) 1177 974 return false; 1178 975 1179 976 // Fail if the target is no longer visible to this entity's owner … … bool CCmpUnitMotion::CheckTargetMovement(const CFixedVector2D& from, entity_pos_ 1183 980 if (cmpOwnership) 1184 981 { 1185 982 CmpPtr<ICmpRangeManager> cmpRangeManager(GetSystemEntity()); 1186 if (cmpRangeManager && cmpRangeManager->GetLosVisibility(m_ TargetEntity, cmpOwnership->GetOwner()) == ICmpRangeManager::VIS_HIDDEN)983 if (cmpRangeManager && cmpRangeManager->GetLosVisibility(m_FinalGoal.GetEntity(), cmpOwnership->GetOwner()) == ICmpRangeManager::VIS_HIDDEN) 1187 984 return false; 1188 985 } 1189 986 1190 987 // The target moved and we need to update our current path; 1191 // change the goal here and expect our caller to start the path request 1192 m_FinalGoal.x = targetPos.X; 1193 m_FinalGoal.z = targetPos.Y; 1194 RequestLongPath(from, m_FinalGoal); 1195 m_PathState = PATHSTATE_FOLLOWING_REQUESTING_LONG; 988 // Expect our caller to recompute 989 // Dump our current path. 990 m_Path.m_Waypoints.clear(); 1196 991 1197 992 return true; 1198 993 } 1199 994 1200 void CCmpUnitMotion::UpdateFinalGoal() 995 // TODO: ought to be cleverer here. 996 // In particular maybe we should support some "margin" for error. 997 bool CCmpUnitMotion::ShouldConsiderOurselvesAtDestination() 1201 998 { 1202 if (m_TargetEntity == INVALID_ENTITY) 1203 return; 1204 CmpPtr<ICmpUnitMotion> cmpUnitMotion(GetSimContext(), m_TargetEntity); 1205 if (!cmpUnitMotion) 1206 return; 1207 if (IsFormationMember()) 1208 return; 1209 CFixedVector2D targetPos; 1210 if (!ComputeTargetPosition(targetPos)) 1211 return; 1212 m_FinalGoal.x = targetPos.X; 1213 m_FinalGoal.z = targetPos.Y; 1214 } 1215 1216 bool CCmpUnitMotion::ShouldConsiderOurselvesAtDestination(const CFixedVector2D& from) 1217 { 1218 if (m_TargetEntity != INVALID_ENTITY || m_FinalGoal.DistanceToPoint(from) > SHORT_PATH_GOAL_RADIUS) 1219 return false; 1220 1221 StopMoving(); 1222 MoveSucceeded(); 1223 1224 if (m_FacePointAfterMove) 1225 FaceTowardsPointFromPos(from, m_FinalGoal.x, m_FinalGoal.z); 1226 return true; 999 if (m_FinalGoal.TargetIsEntity()) 1000 return IsInTargetRange(m_FinalGoal.GetEntity(), m_FinalGoal.MinRange(), m_FinalGoal.MaxRange()); 1001 else 1002 return IsInPointRange(m_FinalGoal.X(),m_FinalGoal.Z(), m_FinalGoal.MinRange(), m_FinalGoal.MaxRange()); 1227 1003 } 1228 1004 1229 1005 bool CCmpUnitMotion::PathIsShort(const WaypointPath& path, const CFixedVector2D& from, entity_pos_t minDistance) const … … void CCmpUnitMotion::FaceTowardsPointFromPos(const CFixedVector2D& pos, entity_p 1275 1051 1276 1052 ControlGroupMovementObstructionFilter CCmpUnitMotion::GetObstructionFilter(bool noTarget) const 1277 1053 { 1278 entity_id_t group = noTarget ? m_TargetEntity : GetGroup(); 1279 return ControlGroupMovementObstructionFilter(ShouldAvoidMovingUnits(), group); 1054 entity_id_t group = noTarget ? m_FinalGoal.GetEntity() : GetGroup(); 1055 // TODO: if we sometimes want to consider moving units, change here. 1056 return ControlGroupMovementObstructionFilter(false, group); 1280 1057 } 1281 1058 1282 1283 1284 void CCmpUnitMotion::BeginPathing(const CFixedVector2D& from, const PathGoal& goal) 1059 // TODO: this should be improved, it's a little limited 1060 // EG use of hierarchical pathfinder,… 1061 // also it should probably make the goal passable directly, to avoid conflict with the paths returned. 1062 void CCmpUnitMotion::RequestNewPath() 1285 1063 { 1286 // reset our state for sanity. 1287 m_ExpectedPathTicket = 0; 1064 ENSURE(m_ExpectedPathTicket == 0); 1288 1065 1289 CmpPtr<ICmp Obstruction> cmpObstruction(GetEntityHandle());1290 if ( cmpObstruction)1291 cmpObstruction->SetMovingFlag(false);1066 CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle()); 1067 if (!cmpPosition) 1068 return; 1292 1069 1293 m_Moving = false; 1070 // dump current path 1071 m_Path.m_Waypoints.clear(); 1294 1072 1295 m_PathState = PATHSTATE_NONE;1073 CFixedVector2D position = cmpPosition->GetPosition2D(); 1296 1074 1297 1075 #if DISABLE_PATHFINDER 1298 1076 { 1299 1077 CmpPtr<ICmpPathfinder> cmpPathfinder (GetSimContext(), SYSTEM_ENTITY); 1300 CFixedVector2D goalPos = m_FinalGoal. NearestPointOnGoal(from);1078 CFixedVector2D goalPos = m_FinalGoal.Goal().NearestPointOnGoal(position); 1301 1079 m_LongPath.m_Waypoints.clear(); 1302 1080 m_ShortPath.m_Waypoints.clear(); 1303 1081 m_ShortPath.m_Waypoints.emplace_back(Waypoint{ goalPos.X, goalPos.Y }); 1304 m_PathState = PATHSTATE_FOLLOWING;1305 1082 return; 1306 1083 } 1307 1084 #endif 1308 1085 1309 // If we're aiming at a target entity and it's close and we can reach1310 // it in a straight line, then we'll just go along the straight line1311 // instead of computing a path.1312 if (TryGoingStraightToTargetEntity(from))1313 {1314 if (!HasValidPath())1315 StartSucceeded();1316 m_PathState = PATHSTATE_FOLLOWING;1317 return;1318 }1319 1320 // Same thing applies to non-entity points1321 if (TryGoingStraightToGoalPoint(from))1322 {1323 if (!HasValidPath())1324 StartSucceeded();1325 m_PathState = PATHSTATE_FOLLOWING;1326 return;1327 }1328 1329 // Otherwise we need to compute a path.1330 1331 1086 // If it's close then just do a short path, not a long path 1332 1087 // TODO: If it's close on the opposite side of a river then we really 1333 1088 // need a long path, so we shouldn't simply check linear distance 1334 1089 // the check is arbitrary but should be a reasonably small distance. 1335 if (goal.DistanceToPoint(from) < LONG_PATH_MIN_DIST) 1336 { 1337 // add our final goal as a long range waypoint so we don't forget 1338 // where we are going if the short-range pathfinder returns 1339 // an aborted path. 1340 m_LongPath.m_Waypoints.clear(); 1341 CFixedVector2D target = m_FinalGoal.NearestPointOnGoal(from); 1342 m_LongPath.m_Waypoints.emplace_back(Waypoint{ target.X, target.Y }); 1343 m_PathState = PATHSTATE_WAITING_REQUESTING_SHORT; 1344 RequestShortPath(from, goal, true); 1345 } 1090 // Maybe use PathIsShort? 1091 if (m_FinalGoal.Goal().DistanceToPoint(position) < LONG_PATH_MIN_DIST) 1092 RequestShortPath(position, m_FinalGoal.Goal(), true); 1346 1093 else 1347 { 1348 m_PathState = PATHSTATE_WAITING_REQUESTING_LONG; 1349 RequestLongPath(from, goal); 1350 } 1094 RequestLongPath(position, m_FinalGoal.Goal()); 1351 1095 } 1352 1096 1353 1097 void CCmpUnitMotion::RequestLongPath(const CFixedVector2D& from, const PathGoal& goal) … … bool CCmpUnitMotion::MoveToPointRange(entity_pos_t x, entity_pos_t z, entity_pos 1391 1135 { 1392 1136 PROFILE("MoveToPointRange"); 1393 1137 1138 DiscardMove(); 1139 1394 1140 CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle()); 1395 1141 if (!cmpPosition || !cmpPosition->IsInWorld()) 1396 1142 return false; … … bool CCmpUnitMotion::MoveToPointRange(entity_pos_t x, entity_pos_t z, entity_pos 1442 1188 } 1443 1189 } 1444 1190 1445 m_State = STATE_INDIVIDUAL_PATH; 1446 m_TargetEntity = target; 1447 m_TargetOffset = CFixedVector2D(); 1448 m_TargetMinRange = minRange; 1449 m_TargetMaxRange = maxRange; 1450 m_FinalGoal = goal; 1451 m_Tries = 0; 1191 if (target == INVALID_ENTITY) 1192 m_FinalGoal = SMotionGoal(goal, minRange, maxRange); 1193 else 1194 m_FinalGoal = SMotionGoal(GetSimContext(), target, goal, minRange, maxRange); 1452 1195 1453 BeginPathing(pos, goal);1196 RequestNewPath(); 1454 1197 1455 1198 return true; 1456 1199 } … … bool CCmpUnitMotion::IsInPointRange(entity_pos_t x, entity_pos_t z, entity_pos_t 1463 1206 1464 1207 CFixedVector2D pos = cmpPosition->GetPosition2D(); 1465 1208 1466 bool hasObstruction = false; 1467 CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSystemEntity()); 1468 ICmpObstructionManager::ObstructionSquare obstruction; 1469 //TODO if (cmpObstructionManager) 1470 // hasObstruction = cmpObstructionManager->FindMostImportantObstruction(GetObstructionFilter(), x, z, m_Radius, obstruction); 1471 1472 if (minRange.IsZero() && maxRange.IsZero() && hasObstruction) 1473 { 1474 // Handle the non-ranged mode: 1475 CFixedVector2D halfSize(obstruction.hw, obstruction.hh); 1476 entity_pos_t distance = Geometry::DistanceToSquare(pos - CFixedVector2D(obstruction.x, obstruction.z), obstruction.u, obstruction.v, halfSize); 1477 1478 // See if we're too close to the target square 1479 if (distance < minRange) 1480 return false; 1481 1482 // See if we're close enough to the target square 1483 if (maxRange < entity_pos_t::Zero() || distance <= maxRange) 1484 return true; 1209 entity_pos_t distance = (pos - CFixedVector2D(x, z)).Length(); 1485 1210 1211 if (distance < minRange) 1212 return false; 1213 else if (maxRange >= entity_pos_t::Zero() && distance > maxRange) 1486 1214 return false; 1487 }1488 1215 else 1489 { 1490 entity_pos_t distance = (pos - CFixedVector2D(x, z)).Length(); 1491 1492 if (distance < minRange) 1493 return false; 1494 else if (maxRange >= entity_pos_t::Zero() && distance > maxRange) 1495 return false; 1496 else 1497 return true; 1498 } 1216 return true; 1499 1217 } 1500 1218 1501 1219 bool CCmpUnitMotion::ShouldTreatTargetAsCircle(entity_pos_t range, entity_pos_t circleRadius) const … … bool CCmpUnitMotion::MoveToTargetRange(entity_id_t target, entity_pos_t minRange 1512 1230 { 1513 1231 PROFILE("MoveToTargetRange"); 1514 1232 1233 DiscardMove(); 1234 1515 1235 CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle()); 1516 1236 if (!cmpPosition || !cmpPosition->IsInWorld()) 1517 1237 return false; … … bool CCmpUnitMotion::MoveToTargetRange(entity_id_t target, entity_pos_t minRange 1655 1375 } 1656 1376 } 1657 1377 1658 m_State = STATE_INDIVIDUAL_PATH; 1659 m_TargetEntity = target; 1660 m_TargetOffset = CFixedVector2D(); 1661 m_TargetMinRange = minRange; 1662 m_TargetMaxRange = maxRange; 1663 m_FinalGoal = goal; 1664 m_Tries = 0; 1378 if (target == INVALID_ENTITY) 1379 m_FinalGoal = SMotionGoal(goal, minRange, maxRange); 1380 else 1381 m_FinalGoal = SMotionGoal(GetSimContext(), target, goal, minRange, maxRange); 1665 1382 1666 BeginPathing(pos, goal);1383 RequestNewPath(); 1667 1384 1668 1385 return true; 1669 1386 } … … bool CCmpUnitMotion::IsInTargetRange(entity_id_t target, entity_pos_t minRange, 1738 1455 } 1739 1456 } 1740 1457 1741 void CCmpUnitMotion::MoveToFormationOffset(entity_id_t target, entity_pos_t x, entity_pos_t z)1742 {1743 CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), target);1744 if (!cmpPosition || !cmpPosition->IsInWorld())1745 return;1746 1747 CFixedVector2D pos = cmpPosition->GetPosition2D();1748 1749 PathGoal goal;1750 goal.type = PathGoal::POINT;1751 goal.x = pos.X;1752 goal.z = pos.Y;1753 1754 m_State = STATE_FORMATIONMEMBER_PATH;1755 m_TargetEntity = target;1756 m_TargetOffset = CFixedVector2D(x, z);1757 m_TargetMinRange = entity_pos_t::Zero();1758 m_TargetMaxRange = entity_pos_t::Zero();1759 m_FinalGoal = goal;1760 m_Tries = 0;1761 1762 BeginPathing(pos, goal);1763 }1764 1765 1766 1767 1768 1458 1769 1459 void CCmpUnitMotion::RenderPath(const WaypointPath& path, std::vector<SOverlayLine>& lines, CColor color) 1770 1460 { … … void CCmpUnitMotion::RenderSubmit(SceneCollector& collector) 1800 1490 if (!m_DebugOverlayEnabled) 1801 1491 return; 1802 1492 1803 RenderPath(m_LongPath, m_DebugOverlayLongPathLines, OVERLAY_COLOR_LONG_PATH); 1804 RenderPath(m_ShortPath, m_DebugOverlayShortPathLines, OVERLAY_COLOR_SHORT_PATH); 1493 RenderPath(m_Path, m_DebugOverlayLongPathLines, OVERLAY_COLOR_LONG_PATH); 1805 1494 1806 1495 for (size_t i = 0; i < m_DebugOverlayLongPathLines.size(); ++i) 1807 1496 collector.Submit(&m_DebugOverlayLongPathLines[i]); -
source/simulation2/components/CCmpVisualActor.cpp
diff --git a/source/simulation2/components/CCmpVisualActor.cpp b/source/simulation2/components/CCmpVisualActor.cpp index f84dc51..9f586fc 100644
a b private: 71 71 72 72 fixed m_R, m_G, m_B; // shading color 73 73 74 std::map<std::string, std::string> m_AnimOverride;75 76 74 // Current animation state 77 fixed m_AnimRunThreshold; // if non-zero this is the special walk/run mode78 75 std::string m_AnimName; 79 76 bool m_AnimOnce; 80 77 fixed m_AnimSpeed; … … private: 83 80 fixed m_AnimSyncRepeatTime; // 0.0 if not synced 84 81 fixed m_AnimSyncOffsetTime; 85 82 83 std::string m_MovingPrefix; 84 fixed m_MovingSpeed; 85 bool m_MovingNow, m_MovingLastTurn; 86 86 87 std::map<CStr, CStr> m_VariantSelections; 87 88 88 89 u32 m_Seed; // seed used for random variations … … public: 206 207 207 208 InitModel(paramNode); 208 209 210 m_MovingLastTurn = m_MovingNow = false; 211 209 212 SelectAnimation("idle", false, fixed::FromInt(1), L""); 210 213 } 211 214 … … public: 225 228 serialize.NumberFixed_Unbounded("g", m_G); 226 229 serialize.NumberFixed_Unbounded("b", m_B); 227 230 228 SerializeMap<SerializeString, SerializeString>()(serialize, "anim overrides", m_AnimOverride);229 230 serialize.NumberFixed_Unbounded("anim run threshold", m_AnimRunThreshold);231 231 serialize.StringASCII("anim name", m_AnimName, 0, 256); 232 232 serialize.Bool("anim once", m_AnimOnce); 233 233 serialize.NumberFixed_Unbounded("anim speed", m_AnimSpeed); … … public: 236 236 serialize.NumberFixed_Unbounded("anim sync repeat time", m_AnimSyncRepeatTime); 237 237 serialize.NumberFixed_Unbounded("anim sync offset time", m_AnimSyncOffsetTime); 238 238 239 serialize.Bool("moving now", m_MovingNow); 240 serialize.Bool("moving last turn", m_MovingLastTurn); 241 239 242 SerializeMap<SerializeString, SerializeString>()(serialize, "variation", m_VariantSelections); 240 243 241 244 serialize.NumberU32_Unbounded("seed", m_Seed); … … public: 423 426 424 427 virtual void SelectAnimation(const std::string& name, bool once, fixed speed, const std::wstring& soundgroup) 425 428 { 426 m_AnimRunThreshold = fixed::Zero();427 429 m_AnimName = name; 428 430 m_AnimOnce = once; 429 431 m_AnimSpeed = speed; … … public: 432 434 m_AnimSyncRepeatTime = fixed::Zero(); 433 435 m_AnimSyncOffsetTime = fixed::Zero(); 434 436 435 SetVariant("animation", m_AnimName); 436 437 if (m_Unit && m_Unit->GetAnimation()) 438 m_Unit->GetAnimation()->SetAnimationState(m_AnimName, m_AnimOnce, m_AnimSpeed.ToFloat(), m_AnimDesync.ToFloat(), m_SoundGroup.c_str()); 439 } 437 // TODO: change this once we support walk/run-anims 438 std::string animName = name; 439 /*if (!m_MovingPrefix.empty() && m_AnimName != "idle") 440 animName = m_MovingPrefix + "-" + m_AnimName; 441 else */if (!m_MovingPrefix.empty()) 442 animName = m_MovingPrefix; 440 443 441 virtual void ReplaceMoveAnimation(const std::string& name, const std::string& replace) 442 { 443 m_AnimOverride[name] = replace; 444 } 444 SetVariant("animation", animName); 445 445 446 virtual void ResetMoveAnimation(const std::string& name) 447 { 448 std::map<std::string, std::string>::const_iterator it = m_AnimOverride.find(name); 449 if (it != m_AnimOverride.end()) 450 m_AnimOverride.erase(name); 446 if (m_Unit && m_Unit->GetAnimation()) 447 m_Unit->GetAnimation()->SetAnimationState(animName, m_AnimOnce, m_MovingSpeed.Multiply(m_AnimSpeed).ToFloat(), m_AnimDesync.ToFloat(), m_SoundGroup.c_str()); 451 448 } 452 449 453 virtual void Se lectMovementAnimation(fixed runThreshold)450 virtual void SetMoving(bool moving) 454 451 { 455 SelectAnimation("walk", false, fixed::FromFloat(1.f), L""); 456 m_AnimRunThreshold = runThreshold; 452 m_MovingNow = moving; 457 453 } 458 454 459 455 virtual void SetAnimationSyncRepeat(fixed repeattime) … … void CCmpVisualActor::Update(fixed UNUSED(turnLength)) 751 747 // far less hacky. We should also take into account the speed when the animation is different 752 748 // from the "special movement mode" walking animation. 753 749 754 // If we're not in the special movement mode, nothing to do. 755 if (m_AnimRunThreshold.IsZero()) 756 return; 750 //if (!m_MovingNow && !m_MovingLastTurn) 751 // return; 757 752 758 753 CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle()); 759 754 if (!cmpPosition || !cmpPosition->IsInWorld()) … … void CCmpVisualActor::Update(fixed UNUSED(turnLength)) 763 758 if (!cmpUnitMotion) 764 759 return; 765 760 766 fixed speed = cmpUnitMotion->GetCurrentSpeed(); 767 std::string name; 761 // TODO: change this to make us run as fast but stop earlier instead of this hack. 762 // Probably should subscribe dynamically too. 763 fixed speed = cmpUnitMotion->GetActualSpeed(); 764 std::string prefix; 768 765 769 if ( speed.IsZero())766 if (!m_MovingNow || speed.IsZero()) 770 767 { 771 768 speed = fixed::FromFloat(1.f); 772 name = "idle";769 prefix = ""; 773 770 } 774 771 else 775 name = speed < m_AnimRunThreshold ? "walk" : "run"; 772 prefix = cmpUnitMotion->GetSpeedRatio() <= fixed::FromInt(1) ? "walk" : "run"; 773 774 m_MovingPrefix = prefix; 775 m_MovingSpeed = speed; 776 776 777 std::map<std::string, std::string>::const_iterator it = m_AnimOverride.find(name); 778 if (it != m_AnimOverride.end()) 779 name = it->second; 777 SelectAnimation(m_AnimName, m_AnimOnce, m_AnimSpeed, m_SoundGroup); 780 778 781 // Selecting the animation is going to reset the anim run threshold, so save it 782 fixed runThreshold = m_AnimRunThreshold; 783 SelectAnimation(name, false, speed, L""); 784 m_AnimRunThreshold = runThreshold; 779 m_MovingLastTurn = m_MovingNow; 785 780 } -
source/simulation2/components/ICmpUnitMotion.cpp
diff --git a/source/simulation2/components/ICmpUnitMotion.cpp b/source/simulation2/components/ICmpUnitMotion.cpp index 28956ce..9099683 100644
a b DEFINE_INTERFACE_METHOD_4("MoveToPointRange", bool, ICmpUnitMotion, MoveToPointR 27 27 DEFINE_INTERFACE_METHOD_4("IsInPointRange", bool, ICmpUnitMotion, IsInPointRange, entity_pos_t, entity_pos_t, entity_pos_t, entity_pos_t) 28 28 DEFINE_INTERFACE_METHOD_3("IsInTargetRange", bool, ICmpUnitMotion, IsInTargetRange, entity_id_t, entity_pos_t, entity_pos_t) 29 29 DEFINE_INTERFACE_METHOD_3("MoveToTargetRange", bool, ICmpUnitMotion, MoveToTargetRange, entity_id_t, entity_pos_t, entity_pos_t) 30 DEFINE_INTERFACE_METHOD_3("MoveToFormationOffset", void, ICmpUnitMotion, MoveToFormationOffset, entity_id_t, entity_pos_t, entity_pos_t)31 30 DEFINE_INTERFACE_METHOD_2("FaceTowardsPoint", void, ICmpUnitMotion, FaceTowardsPoint, entity_pos_t, entity_pos_t) 32 DEFINE_INTERFACE_METHOD_0("StopMoving", void, ICmpUnitMotion, StopMoving) 33 DEFINE_INTERFACE_METHOD_0("GetCurrentSpeed", fixed, ICmpUnitMotion, GetCurrentSpeed) 31 DEFINE_INTERFACE_METHOD_1("SetAbortIfStuck", void, ICmpUnitMotion, SetAbortIfStuck, u8) 32 DEFINE_INTERFACE_METHOD_0("DiscardMove", void, ICmpUnitMotion, DiscardMove) 33 DEFINE_INTERFACE_METHOD_0("CompleteMove", void, ICmpUnitMotion, CompleteMove) 34 DEFINE_INTERFACE_METHOD_0("GetActualSpeed", fixed, ICmpUnitMotion, GetActualSpeed) 35 DEFINE_INTERFACE_METHOD_0("GetTopSpeedRatio", fixed, ICmpUnitMotion, GetTopSpeedRatio) 34 36 DEFINE_INTERFACE_METHOD_1("SetSpeed", void, ICmpUnitMotion, SetSpeed, fixed) 35 DEFINE_INTERFACE_METHOD_0("IsMoving", bool, ICmpUnitMotion, IsMoving) 36 DEFINE_INTERFACE_METHOD_0("GetWalkSpeed", fixed, ICmpUnitMotion, GetWalkSpeed) 37 DEFINE_INTERFACE_METHOD_0("GetRunSpeed", fixed, ICmpUnitMotion, GetRunSpeed) 37 DEFINE_INTERFACE_METHOD_0("IsActuallyMoving", bool, ICmpUnitMotion, IsActuallyMoving) 38 DEFINE_INTERFACE_METHOD_0("IsTryingToMove", bool, ICmpUnitMotion, IsTryingToMove) 39 DEFINE_INTERFACE_METHOD_0("GetSpeed", fixed, ICmpUnitMotion, GetSpeed) 40 DEFINE_INTERFACE_METHOD_0("GetTemplateSpeed", fixed, ICmpUnitMotion, GetTemplateSpeed) 38 41 DEFINE_INTERFACE_METHOD_0("GetPassabilityClassName", std::string, ICmpUnitMotion, GetPassabilityClassName) 39 42 DEFINE_INTERFACE_METHOD_0("GetUnitClearance", entity_pos_t, ICmpUnitMotion, GetUnitClearance) 40 43 DEFINE_INTERFACE_METHOD_1("SetFacePointAfterMove", void, ICmpUnitMotion, SetFacePointAfterMove, bool) … … public: 66 69 return m_Script.Call<bool>("MoveToTargetRange", target, minRange, maxRange); 67 70 } 68 71 69 virtual void MoveToFormationOffset(entity_id_t target,entity_pos_t x, entity_pos_t z)72 virtual void FaceTowardsPoint(entity_pos_t x, entity_pos_t z) 70 73 { 71 m_Script.CallVoid(" MoveToFormationOffset", target, x, z);74 m_Script.CallVoid("FaceTowardsPoint", x, z); 72 75 } 73 76 74 virtual void FaceTowardsPoint(entity_pos_t x, entity_pos_t z)77 virtual void DiscardMove() 75 78 { 76 m_Script.CallVoid(" FaceTowardsPoint", x, z);79 m_Script.CallVoid("DiscardMove"); 77 80 } 78 81 79 virtual void StopMoving()82 virtual void CompleteMove() 80 83 { 81 m_Script.CallVoid(" StopMoving");84 m_Script.CallVoid("CompleteMove"); 82 85 } 83 86 84 virtual fixed GetCurrentSpeed()87 virtual void SetAbortIfStuck(u8 shouldAbort) 85 88 { 86 return m_Script.Call<fixed>("GetCurrentSpeed"); 89 m_Script.CallVoid("SetAbortIfStuck", shouldAbort); 90 } 91 92 virtual fixed GetActualSpeed() 93 { 94 return m_Script.Call<fixed>("GetActualSpeed"); 87 95 } 88 96 89 97 virtual void SetSpeed(fixed speed) … … public: 91 99 m_Script.CallVoid("SetSpeed", speed); 92 100 } 93 101 94 virtual bool IsMoving() 102 virtual fixed GetTopSpeedRatio() 103 { 104 return m_Script.Call<fixed>("GetTopSpeedRatio"); 105 } 106 107 virtual bool IsActuallyMoving() 108 { 109 return m_Script.Call<bool>("IsActuallyMoving"); 110 } 111 112 virtual bool IsTryingToMove() 95 113 { 96 return m_Script.Call<bool>("Is Moving");114 return m_Script.Call<bool>("IsTryingToMove"); 97 115 } 98 116 99 virtual fixed Get WalkSpeed()117 virtual fixed GetSpeed() 100 118 { 101 return m_Script.Call<fixed>("Get WalkSpeed");119 return m_Script.Call<fixed>("GetSpeed"); 102 120 } 103 121 104 virtual fixed Get RunSpeed()122 virtual fixed GetTemplateSpeed() 105 123 { 106 return m_Script.Call<fixed>("Get RunSpeed");124 return m_Script.Call<fixed>("GetTemplateSpeed"); 107 125 } 108 126 109 127 virtual void SetFacePointAfterMove(bool facePointAfterMove) … … public: 116 134 return m_Script.Call<pass_class_t>("GetPassabilityClass"); 117 135 } 118 136 137 virtual fixed GetSpeedRatio() 138 { 139 return fixed::FromInt(1); 140 } 141 119 142 virtual std::string GetPassabilityClassName() 120 143 { 121 144 return m_Script.Call<std::string>("GetPassabilityClassName"); -
source/simulation2/components/ICmpUnitMotion.h
diff --git a/source/simulation2/components/ICmpUnitMotion.h b/source/simulation2/components/ICmpUnitMotion.h index de5c674..dd40777 100644
a b public: 71 71 virtual bool MoveToTargetRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange) = 0; 72 72 73 73 /** 74 * Join a formation, and move towards a given offset relative to the formation controller entity. 75 * Continues following the formation until given a different command. 74 * Turn to look towards the given point. 76 75 */ 77 virtual void MoveToFormationOffset(entity_id_t target,entity_pos_t x, entity_pos_t z) = 0;76 virtual void FaceTowardsPoint(entity_pos_t x, entity_pos_t z) = 0; 78 77 79 78 /** 80 * Turn to look towards the given point. 79 * Determine whether to abort or retry X times if pathing fails. 80 * Generally safer to let it abort and inform us. 81 81 */ 82 virtual void FaceTowardsPoint(entity_pos_t x, entity_pos_t z) = 0; 82 virtual void SetAbortIfStuck(u8 shouldAbort) = 0; 83 84 /** 85 * Stop moving immediately, don't send messages. 86 * This should be used if you are going to ask for a new path, 87 * in the same function, for example. 88 * In doubt, UnitAI should probably call this. 89 * Use with caution. 90 */ 91 virtual void DiscardMove() = 0; 92 93 /** 94 * Stop moving immediately, send messages. 95 * In doubt, components that are not UnitIA should probably call this. 96 */ 97 virtual void CompleteMove() = 0; 98 99 /** 100 * Get the movement speed from last turn to this turn 101 * This is effectively historical data 102 * And not a good indicator of whether we are actually moving, 103 * Prefer IsActuallyMoving 104 */ 105 virtual fixed GetActualSpeed() = 0; 83 106 84 107 /** 85 * Stop moving immediately.108 * Get how much faster/slower we are at than normal. 86 109 */ 87 virtual void StopMoving() = 0;110 virtual fixed GetSpeedRatio() = 0; 88 111 89 112 /** 90 * Get the current movement speed.113 * Get how much faster than our regular speed we can go. 91 114 */ 92 virtual fixed Get CurrentSpeed() = 0;115 virtual fixed GetTopSpeedRatio() = 0; 93 116 94 117 /** 95 118 * Set the current movement speed. 119 * 'speed' in % of top speed (ie 3.0 will be 3 times top speed). 96 120 */ 97 121 virtual void SetSpeed(fixed speed) = 0; 98 122 99 123 /** 100 * Get whether the unit is moving. 124 * Get whether the unit is actually moving on the map this turn. 125 */ 126 virtual bool IsActuallyMoving() = 0; 127 128 /** 129 * Get whether a unit is trying to go somewhere 130 * NB: this does not mean its position is actually changing right now. 101 131 */ 102 virtual bool Is Moving() = 0;132 virtual bool IsTryingToMove() = 0; 103 133 104 134 /** 105 * Get the default speed that this unit will have when walking, in metres per second. 135 * Get the unit theoretical speed in metres per second. 136 * GetActualSpeed will return historical speed 137 * This is affected by SetSpeed. 106 138 */ 107 virtual fixed Get WalkSpeed() = 0;139 virtual fixed GetSpeed() = 0; 108 140 109 141 /** 110 * Get the default speed that this unit will have when running, in metres per second. 142 * Get the unit template speed in metres per second. 143 * This is NOT affected by SetSpeed. 111 144 */ 112 virtual fixed Get RunSpeed() = 0;145 virtual fixed GetTemplateSpeed() = 0; 113 146 114 147 /** 115 148 * Set whether the unit will turn to face the target point after finishing moving. -
source/simulation2/components/ICmpVisual.cpp
diff --git a/source/simulation2/components/ICmpVisual.cpp b/source/simulation2/components/ICmpVisual.cpp index e39942d..9305c5f 100644
a b 24 24 BEGIN_INTERFACE_WRAPPER(Visual) 25 25 DEFINE_INTERFACE_METHOD_2("SetVariant", void, ICmpVisual, SetVariant, CStr, CStr) 26 26 DEFINE_INTERFACE_METHOD_4("SelectAnimation", void, ICmpVisual, SelectAnimation, std::string, bool, fixed, std::wstring) 27 DEFINE_INTERFACE_METHOD_1("SelectMovementAnimation", void, ICmpVisual, SelectMovementAnimation, fixed)28 DEFINE_INTERFACE_METHOD_1("ResetMoveAnimation", void, ICmpVisual, ResetMoveAnimation, std::string)29 DEFINE_INTERFACE_METHOD_2("ReplaceMoveAnimation", void, ICmpVisual, ReplaceMoveAnimation, std::string, std::string)30 27 DEFINE_INTERFACE_METHOD_1("SetAnimationSyncRepeat", void, ICmpVisual, SetAnimationSyncRepeat, fixed) 31 28 DEFINE_INTERFACE_METHOD_1("SetAnimationSyncOffset", void, ICmpVisual, SetAnimationSyncOffset, fixed) 32 29 DEFINE_INTERFACE_METHOD_4("SetShadingColor", void, ICmpVisual, SetShadingColor, fixed, fixed, fixed, fixed) -
source/simulation2/components/ICmpVisual.h
diff --git a/source/simulation2/components/ICmpVisual.h b/source/simulation2/components/ICmpVisual.h index 6b11fdc..c31d2a0 100644
a b public: 99 99 virtual void SelectAnimation(const std::string& name, bool once, fixed speed, const std::wstring& soundgroup) = 0; 100 100 101 101 /** 102 * Replaces a specified animation with another. Only affects the special speed-based 103 * animation determination behaviour. 104 * @param name Animation to match. 105 * @param replace Animation that should replace the matched animation. 102 * if moving is true, start playing the walk/run animations, scaled to the unit's movement speed. 106 103 */ 107 virtual void ReplaceMoveAnimation(const std::string& name, const std::string& replace) = 0; 108 109 /** 110 * Ensures that the given animation will be used when it normally would be, 111 * removing reference to any animation that might replace it. 112 * @param name Animation name to remove from the replacement map. 113 */ 114 virtual void ResetMoveAnimation(const std::string& name) = 0; 115 116 /** 117 * Start playing the walk/run animations, scaled to the unit's movement speed. 118 * @param runThreshold movement speed at which to switch to the run animation 119 */ 120 virtual void SelectMovementAnimation(fixed runThreshold) = 0; 104 virtual void SetMoving(bool moving) = 0; 121 105 122 106 /** 123 107 * Adjust the speed of the current animation, so it can match simulation events. -
source/simulation2/scripting/MessageTypeConversions.cpp
diff --git a/source/simulation2/scripting/MessageTypeConversions.cpp b/source/simulation2/scripting/MessageTypeConversions.cpp index cb55a17..addc730 100644
a b CMessage* CMessageTerritoryPositionChanged::FromJSVal(ScriptInterface& scriptInt 267 267 268 268 //////////////////////////////// 269 269 270 JS::Value CMessage MotionChanged::ToJSVal(ScriptInterface& scriptInterface) const270 JS::Value CMessageBeginMove::ToJSVal(ScriptInterface& scriptInterface) const 271 271 { 272 272 TOJSVAL_SETUP(); 273 SET_MSG_PROPERTY(starting);274 SET_MSG_PROPERTY(error);275 273 return JS::ObjectValue(*obj); 276 274 } 277 275 278 CMessage* CMessage MotionChanged::FromJSVal(ScriptInterface& scriptInterface, JS::HandleValue val)276 CMessage* CMessageBeginMove::FromJSVal(ScriptInterface& scriptInterface, JS::HandleValue val) 279 277 { 280 278 FROMJSVAL_SETUP(); 281 GET_MSG_PROPERTY(bool, starting); 282 GET_MSG_PROPERTY(bool, error); 283 return new CMessageMotionChanged(starting, error); 279 return new CMessageBeginMove(); 280 } 281 282 //////////////////////////////// 283 284 JS::Value CMessagePausedMove::ToJSVal(ScriptInterface& scriptInterface) const 285 { 286 TOJSVAL_SETUP(); 287 return JS::ObjectValue(*obj); 288 } 289 290 CMessage* CMessagePausedMove::FromJSVal(ScriptInterface& scriptInterface, JS::HandleValue val) 291 { 292 FROMJSVAL_SETUP(); 293 return new CMessagePausedMove(); 294 } 295 296 //////////////////////////////// 297 298 JS::Value CMessageFinishedMove::ToJSVal(ScriptInterface& scriptInterface) const 299 { 300 TOJSVAL_SETUP(); 301 SET_MSG_PROPERTY(failed); 302 return JS::ObjectValue(*obj); 303 } 304 305 CMessage* CMessageFinishedMove::FromJSVal(ScriptInterface& scriptInterface, JS::HandleValue val) 306 { 307 FROMJSVAL_SETUP(); 308 GET_MSG_PROPERTY(bool, failed); 309 return new CMessageFinishedMove(failed); 284 310 } 285 311 286 312 //////////////////////////////// -
source/tools/atlas/GameInterface/ActorViewer.cpp
diff --git a/source/tools/atlas/GameInterface/ActorViewer.cpp b/source/tools/atlas/GameInterface/ActorViewer.cpp index f09da1a..812a3cd 100644
a b void ActorViewer::SetActor(const CStrW& name, const CStrW& animation, player_id_ 375 375 { 376 376 CmpPtr<ICmpUnitMotion> cmpUnitMotion(m.Simulation2, m.Entity); 377 377 if (cmpUnitMotion) 378 speed = cmpUnitMotion->Get WalkSpeed().ToFloat();378 speed = cmpUnitMotion->GetTemplateSpeed().ToFloat(); 379 379 else 380 380 speed = 7.f; // typical unit speed 381 381 … … void ActorViewer::SetActor(const CStrW& name, const CStrW& animation, player_id_ 385 385 { 386 386 CmpPtr<ICmpUnitMotion> cmpUnitMotion(m.Simulation2, m.Entity); 387 387 if (cmpUnitMotion) 388 speed = cmpUnitMotion->Get RunSpeed().ToFloat();388 speed = cmpUnitMotion->GetTemplateSpeed().ToFloat(); 389 389 else 390 390 speed = 12.f; // typical unit speed 391 391