HeroMovementController.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379
  1. /*
  2. * CPlayerInterface.cpp, part of VCMI engine
  3. *
  4. * Authors: listed in file AUTHORS in main folder
  5. *
  6. * License: GNU General Public License v2.0 or later
  7. * Full text of license available in license.txt file, in main folder
  8. *
  9. */
  10. #include "StdInc.h"
  11. #include "HeroMovementController.h"
  12. #include "CGameInfo.h"
  13. #include "CMusicHandler.h"
  14. #include "CPlayerInterface.h"
  15. #include "PlayerLocalState.h"
  16. #include "adventureMap/AdventureMapInterface.h"
  17. #include "eventsSDL/InputHandler.h"
  18. #include "gui/CGuiHandler.h"
  19. #include "gui/CursorHandler.h"
  20. #include "mapView/mapHandler.h"
  21. #include "../CCallback.h"
  22. #include "../lib/pathfinder/CGPathNode.h"
  23. #include "../lib/mapObjects/CGHeroInstance.h"
  24. #include "../lib/mapObjects/MiscObjects.h"
  25. #include "../lib/RoadHandler.h"
  26. #include "../lib/TerrainHandler.h"
  27. #include "../lib/NetPacks.h"
  28. #include "../lib/CondSh.h"
  29. HeroMovementController::HeroMovementController()
  30. {
  31. destinationTeleport = ObjectInstanceID();
  32. destinationTeleportPos = int3(-1);
  33. duringMovement = false;
  34. }
  35. void HeroMovementController::setMovementStatus(bool value)
  36. {
  37. duringMovement = value;
  38. if (value)
  39. {
  40. CCS->curh->hide();
  41. }
  42. else
  43. {
  44. CCS->curh->show();
  45. }
  46. }
  47. bool HeroMovementController::isHeroMovingThroughGarrison(const CGHeroInstance * hero) const
  48. {
  49. //to ignore calls on passing through garrisons
  50. return (movementState == EMoveState::DURING_MOVE && LOCPLINT->localState->hasPath(hero) && LOCPLINT->localState->getPath(hero).nodes.size() > 1);
  51. }
  52. bool HeroMovementController::isHeroMoving() const
  53. {
  54. return duringMovement;
  55. }
  56. void HeroMovementController::onMoveHeroApplied()
  57. {
  58. assert(movementState == EMoveState::DURING_MOVE);
  59. if(movementState == EMoveState::DURING_MOVE)
  60. {
  61. assert(destinationTeleport == ObjectInstanceID::NONE);
  62. movementState = EMoveState::CONTINUE_MOVE;
  63. }
  64. }
  65. void HeroMovementController::onQueryReplyApplied()
  66. {
  67. assert(movementState == EMoveState::DURING_MOVE);
  68. if(movementState == EMoveState::DURING_MOVE)
  69. {
  70. // After teleportation via CGTeleport object is finished
  71. assert(destinationTeleport != ObjectInstanceID::NONE);
  72. destinationTeleport = ObjectInstanceID();
  73. destinationTeleportPos = int3(-1);
  74. movementState = EMoveState::CONTINUE_MOVE;
  75. }
  76. }
  77. void HeroMovementController::onPlayerTurnStarted()
  78. {
  79. assert(movementState == EMoveState::STOP_MOVE);
  80. movementState = EMoveState::STOP_MOVE;
  81. }
  82. void HeroMovementController::onBattleStarted()
  83. {
  84. // when battle starts, game will send battleStart pack *before* movement confirmation
  85. // and since network thread wait for battle intro to play, movement confirmation will only happen after intro
  86. // leading to several bugs, such as blocked input during intro
  87. movementState = EMoveState::STOP_MOVE;
  88. }
  89. void HeroMovementController::showTeleportDialog(TeleportChannelID channel, TTeleportExitsList exits, bool impassable, QueryID askID)
  90. {
  91. int choosenExit = -1;
  92. auto neededExit = std::make_pair(destinationTeleport, destinationTeleportPos);
  93. if (destinationTeleport != ObjectInstanceID() && vstd::contains(exits, neededExit))
  94. choosenExit = vstd::find_pos(exits, neededExit);
  95. LOCPLINT->cb->selectionMade(choosenExit, askID);
  96. }
  97. void HeroMovementController::heroMoved(const CGHeroInstance * hero, const TryMoveHero & details)
  98. {
  99. if (details.result == TryMoveHero::EMBARK || details.result == TryMoveHero::DISEMBARK)
  100. {
  101. if(hero->getRemovalSound() && hero->tempOwner == LOCPLINT->playerID)
  102. CCS->soundh->playSound(hero->getRemovalSound().value());
  103. }
  104. std::unordered_set<int3> changedTiles {
  105. hero->convertToVisitablePos(details.start),
  106. hero->convertToVisitablePos(details.end)
  107. };
  108. adventureInt->onMapTilesChanged(changedTiles);
  109. adventureInt->onHeroMovementStarted(hero);
  110. bool directlyAttackingCreature = details.attackedFrom && LOCPLINT->localState->hasPath(hero) && LOCPLINT->localState->getPath(hero).endPos() == *details.attackedFrom;
  111. if(LOCPLINT->makingTurn && hero->tempOwner == LOCPLINT->playerID) //we are moving our hero - we may need to update assigned path
  112. {
  113. if(details.result == TryMoveHero::TELEPORTATION)
  114. {
  115. if(LOCPLINT->localState->hasPath(hero))
  116. {
  117. assert(LOCPLINT->localState->getPath(hero).nodes.size() >= 2);
  118. auto nodesIt = LOCPLINT->localState->getPath(hero).nodes.end() - 1;
  119. if((nodesIt)->coord == hero->convertToVisitablePos(details.start)
  120. && (nodesIt - 1)->coord == hero->convertToVisitablePos(details.end))
  121. {
  122. //path was between entrance and exit of teleport -> OK, erase node as usual
  123. LOCPLINT->localState->removeLastNode(hero);
  124. }
  125. else
  126. {
  127. //teleport was not along current path, it'll now be invalid (hero is somewhere else)
  128. LOCPLINT->localState->erasePath(hero);
  129. }
  130. }
  131. }
  132. if(hero->pos != details.end //hero didn't change tile but visit succeeded
  133. || directlyAttackingCreature) // or creature was attacked from endangering tile.
  134. {
  135. LOCPLINT->localState->erasePath(hero);
  136. }
  137. else if(LOCPLINT->localState->hasPath(hero) && hero->pos == details.end) //&& hero is moving
  138. {
  139. if(details.start != details.end) //so we don't touch path when revisiting with spacebar
  140. LOCPLINT->localState->removeLastNode(hero);
  141. }
  142. }
  143. if(details.stopMovement()) //hero failed to move
  144. {
  145. movementState = EMoveState::STOP_MOVE;
  146. adventureInt->onHeroChanged(hero);
  147. return;
  148. }
  149. CGI->mh->waitForOngoingAnimations();
  150. //move finished
  151. adventureInt->onHeroChanged(hero);
  152. //check if user cancelled movement
  153. {
  154. if (GH.input().ignoreEventsUntilInput())
  155. movementState = EMoveState::STOP_MOVE;
  156. }
  157. if (movementState == EMoveState::WAITING_MOVE)
  158. movementState = EMoveState::DURING_MOVE;
  159. // Hero attacked creature directly, set direction to face it.
  160. if (directlyAttackingCreature)
  161. {
  162. // Get direction to attacker.
  163. int3 posOffset = *details.attackedFrom - details.end + int3(2, 1, 0);
  164. static const ui8 dirLookup[3][3] =
  165. {
  166. { 1, 2, 3 },
  167. { 8, 0, 4 },
  168. { 7, 6, 5 }
  169. };
  170. // FIXME: Avoid const_cast, make moveDir mutable in some other way?
  171. const_cast<CGHeroInstance *>(hero)->moveDir = dirLookup[posOffset.y][posOffset.x];
  172. }
  173. }
  174. void HeroMovementController::movementStopRequested()
  175. {
  176. if (movementState == EMoveState::DURING_MOVE)//if we are in the middle of hero movement
  177. movementState = EMoveState::STOP_MOVE;
  178. }
  179. void HeroMovementController::doMoveHero(const CGHeroInstance * h, const CGPath & path)
  180. {
  181. setMovementStatus(true);
  182. auto getObj = [&](int3 coord, bool ignoreHero)
  183. {
  184. return LOCPLINT->cb->getTile(h->convertToVisitablePos(coord))->topVisitableObj(ignoreHero);
  185. };
  186. auto isTeleportAction = [&](EPathNodeAction action) -> bool
  187. {
  188. if (action != EPathNodeAction::TELEPORT_NORMAL &&
  189. action != EPathNodeAction::TELEPORT_BLOCKING_VISIT &&
  190. action != EPathNodeAction::TELEPORT_BATTLE)
  191. {
  192. return false;
  193. }
  194. return true;
  195. };
  196. auto getDestTeleportObj = [&](const CGObjectInstance * currentObject, const CGObjectInstance * nextObjectTop, const CGObjectInstance * nextObject) -> const CGObjectInstance *
  197. {
  198. if (CGTeleport::isConnected(currentObject, nextObjectTop))
  199. return nextObjectTop;
  200. if (nextObjectTop && nextObjectTop->ID == Obj::HERO && CGTeleport::isConnected(currentObject, nextObject))
  201. {
  202. return nextObject;
  203. }
  204. return nullptr;
  205. };
  206. auto doMovement = [&](int3 dst, bool transit)
  207. {
  208. movementState = EMoveState::WAITING_MOVE;
  209. LOCPLINT->cb->moveHero(h, dst, transit);
  210. while(movementState != EMoveState::STOP_MOVE && movementState != EMoveState::CONTINUE_MOVE)
  211. boost::this_thread::sleep_for(boost::chrono::milliseconds(1));
  212. };
  213. auto getMovementSoundFor = [&](const CGHeroInstance * hero, int3 posPrev, int3 posNext, EPathNodeAction moveType) -> AudioPath
  214. {
  215. if (moveType == EPathNodeAction::TELEPORT_BATTLE || moveType == EPathNodeAction::TELEPORT_BLOCKING_VISIT || moveType == EPathNodeAction::TELEPORT_NORMAL)
  216. return {};
  217. if (moveType == EPathNodeAction::EMBARK || moveType == EPathNodeAction::DISEMBARK)
  218. return {};
  219. if (moveType == EPathNodeAction::BLOCKING_VISIT)
  220. return {};
  221. // flying movement sound
  222. if (hero->hasBonusOfType(BonusType::FLYING_MOVEMENT))
  223. return AudioPath::builtin("HORSE10.wav");
  224. auto prevTile = LOCPLINT->cb->getTile(h->convertToVisitablePos(posPrev));
  225. auto nextTile = LOCPLINT->cb->getTile(h->convertToVisitablePos(posNext));
  226. auto prevRoad = prevTile->roadType;
  227. auto nextRoad = nextTile->roadType;
  228. bool movingOnRoad = prevRoad->getId() != Road::NO_ROAD && nextRoad->getId() != Road::NO_ROAD;
  229. if (movingOnRoad)
  230. return nextTile->terType->horseSound;
  231. else
  232. return nextTile->terType->horseSoundPenalty;
  233. };
  234. auto canStop = [&](const CGPathNode * node) -> bool
  235. {
  236. if (node->layer != EPathfindingLayer::LAND && node->layer != EPathfindingLayer::SAIL)
  237. return false;
  238. if (node->accessible != EPathAccessibility::ACCESSIBLE)
  239. return false;
  240. return true;
  241. };
  242. int i = 1;
  243. movementState = EMoveState::CONTINUE_MOVE;
  244. int soundChannel = -1;
  245. AudioPath soundName;
  246. for (i=(int)path.nodes.size()-1; i>0 && (movementState == EMoveState::CONTINUE_MOVE || !canStop(&path.nodes[i])); i--)
  247. {
  248. int3 prevCoord = h->convertFromVisitablePos(path.nodes[i].coord);
  249. int3 nextCoord = h->convertFromVisitablePos(path.nodes[i-1].coord);
  250. auto prevObject = getObj(prevCoord, prevCoord == h->pos);
  251. auto nextObjectTop = getObj(nextCoord, false);
  252. auto nextObject = getObj(nextCoord, true);
  253. auto destTeleportObj = getDestTeleportObj(prevObject, nextObjectTop, nextObject);
  254. if (isTeleportAction(path.nodes[i-1].action) && destTeleportObj != nullptr)
  255. {
  256. CCS->soundh->stopSound(soundChannel);
  257. destinationTeleport = destTeleportObj->id;
  258. destinationTeleportPos = nextCoord;
  259. doMovement(h->pos, false);
  260. if (path.nodes[i-1].action == EPathNodeAction::TELEPORT_BLOCKING_VISIT
  261. || path.nodes[i-1].action == EPathNodeAction::TELEPORT_BATTLE)
  262. {
  263. destinationTeleport = ObjectInstanceID();
  264. destinationTeleportPos = int3(-1);
  265. }
  266. if(i != path.nodes.size() - 1)
  267. {
  268. soundName = getMovementSoundFor(h, prevCoord, nextCoord, path.nodes[i-1].action);
  269. if (!soundName.empty())
  270. soundChannel = CCS->soundh->playSound(soundName, -1);
  271. else
  272. soundChannel = -1;
  273. }
  274. continue;
  275. }
  276. if (path.nodes[i-1].turns)
  277. { //stop sending move requests if the next node can't be reached at the current turn (hero exhausted his move points)
  278. movementState = EMoveState::STOP_MOVE;
  279. break;
  280. }
  281. {
  282. // Start a new sound for the hero movement or let the existing one carry on.
  283. AudioPath newSoundName = getMovementSoundFor(h, prevCoord, nextCoord, path.nodes[i-1].action);
  284. if(newSoundName != soundName)
  285. {
  286. soundName = newSoundName;
  287. CCS->soundh->stopSound(soundChannel);
  288. if (!soundName.empty())
  289. soundChannel = CCS->soundh->playSound(soundName, -1);
  290. else
  291. soundChannel = -1;
  292. }
  293. }
  294. assert(h->pos.z == nextCoord.z); // Z should change only if it's movement via teleporter and in this case this code shouldn't be executed at all
  295. int3 endpos(nextCoord.x, nextCoord.y, h->pos.z);
  296. logGlobal->trace("Requesting hero movement to %s", endpos.toString());
  297. bool useTransit = false;
  298. if ((i-2 >= 0) // Check there is node after next one; otherwise transit is pointless
  299. && (CGTeleport::isConnected(nextObjectTop, getObj(h->convertFromVisitablePos(path.nodes[i-2].coord), false))
  300. || CGTeleport::isTeleport(nextObjectTop)))
  301. { // Hero should be able to go through object if it's allow transit
  302. useTransit = true;
  303. }
  304. else if (path.nodes[i-1].layer == EPathfindingLayer::AIR)
  305. useTransit = true;
  306. doMovement(endpos, useTransit);
  307. logGlobal->trace("Resuming %s", __FUNCTION__);
  308. bool guarded = LOCPLINT->cb->isInTheMap(LOCPLINT->cb->getGuardingCreaturePosition(endpos - int3(1, 0, 0)));
  309. if ((!useTransit && guarded) || LOCPLINT->showingDialog->get() == true) // Abort movement if a guard was fought or there is a dialog to display (Mantis #1136)
  310. break;
  311. }
  312. CCS->soundh->stopSound(soundChannel);
  313. //Update cursor so icon can change if needed when it reappears; doesn;'t apply if a dialog box pops up at the end of the movement
  314. if (!LOCPLINT->showingDialog->get())
  315. GH.fakeMouseMove();
  316. CGI->mh->waitForOngoingAnimations();
  317. setMovementStatus(false);
  318. }