diff --git a/src/caveexpress/client/CaveExpressClientMap.cpp b/src/caveexpress/client/CaveExpressClientMap.cpp index 63aac8dc6..e4aec504f 100644 --- a/src/caveexpress/client/CaveExpressClientMap.cpp +++ b/src/caveexpress/client/CaveExpressClientMap.cpp @@ -1,9 +1,11 @@ #include "caveexpress/client/CaveExpressClientMap.h" +#include "caveexpress/server/entities/Player.h" #include "caveexpress/shared/CaveExpressEntityType.h" #include "caveexpress/shared/CaveExpressCooldown.h" #include "caveexpress/client/entities/ClientWindowTile.h" #include "caveexpress/client/entities/ClientCaveTile.h" #include "caveexpress/shared/network/messages/ProtocolMessages.h" +#include "common/Math.h" #include "common/ThemeType.h" #include "common/vec2.h" #include "particles/Bubble.h" @@ -20,6 +22,8 @@ #include "common/Log.h" #include "service/ServiceProvider.h" #include "common/DateUtil.h" +#include "ui/nodes/UINodeSprite.h" +#include "ui/windows/IUIMapWindow.h" #include #include @@ -63,6 +67,42 @@ void CaveExpressClientMap::renderWater (int x, int y) const } } +// _player arrow to cave +void CaveExpressClientMap::renderArrow () const +{ + ClientPlayer *player = getPlayer(); + if (!player) + return; + UINodeSpriteRot* arrow = UI::get().getNode(UI_WINDOW_MAP, UINODE_TARGET_ARROW); + float ang = arrow->_angle; + if (ang < 0.f) + return; + + float xdir = cosf(ang), ydir = -sinf(ang); + + int x1, y1; + player->getScreenPos(x1,y1); + const vec2& s = player->getSize(); + y1 -= s.y / 2; + const float d1 = 40.f, d2 = 90.f; + int x2 = x1 + xdir * d2; + int y2 = y1 + ydir * d2; + x1 += xdir * d1; + y1 += ydir * d1; + _frontend->renderLine(x1, y1, x2, y2, colorGreen); + + const float a = M_PI + M_PI / 6.f, d3 = 15.f; + float xa = cosf(ang + a), ya = -sinf(ang + a); + int x3 = x2 + xa * d3; + int y3 = y2 + ya * d3; + const float b = M_PI - M_PI / 6.f; + _frontend->renderLine(x2, y2, x3, y3, colorGreen); + float xb = cosf(ang + b), yb = -sinf(ang + b); + int x4 = x2 + xb * d3; + int y4 = y2 + yb * d3; + _frontend->renderLine(x2, y2, x4, y4, colorGreen); +} + bool CaveExpressClientMap::drop () { if (isPause() || !isActive()) @@ -267,6 +307,7 @@ void CaveExpressClientMap::renderEnd (int x, int y) const if (_target) _frontend->renderTarget(_target); renderWater(x, y); + renderArrow(); } int CaveExpressClientMap::renderCooldownDescription (uint32_t cooldownIndex, int x, int y, int w, int h) const diff --git a/src/caveexpress/client/CaveExpressClientMap.h b/src/caveexpress/client/CaveExpressClientMap.h index 431e1beac..ec71b3f4a 100644 --- a/src/caveexpress/client/CaveExpressClientMap.h +++ b/src/caveexpress/client/CaveExpressClientMap.h @@ -11,6 +11,7 @@ class CaveExpressClientMap: public ClientMap { float _wind = 0.0f; mutable RenderTarget* _target = nullptr; + void renderArrow () const; void renderWater (int x, int y) const; SDL_Rect getWaterRect(int x, int y) const; void couldNotFindEntity (const std::string& prefix, uint16_t id) const override; diff --git a/src/caveexpress/client/network/TargetCaveHandler.h b/src/caveexpress/client/network/TargetCaveHandler.h index f94944946..e7b49e115 100644 --- a/src/caveexpress/client/network/TargetCaveHandler.h +++ b/src/caveexpress/client/network/TargetCaveHandler.h @@ -20,7 +20,14 @@ class TargetCaveHandler: public ClientProtocolHandler { void execute(const TargetCaveMessage* msg) override { + const float ang = msg->getAngle(); + UINodeSpriteRot* arrow = UI::get().getNode(UI_WINDOW_MAP, UINODE_TARGET_ARROW); + arrow->_angle = ang; + const uint8_t caveNumber = msg->getCaveNumber(); + if (caveNumber >= 200) // from update + return; + UINodeSprite* node = UI::get().getNode(UI_WINDOW_MAP, UINODE_TARGETCAVEID); if (caveNumber == 0) { node->clearSprites(); diff --git a/src/caveexpress/client/ui/windows/UIMapWindow.cpp b/src/caveexpress/client/ui/windows/UIMapWindow.cpp index f4d580fe6..95a3c9e57 100644 --- a/src/caveexpress/client/ui/windows/UIMapWindow.cpp +++ b/src/caveexpress/client/ui/windows/UIMapWindow.cpp @@ -134,6 +134,10 @@ void UIMapWindow::initHudNodes() _panel->add(pkgLeft); add(_panel); + + UINodeSpriteRot *arrow = new UINodeSpriteRot(_frontend, spriteHeight, spriteHeight); + arrow->setId(UINODE_TARGET_ARROW); + add(arrow); } } diff --git a/src/caveexpress/server/entities/Player.cpp b/src/caveexpress/server/entities/Player.cpp index d09996a51..8244264e4 100644 --- a/src/caveexpress/server/entities/Player.cpp +++ b/src/caveexpress/server/entities/Player.cpp @@ -18,6 +18,7 @@ #include "caveexpress/shared/CaveExpressCooldown.h" #include "caveexpress/shared/CaveExpressSoundType.h" #include "caveexpress/shared/constants/ConfigVars.h" +#include namespace caveexpress { @@ -28,7 +29,8 @@ const float gravityScale = 0.3f; Player::Player (Map& map, ClientId clientId) : IEntity(EntityTypes::PLAYER, map), _touching(nullptr), _invulnerableTime(0u), _powerUpTime(0u), _collectedNPC(nullptr), _acceleration(b2Vec2_zero), _fingerAcceleration( false), _accelerateX(0), _accelerateY(0), _clientId(clientId), _lastAccelerate(0), _name(""), _lastFruitCollected(0), _hitpoints( - 0), _lives(0), _fruitsCollectedInARow(0), _revoluteJoint(nullptr), _crashReason(CRASH_NONE) { + 0), _lives(0), _fruitsCollectedInARow(0), _revoluteJoint(nullptr), _crashReason(CRASH_NONE) +{ _godMode = Config.getConfigVar(GOD_MODE); _maxHitPoints = Config.getConfigVar(MAX_HITPOINTS); _hitpoints = _maxHitPoints->getIntValue(); @@ -39,6 +41,8 @@ Player::Player (Map& map, ClientId clientId) : setAnimationType(Animations::ANIMATION_IDLE); setState(PlayerState::PLAYER_IDLE); memset(_collectedEntities, 0, sizeof(_collectedEntities)); + _targetCavePos.x = -1.f; + _targetCavePos.y = -1.f; } Player::~Player () @@ -154,6 +158,8 @@ void Player::update (uint32_t deltaTime) { IEntity::update(deltaTime); + sendTargetCaveAngle(); + if (isCrashed()) { // before we crash, we should drop the stuff we are carrying drop(); @@ -462,7 +468,7 @@ bool Player::collect (CollectableEntity* entity) break; } if (EntityTypes::isStone(entityType)) { - GameEvent.sendTargetCave(ClientIdToClientMask(_clientId), 100); + GameEvent.sendTargetCave(ClientIdToClientMask(_clientId), 100, -1.f); Achievements::COLLECT_10_STONES.unlock(); Achievements::COLLECT_100_STONES.unlock(); } @@ -481,7 +487,7 @@ void Player::drop () if (EntityTypes::isStone(*entityType)) { Stone *entity = new Stone(_map, getPos().x, getPos().y, this); entity->createBody(); - GameEvent.sendTargetCave(ClientIdToClientMask(_clientId), 0); + GameEvent.sendTargetCave(ClientIdToClientMask(_clientId), 0, -1.f); } else if (EntityTypes::isBomb(*entityType)) { Bomb *entity = new Bomb(_map, getPos().x, getPos().y, this); entity->createBody(); @@ -629,7 +635,33 @@ void Player::setPlatform (Platform* entity) npc->resetTriggerMovement(); } -void Player::setCollectedNPC(NPCFriendly *npc) { +static float GetAngle(float x, float y) +{ + if (x == 0.f && y == 0.f) + return 0.f; + + if (y == 0.f) + return (x < 0.f) ? M_PI : 0.f; + else + return (y < 0.f) ? atan2f(-y, x) : (2.f * M_PI - atan2f(y, x)); +} + +void Player::sendTargetCaveAngle() +{ + if (_collectedNPC == nullptr) + return; + if (_collectedNPC->getTargetCave() == nullptr) + return; + + _targetCavePos = _collectedNPC->getTargetCave()->getPos(); + const b2Vec2 dir = _targetCavePos - getPos(); + const float angle = GetAngle(dir.x, dir.y); + + GameEvent.sendTargetCave(ClientIdToClientMask(_clientId), 200, angle); +} + +void Player::setCollectedNPC(NPCFriendly *npc) +{ // we can't collect a npc if we have collected something else if (npc && !isFree()) return; @@ -637,9 +669,9 @@ void Player::setCollectedNPC(NPCFriendly *npc) { _collectedNPC = npc; if (npc != nullptr) { npc->setCollected(); - GameEvent.sendTargetCave(ClientIdToClientMask(_clientId), npc->getTargetCaveNumber()); + GameEvent.sendTargetCave(ClientIdToClientMask(_clientId), npc->getTargetCaveNumber(), -1.f); } else { - GameEvent.sendTargetCave(ClientIdToClientMask(_clientId), 0); + GameEvent.sendTargetCave(ClientIdToClientMask(_clientId), 0, -1.f); } } diff --git a/src/caveexpress/server/entities/Player.h b/src/caveexpress/server/entities/Player.h index 819665bd9..eee28aeb2 100644 --- a/src/caveexpress/server/entities/Player.h +++ b/src/caveexpress/server/entities/Player.h @@ -46,6 +46,7 @@ class Player: public IEntity { uint32_t _powerUpTime; NPCFriendly* _collectedNPC; + b2Vec2 _targetCavePos; b2Vec2 _acceleration; @@ -127,6 +128,7 @@ class Player: public IEntity { void createBody (const b2Vec2 &pos); void setCollectedNPC(NPCFriendly *npc); + void sendTargetCaveAngle(); void reset (); ClientId getClientId () const; diff --git a/src/caveexpress/server/events/GameEventHandler.cpp b/src/caveexpress/server/events/GameEventHandler.cpp index a1dce31f2..1e0b32052 100644 --- a/src/caveexpress/server/events/GameEventHandler.cpp +++ b/src/caveexpress/server/events/GameEventHandler.cpp @@ -115,8 +115,8 @@ void GameEventHandler::announceTargetCave(int clientMask, const NPCFriendly& npc _serviceProvider->getNetwork().sendToClients(clientMask, msg); } -void GameEventHandler::sendTargetCave(int clientMask, uint8_t number) const { - const TargetCaveMessage msg(number); +void GameEventHandler::sendTargetCave(int clientMask, uint8_t number, float angle) const { + const TargetCaveMessage msg(number, angle); _serviceProvider->getNetwork().sendToClients(clientMask, msg); } diff --git a/src/caveexpress/server/events/GameEventHandler.h b/src/caveexpress/server/events/GameEventHandler.h index af5e029c2..8a81299b9 100644 --- a/src/caveexpress/server/events/GameEventHandler.h +++ b/src/caveexpress/server/events/GameEventHandler.h @@ -68,7 +68,7 @@ class GameEventHandler: public NonCopyable { void announceTargetCave (int clientMask, const NPCFriendly& npc, int16_t delayMillis) const; // inform the client about the target cave number the npc wants to get carried to - void sendTargetCave (int clientMask, uint8_t number) const; + void sendTargetCave (int clientMask, uint8_t number, float angle) const; // inform the clients that the given entity changed its animation // used to e.g. change a npc animation to walking diff --git a/src/caveexpress/shared/network/messages/TargetCaveMessage.h b/src/caveexpress/shared/network/messages/TargetCaveMessage.h index fd871212c..42ec07268 100644 --- a/src/caveexpress/shared/network/messages/TargetCaveMessage.h +++ b/src/caveexpress/shared/network/messages/TargetCaveMessage.h @@ -7,27 +7,35 @@ namespace caveexpress { class TargetCaveMessage: public IProtocolMessage { private: uint8_t _targetCave; // 0 none, 100 stone + float _angle; // arrow angle to target cave -1 if none public: - TargetCaveMessage(uint8_t targetCave) : - IProtocolMessage(protocol::PROTO_TARGETCAVE), _targetCave(targetCave) { + TargetCaveMessage(uint8_t targetCave, float angle) : + IProtocolMessage(protocol::PROTO_TARGETCAVE), _targetCave(targetCave), _angle(angle) { } PROTOCOL_CLASS_FACTORY(TargetCaveMessage); explicit TargetCaveMessage(ByteStream& input) : - IProtocolMessage(protocol::PROTO_TARGETCAVE) { + IProtocolMessage(protocol::PROTO_TARGETCAVE) + { _targetCave = input.readByte(); + _angle = input.readFloat(); } void serialize(ByteStream& out) const override { out.addByte(_id); out.addByte(_targetCave); + out.addFloat(_angle); } inline uint8_t getCaveNumber() const { return _targetCave; } + + inline float getAngle() const { + return _angle; + } }; } diff --git a/src/modules/ui/nodes/UINodeSprite.cpp b/src/modules/ui/nodes/UINodeSprite.cpp index b8d868e4b..076cbb8e9 100644 --- a/src/modules/ui/nodes/UINodeSprite.cpp +++ b/src/modules/ui/nodes/UINodeSprite.cpp @@ -9,6 +9,10 @@ UINodeSprite::UINodeSprite (IFrontend *frontend, int spriteWidth, int spriteHeig 0.0f), _movementSpeed(0.0f), _movementActive(false) { } +UINodeSpriteRot::UINodeSpriteRot (IFrontend *frontend, int spriteWidth, int spriteHeight) + : UINodeSprite(frontend, spriteWidth, spriteHeight) +{ +} UINodeSprite::UINodeSprite (IFrontend *frontend, const EntityType& type, const Animation& animation, int spriteWidth, int spriteHeight) : UINode(frontend), _offset(0), _borderWidth(-1.0f), _borderHeight(-1.0f), _spriteWidth( diff --git a/src/modules/ui/nodes/UINodeSprite.h b/src/modules/ui/nodes/UINodeSprite.h index 1fcea6902..f519f4028 100644 --- a/src/modules/ui/nodes/UINodeSprite.h +++ b/src/modules/ui/nodes/UINodeSprite.h @@ -40,6 +40,14 @@ class UINodeSprite: public UINode { void update (uint32_t deltaTime) override; }; +class UINodeSpriteRot: public UINodeSprite { +public: + float _angle = -1.f; + + UINodeSpriteRot (IFrontend *frontend, int spriteWidth = -1, int spriteHeight = -1); +}; + + inline void UINodeSprite::addSprite (const SpritePtr& sprite) { _sprites.push_back(sprite); diff --git a/src/modules/ui/windows/IUIMapWindow.h b/src/modules/ui/windows/IUIMapWindow.h index 24202b8ff..ba318c511 100644 --- a/src/modules/ui/windows/IUIMapWindow.h +++ b/src/modules/ui/windows/IUIMapWindow.h @@ -9,6 +9,7 @@ #define UINODE_SECONDS_REMAINING "seconds" #define UINODE_MAP "map" #define UINODE_TARGETCAVEID "targetcave" +#define UINODE_TARGET_ARROW "target_arrow" #define UINODE_COLLECTED "collected" #define UINODE_TRANSFERS "transfers" #define UINODE_TRANSFERS_LEFT "transfers_left"