HeroMovementController.cpp 13 KB

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