From cce297615671abd624d8c3a5225ffef0a96d6447 Mon Sep 17 00:00:00 2001 From: Eduardo Bart Date: Wed, 9 Jan 2013 17:29:58 -0200 Subject: [PATCH] Many enhancements in client API * Fix issues in item use * Stack animated texts values * Add utility functions for changing creature color and jumping * Add some new extended functionality * Improve map shader API --- modules/game_interface/gameinterface.lua | 16 +- modules/game_interface/widgets/uigamemap.lua | 3 +- src/client/animatedtext.cpp | 48 +++++- src/client/animatedtext.h | 9 ++ src/client/client.cpp | 1 + src/client/const.h | 23 +-- src/client/creature.cpp | 104 ++++++++++++- src/client/creature.h | 15 ++ src/client/game.cpp | 68 +++++++-- src/client/game.h | 17 ++- src/client/localplayer.cpp | 2 +- src/client/luafunctions.cpp | 18 +++ src/client/map.cpp | 111 ++++++++++++-- src/client/map.h | 21 +++ src/client/mapview.cpp | 67 +++++++- src/client/mapview.h | 8 +- src/client/protocolcodes.h | 6 +- src/client/protocolgame.cpp | 3 +- src/client/protocolgame.h | 7 +- src/client/protocolgameparse.cpp | 152 +++++++++++++++---- src/client/protocolgamesend.cpp | 41 ++++- src/client/shadermanager.cpp | 23 ++- src/client/shadermanager.h | 11 +- src/client/spritemanager.cpp | 4 +- src/client/spritemanager.h | 2 +- src/client/statictext.cpp | 12 ++ src/client/statictext.h | 5 + src/client/thingtype.cpp | 13 ++ src/client/thingtype.h | 12 ++ src/client/thingtypemanager.cpp | 40 ++++- src/client/thingtypemanager.h | 3 +- src/client/tile.cpp | 33 ++-- src/client/tile.h | 2 +- src/client/uimap.h | 2 +- src/framework/cmake/FindICU.cmake | 21 +++ src/framework/luafunctions.cpp | 20 +++ src/framework/stdext/math.cpp | 5 + src/framework/stdext/math.h | 2 + 38 files changed, 832 insertions(+), 118 deletions(-) create mode 100644 src/framework/cmake/FindICU.cmake diff --git a/modules/game_interface/gameinterface.lua b/modules/game_interface/gameinterface.lua index 99232c07..2fe9cdb2 100644 --- a/modules/game_interface/gameinterface.lua +++ b/modules/game_interface/gameinterface.lua @@ -284,7 +284,7 @@ function onUseWith(clickedWidget, mousePosition) if clickedWidget:getClassName() == 'UIMap' then local tile = clickedWidget:getTile(mousePosition) if tile then - g_game.useWith(selectedThing, tile:getTopMultiUseThing(false)) + g_game.useWith(selectedThing, tile:getTopMultiUseThing()) end elseif clickedWidget:getClassName() == 'UIItem' and not clickedWidget:isVirtual() then g_game.useWith(selectedThing, clickedWidget:getItem()) @@ -450,7 +450,7 @@ function createThingMenu(menuPosition, lookThing, useThing, creatureThing) menu:display(menuPosition) end -function processMouseAction(menuPosition, mouseButton, autoWalkPos, lookThing, useThing, creatureThing, multiUseThing) +function processMouseAction(menuPosition, mouseButton, autoWalkPos, lookThing, useThing, creatureThing) local keyboardModifiers = g_keyboard.getModifiers() if not Options.getOption('classicControl') then @@ -482,20 +482,20 @@ function processMouseAction(menuPosition, mouseButton, autoWalkPos, lookThing, u -- classic control else - if multiUseThing and keyboardModifiers == KeyboardNoModifier and mouseButton == MouseRightButton and not g_mouse.isPressed(MouseLeftButton) then + if useThing and keyboardModifiers == KeyboardNoModifier and mouseButton == MouseRightButton and not g_mouse.isPressed(MouseLeftButton) then local player = g_game.getLocalPlayer() if creatureThing and creatureThing ~= player then g_game.attack(creatureThing) return true - elseif multiUseThing:isContainer() then - if multiUseThing:getParentContainer() then - g_game.open(multiUseThing, multiUseThing:getParentContainer()) + elseif useThing:isContainer() then + if useThing:getParentContainer() then + g_game.open(useThing, useThing:getParentContainer()) return true else - g_game.open(multiUseThing) + g_game.open(useThing) return true end - elseif multiUseThing:isMultiUse() then + elseif useThing:isMultiUse() then startUseWith(useThing) return true else diff --git a/modules/game_interface/widgets/uigamemap.lua b/modules/game_interface/widgets/uigamemap.lua index 8072ff61..b6b55e18 100644 --- a/modules/game_interface/widgets/uigamemap.lua +++ b/modules/game_interface/widgets/uigamemap.lua @@ -81,10 +81,9 @@ function UIGameMap:onMouseRelease(mousePosition, mouseButton) lookThing = tile:getTopLookThing() useThing = tile:getTopUseThing() creatureThing = tile:getTopCreature() - multiUseThing = tile:getTopMultiUseThing() end - local ret = modules.game_interface.processMouseAction(mousePosition, mouseButton, autoWalkPos, lookThing, useThing, creatureThing, multiUseThing) + local ret = modules.game_interface.processMouseAction(mousePosition, mouseButton, autoWalkPos, lookThing, useThing, creatureThing) if ret then self.cancelNextRelease = true end diff --git a/src/client/animatedtext.cpp b/src/client/animatedtext.cpp index 7ac92427..c0c0e10f 100644 --- a/src/client/animatedtext.cpp +++ b/src/client/animatedtext.cpp @@ -22,6 +22,7 @@ #include "animatedtext.h" #include "map.h" +#include "game.h" #include #include #include @@ -34,15 +35,32 @@ AnimatedText::AnimatedText() void AnimatedText::drawText(const Point& dest, const Rect& visibleRect) { + static float tf = Otc::ANIMATED_TEXT_DURATION; + static float tftf = Otc::ANIMATED_TEXT_DURATION * Otc::ANIMATED_TEXT_DURATION; + Point p = dest; Size textSize = m_cachedText.getTextSize(); - p.x += 20 - textSize.width() / 2; - p.y += (-20 * m_animationTimer.ticksElapsed()) / Otc::ANIMATED_TEXT_DURATION; + float t = m_animationTimer.ticksElapsed(); + p.x += (24 - textSize.width() / 2); + + if(g_game.getFeature(Otc::GameDiagonalAnimatedText)) { + p.x -= (4 * t / tf) + (8 * t * t / tftf); + } + + p.y += 8 + (-48 * t) / tf; + p += m_offset; Rect rect(p, textSize); if(visibleRect.contains(rect)) { //TODO: cache into a framebuffer - g_painter->setColor(m_color); + float t0 = tf / 1.2; + if(t > t0) { + Color color = m_color; + color.setAlpha((float)(1 - (t - t0) / (tf - t0))); + g_painter->setColor(color); + } + else + g_painter->setColor(m_color); m_cachedText.draw(rect); } } @@ -65,3 +83,27 @@ void AnimatedText::setText(const std::string& text) { m_cachedText.setText(text); } + +bool AnimatedText::merge(const AnimatedTextPtr& other) +{ + if(other->getColor() != m_color) + return false; + + if(other->getCachedText().getFont() != m_cachedText.getFont()) + return false; + + if(m_animationTimer.ticksElapsed() > Otc::ANIMATED_TEXT_DURATION / 2.5) + return false; + + try { + int number = stdext::safe_cast(m_cachedText.getText()); + int otherNumber = stdext::safe_cast(other->getCachedText().getText()); + + std::string text = stdext::format("%d", number + otherNumber); + m_cachedText.setText(text); + return true; + } + catch(...) { + return false; + } +} diff --git a/src/client/animatedtext.h b/src/client/animatedtext.h index 728564d3..85970c89 100644 --- a/src/client/animatedtext.h +++ b/src/client/animatedtext.h @@ -38,6 +38,14 @@ public: void setColor(int color); void setText(const std::string& text); + void setOffset(const Point& offset) { m_offset = offset; } + + Color getColor() { return m_color; } + const CachedText& getCachedText() const { return m_cachedText; } + Point getOffset() { return m_offset; } + Timer getTimer() { return m_animationTimer; } + + bool merge(const AnimatedTextPtr& other); AnimatedTextPtr asAnimatedText() { return static_self_cast(); } bool isAnimatedText() { return true; } @@ -49,6 +57,7 @@ private: Color m_color; Timer m_animationTimer; CachedText m_cachedText; + Point m_offset; }; #endif diff --git a/src/client/client.cpp b/src/client/client.cpp index eb63f61e..bf01fef1 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -37,6 +37,7 @@ void Client::init(std::vector& args) // register needed lua functions registerLuaFunctions(); + g_map.init(); g_game.init(); g_shaders.init(); g_things.init(); diff --git a/src/client/const.h b/src/client/const.h index 722490d7..c9bfc7a0 100644 --- a/src/client/const.h +++ b/src/client/const.h @@ -32,15 +32,7 @@ namespace Otc SEA_FLOOR = 7, MAX_Z = 15, UNDERGROUND_FLOOR = SEA_FLOOR+1, - VISIBLE_X_TILES = 15, - VISIBLE_Y_TILES = 11, AWARE_UNDEGROUND_FLOOR_RANGE = 2, - AWARE_X_TILES = VISIBLE_X_TILES + 3, - AWARE_Y_TILES = VISIBLE_Y_TILES + 3, - AWARE_X_LEFT_TILES = AWARE_X_TILES/2 - 1, - AWARE_X_RIGHT_TILES = AWARE_X_TILES/2, - AWARE_Y_TOP_TILES = AWARE_Y_TILES/2 - 1, - AWARE_Y_BOTTOM_TILES = AWARE_Y_TILES/2, INVISIBLE_TICKS_PER_FRAME = 500, ITEM_TICKS_PER_FRAME = 500, @@ -343,9 +335,18 @@ namespace Otc GameFormatCreatureName = 22, GameSpellList = 23, GameClientPing = 24, - GameLoginPending = 25, - GameNewSpeedLaw = 26, - // 23-50 unused yet + GameExtendedClientPing = 25, + GameUpdater = 26, + GameLoginLocale = 27, + GameDoubleHealth = 28, + GameDoubleSkills = 29, + GameChangeMapAwareRange = 30, + GameMapMovePosition = 31, + GameAttackSeq = 32, + GameBlueNpcNameColor = 33, + GameDiagonalAnimatedText = 34, + GameLoginPending = 35, + GameNewSpeedLaw = 36, // 51-100 reserved to be defined in lua LastGameFeature = 101 }; diff --git a/src/client/creature.cpp b/src/client/creature.cpp index 6ae35a7d..5579a7ed 100644 --- a/src/client/creature.cpp +++ b/src/client/creature.cpp @@ -58,6 +58,7 @@ Creature::Creature() : Thing() m_nameCache.setAlign(Fw::AlignTopCenter); m_footStep = 0; m_speedFormula.fill(-1); + m_outfitColor = Color::white; } void Creature::draw(const Point& dest, float scaleFactor, bool animate, LightView *lightView) @@ -89,7 +90,7 @@ void Creature::draw(const Point& dest, float scaleFactor, bool animate, LightVie // local player always have a minimum light in complete darkness if(isLocalPlayer() && (g_map.getLight().intensity < 64 || m_position.z > Otc::SEA_FLOOR)) { - light.intensity = std::max(light.intensity, 2); + light.intensity = std::max(light.intensity, 3); if(light.color == 0 || light.color > 215) light.color = 215; } @@ -101,6 +102,8 @@ void Creature::draw(const Point& dest, float scaleFactor, bool animate, LightVie void Creature::internalDrawOutfit(Point dest, float scaleFactor, bool animateWalk, bool animateIdle, Otc::Direction direction, LightView *lightView) { + g_painter->setColor(m_outfitColor); + // outfit is a real creature if(m_outfit.getCategory() == ThingCategoryCreature) { int animationPhase = animateWalk ? m_walkAnimationPhase : 0; @@ -128,6 +131,9 @@ void Creature::internalDrawOutfit(Point dest, float scaleFactor, bool animateWal zPattern = 1; } + PointF jumpOffset = m_jumpOffset * scaleFactor; + dest -= Point(stdext::round(jumpOffset.x), stdext::round(jumpOffset.y)); + // yPattern => creature addon for(int yPattern = 0; yPattern < getNumPatternY(); yPattern++) { @@ -181,6 +187,8 @@ void Creature::internalDrawOutfit(Point dest, float scaleFactor, bool animateWal type->draw(dest - (getDisplacement() * scaleFactor), scaleFactor, 0, 0, 0, 0, animationPhase, lightView); } + + g_painter->resetColor(); } void Creature::drawOutfit(const Rect& destRect, bool resize) @@ -250,6 +258,11 @@ void Creature::drawInformation(const Point& point, bool useGray, const Rect& par g_painter->setColor(Color::black); g_painter->drawFilledRect(backgroundRect); + if(g_game.getFeature(Otc::GameBlueNpcNameColor) && isNpc() && m_healthPercent == 100 && !useGray) + g_painter->setColor(Color(0x66, 0xcc, 0xff)); + else + g_painter->setColor(fillColor); + g_painter->setColor(fillColor); g_painter->drawFilledRect(healthRect); @@ -320,6 +333,58 @@ void Creature::stopWalk() terminateWalk(); } +void Creature::jump(int height, int duration) +{ + if(!m_jumpOffset.isNull()) + return; + + m_jumpTimer.restart(); + m_jumpHeight = height; + m_jumpDuration = duration; + + updateJump(); +} + +void Creature::updateJump() +{ + int t = m_jumpTimer.ticksElapsed(); + double a = -4 * m_jumpHeight / (m_jumpDuration * m_jumpDuration); + double b = +4 * m_jumpHeight / (m_jumpDuration); + + double height = a*t*t + b*t; + int roundHeight = stdext::round(height); + int halfJumpDuration = m_jumpDuration / 2; + + // schedules next update + if(m_jumpTimer.ticksElapsed() < m_jumpDuration) { + m_jumpOffset = PointF(height, height); + + int diff = 0; + if(m_jumpTimer.ticksElapsed() < halfJumpDuration) + diff = 1; + else if(m_jumpTimer.ticksElapsed() > halfJumpDuration) + diff = -1; + + int nextT, i = 1; + do { + nextT = stdext::round((-b + std::sqrt(std::max(b*b + 4*a*(roundHeight+diff*i), 0.0)) * diff) / (2*a)); + ++i; + + if(nextT < halfJumpDuration) + diff = 1; + else if(nextT > halfJumpDuration) + diff = -1; + } while(nextT - m_jumpTimer.ticksElapsed() == 0 && i < 3); + + auto self = static_self_cast(); + g_dispatcher.scheduleEvent([self] { + self->updateJump(); + }, nextT - m_jumpTimer.ticksElapsed()); + } + else + m_jumpOffset = PointF(0, 0); +} + void Creature::onPositionChange(const Position& newPos, const Position& oldPos) { callLuaField("onPositionChange", newPos, oldPos); @@ -560,6 +625,37 @@ void Creature::setOutfit(const Outfit& outfit) callLuaField("onOutfitChange", m_outfit, oldOutfit); } +void Creature::setOutfitColor(const Color& color, int duration) +{ + if(m_outfitColorUpdateEvent) { + m_outfitColorUpdateEvent->cancel(); + m_outfitColorUpdateEvent = nullptr; + } + + if(duration > 0) { + Color delta = (color - m_outfitColor) / (float)duration; + m_outfitColorTimer.restart(); + updateOutfitColor(m_outfitColor, color, delta, duration); + } + else + m_outfitColor = color; +} + +void Creature::updateOutfitColor(Color color, Color finalColor, Color delta, int duration) +{ + if(m_outfitColorTimer.ticksElapsed() < duration) { + m_outfitColor = color + delta * m_outfitColorTimer.ticksElapsed(); + + auto self = static_self_cast(); + m_outfitColorUpdateEvent = g_dispatcher.scheduleEvent([=] { + self->updateOutfitColor(color, finalColor, delta, duration); + }, 100); + } + else { + m_outfitColor = finalColor; + } +} + void Creature::setSpeed(uint16 speed) { uint16 oldSpeed = m_speed; @@ -707,11 +803,15 @@ int Creature::getStepDuration(bool ignoreDiagonal) if(g_game.getProtocolVersion() >= 900) interval = (interval / g_game.getServerBeat()) * g_game.getServerBeat(); + float factor = 3; + if(g_game.getProtocolVersion() <= 810) + factor = 2; + interval = std::max(interval, g_game.getServerBeat()); if(!ignoreDiagonal && (m_lastStepDirection == Otc::NorthWest || m_lastStepDirection == Otc::NorthEast || m_lastStepDirection == Otc::SouthWest || m_lastStepDirection == Otc::SouthEast)) - interval *= 3; + interval *= factor; return interval; } diff --git a/src/client/creature.h b/src/client/creature.h index aebec973..752ed770 100644 --- a/src/client/creature.h +++ b/src/client/creature.h @@ -55,6 +55,7 @@ public: void setHealthPercent(uint8 healthPercent); void setDirection(Otc::Direction direction); void setOutfit(const Outfit& outfit); + void setOutfitColor(const Color& color, int duration); void setLight(const Light& light) { m_light = light; } void setSpeed(uint16 speed); void setSkull(uint8 skull); @@ -98,11 +99,13 @@ public: virtual int getDisplacementX(); virtual int getDisplacementY(); virtual int getExactSize(int layer = 0, int xPattern = 0, int yPattern = 0, int zPattern = 0, int animationPhase = 0); + PointF getJumpOffset() { return m_jumpOffset; } void updateShield(); // walk related void turn(Otc::Direction direction); + void jump(int height, int duration); virtual void walk(const Position& oldPos, const Position& newPos); virtual void stopWalk(); void allowAppearWalk() { m_allowAppearWalk = true; } @@ -129,6 +132,9 @@ protected: virtual void updateWalk(); virtual void terminateWalk(); + void updateOutfitColor(Color color, Color finalColor, Color delta, int duration); + void updateJump(); + uint32 m_id; std::string m_name; uint8 m_healthPercent; @@ -152,6 +158,9 @@ protected: stdext::boolean m_removed; CachedText m_nameCache; Color m_informationColor; + Color m_outfitColor; + ScheduledEventPtr m_outfitColorUpdateEvent; + Timer m_outfitColorTimer; std::array m_speedFormula; @@ -173,6 +182,12 @@ protected: Position m_lastStepFromPosition; Position m_lastStepToPosition; Position m_oldPosition; + + // jump related + float m_jumpHeight; + float m_jumpDuration; + PointF m_jumpOffset; + Timer m_jumpTimer; }; // @bindclass diff --git a/src/client/game.cpp b/src/client/game.cpp index bb0c2cbb..6b7f2d6c 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -39,6 +39,7 @@ Game g_game; Game::Game() { m_protocolVersion = 0; + m_clientCustomOs = -1; m_clientVersion = 0; m_online = false; m_denyBotCall = false; @@ -98,6 +99,7 @@ void Game::resetGameStates() m_containers.clear(); m_vips.clear(); m_gmActions.clear(); + g_map.resetAwareRange(); } void Game::processConnectionError(const boost::system::error_code& ec) @@ -123,6 +125,11 @@ void Game::processDisconnect() } } +void Game::processUpdateNeeded(const std::string& signature) +{ + g_lua.callGlobalField("g_game", "onUpdateNeeded", signature); +} + void Game::processLoginError(const std::string& error) { g_lua.callGlobalField("g_game", "onLoginError", error); @@ -164,7 +171,7 @@ void Game::processGameStart() g_lua.callGlobalField("g_game", "onGameStart"); disableBotCall(); - if(g_game.getFeature(Otc::GameClientPing)) { + if(g_game.getFeature(Otc::GameClientPing) || g_game.getFeature(Otc::GameExtendedClientPing)) { m_protocolGame->sendPing(); m_pingEvent = g_dispatcher.cycleEvent([this] { if(m_protocolGame && m_protocolGame->isConnected()) { @@ -464,7 +471,7 @@ void Game::processWalkCancel(Otc::Direction direction) m_localPlayer->cancelWalk(direction); } -void Game::loginWorld(const std::string& account, const std::string& password, const std::string& worldName, const std::string& worldHost, int worldPort, const std::string& characterName) +void Game::loginWorld(const std::string& account, const std::string& password, const std::string& worldName, const std::string& worldHost, int worldPort, const std::string& characterName, const std::string& locale) { if(m_protocolGame || isOnline()) stdext::throw_exception("Unable to login into a world while already online or logging."); @@ -479,7 +486,7 @@ void Game::loginWorld(const std::string& account, const std::string& password, c m_localPlayer->setName(characterName); m_protocolGame = ProtocolGamePtr(new ProtocolGame); - m_protocolGame->login(account, password, worldHost, (uint16)worldPort, characterName); + m_protocolGame->login(account, password, worldHost, (uint16)worldPort, characterName, locale); m_characterName = characterName; m_worldName = worldName; } @@ -608,15 +615,13 @@ void Game::autoWalk(std::vector dirs) if(isFollowing()) cancelFollow(); - auto it = dirs.begin(); - Otc::Direction direction = *it; + Otc::Direction direction = dirs.front(); if(m_localPlayer->canWalk(direction)) { TilePtr toTile = g_map.getTile(m_localPlayer->getPosition().translatedToDirection(direction)); - if(toTile && toTile->isWalkable() && !m_localPlayer->isAutoWalking()) - { + if(toTile && toTile->isWalkable() && !m_localPlayer->isAutoWalking()) { m_localPlayer->preWalk(direction); - forceWalk(direction); - dirs.erase(it); + //forceWalk(direction); + //dirs.erase(it); } } @@ -774,7 +779,7 @@ void Game::useWith(const ItemPtr& item, const ThingPtr& toThing) if(!pos.isValid()) // virtual item pos = Position(0xFFFF, 0, 0); // means that is a item in inventory - if(toThing->isCreature() && g_game.getProtocolVersion() >= 860) + if(toThing->isCreature()) m_protocolGame->sendUseOnCreature(pos, item->getId(), item->getStackpos(), toThing->getId()); else m_protocolGame->sendUseItemWith(pos, item->getId(), item->getStackpos(), toThing->getPosition(), toThing->getId(), toThing->getStackpos()); @@ -793,10 +798,10 @@ void Game::useInventoryItemWith(int itemId, const ThingPtr& toThing) m_protocolGame->sendUseItemWith(pos, itemId, 0, toThing->getPosition(), toThing->getId(), toThing->getStackpos()); } -void Game::open(const ItemPtr& item, const ContainerPtr& previousContainer) +int Game::open(const ItemPtr& item, const ContainerPtr& previousContainer) { if(!canPerformGameAction() || !item) - return; + return -1; int id = 0; if(!previousContainer) @@ -805,6 +810,7 @@ void Game::open(const ItemPtr& item, const ContainerPtr& previousContainer) id = previousContainer->getId(); m_protocolGame->sendUseItem(item->getPosition(), item->getId(), item->getStackpos(), id); + return id; } void Game::openParent(const ContainerPtr& container) @@ -1218,6 +1224,13 @@ void Game::ping() m_denyBotCall = true; } +void Game::changeMapAwareRange(int xrange, int yrange) +{ + if(!canPerformGameAction()) + return; + m_protocolGame->sendChangeMapAwareRange(xrange, yrange); +} + bool Game::checkBotProtection() { #ifdef BOT_PROTECTION @@ -1269,6 +1282,10 @@ void Game::setProtocolVersion(int version) enableFeature(Otc::GameCreatureEmblems); } + if(version >= 860) { + enableFeature(Otc::GameAttackSeq); + } + if(version >= 862) { enableFeature(Otc::GamePenalityOnDeath); } @@ -1350,8 +1367,18 @@ void Game::setFollowingCreature(const CreaturePtr& creature) std::string Game::formatCreatureName(const std::string& name) { std::string formatedName = name; - if(getFeature(Otc::GameFormatCreatureName) && name.length() > 0) - formatedName[0] = stdext::upchar(formatedName[0]); + if(getFeature(Otc::GameFormatCreatureName) && name.length() > 0) { + bool upnext = true; + for(uint i=0;i= 0) + return m_clientCustomOs; + + if(g_app.getOs() == "windows") + return 10; + else if(g_app.getOs() == "mac") + return 12; + else + return 10; +} diff --git a/src/client/game.h b/src/client/game.h index 9d3825ec..68d95e76 100644 --- a/src/client/game.h +++ b/src/client/game.h @@ -56,6 +56,7 @@ protected: void processPing(); void processPingBack(int elapsed); + void processUpdateNeeded(const std::string& signature); void processLoginError(const std::string& error); void processLoginAdvice(const std::string& message); void processLoginWait(const std::string& message, int time); @@ -134,7 +135,7 @@ protected: public: // login related - void loginWorld(const std::string& account, const std::string& password, const std::string& worldName, const std::string& worldHost, int worldPort, const std::string& characterName); + void loginWorld(const std::string& account, const std::string& password, const std::string& worldName, const std::string& worldHost, int worldPort, const std::string& characterName, const std::string& locale); void cancelLogin(); void forceLogout(); void safeLogout(); @@ -157,7 +158,7 @@ public: void useInventoryItemWith(int itemId, const ThingPtr& toThing); // container related - void open(const ItemPtr& item, const ContainerPtr& previousContainer); + int open(const ItemPtr& item, const ContainerPtr& previousContainer); void openParent(const ContainerPtr& container); void close(const ContainerPtr& container); void refreshContainer(const ContainerPtr& container); @@ -246,6 +247,9 @@ public: //void reportRuleViolation2(); void ping(); + // otclient only + void changeMapAwareRange(int xrange, int yrange); + // dynamic support for game features void enableFeature(Otc::GameFeature feature) { m_features.set(feature, true); } void disableFeature(Otc::GameFeature feature) { m_features.set(feature, false); } @@ -258,6 +262,12 @@ public: void setClientVersion(int version); int getClientVersion() { return m_clientVersion; } + void setCustomOs(int os) { m_clientCustomOs = os; } + int getOs(); + + void setUpdaterSignature(const std::string& sig) { m_clientSignature = sig; } + std::string getUpdaterSignature() { return m_clientSignature; } + bool canPerformGameAction(); bool checkBotProtection(); @@ -281,6 +291,7 @@ public: std::string getCharacterName() { return m_characterName; } std::string getWorldName() { return m_worldName; } std::vector getGMActions() { return m_gmActions; } + bool isGM() { return m_gmActions.size() > 0; } std::string formatCreatureName(const std::string &name); int findEmptyContainerId(); @@ -319,6 +330,8 @@ private: ScheduledEventPtr m_walkEvent; int m_protocolVersion; int m_clientVersion; + std::string m_clientSignature; + int m_clientCustomOs; }; extern Game g_game; diff --git a/src/client/localplayer.cpp b/src/client/localplayer.cpp index 3fa39e75..e49f7926 100644 --- a/src/client/localplayer.cpp +++ b/src/client/localplayer.cpp @@ -513,5 +513,5 @@ void LocalPlayer::setSpells(const std::vector& spells) bool LocalPlayer::hasSight(const Position& pos) { - return m_position.isInRange(pos, (Otc::VISIBLE_X_TILES - 1)/2, (Otc::VISIBLE_Y_TILES - 1)/2); + return m_position.isInRange(pos, g_map.getAwareRange().left - 1, g_map.getAwareRange().top - 1); } diff --git a/src/client/luafunctions.cpp b/src/client/luafunctions.cpp index ee3dd551..419484e3 100644 --- a/src/client/luafunctions.cpp +++ b/src/client/luafunctions.cpp @@ -54,6 +54,7 @@ void Client::registerLuaFunctions() g_lua.bindSingletonFunction("g_things", "loadDat", &ThingTypeManager::loadDat, &g_things); g_lua.bindSingletonFunction("g_things", "loadOtb", &ThingTypeManager::loadOtb, &g_things); g_lua.bindSingletonFunction("g_things", "loadXml", &ThingTypeManager::loadXml, &g_things); + g_lua.bindSingletonFunction("g_things", "loadOtml", &ThingTypeManager::loadOtml, &g_things); g_lua.bindSingletonFunction("g_things", "isDatLoaded", &ThingTypeManager::isDatLoaded, &g_things); g_lua.bindSingletonFunction("g_things", "isOtbLoaded", &ThingTypeManager::isOtbLoaded, &g_things); g_lua.bindSingletonFunction("g_things", "getDatSignature", &ThingTypeManager::getDatSignature, &g_things); @@ -205,6 +206,7 @@ void Client::registerLuaFunctions() g_lua.bindSingletonFunction("g_game", "mount", &Game::mount, &g_game); g_lua.bindSingletonFunction("g_game", "requestItemInfo", &Game::requestItemInfo, &g_game); g_lua.bindSingletonFunction("g_game", "ping", &Game::ping, &g_game); + g_lua.bindSingletonFunction("g_game", "changeMapAwareRange", &Game::changeMapAwareRange, &g_game); g_lua.bindSingletonFunction("g_game", "canPerformGameAction", &Game::canPerformGameAction, &g_game); g_lua.bindSingletonFunction("g_game", "canReportBugs", &Game::canReportBugs, &g_game); g_lua.bindSingletonFunction("g_game", "checkBotProtection", &Game::checkBotProtection, &g_game); @@ -225,12 +227,17 @@ void Client::registerLuaFunctions() g_lua.bindSingletonFunction("g_game", "setProtocolVersion", &Game::setProtocolVersion, &g_game); g_lua.bindSingletonFunction("g_game", "getClientVersion", &Game::getClientVersion, &g_game); g_lua.bindSingletonFunction("g_game", "setClientVersion", &Game::setClientVersion, &g_game); + g_lua.bindSingletonFunction("g_game", "setUpdaterSignature", &Game::setUpdaterSignature, &g_game); + g_lua.bindSingletonFunction("g_game", "setCustomOs", &Game::setCustomOs, &g_game); + g_lua.bindSingletonFunction("g_game", "getOs", &Game::getOs, &g_game); g_lua.bindSingletonFunction("g_game", "getCharacterName", &Game::getCharacterName, &g_game); g_lua.bindSingletonFunction("g_game", "getWorldName", &Game::getWorldName, &g_game); g_lua.bindSingletonFunction("g_game", "getGMActions", &Game::getGMActions, &g_game); g_lua.bindSingletonFunction("g_game", "getFeature", &Game::getFeature, &g_game); g_lua.bindSingletonFunction("g_game", "setFeature", &Game::setFeature, &g_game); g_lua.bindSingletonFunction("g_game", "enableFeature", &Game::enableFeature, &g_game); + g_lua.bindSingletonFunction("g_game", "disableFeature", &Game::disableFeature, &g_game); + g_lua.bindSingletonFunction("g_game", "isGM", &Game::isGM, &g_game); g_lua.bindSingletonFunction("g_game", "answerModalDialog", &Game::answerModalDialog, &g_game); g_lua.registerSingletonClass("g_shaders"); @@ -278,6 +285,7 @@ void Client::registerLuaFunctions() g_lua.bindClassMemberFunction("getPosition", &Thing::getPosition); g_lua.bindClassMemberFunction("getStackPriority", &Thing::getStackPriority); g_lua.bindClassMemberFunction("getAnimationPhases", &Thing::getAnimationPhases); + g_lua.bindClassMemberFunction("getTile", &Thing::getTile); g_lua.bindClassMemberFunction("isItem", &Thing::isItem); g_lua.bindClassMemberFunction("isMonster", &Thing::isMonster); g_lua.bindClassMemberFunction("isNpc", &Thing::isNpc); @@ -364,6 +372,7 @@ void Client::registerLuaFunctions() g_lua.bindClassMemberFunction("getEmblem", &Creature::getEmblem); g_lua.bindClassMemberFunction("setOutfit", &Creature::setOutfit); g_lua.bindClassMemberFunction("getOutfit", &Creature::getOutfit); + g_lua.bindClassMemberFunction("setOutfitColor", &Creature::setOutfitColor); g_lua.bindClassMemberFunction("getDirection", &Creature::getDirection); g_lua.bindClassMemberFunction("getStepDuration", &Creature::getStepDuration); g_lua.bindClassMemberFunction("getStepProgress", &Creature::getStepProgress); @@ -378,6 +387,7 @@ void Client::registerLuaFunctions() g_lua.bindClassMemberFunction("isWalking", &Creature::isWalking); g_lua.bindClassMemberFunction("isInvisible", &Creature::isInvisible); g_lua.bindClassMemberFunction("canBeSeen", &Creature::canBeSeen); + g_lua.bindClassMemberFunction("jump", &Creature::jump); g_lua.registerClass(); g_lua.bindClassMemberFunction("getServerId", &ItemType::getServerId); @@ -407,6 +417,10 @@ void Client::registerLuaFunctions() g_lua.registerClass(); g_lua.bindClassStaticFunction("create", []{ return StaticTextPtr(new StaticText); }); g_lua.bindClassMemberFunction("addMessage", &StaticText::addMessage); + g_lua.bindClassMemberFunction("setText", &StaticText::setText); + g_lua.bindClassMemberFunction("setFont", &StaticText::setFont); + g_lua.bindClassMemberFunction("setColor", &StaticText::setColor); + g_lua.bindClassMemberFunction("getColor", &StaticText::getColor); g_lua.registerClass(); @@ -469,6 +483,8 @@ void Client::registerLuaFunctions() g_lua.bindClassMemberFunction("clean", &Tile::clean); g_lua.bindClassMemberFunction("addThing", &Tile::addThing); g_lua.bindClassMemberFunction("getThing", &Tile::getThing); + g_lua.bindClassMemberFunction("getThings", &Tile::getThings); + g_lua.bindClassMemberFunction("getItems", &Tile::getItems); g_lua.bindClassMemberFunction("getThingStackpos", &Tile::getThingStackpos); g_lua.bindClassMemberFunction("getThingCount", &Tile::getThingCount); g_lua.bindClassMemberFunction("getTopThing", &Tile::getTopThing); @@ -495,6 +511,7 @@ void Client::registerLuaFunctions() g_lua.bindClassMemberFunction("setItemId", &UIItem::setItemId); g_lua.bindClassMemberFunction("setItemCount", &UIItem::setItemCount); g_lua.bindClassMemberFunction("setItemSubType", &UIItem::setItemSubType); + g_lua.bindClassMemberFunction("setItemVisible", &UIItem::setItemVisible); g_lua.bindClassMemberFunction("setItem", &UIItem::setItem); g_lua.bindClassMemberFunction("setVirtual", &UIItem::setVirtual); g_lua.bindClassMemberFunction("clearItem", &UIItem::clearItem); @@ -503,6 +520,7 @@ void Client::registerLuaFunctions() g_lua.bindClassMemberFunction("getItemSubType", &UIItem::getItemSubType); g_lua.bindClassMemberFunction("getItem", &UIItem::getItem); g_lua.bindClassMemberFunction("isVirtual", &UIItem::isVirtual); + g_lua.bindClassMemberFunction("isItemVisible", &UIItem::isItemVisible); g_lua.registerClass(); g_lua.bindClassStaticFunction("create", []{ return UICreaturePtr(new UICreature); } ); diff --git a/src/client/map.cpp b/src/client/map.cpp index 080bd47f..de37fae0 100644 --- a/src/client/map.cpp +++ b/src/client/map.cpp @@ -35,6 +35,11 @@ Map g_map; TilePtr Map::m_nulltile; +void Map::init() +{ + resetAwareRange(); +} + void Map::terminate() { clean(); @@ -100,14 +105,39 @@ void Map::addThing(const ThingPtr& thing, const Position& pos, int stackPos) if(thing->isItem() || thing->isCreature() || thing->isEffect()) { const TilePtr& tile = getOrCreateTile(pos); - tile->addThing(thing, stackPos); + if(tile) + tile->addThing(thing, stackPos); } else { if(thing->isMissile()) { m_floorMissiles[pos.z].push_back(thing->static_self_cast()); thing->onAppear(); } else if(thing->isAnimatedText()) { + // this code will stack animated texts of the same color AnimatedTextPtr animatedText = thing->static_self_cast(); - m_animatedTexts.push_back(animatedText); + AnimatedTextPtr prevAnimatedText; + bool merged = false; + for(auto other : m_animatedTexts) { + if(other->getPosition() == pos) { + prevAnimatedText = other; + if(other->merge(animatedText)) { + merged = true; + break; + } + } + } + if(!merged) { + if(prevAnimatedText) { + Point offset = prevAnimatedText->getOffset(); + float t = prevAnimatedText->getTimer().ticksElapsed(); + if(t < Otc::ANIMATED_TEXT_DURATION / 4.0) { // didnt move 12 pixels + int y = 12 - 48 * t / (float)Otc::ANIMATED_TEXT_DURATION; + offset += Point(0, y); + } + offset.y = std::min(offset.y, 12); + animatedText->setOffset(offset); + } + m_animatedTexts.push_back(animatedText); + } } else if(thing->isStaticText()) { StaticTextPtr staticText = thing->static_self_cast(); bool mustAdd = true; @@ -119,10 +149,10 @@ void Map::addThing(const ThingPtr& thing, const Position& pos, int stackPos) } } - if(mustAdd) { + if(mustAdd) m_staticTexts.push_back(staticText); - staticText->onAppear(); - } + else + return; } thing->setPosition(pos); @@ -181,6 +211,16 @@ bool Map::removeThingByPos(const Position& pos, int stackPos) return false; } +StaticTextPtr Map::getStaticText(const Position& pos) +{ + for(auto staticText : m_staticTexts) { + // try to combine messages + if(staticText->getPosition() == pos) + return staticText; + } + return nullptr; +} + const TilePtr& Map::createTile(const Position& pos) { if(!pos.isMapPosition()) @@ -251,6 +291,13 @@ void Map::cleanTile(const Position& pos) notificateTileUpdateToMapViews(pos); } } + for(auto it = m_staticTexts.begin();it != m_staticTexts.end();) { + const StaticTextPtr& staticText = *it; + if(staticText->getPosition() == pos && staticText->getMessageMode() == Otc::MessageNone) + it = m_staticTexts.erase(it); + else + ++it; + } } void Map::addCreature(const CreaturePtr& creature) @@ -276,10 +323,8 @@ void Map::removeCreatureById(uint32 id) m_knownCreatures.erase(it); } -void Map::setCentralPosition(const Position& centralPosition) +void Map::removeUnawareThings() { - m_centralPosition = centralPosition; - // remove creatures from tiles that we are not aware anymore for(const auto& pair : m_knownCreatures) { const CreaturePtr& creature = pair.second; @@ -287,6 +332,25 @@ void Map::setCentralPosition(const Position& centralPosition) removeThing(creature); } + // remove static texts from tiles that we are not aware anymore + for(auto it = m_staticTexts.begin(); it != m_staticTexts.end();) { + const StaticTextPtr& staticText = *it; + if(staticText->getMessageMode() == Otc::MessageNone && !isAwareOfPosition(staticText->getPosition())) + it = m_staticTexts.erase(it); + else + ++it; + } +} + +void Map::setCentralPosition(const Position& centralPosition) +{ + if(m_centralPosition == centralPosition) + return; + + m_centralPosition = centralPosition; + + removeUnawareThings(); + // this fixes local player position when the local player is removed from the map, // the local player is removed from the map when there are too many creatures on his tile, // so there is no enough stackpos to the server send him @@ -313,9 +377,14 @@ void Map::setCentralPosition(const Position& centralPosition) mapView->onMapCenterChange(centralPosition); } +std::vector Map::getSightSpectators(const Position& centerPos, bool multiFloor) +{ + return getSpectatorsInRangeEx(centerPos, multiFloor, m_awareRange.left - 1, m_awareRange.right - 2, m_awareRange.top - 1, m_awareRange.bottom - 2); +} + std::vector Map::getSpectators(const Position& centerPos, bool multiFloor) { - return getSpectatorsInRange(centerPos, multiFloor, (Otc::VISIBLE_X_TILES - 1)/2, (Otc::VISIBLE_Y_TILES - 1)/2); + return getSpectatorsInRangeEx(centerPos, multiFloor, m_awareRange.left, m_awareRange.right, m_awareRange.top, m_awareRange.bottom); } std::vector Map::getSpectatorsInRange(const Position& centerPos, bool multiFloor, int xRange, int yRange) @@ -410,10 +479,26 @@ bool Map::isAwareOfPosition(const Position& pos) else groundedPos.coveredDown(); } - return m_centralPosition.isInRange(groundedPos, Otc::AWARE_X_LEFT_TILES, - Otc::AWARE_X_RIGHT_TILES, - Otc::AWARE_Y_TOP_TILES, - Otc::AWARE_Y_BOTTOM_TILES); + return m_centralPosition.isInRange(groundedPos, m_awareRange.left, + m_awareRange.right, + m_awareRange.top, + m_awareRange.bottom); +} + +void Map::setAwareRange(const AwareRange& range) +{ + m_awareRange = range; + removeUnawareThings(); +} + +void Map::resetAwareRange() +{ + AwareRange range; + range.left = 8; + range.top = 6; + range.bottom = 7; + range.right = 9; + setAwareRange(range); } int Map::getFirstAwareFloor() diff --git a/src/client/map.h b/src/client/map.h index 07de2154..903c8fbe 100644 --- a/src/client/map.h +++ b/src/client/map.h @@ -119,10 +119,22 @@ private: std::array m_tiles; }; +struct AwareRange +{ + int top; + int right; + int bottom; + int left; + + int horizontal() { return left + right + 1; } + int vertical() { return top + bottom + 1; } +}; + //@bindsingleton g_map class Map { public: + void init(); void terminate(); void addMapView(const MapViewPtr& mapView); @@ -157,6 +169,8 @@ public: bool removeThing(const ThingPtr& thing); bool removeThingByPos(const Position& pos, int stackPos); + StaticTextPtr getStaticText(const Position& pos); + // tile related const TilePtr& createTile(const Position& pos); template @@ -169,6 +183,7 @@ public: void addCreature(const CreaturePtr& creature); CreaturePtr getCreatureById(uint32 id); void removeCreatureById(uint32 id); + std::vector getSightSpectators(const Position& centerPos, bool multiFloor); std::vector getSpectators(const Position& centerPos, bool multiFloor); std::vector getSpectatorsInRange(const Position& centerPos, bool multiFloor, int xRange, int yRange); std::vector getSpectatorsInRangeEx(const Position& centerPos, bool multiFloor, int minXRange, int maxXRange, int minYRange, int maxYRange); @@ -181,6 +196,10 @@ public: bool isCompletelyCovered(const Position& pos, int firstFloor = 0); bool isAwareOfPosition(const Position& pos); + void setAwareRange(const AwareRange& range); + void resetAwareRange(); + AwareRange getAwareRange() { return m_awareRange; } + Light getLight() { return m_light; } Position getCentralPosition() { return m_centralPosition; } int getFirstAwareFloor(); @@ -193,6 +212,7 @@ public: std::tuple, Otc::PathFindResult> findPath(const Position& start, const Position& goal, int maxSteps, int flags = 0); private: + void removeUnawareThings(); uint getBlockIndex(const Position& pos) { return ((pos.y / BLOCK_SIZE) * (65536 / BLOCK_SIZE)) + (pos.x / BLOCK_SIZE); } std::unordered_map m_tileBlocks[Otc::MAX_Z+1]; @@ -208,6 +228,7 @@ private: Rect m_tilesRect; stdext::packed_storage m_attribs; + AwareRange m_awareRange; static TilePtr m_nulltile; }; diff --git a/src/client/mapview.cpp b/src/client/mapview.cpp index b6e8affc..42dfd8c3 100644 --- a/src/client/mapview.cpp +++ b/src/client/mapview.cpp @@ -57,8 +57,10 @@ MapView::MapView() m_cachedFirstVisibleFloor = 7; m_cachedLastVisibleFloor = 7; m_updateTilesPos = 0; + m_fadeOutTime = 0; + m_fadeInTime = 0; m_minimumAmbientLight = 0; - m_optimizedSize = Size(Otc::AWARE_X_TILES, Otc::AWARE_Y_TILES) * Otc::TILE_PIXELS; + m_optimizedSize = Size(g_map.getAwareRange().horizontal(), g_map.getAwareRange().vertical()) * Otc::TILE_PIXELS; m_framebuffer = g_framebuffers.createFrameBuffer(); setVisibleDimension(Size(15, 11)); @@ -155,6 +157,21 @@ void MapView::draw(const Rect& rect) m_mustDrawVisibleTilesCache = false; } + + float fadeOpacity = 1.0f; + if(!m_shaderSwitchDone && m_fadeOutTime > 0) { + fadeOpacity = 1.0f - (m_fadeTimer.timeElapsed() / m_fadeOutTime); + if(fadeOpacity < 0.0f) { + m_shader = m_nextShader; + m_nextShader = nullptr; + m_shaderSwitchDone = true; + m_fadeTimer.restart(); + } + } + + if(m_shaderSwitchDone && m_shader && m_fadeInTime > 0) + fadeOpacity = std::min(m_fadeTimer.timeElapsed() / m_fadeInTime, 1.0f); + Point drawOffset = ((m_drawDimension - m_visibleDimension - Size(1,1)).toPoint()/2) * m_tileSize; if(isFollowingCreature()) drawOffset += m_followingCreature->getWalkOffset() * scaleFactor; @@ -166,9 +183,20 @@ void MapView::draw(const Rect& rect) drawOffset.y += (srcVisible.height() - srcSize.height()) / 2; Rect srcRect = Rect(drawOffset, srcSize); + if(m_shader && g_painter->hasShaders() && g_graphics.shouldUseShaders() && m_viewMode == NEAR_VIEW) { + Rect framebufferRect = Rect(0,0, m_drawDimension * m_tileSize); + Point center = srcRect.center(); + Point globalCoord = Point(cameraPosition.x - m_drawDimension.width()/2, -(cameraPosition.y - m_drawDimension.height()/2)) * m_tileSize; + m_shader->bind(); + m_shader->setUniformValue(ShaderManager::MAP_CENTER_COORD, center.x / (float)framebufferRect.width(), 1.0f - center.y / (float)framebufferRect.height()); + m_shader->setUniformValue(ShaderManager::MAP_GLOBAL_COORD, globalCoord.x / (float)framebufferRect.height(), globalCoord.y / (float)framebufferRect.height()); + m_shader->setUniformValue(ShaderManager::MAP_ZOOM, scaleFactor); + g_painter->setShaderProgram(m_shader); + } + g_painter->setColor(Color::white); + g_painter->setOpacity(fadeOpacity); glDisable(GL_BLEND); - g_painter->setShaderProgram(m_shader); #if 0 // debug source area g_painter->saveAndResetState(); @@ -182,6 +210,7 @@ void MapView::draw(const Rect& rect) m_framebuffer->draw(rect, srcRect); #endif g_painter->resetShaderProgram(); + g_painter->resetOpacity(); glEnable(GL_BLEND); @@ -198,10 +227,11 @@ void MapView::draw(const Rect& rect) if(!creature->canBeSeen()) continue; + PointF jumpOffset = creature->getJumpOffset() * scaleFactor; Point creatureOffset = Point(16 - creature->getDisplacementX(), -3 - creature->getDisplacementY()); Position pos = creature->getPosition(); Point p = transformPositionTo2D(pos, cameraPosition) - drawOffset; - p += (creature->getDrawOffset() + creatureOffset) * scaleFactor; + p += (creature->getDrawOffset() + creatureOffset) * scaleFactor - Point(stdext::round(jumpOffset.x), stdext::round(jumpOffset.y)); p.x = p.x * horizontalStretchFactor; p.y = p.y * verticalStretchFactor; p += rect.topLeft(); @@ -222,6 +252,9 @@ void MapView::draw(const Rect& rect) //if(pos.z != cameraPosition.z && !staticText->isYell()) // continue; + if(pos.z != cameraPosition.z && staticText->getMessageMode() == Otc::MessageNone) + continue; + Point p = transformPositionTo2D(pos, cameraPosition) - drawOffset; p.x = p.x * horizontalStretchFactor; p.y = p.y * verticalStretchFactor; @@ -232,6 +265,7 @@ void MapView::draw(const Rect& rect) for(const AnimatedTextPtr& animatedText : g_map.getAnimatedTexts()) { Position pos = animatedText->getPosition(); + /* // only draw animated texts from visible floors if(pos.z < m_cachedFirstVisibleFloor || pos.z > m_cachedLastVisibleFloor) continue; @@ -239,6 +273,9 @@ void MapView::draw(const Rect& rect) // dont draw animated texts from covered tiles if(pos.z != cameraPosition.z && g_map.isCovered(pos, m_cachedFirstVisibleFloor)) continue; + */ + if(pos.z != cameraPosition.z) + continue; Point p = transformPositionTo2D(pos, cameraPosition) - drawOffset; p.x = p.x * horizontalStretchFactor; @@ -408,7 +445,7 @@ void MapView::updateVisibleTilesCache(int start) } if(start == 0 && m_viewMode <= NEAR_VIEW) - m_cachedFloorVisibleCreatures = g_map.getSpectators(cameraPosition, false); + m_cachedFloorVisibleCreatures = g_map.getSightSpectators(cameraPosition, false); } void MapView::updateGeometry(const Size& visibleDimension, const Size& optimizedSize) @@ -459,7 +496,7 @@ void MapView::updateGeometry(const Size& visibleDimension, const Size& optimized } // draw actually more than what is needed to avoid massive recalculations on huge views - /* + /* if(viewMode >= HUGE_VIEW) { Size oldDimension = drawDimension; drawDimension = (m_framebuffer->getSize() / tileSize); @@ -656,10 +693,30 @@ void MapView::setDrawMinimapColors(bool enable) m_framebuffer->setSmooth(m_smooth); } +void MapView::setShader(const PainterShaderProgramPtr& shader, float fadein, float fadeout) +{ + if((m_shader == shader && m_shaderSwitchDone) || (m_nextShader == shader && !m_shaderSwitchDone)) + return; + + if(fadeout > 0.0f && m_shader) { + m_nextShader = shader; + m_shaderSwitchDone = false; + } else { + m_shader = shader; + m_nextShader = nullptr; + m_shaderSwitchDone = true; + } + m_fadeTimer.restart(); + m_fadeInTime = fadein; + m_fadeOutTime = fadeout; +} + + void MapView::setDrawLights(bool enable) { if(enable == m_drawLights) return; + if(enable) m_lightView = LightViewPtr(new LightView); else diff --git a/src/client/mapview.h b/src/client/mapview.h index d915353b..8b0cc8a8 100644 --- a/src/client/mapview.h +++ b/src/client/mapview.h @@ -107,10 +107,9 @@ public: void setDrawLights(bool enable); bool isDrawingLights() { return m_drawLights; } - void setShader(const PainterShaderProgramPtr& shader) { m_shader = shader; } + void setShader(const PainterShaderProgramPtr& shader, float fadein, float fadeout); PainterShaderProgramPtr getShader() { return m_shader; } - MapViewPtr asMapView() { return static_self_cast(); } private: @@ -153,6 +152,11 @@ private: std::vector m_spiral; LightViewPtr m_lightView; float m_minimumAmbientLight; + Timer m_fadeTimer; + PainterShaderProgramPtr m_nextShader; + float m_fadeInTime; + float m_fadeOutTime; + stdext::boolean m_shaderSwitchDone; }; #endif diff --git a/src/client/protocolcodes.h b/src/client/protocolcodes.h index ea1e1ebe..0623b344 100644 --- a/src/client/protocolcodes.h +++ b/src/client/protocolcodes.h @@ -33,7 +33,8 @@ namespace Proto { LoginServerCharacterList = 100 }; - enum CreatureOpcode { + enum ItemOpcode { + StaticText = 96, UnknownCreature = 97, OutdatedCreature = 98, Creature = 99 @@ -44,6 +45,7 @@ namespace Proto { GameServerInitGame = 10, GameServerGMActions = 11, GameServerEnterGame = 15, + GameServerUpdateNeeded = 17, GameServerLoginError = 20, GameServerLoginAdvice = 21, GameServerLoginWait = 22, @@ -61,6 +63,7 @@ namespace Proto { // NOTE: add any custom opcodes in this range // 51 - 99 + GameServerChangeMapAwareRange = 51, // original tibia ONLY GameServerFullMap = 100, @@ -156,6 +159,7 @@ namespace Proto { // otclient ONLY ClientExtendedOpcode = 50, + ClientChangeMapAwareRange = 51, // NOTE: add any custom opcodes in this range // 51 - 99 diff --git a/src/client/protocolgame.cpp b/src/client/protocolgame.cpp index 4dd934b5..34b6bd26 100644 --- a/src/client/protocolgame.cpp +++ b/src/client/protocolgame.cpp @@ -26,11 +26,12 @@ #include "item.h" #include "localplayer.h" -void ProtocolGame::login(const std::string& accountName, const std::string& accountPassword, const std::string& host, uint16 port, const std::string& characterName) +void ProtocolGame::login(const std::string& accountName, const std::string& accountPassword, const std::string& host, uint16 port, const std::string& characterName, const std::string& locale) { m_accountName = accountName; m_accountPassword = accountPassword; m_characterName = characterName; + m_locale = locale; connect(host, port); } diff --git a/src/client/protocolgame.h b/src/client/protocolgame.h index 3bc03152..d27c5abe 100644 --- a/src/client/protocolgame.h +++ b/src/client/protocolgame.h @@ -31,7 +31,7 @@ class ProtocolGame : public Protocol { public: - void login(const std::string& accountName, const std::string& accountPassword, const std::string& host, uint16 port, const std::string& characterName); + void login(const std::string& accountName, const std::string& accountPassword, const std::string& host, uint16 port, const std::string& characterName, const std::string& locale); void send(const OutputMessagePtr& outputMessage); void sendExtendedOpcode(uint8 opcode, const std::string& buffer); @@ -107,6 +107,7 @@ public: void sendNewNewRuleViolation(int reason, int action, const std::string& characterName, const std::string& comment, const std::string& translation); void sendRequestItemInfo(int itemId, int subType, int index); void sendAnswerModalDialog(int dialog, int button, int choice); + void sendChangeMapAwareRange(int xrange, int yrange); protected: void onConnect(); @@ -124,6 +125,7 @@ private: void parseEnterGame(const InputMessagePtr& msg); void parseInitGame(const InputMessagePtr& msg); void parseGMActions(const InputMessagePtr& msg); + void parseUpdateNeeded(const InputMessagePtr& msg); void parseLoginError(const InputMessagePtr& msg); void parseLoginAdvice(const InputMessagePtr& msg); void parseLoginWait(const InputMessagePtr& msg); @@ -205,6 +207,7 @@ private: void parsePlayerInventory(const InputMessagePtr& msg); void parseShowModalDialog(const InputMessagePtr& msg); void parseExtendedOpcode(const InputMessagePtr& msg); + void parseChangeMapAwareRange(const InputMessagePtr& msg); public: void setMapDescription(const InputMessagePtr& msg, int x, int y, int z, int width, int height); @@ -215,6 +218,7 @@ public: ThingPtr getThing(const InputMessagePtr& msg); ThingPtr getMappedThing(const InputMessagePtr& msg); CreaturePtr getCreature(const InputMessagePtr& msg, int type = 0); + StaticTextPtr getStaticText(const InputMessagePtr& msg, int type = 0); ItemPtr getItem(const InputMessagePtr& msg, int id = 0); Position getPosition(const InputMessagePtr& msg); @@ -226,6 +230,7 @@ private: std::string m_accountName; std::string m_accountPassword; std::string m_characterName; + std::string m_locale; stdext::timer m_pingTimer; LocalPlayerPtr m_localPlayer; }; diff --git a/src/client/protocolgameparse.cpp b/src/client/protocolgameparse.cpp index 58dd5edb..30aa7689 100644 --- a/src/client/protocolgameparse.cpp +++ b/src/client/protocolgameparse.cpp @@ -72,6 +72,9 @@ void ProtocolGame::parseMessage(const InputMessagePtr& msg) case Proto::GameServerGMActions: parseGMActions(msg); break; + case Proto::GameServerUpdateNeeded: + parseUpdateNeeded(msg); + break; case Proto::GameServerLoginError: parseLoginError(msg); break; @@ -314,13 +317,16 @@ void ProtocolGame::parseMessage(const InputMessagePtr& msg) case Proto::GameServerPlayerDataBasic: parsePlayerInfo(msg); break; + // PROTOCOL>=970 + case Proto::GameServerShowModalDialog: + parseShowModalDialog(msg); + break; // otclient ONLY case Proto::GameServerExtendedOpcode: parseExtendedOpcode(msg); break; - // PROTOCOL>=970 - case Proto::GameServerShowModalDialog: - parseShowModalDialog(msg); + case Proto::GameServerChangeMapAwareRange: + parseChangeMapAwareRange(msg); break; default: stdext::throw_exception(stdext::format("unhandled opcode %d", (int)opcode)); @@ -386,6 +392,12 @@ void ProtocolGame::parseGMActions(const InputMessagePtr& msg) g_game.processGMActions(actions); } +void ProtocolGame::parseUpdateNeeded(const InputMessagePtr& msg) +{ + std::string signature = msg->getString(); + g_game.processUpdateNeeded(signature); +} + void ProtocolGame::parseLoginError(const InputMessagePtr& msg) { std::string error = msg->getString(); @@ -441,7 +453,9 @@ void ProtocolGame::parseMapDescription(const InputMessagePtr& msg) m_localPlayer->setPosition(pos); g_map.setCentralPosition(pos); - setMapDescription(msg, pos.x - Otc::AWARE_X_LEFT_TILES, pos.y - Otc::AWARE_Y_TOP_TILES, pos.z, Otc::AWARE_X_TILES, Otc::AWARE_Y_TILES); + + AwareRange range = g_map.getAwareRange(); + setMapDescription(msg, pos.x - range.left, pos.y - range.top, pos.z, range.horizontal(), range.vertical()); if(!m_mapKnown) { g_dispatcher.addEvent([] { g_lua.callGlobalField("g_game", "onMapKnown"); }); @@ -453,37 +467,57 @@ void ProtocolGame::parseMapDescription(const InputMessagePtr& msg) void ProtocolGame::parseMapMoveNorth(const InputMessagePtr& msg) { - Position pos = g_map.getCentralPosition(); + Position pos; + if(g_game.getFeature(Otc::GameMapMovePosition)) + pos = getPosition(msg); + else + pos = g_map.getCentralPosition(); pos.y--; - setMapDescription(msg, pos.x - Otc::AWARE_X_LEFT_TILES, pos.y - Otc::AWARE_Y_TOP_TILES, pos.z, Otc::AWARE_X_TILES, 1); + AwareRange range = g_map.getAwareRange(); + setMapDescription(msg, pos.x - range.left, pos.y - range.top, pos.z, range.horizontal(), 1); g_map.setCentralPosition(pos); } void ProtocolGame::parseMapMoveEast(const InputMessagePtr& msg) { - Position pos = g_map.getCentralPosition(); + Position pos; + if(g_game.getFeature(Otc::GameMapMovePosition)) + pos = getPosition(msg); + else + pos = g_map.getCentralPosition(); pos.x++; - setMapDescription(msg, pos.x + Otc::AWARE_X_RIGHT_TILES, pos.y - Otc::AWARE_Y_TOP_TILES, pos.z, 1, Otc::AWARE_Y_TILES); + AwareRange range = g_map.getAwareRange(); + setMapDescription(msg, pos.x + range.right, pos.y - range.top, pos.z, 1, range.vertical()); g_map.setCentralPosition(pos); } void ProtocolGame::parseMapMoveSouth(const InputMessagePtr& msg) { - Position pos = g_map.getCentralPosition(); + Position pos; + if(g_game.getFeature(Otc::GameMapMovePosition)) + pos = getPosition(msg); + else + pos = g_map.getCentralPosition(); pos.y++; - setMapDescription(msg, pos.x - Otc::AWARE_X_LEFT_TILES, pos.y + Otc::AWARE_Y_BOTTOM_TILES, pos.z, Otc::AWARE_X_TILES, 1); + AwareRange range = g_map.getAwareRange(); + setMapDescription(msg, pos.x - range.left, pos.y + range.bottom, pos.z, range.horizontal(), 1); g_map.setCentralPosition(pos); } void ProtocolGame::parseMapMoveWest(const InputMessagePtr& msg) { - Position pos = g_map.getCentralPosition(); + Position pos; + if(g_game.getFeature(Otc::GameMapMovePosition)) + pos = getPosition(msg); + else + pos = g_map.getCentralPosition(); pos.x--; - setMapDescription(msg, pos.x - Otc::AWARE_X_LEFT_TILES, pos.y - Otc::AWARE_Y_TOP_TILES, pos.z, 1, Otc::AWARE_Y_TILES); + AwareRange range = g_map.getAwareRange(); + setMapDescription(msg, pos.x - range.left, pos.y - range.top, pos.z, 1, range.vertical()); g_map.setCentralPosition(pos); } @@ -914,8 +948,16 @@ void ProtocolGame::parsePlayerInfo(const InputMessagePtr& msg) void ProtocolGame::parsePlayerStats(const InputMessagePtr& msg) { - double health = msg->getU16(); - double maxHealth = msg->getU16(); + double health; + double maxHealth; + + if(g_game.getFeature(Otc::GameDoubleHealth)) { + health = msg->getU32(); + maxHealth = msg->getU32(); + } else { + health = msg->getU16(); + maxHealth = msg->getU16(); + } double freeCapacity; if(g_game.getFeature(Otc::GameDoubleFreeCapacity)) @@ -935,8 +977,17 @@ void ProtocolGame::parsePlayerStats(const InputMessagePtr& msg) double level = msg->getU16(); double levelPercent = msg->getU8(); - double mana = msg->getU16(); - double maxMana = msg->getU16(); + double mana; + double maxMana; + + if(g_game.getFeature(Otc::GameDoubleHealth)) { + mana = msg->getU32(); + maxMana = msg->getU32(); + } else { + mana = msg->getU16(); + maxMana = msg->getU16(); + } + double magicLevel = msg->getU8(); double baseMagicLevel; @@ -979,10 +1030,16 @@ void ProtocolGame::parsePlayerStats(const InputMessagePtr& msg) void ProtocolGame::parsePlayerSkills(const InputMessagePtr& msg) { for(int skill = 0; skill < Otc::LastSkill; skill++) { - int level = msg->getU8(); + int level; + + if(g_game.getFeature(Otc::GameDoubleSkills)) + level = msg->getU16(); + else + level = msg->getU8(); + int baseLevel; if(g_game.getFeature(Otc::GameSkillsBase)) - baseLevel = msg->getU8(); // base + baseLevel = msg->getU8(); else baseLevel = level; @@ -1002,7 +1059,7 @@ void ProtocolGame::parsePlayerState(const InputMessagePtr& msg) void ProtocolGame::parsePlayerCancelAttack(const InputMessagePtr& msg) { uint seq = 0; - if(g_game.getProtocolVersion() >= 860) + if(g_game.getFeature(Otc::GameAttackSeq)) seq = msg->getU32(); g_game.processAttackCancel(seq); @@ -1234,15 +1291,20 @@ void ProtocolGame::parseWalkWait(const InputMessagePtr& msg) void ProtocolGame::parseFloorChangeUp(const InputMessagePtr& msg) { - Position pos = g_map.getCentralPosition(); + Position pos; + if(g_game.getFeature(Otc::GameMapMovePosition)) + pos = getPosition(msg); + else + pos = g_map.getCentralPosition(); + AwareRange range = g_map.getAwareRange(); pos.z--; int skip = 0; if(pos.z == Otc::SEA_FLOOR) for(int i = Otc::SEA_FLOOR - Otc::AWARE_UNDEGROUND_FLOOR_RANGE; i >= 0; i--) - skip = setFloorDescription(msg, pos.x - Otc::AWARE_X_LEFT_TILES, pos.y - Otc::AWARE_Y_TOP_TILES, i, Otc::AWARE_X_TILES, Otc::AWARE_Y_TILES, 8 - i, skip); + skip = setFloorDescription(msg, pos.x - range.left, pos.y - range.top, i, range.horizontal(), range.vertical(), 8 - i, skip); else if(pos.z > Otc::SEA_FLOOR) - skip = setFloorDescription(msg, pos.x - Otc::AWARE_X_LEFT_TILES, pos.y - Otc::AWARE_Y_TOP_TILES, pos.z - Otc::AWARE_UNDEGROUND_FLOOR_RANGE, Otc::AWARE_X_TILES, Otc::AWARE_Y_TILES, 3, skip); + skip = setFloorDescription(msg, pos.x - range.left, pos.y - range.top, pos.z - Otc::AWARE_UNDEGROUND_FLOOR_RANGE, range.horizontal(), range.vertical(), 3, skip); pos.x++; pos.y++; @@ -1251,17 +1313,22 @@ void ProtocolGame::parseFloorChangeUp(const InputMessagePtr& msg) void ProtocolGame::parseFloorChangeDown(const InputMessagePtr& msg) { - Position pos = g_map.getCentralPosition(); + Position pos; + if(g_game.getFeature(Otc::GameMapMovePosition)) + pos = getPosition(msg); + else + pos = g_map.getCentralPosition(); + AwareRange range = g_map.getAwareRange(); pos.z++; int skip = 0; if(pos.z == Otc::UNDERGROUND_FLOOR) { int j, i; for(i = pos.z, j = -1; i <= pos.z + Otc::AWARE_UNDEGROUND_FLOOR_RANGE; ++i, --j) - skip = setFloorDescription(msg, pos.x - Otc::AWARE_X_LEFT_TILES, pos.y - Otc::AWARE_Y_TOP_TILES, i, Otc::AWARE_X_TILES, Otc::AWARE_Y_TILES, j, skip); + skip = setFloorDescription(msg, pos.x - range.left, pos.y - range.top, i, range.horizontal(), range.vertical(), j, skip); } else if(pos.z > Otc::UNDERGROUND_FLOOR && pos.z < Otc::MAX_Z-1) - skip = setFloorDescription(msg, pos.x - Otc::AWARE_X_LEFT_TILES, pos.y - Otc::AWARE_Y_TOP_TILES, pos.z + Otc::AWARE_UNDEGROUND_FLOOR_RANGE, Otc::AWARE_X_TILES, Otc::AWARE_Y_TILES, -3, skip); + skip = setFloorDescription(msg, pos.x - range.left, pos.y - range.top, pos.z + Otc::AWARE_UNDEGROUND_FLOOR_RANGE, range.horizontal(), range.vertical(), -3, skip); pos.x--; pos.y--; @@ -1458,10 +1525,27 @@ void ProtocolGame::parseExtendedOpcode(const InputMessagePtr& msg) if(opcode == 0) m_enableSendExtendedOpcode = true; + else if(opcode == 2) + parsePingBack(msg); else callLuaField("onExtendedOpcode", opcode, buffer); } +void ProtocolGame::parseChangeMapAwareRange(const InputMessagePtr& msg) +{ + int xrange = msg->getU8(); + int yrange = msg->getU8(); + + AwareRange range; + range.left = xrange/2 - ((xrange+1) % 2); + range.right = xrange/2; + range.top = yrange/2 - ((yrange+1) % 2); + range.bottom = yrange/2; + + g_map.setAwareRange(range); + g_lua.callGlobalField("g_game", "onMapChangeAwareRange", xrange, yrange); +} + void ProtocolGame::setMapDescription(const InputMessagePtr& msg, int x, int y, int z, int width, int height) { int startz, endz, zstep; @@ -1502,7 +1586,7 @@ int ProtocolGame::setTileDescription(const InputMessagePtr& msg, Position positi { g_map.cleanTile(position); - bool gotEffect = 0; + bool gotEffect = false; for(int stackPos=0;stackPos<256;stackPos++) { if(msg->peekU16() >= 0xff00) return msg->getU16() & 0xff; @@ -1558,6 +1642,7 @@ Outfit ProtocolGame::getOutfit(const InputMessagePtr& msg) int mount = msg->getU16(); outfit.setMount(mount); } + return outfit; } @@ -1571,6 +1656,8 @@ ThingPtr ProtocolGame::getThing(const InputMessagePtr& msg) stdext::throw_exception("invalid thing id"); else if(id == Proto::UnknownCreature || id == Proto::OutdatedCreature || id == Proto::Creature) thing = getCreature(msg, id); + else if(id == Proto::StaticText) // otclient only + thing = getStaticText(msg, id); else // item thing = getItem(msg, id); @@ -1745,6 +1832,19 @@ ItemPtr ProtocolGame::getItem(const InputMessagePtr& msg, int id) return item; } +StaticTextPtr ProtocolGame::getStaticText(const InputMessagePtr& msg, int id) +{ + int colorByte = msg->getU8(); + Color color = Color::from8bit(colorByte); + std::string fontName = msg->getString(); + std::string text = msg->getString(); + StaticTextPtr staticText = StaticTextPtr(new StaticText); + staticText->setText(text); + staticText->setFont(fontName); + staticText->setColor(color); + return staticText; +} + Position ProtocolGame::getPosition(const InputMessagePtr& msg) { uint16 x = msg->getU16(); diff --git a/src/client/protocolgamesend.cpp b/src/client/protocolgamesend.cpp index dcaf4c48..ff04e3aa 100644 --- a/src/client/protocolgamesend.cpp +++ b/src/client/protocolgamesend.cpp @@ -22,6 +22,8 @@ #include "protocolgame.h" #include "game.h" +#include "client.h" +#include void ProtocolGame::send(const OutputMessagePtr& outputMessage) { @@ -50,7 +52,16 @@ void ProtocolGame::sendLoginPacket(uint challangeTimestamp, uint8 challangeRando msg->addU8(Proto::ClientPendingGame); - msg->addU16(g_lua.callGlobalField("g_game", "getOsType")); + msg->addU16(g_game.getOs()); + + if(g_game.getFeature(Otc::GameUpdater)) { + msg->addString(g_app.getOs()); + msg->addString(g_game.getUpdaterSignature()); + } + + if(g_game.getFeature(Otc::GameLoginLocale)) + msg->addString(m_locale); + msg->addU16(g_game.getProtocolVersion()); if(g_game.getProtocolVersion() >= 971) { @@ -92,6 +103,12 @@ void ProtocolGame::sendLoginPacket(uint challangeTimestamp, uint8 challangeRando paddingBytes -= 5; } + if(paddingBytes < 0) { + g_game.processLoginError("AccountName or Password or CharacterName are too big!\nPlease contact game support."); + g_game.processDisconnect(); + return; + } + // complete the 128 bytes for rsa encryption with zeros msg->addPaddingBytes(paddingBytes); @@ -119,10 +136,14 @@ void ProtocolGame::sendLogout() void ProtocolGame::sendPing() { - OutputMessagePtr msg(new OutputMessage); - msg->addU8(Proto::ClientPing); + if(g_game.getFeature(Otc::GameExtendedClientPing)) + sendExtendedOpcode(2, ""); + else { + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientPing); + Protocol::send(msg); + } m_pingTimer.restart(); - Protocol::send(msg); } void ProtocolGame::sendPingBack() @@ -774,6 +795,18 @@ void ProtocolGame::sendAnswerModalDialog(int dialog, int button, int choice) send(msg); } +void ProtocolGame::sendChangeMapAwareRange(int xrange, int yrange) +{ + if(!g_game.getFeature(Otc::GameChangeMapAwareRange)) + return; + + OutputMessagePtr msg(new OutputMessage); + msg->addU8(Proto::ClientChangeMapAwareRange); + msg->addU8(xrange); + msg->addU8(yrange); + send(msg); +} + void ProtocolGame::addPosition(const OutputMessagePtr& msg, const Position& position) { msg->addU16(position.x); diff --git a/src/client/shadermanager.cpp b/src/client/shadermanager.cpp index 3cecfb3d..1b76c097 100644 --- a/src/client/shadermanager.cpp +++ b/src/client/shadermanager.cpp @@ -24,6 +24,7 @@ #include #include #include +#include ShaderManager g_shaders; @@ -59,12 +60,14 @@ PainterShaderProgramPtr ShaderManager::createShader(const std::string& name) return shader; } -PainterShaderProgramPtr ShaderManager::createFragmentShader(const std::string& name, const std::string& file) +PainterShaderProgramPtr ShaderManager::createFragmentShader(const std::string& name, std::string file) { PainterShaderProgramPtr shader = createShader(name); if(!shader) return nullptr; + file = g_resources.guessFileType(file, "frag"); + shader->addShaderFromSourceCode(Shader::Vertex, glslMainWithTexCoordsVertexShader + glslPositionOnlyVertexShader); if(!shader->addShaderFromSourceFile(Shader::Fragment, file)) { g_logger.error(stdext::format("unable to load fragment shader '%s' from source file '%s'", name, file)); @@ -109,6 +112,14 @@ PainterShaderProgramPtr ShaderManager::createItemShader(const std::string& name, return shader; } +PainterShaderProgramPtr ShaderManager::createMapShader(const std::string& name, const std::string& file) +{ + PainterShaderProgramPtr shader = createFragmentShader(name, file); + if(shader) + setupMapShader(shader); + return shader; +} + void ShaderManager::setupItemShader(const PainterShaderProgramPtr& shader) { if(!shader) @@ -116,6 +127,15 @@ void ShaderManager::setupItemShader(const PainterShaderProgramPtr& shader) shader->bindUniformLocation(ITEM_ID_UNIFORM, "u_ItemId"); } +void ShaderManager::setupMapShader(const PainterShaderProgramPtr& shader) +{ + if(!shader) + return; + shader->bindUniformLocation(MAP_CENTER_COORD, "u_MapCenterCoord"); + shader->bindUniformLocation(MAP_GLOBAL_COORD, "u_MapGlobalCoord"); + shader->bindUniformLocation(MAP_ZOOM, "u_MapZoom"); +} + PainterShaderProgramPtr ShaderManager::getShader(const std::string& name) { auto it = m_shaders.find(name); @@ -123,3 +143,4 @@ PainterShaderProgramPtr ShaderManager::getShader(const std::string& name) return it->second; return nullptr; } + diff --git a/src/client/shadermanager.h b/src/client/shadermanager.h index 0d375b27..c81bbcc2 100644 --- a/src/client/shadermanager.h +++ b/src/client/shadermanager.h @@ -31,18 +31,21 @@ class ShaderManager { public: enum { - ITEM_ID_UNIFORM = 10 + ITEM_ID_UNIFORM = 10, + MAP_CENTER_COORD = 10, + MAP_GLOBAL_COORD = 11, + MAP_ZOOM = 12 }; void init(); void terminate(); PainterShaderProgramPtr createShader(const std::string& name); - PainterShaderProgramPtr createFragmentShader(const std::string& name, const std::string& file); + PainterShaderProgramPtr createFragmentShader(const std::string& name, std::string file); PainterShaderProgramPtr createFragmentShaderFromCode(const std::string& name, const std::string& code); PainterShaderProgramPtr createItemShader(const std::string& name, const std::string& file); - PainterShaderProgramPtr createMapShader(const std::string& name, const std::string& file) { return createFragmentShader(name, file); } + PainterShaderProgramPtr createMapShader(const std::string& name, const std::string& file); const PainterShaderProgramPtr& getDefaultItemShader() { return m_defaultItemShader; } const PainterShaderProgramPtr& getDefaultMapShader() { return m_defaultMapShader; } @@ -51,6 +54,7 @@ public: private: void setupItemShader(const PainterShaderProgramPtr& shader); + void setupMapShader(const PainterShaderProgramPtr& shader); PainterShaderProgramPtr m_defaultItemShader; PainterShaderProgramPtr m_defaultMapShader; @@ -61,3 +65,4 @@ private: extern ShaderManager g_shaders; #endif + diff --git a/src/client/spritemanager.cpp b/src/client/spritemanager.cpp index 55fb87f7..165f66b4 100644 --- a/src/client/spritemanager.cpp +++ b/src/client/spritemanager.cpp @@ -39,12 +39,14 @@ void SpriteManager::terminate() unload(); } -bool SpriteManager::loadSpr(const std::string& file) +bool SpriteManager::loadSpr(std::string file) { m_spritesCount = 0; m_signature = 0; m_loaded = false; try { + file = g_resources.guessFileType(file, "spr"); + m_spritesFile = g_resources.openFile(file); // cache file buffer to avoid lags from hard drive m_spritesFile->cache(); diff --git a/src/client/spritemanager.h b/src/client/spritemanager.h index e07ab621..cd074f4f 100644 --- a/src/client/spritemanager.h +++ b/src/client/spritemanager.h @@ -34,7 +34,7 @@ public: void terminate(); - bool loadSpr(const std::string& file); + bool loadSpr(std::string file); void unload(); uint32 getSignature() { return m_signature; } diff --git a/src/client/statictext.cpp b/src/client/statictext.cpp index cb195c93..4ff1ef78 100644 --- a/src/client/statictext.cpp +++ b/src/client/statictext.cpp @@ -29,6 +29,8 @@ StaticText::StaticText() { + m_mode = Otc::MessageNone; + m_color = Color::white; m_cachedText.setFont(g_fonts.getFont("verdana-11px-rounded")); m_cachedText.setAlign(Fw::AlignCenter); } @@ -47,6 +49,16 @@ void StaticText::drawText(const Point& dest, const Rect& parentRect) //} } +void StaticText::setFont(const std::string& fontName) +{ + m_cachedText.setFont(g_fonts.getFont(fontName)); +} + +void StaticText::setText(const std::string& text) +{ + m_cachedText.setText(text); +} + bool StaticText::addMessage(const std::string& name, Otc::MessageMode mode, const std::string& text) { //TODO: this could be moved to lua diff --git a/src/client/statictext.h b/src/client/statictext.h index e0825b1a..d03e7896 100644 --- a/src/client/statictext.h +++ b/src/client/statictext.h @@ -41,11 +41,16 @@ public: bool isYell() { return m_mode == Otc::MessageYell || m_mode == Otc::MessageMonsterYell || m_mode == Otc::MessageBarkLoud; } + void setText(const std::string& text); + void setFont(const std::string& fontName); bool addMessage(const std::string& name, Otc::MessageMode mode, const std::string& text); StaticTextPtr asStaticText() { return static_self_cast(); } bool isStaticText() { return true; } + void setColor(const Color& color) { m_color = color; } + Color getColor() { return m_color; } + private: void update(); void scheduleUpdate(); diff --git a/src/client/thingtype.cpp b/src/client/thingtype.cpp index 3943ae0d..7a3a409a 100644 --- a/src/client/thingtype.cpp +++ b/src/client/thingtype.cpp @@ -30,6 +30,7 @@ #include #include #include +#include ThingType::ThingType() { @@ -41,6 +42,7 @@ ThingType::ThingType() m_animationPhases = 0; m_layers = 0; m_elevation = 0; + m_opacity = 1.0f; } void ThingType::unserialize(uint16 clientId, ThingCategory category, const FileStreamPtr& fin) @@ -139,6 +141,16 @@ void ThingType::unserialize(uint16 clientId, ThingCategory category, const FileS m_texturesFramesOffsets.resize(m_animationPhases); } +void ThingType::unserializeOtml(const OTMLNodePtr& node) +{ + for(const OTMLNodePtr& node2 : node->children()) { + if(node2->tag() == "opacity") + m_opacity = node2->value(); + if(node2->tag() == "notprewalkable") + m_attribs.set(ThingAttrNotPreWalkable, node2->value()); + } +} + void ThingType::draw(const Point& dest, float scaleFactor, int layer, int xPattern, int yPattern, int zPattern, int animationPhase, LightView *lightView) { if(m_null) @@ -160,6 +172,7 @@ void ThingType::draw(const Point& dest, float scaleFactor, int layer, int xPatte Rect screenRect(dest + (textureOffset - m_displacement - (m_size.toPoint() - Point(1, 1)) * 32) * scaleFactor, textureRect.size() * scaleFactor); + g_painter->setOpacity(m_opacity); g_painter->drawTexturedRect(screenRect, texture, textureRect); if(lightView && hasLight()) { diff --git a/src/client/thingtype.h b/src/client/thingtype.h index 6e7afcce..18cd9f92 100644 --- a/src/client/thingtype.h +++ b/src/client/thingtype.h @@ -26,6 +26,7 @@ #include "declarations.h" #include +#include #include #include #include @@ -75,6 +76,11 @@ enum ThingAttr : uint8 { ThingAttrLook = 31, ThingAttrCloth = 32, ThingAttrMarket = 33, + + // additional + ThingAttrOpacity = 100, + ThingAttrNotPreWalkable = 101, + ThingAttrChargeable = 254, // deprecated ThingLastAttr = 255 }; @@ -107,6 +113,7 @@ public: ThingType(); void unserialize(uint16 clientId, ThingCategory category, const FileStreamPtr& fin); + void unserializeOtml(const OTMLNodePtr& node); void draw(const Point& dest, float scaleFactor, int layer, int xPattern, int yPattern, int zPattern, int animationPhase, LightView *lightView = nullptr); @@ -172,6 +179,10 @@ public: bool isCloth() { return m_attribs.has(ThingAttrCloth); } bool isMarketable() { return m_attribs.has(ThingAttrMarket); } + // additional + float getOpacity() { return m_opacity; } + bool isNotPreWalkable() { return m_attribs.has(ThingAttrNotPreWalkable); } + private: const TexturePtr& getTexture(int animationPhase); Size getBestTextureDimension(int w, int h, int count); @@ -190,6 +201,7 @@ private: int m_animationPhases; int m_layers; int m_elevation; + float m_opacity; std::vector m_spritesIndex; std::vector m_textures; diff --git a/src/client/thingtypemanager.cpp b/src/client/thingtypemanager.cpp index e5829cbc..999235da 100644 --- a/src/client/thingtypemanager.cpp +++ b/src/client/thingtypemanager.cpp @@ -32,6 +32,7 @@ #include #include #include +#include ThingTypeManager g_things; @@ -60,11 +61,13 @@ void ThingTypeManager::terminate() m_nullItemType = nullptr; } -bool ThingTypeManager::loadDat(const std::string& file) +bool ThingTypeManager::loadDat(std::string file) { m_datLoaded = false; m_datSignature = 0; try { + file = g_resources.guessFileType(file, "dat"); + FileStreamPtr fin = g_resources.openFile(file); m_datSignature = fin->getU32(); @@ -95,6 +98,41 @@ bool ThingTypeManager::loadDat(const std::string& file) } } +bool ThingTypeManager::loadOtml(std::string file) +{ + try { + file = g_resources.guessFileType(file, "otml"); + + OTMLDocumentPtr doc = OTMLDocument::parse(file); + for(const OTMLNodePtr& node : doc->children()) { + ThingCategory category; + if(node->tag() == "creatures") + category = ThingCategoryCreature; + else if(node->tag() == "items") + category = ThingCategoryItem; + else if(node->tag() == "effects") + category = ThingCategoryEffect; + else if(node->tag() == "missiles") + category = ThingCategoryMissile; + else { + throw OTMLException(node, "not a valid thing category"); + } + + for(const OTMLNodePtr& node2 : node->children()) { + uint16 id = stdext::safe_cast(node2->tag()); + ThingTypePtr type = getThingType(id, category); + if(!type) + throw OTMLException(node2, "thing not found"); + type->unserializeOtml(node2); + } + } + return true; + } catch(std::exception& e) { + g_logger.error(stdext::format("Failed to read dat otml '%s': %s'", file, e.what())); + return false; + } +} + void ThingTypeManager::loadOtb(const std::string& file) { FileStreamPtr fin = g_resources.openFile(file); diff --git a/src/client/thingtypemanager.h b/src/client/thingtypemanager.h index 35a0954e..6b86ef32 100644 --- a/src/client/thingtypemanager.h +++ b/src/client/thingtypemanager.h @@ -35,7 +35,8 @@ public: void init(); void terminate(); - bool loadDat(const std::string& file); + bool loadDat(std::string file); + bool loadOtml(std::string file); void loadOtb(const std::string& file); void loadXml(const std::string& file); void parseItemType(uint16 id, TiXmlElement *elem); diff --git a/src/client/tile.cpp b/src/client/tile.cpp index 86cec657..613e437c 100644 --- a/src/client/tile.cpp +++ b/src/client/tile.cpp @@ -278,14 +278,13 @@ bool Tile::hasThing(const ThingPtr& thing) int Tile::getThingStackpos(const ThingPtr& thing) { - for(uint stackpos = 0; stackpos < m_things.size(); ++stackpos) { + for(uint stackpos = 0; stackpos < m_things.size(); ++stackpos) if(thing == m_things[stackpos]) return stackpos; - } return -1; } -ThingPtr Tile:: getTopThing() +ThingPtr Tile::getTopThing() { if(isEmpty()) return nullptr; @@ -374,6 +373,12 @@ ThingPtr Tile::getTopUseThing() return thing; } + for(uint i = 0; i < m_things.size(); ++i) { + ThingPtr thing = m_things[i]; + if(!thing->isGround() && !thing->isGroundBorder() && !thing->isCreature()) + return thing; + } + return m_things[0]; } @@ -434,30 +439,34 @@ ThingPtr Tile::getTopMoveThing() return m_things[0]; } -ThingPtr Tile::getTopMultiUseThing(bool ignoreCreature) +ThingPtr Tile::getTopMultiUseThing() { - // this is related to classic controls, getting top item, forceuse for creature if(isEmpty()) return nullptr; for(uint i = 0; i < m_things.size(); ++i) { ThingPtr thing = m_things[i]; - if(thing->isForceUse() || (!thing->isGround() && !thing->isGroundBorder() && !thing->isOnBottom() && !thing->isOnTop())) { - if(thing->isCreature() && ignoreCreature) - continue; + if(thing->isForceUse()) + return thing; + } + + if(CreaturePtr topCreature = getTopCreature()) + return topCreature; + + for(uint i = 0; i < m_things.size(); ++i) { + ThingPtr thing = m_things[i]; + if(!thing->isGround() && !thing->isGroundBorder() && !thing->isOnBottom() && !thing->isOnTop()) { if(i > 0 && thing->isSplash()) return m_things[i-1]; return thing; } } + for(uint i = 0; i < m_things.size(); ++i) { ThingPtr thing = m_things[i]; - if(!thing->isGround() && !thing->isGroundBorder() && !thing->isOnTop()) { - if(thing->isCreature() && ignoreCreature) - continue; + if(!thing->isGround() && !thing->isGroundBorder() && !thing->isOnTop()) return thing; - } } return m_things[0]; diff --git a/src/client/tile.h b/src/client/tile.h index eb20313b..4ed74845 100644 --- a/src/client/tile.h +++ b/src/client/tile.h @@ -81,7 +81,7 @@ public: ThingPtr getTopUseThing(); CreaturePtr getTopCreature(); ThingPtr getTopMoveThing(); - ThingPtr getTopMultiUseThing(bool ignoreCreature = true); + ThingPtr getTopMultiUseThing(); const Position& getPosition() { return m_position; } int getDrawElevation() { return m_drawElevation; } diff --git a/src/client/uimap.h b/src/client/uimap.h index 49cbbb93..93a36072 100644 --- a/src/client/uimap.h +++ b/src/client/uimap.h @@ -55,7 +55,7 @@ public: void setDrawLights(bool enable) { m_mapView->setDrawLights(enable); } void setAnimated(bool enable) { m_mapView->setAnimated(enable); } void setKeepAspectRatio(bool enable); - void setMapShader(const PainterShaderProgramPtr& shader) { m_mapView->setShader(shader); } + void setMapShader(const PainterShaderProgramPtr& shader, float fadeout, float fadein) { m_mapView->setShader(shader, fadein, fadeout); } void setMinimumAmbientLight(float intensity) { m_mapView->setMinimumAmbientLight(intensity); } bool isMultifloor() { return m_mapView->isMultifloor(); } diff --git a/src/framework/cmake/FindICU.cmake b/src/framework/cmake/FindICU.cmake new file mode 100644 index 00000000..4f3bee3e --- /dev/null +++ b/src/framework/cmake/FindICU.cmake @@ -0,0 +1,21 @@ +# Try to find the ICU library +# ICU_FOUND - system has ICU +# ICU_INCLUDE_DIR - the ICU include directory +# ICU_LIBRARY - the ICU library + +FIND_PATH(ICU_INCLUDE_DIR NAMES unicode/utf8.h) +SET(_ICUI18N_STATIC_LIBS libicui18n.a) +SET(_ICUI18N_SHARED_LIBS libicui18n.dll.a icui18n) +SET(_ICUUC_STATIC_LIBS libicuuc.a) +SET(_ICUUC_SHARED_LIBS libicuuc.dll.a icuuc) +IF(USE_STATIC_LIBS) + FIND_LIBRARY(ICUI18N_LIBRARY NAMES ${_ICUI18N_STATIC_LIBS} ${_ICUI18N_SHARED_LIBS}) + FIND_LIBRARY(ICUUC_LIBRARY NAMES ${_ICUUC_STATIC_LIBS} ${_ICUUC_SHARED_LIBS}) +ELSE() + FIND_LIBRARY(ICUI18N_LIBRARY NAMES ${_ICUI18N_SHARED_LIBS} ${_ICUI18N_STATIC_LIBS}) + FIND_LIBRARY(ICUUC_LIBRARY NAMES ${_ICUUC_SHARED_LIBS} ${_ICUUC_STATIC_LIBS}) +ENDIF() +SET(ICU_LIBRARIES ${ICUI18N_LIBRARY} ${ICUUC_LIBRARY}) +INCLUDE(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(ICU DEFAULT_MSG ICU_LIBRARY ICU_INCLUDE_DIR) +MARK_AS_ADVANCED(ICU_LIBRARIES ICU_INCLUDE_DIR) \ No newline at end of file diff --git a/src/framework/luafunctions.cpp b/src/framework/luafunctions.cpp index 53a70ce0..d8c62a66 100644 --- a/src/framework/luafunctions.cpp +++ b/src/framework/luafunctions.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #ifdef FW_GRAPHICS #include @@ -68,6 +69,17 @@ void Application::registerLuaFunctions() g_lua.bindGlobalFunction("listSubnetAddresses", [](uint32 a, uint8 b) { return stdext::listSubnetAddresses(a, b); }); g_lua.bindGlobalFunction("ucwords", [](std::string s) { return stdext::ucwords(s); }); + // Platform + g_lua.registerSingletonClass("g_platform"); + g_lua.bindSingletonFunction("g_platform", "spawnProcess", &Platform::spawnProcess, &g_platform); + g_lua.bindSingletonFunction("g_platform", "getProcessId", &Platform::getProcessId, &g_platform); + g_lua.bindSingletonFunction("g_platform", "copyFile", &Platform::copyFile, &g_platform); + g_lua.bindSingletonFunction("g_platform", "getTempPath", &Platform::getTempPath, &g_platform); + g_lua.bindSingletonFunction("g_platform", "openUrl", &Platform::openUrl, &g_platform); + g_lua.bindSingletonFunction("g_platform", "getCPUName", &Platform::getCPUName, &g_platform); + g_lua.bindSingletonFunction("g_platform", "getTotalSystemMemory", &Platform::getTotalSystemMemory, &g_platform); + g_lua.bindSingletonFunction("g_platform", "getOSName", &Platform::getOSName, &g_platform); + // Application g_lua.registerSingletonClass("g_app"); g_lua.bindSingletonFunction("g_app", "setName", &Application::setName, static_cast(&g_app)); @@ -85,6 +97,7 @@ void Application::registerLuaFunctions() g_lua.bindSingletonFunction("g_app", "getBuildType", &Application::getBuildType, static_cast(&g_app)); g_lua.bindSingletonFunction("g_app", "getBuildArch", &Application::getBuildArch, static_cast(&g_app)); g_lua.bindSingletonFunction("g_app", "getOs", &Application::getOs, static_cast(&g_app)); + g_lua.bindSingletonFunction("g_app", "getStartupOptions", &Application::getStartupOptions, static_cast(&g_app)); g_lua.bindSingletonFunction("g_app", "exit", &Application::exit, static_cast(&g_app)); // Crypt @@ -160,6 +173,7 @@ void Application::registerLuaFunctions() g_lua.bindSingletonFunction("g_resources", "getWorkDir", &ResourceManager::getWorkDir, &g_resources); g_lua.bindSingletonFunction("g_resources", "getSearchPaths", &ResourceManager::getSearchPaths, &g_resources); g_lua.bindSingletonFunction("g_resources", "listDirectoryFiles", &ResourceManager::listDirectoryFiles, &g_resources); + g_lua.bindSingletonFunction("g_resources", "readFileContents", &ResourceManager::readFileContents, &g_resources); // Module g_lua.registerClass(); @@ -212,6 +226,7 @@ void Application::registerLuaFunctions() g_lua.bindSingletonFunction("g_window", "resize", &PlatformWindow::resize, &g_window); g_lua.bindSingletonFunction("g_window", "show", &PlatformWindow::show, &g_window); g_lua.bindSingletonFunction("g_window", "hide", &PlatformWindow::hide, &g_window); + g_lua.bindSingletonFunction("g_window", "poll", &PlatformWindow::poll, &g_window); g_lua.bindSingletonFunction("g_window", "maximize", &PlatformWindow::maximize, &g_window); g_lua.bindSingletonFunction("g_window", "restoreMouseCursor", &PlatformWindow::restoreMouseCursor, &g_window); g_lua.bindSingletonFunction("g_window", "showMouse", &PlatformWindow::showMouse, &g_window); @@ -261,6 +276,8 @@ void Application::registerLuaFunctions() g_lua.bindSingletonFunction("g_graphics", "selectPainterEngine", &Graphics::selectPainterEngine, &g_graphics); g_lua.bindSingletonFunction("g_graphics", "canCacheBackbuffer", &Graphics::canCacheBackbuffer, &g_graphics); g_lua.bindSingletonFunction("g_graphics", "canUseShaders", &Graphics::canUseShaders, &g_graphics); + g_lua.bindSingletonFunction("g_graphics", "shouldUseShaders", &Graphics::shouldUseShaders, &g_graphics); + g_lua.bindSingletonFunction("g_graphics", "setShouldUseShaders", &Graphics::setShouldUseShaders, &g_graphics); g_lua.bindSingletonFunction("g_graphics", "getPainterEngine", &Graphics::getPainterEngine, &g_graphics); g_lua.bindSingletonFunction("g_graphics", "getViewportSize", &Graphics::getViewportSize, &g_graphics); g_lua.bindSingletonFunction("g_graphics", "getVendor", &Graphics::getVendor, &g_graphics); @@ -607,6 +624,7 @@ void Application::registerLuaFunctions() g_lua.bindClassMemberFunction("getNumColumns", &UIGridLayout::getNumColumns); g_lua.bindClassMemberFunction("getNumLines", &UIGridLayout::getNumLines); g_lua.bindClassMemberFunction("getCellSize", &UIGridLayout::getCellSize); + g_lua.bindClassMemberFunction("getCellSpacing", &UIGridLayout::getCellSpacing); g_lua.bindClassMemberFunction("isUIGridLayout", &UIGridLayout::isUIGridLayout); // UIAnchorLayout @@ -756,8 +774,10 @@ void Application::registerLuaFunctions() g_lua.bindClassMemberFunction("getDataInt", &DBResult::getDataInt); g_lua.bindClassMemberFunction("getDataLong", &DBResult::getDataLong); g_lua.bindClassMemberFunction("getDataString", &DBResult::getDataString); + g_lua.bindClassMemberFunction("getRowCount", &DBResult::getRowCount); g_lua.bindClassMemberFunction("next", &DBResult::next); + // Mysql g_lua.registerClass(); g_lua.bindClassStaticFunction("create", []{ return DatabaseMySQLPtr(new DatabaseMySQL); }); diff --git a/src/framework/stdext/math.cpp b/src/framework/stdext/math.cpp index 9fa59cf3..38d5f793 100644 --- a/src/framework/stdext/math.cpp +++ b/src/framework/stdext/math.cpp @@ -57,4 +57,9 @@ float random_range(float min, float max) return min + (max - min)*dis(gen); } +double round(double r) +{ + return (r > 0.0) ? floor(r + 0.5) : ceil(r - 0.5); +} + } \ No newline at end of file diff --git a/src/framework/stdext/math.h b/src/framework/stdext/math.h index 1f9c26a7..01c5f681 100644 --- a/src/framework/stdext/math.h +++ b/src/framework/stdext/math.h @@ -43,6 +43,8 @@ uint32_t adler32(const uint8_t *buffer, size_t size); long random_range(long min, long max); float random_range(float min, float max); +double round(double r); + } #endif