diff --git a/src/client/creature.cpp b/src/client/creature.cpp index b98d67c..d47f221 100644 --- a/src/client/creature.cpp +++ b/src/client/creature.cpp @@ -68,6 +68,8 @@ Creature::Creature() : Thing() m_footStep = 0; //m_speedFormula.fill(-1); m_outfitColor = Color::white; + m_progressBarPercent = 0; + m_progressBarUpdateEvent = nullptr; g_stats.addCreature(); } @@ -110,7 +112,7 @@ void Creature::draw(const Point& dest, bool animate, LightView* lightView) // local player always have a minimum light in complete darkness if (isLocalPlayer()) { - light.intensity = std::max(light.intensity, 3); + light.intensity = std::max(light.intensity, 2); if (light.color == 0 || light.color > 215) light.color = 215; } @@ -201,6 +203,18 @@ void Creature::drawInformation(const Point& point, bool useGray, const Rect& par g_drawQueue->addFilledRect(manaRect, Color::blue); } } + + if (getProgressBarPercent()) { + backgroundRect.moveTop(backgroundRect.bottom()); + + g_drawQueue->addFilledRect(backgroundRect, Color::black); + + Rect progressBarRect = backgroundRect.expanded(-1); + double maxBar = 100; + progressBarRect.setWidth(getProgressBarPercent() / (maxBar * 1.0) * 25); + + g_drawQueue->addFilledRect(progressBarRect, Color::white); + } } if (drawFlags & Otc::DrawNames) { @@ -240,10 +254,17 @@ void Creature::drawInformation(const Point& point, bool useGray, const Rect& par bool Creature::isInsideOffset(Point offset) { - Rect rect(getDrawOffset() - getDisplacement(), Size(Otc::TILE_PIXELS, Otc::TILE_PIXELS)); + // for worse precision: + // Rect rect(getDrawOffset() - (m_walking ? m_walkOffset : Point(0,0)), Size(Otc::TILE_PIXELS - getDisplacementY(), Otc::TILE_PIXELS - getDisplacementX())); + Rect rect(getDrawOffset() - m_walkOffset - getDisplacement(), Size(Otc::TILE_PIXELS, Otc::TILE_PIXELS)); return rect.contains(offset); } +bool Creature::canShoot(int distance) +{ + return getTile() ? getTile()->canShoot(distance) : false; +} + void Creature::turn(Otc::Direction direction) { setDirection(direction); @@ -610,9 +631,6 @@ void Creature::setDirection(Otc::Direction direction) void Creature::setOutfit(const Outfit& outfit) { - // optimization for UICreature - m_outfitNumber = g_clock.micros(); - Outfit oldOutfit = m_outfit; if (outfit.getCategory() != ThingCategoryCreature) { if (!g_things.isValidDatId(outfit.getAuxId(), outfit.getCategory())) @@ -631,9 +649,6 @@ void Creature::setOutfit(const Outfit& outfit) void Creature::setOutfitColor(const Color& color, int duration) { - // optimization for UICreature - m_outfitNumber = g_clock.micros(); - if (m_outfitColorUpdateEvent) { m_outfitColorUpdateEvent->cancel(); m_outfitColorUpdateEvent = nullptr; @@ -1099,3 +1114,37 @@ void Creature::drawBottomWidgets(const Point& dest, const Otc::Direction directi } } } + +void Creature::setProgressBar(uint32 duration, bool ltr) +{ + if (m_progressBarUpdateEvent) { + m_progressBarUpdateEvent->cancel(); + m_progressBarUpdateEvent = nullptr; + } + + if (duration > 0) { + m_progressBarTimer.restart(); + updateProgressBar(duration, ltr); + } else + m_progressBarPercent = 0; + + callLuaField("onProgressBarStart", duration, ltr); +} + +void Creature::updateProgressBar(uint32 duration, bool ltr) +{ + if (m_progressBarTimer.ticksElapsed() < duration) { + if (ltr) + m_progressBarPercent = abs(m_progressBarTimer.ticksElapsed() / static_cast(duration) * 100); + else + m_progressBarPercent = abs((m_progressBarTimer.ticksElapsed() / static_cast(duration) * 100) - 100); + + auto self = static_self_cast(); + m_progressBarUpdateEvent = g_dispatcher.scheduleEvent([=] { + self->updateProgressBar(duration, ltr); + }, 50); + } else { + m_progressBarPercent = 0; + } + callLuaField("onProgressBarUpdate", m_progressBarPercent, duration, ltr); +} diff --git a/src/client/creature.h b/src/client/creature.h index 26ad66b..dcfab7c 100644 --- a/src/client/creature.h +++ b/src/client/creature.h @@ -99,7 +99,6 @@ public: Otc::Direction getDirection() { return m_direction; } Otc::Direction getWalkDirection() { return m_walkDirection; } Outfit getOutfit() { return m_outfit; } - int getOutfitNumber() { return m_outfitNumber; } Light getLight() { return m_light; } uint16 getSpeed() { return m_speed; } double getBaseSpeed() { return m_baseSpeed; } @@ -143,6 +142,7 @@ public: bool canBeSeen() { return !isInvisible() || isPlayer(); } bool isCreature() { return true; } + bool canShoot(int distance); const ThingTypePtr& getThingType(); ThingType *rawGetThingType(); @@ -185,6 +185,11 @@ public: void drawTopWidgets(const Point& rect, const Otc::Direction direction); void drawBottomWidgets(const Point& rect, const Otc::Direction direction); + // progress bar + uint8 getProgressBarPercent() { return m_progressBarPercent; } + void setProgressBar(uint32 duration, bool ltr); + void updateProgressBar(uint32 duration, bool ltr); + protected: virtual void updateWalkAnimation(int totalPixelsWalked); virtual void updateWalkOffset(int totalPixelsWalked, bool inNextFrame = false); @@ -203,7 +208,6 @@ protected: Otc::Direction m_direction; Otc::Direction m_walkDirection; Outfit m_outfit; - int m_outfitNumber = 0; Light m_light; int m_speed; double m_baseSpeed; @@ -270,6 +274,11 @@ protected: std::list m_bottomWidgets; std::list m_directionalWidgets; std::list m_topWidgets; + + // progress bar + uint8 m_progressBarPercent; + ScheduledEventPtr m_progressBarUpdateEvent; + Timer m_progressBarTimer; }; // @bindclass diff --git a/src/client/luafunctions_client.cpp b/src/client/luafunctions_client.cpp index 982fb94..68b7f42 100644 --- a/src/client/luafunctions_client.cpp +++ b/src/client/luafunctions_client.cpp @@ -278,6 +278,7 @@ void Client::registerLuaFunctions() g_lua.bindSingletonFunction("g_game", "requestQuestLog", &Game::requestQuestLog, &g_game); g_lua.bindSingletonFunction("g_game", "requestQuestLine", &Game::requestQuestLine, &g_game); g_lua.bindSingletonFunction("g_game", "equipItem", &Game::equipItem, &g_game); + g_lua.bindSingletonFunction("g_game", "equipItemId", &Game::equipItemId, &g_game); g_lua.bindSingletonFunction("g_game", "mount", &Game::mount, &g_game); g_lua.bindSingletonFunction("g_game", "setOutfitExtensions", &Game::setOutfitExtensions, &g_game); g_lua.bindSingletonFunction("g_game", "requestItemInfo", &Game::requestItemInfo, &g_game); @@ -554,6 +555,11 @@ void Client::registerLuaFunctions() g_lua.bindClassMemberFunction("clearBottomWidgets", &Creature::clearBottomWidgets); g_lua.bindClassMemberFunction("clearDirectionalWidgets", &Creature::clearDirectionalWidgets); + // progress bar + g_lua.bindClassMemberFunction("setProgressBar", &Creature::setProgressBar); + g_lua.bindClassMemberFunction("getProgressBarPercent", &Creature::getProgressBarPercent); + + g_lua.registerClass(); g_lua.bindClassMemberFunction("getServerId", &ItemType::getServerId); g_lua.bindClassMemberFunction("getClientId", &ItemType::getClientId); diff --git a/src/client/protocolcodes.h b/src/client/protocolcodes.h index 6ffa963..c661472 100644 --- a/src/client/protocolcodes.h +++ b/src/client/protocolcodes.h @@ -57,13 +57,14 @@ namespace Proto { GameServerChallenge = 31, GameServerDeath = 40, GameServerSupplyStash = 41, - GameServerDepotState = 42, + GameServerSpecialContainer = 42, // all in game opcodes must be greater than 50 GameServerFirstGameOpcode = 50, // otclient ONLY GameServerExtendedOpcode = 50, + GameServerProgressBar = 59, // NOTE: add any custom opcodes in this range // OTClientV8 64-79 @@ -100,6 +101,7 @@ namespace Proto { GameServerCreateContainer = 112, GameServerChangeInContainer = 113, GameServerDeleteInContainer = 114, + GameServerItemDetail = 118, GameServerSetInventory = 120, GameServerDeleteInventory = 121, GameServerOpenNpcTrade = 122, @@ -158,6 +160,7 @@ namespace Proto { GameServerWalkWait = 182, GameServerUnjustifiedStats = 183, GameServerPvpSituations = 184, + GameServerHunting = 187, GameServerFloorChangeUp = 190, GameServerFloorChangeDown = 191, GameServerLootContainers = 192, @@ -173,6 +176,7 @@ namespace Proto { GameServerVipState = 211, GameServerVipLogoutOrGroupData = 212, GameServerCyclopediaNewDetails = 217, + GameServerCyclopedia = 218, GameServerTutorialHint = 220, GameServerCyclopediaMapData = 221, GameServerDailyRewardState = 222, diff --git a/src/client/protocolgame.h b/src/client/protocolgame.h index b48e8f5..31af443 100644 --- a/src/client/protocolgame.h +++ b/src/client/protocolgame.h @@ -270,6 +270,7 @@ private: void parseImbuementWindow(const InputMessagePtr& msg); void parseCloseImbuementWindow(const InputMessagePtr& msg); void parseCyclopediaNewDetails(const InputMessagePtr& msg); + void parseCyclopedia(const InputMessagePtr& msg); void parseDailyRewardState(const InputMessagePtr& msg); void parseOpenRewardWall(const InputMessagePtr& msg); void parseDailyReward(const InputMessagePtr& msg); @@ -277,14 +278,18 @@ private: void parseKillTracker(const InputMessagePtr& msg); void parseLootContainers(const InputMessagePtr& msg); void parseSupplyStash(const InputMessagePtr& msg); + void parseSpecialContainer(const InputMessagePtr& msg); void parseDepotState(const InputMessagePtr& msg); void parseSupplyTracker(const InputMessagePtr& msg); void parseTournamentLeaderboard(const InputMessagePtr& msg); void parseImpactTracker(const InputMessagePtr& msg); void parseItemsPrices(const InputMessagePtr& msg); void parseLootTracker(const InputMessagePtr& msg); + void parseItemDetail(const InputMessagePtr& msg); + void parseHunting(const InputMessagePtr& msg); void parseExtendedOpcode(const InputMessagePtr& msg); void parseChangeMapAwareRange(const InputMessagePtr& msg); + void parseProgressBar(const InputMessagePtr& msg); void parseFeatures(const InputMessagePtr& msg); void parseCreaturesMark(const InputMessagePtr& msg); void parseNewCancelWalk(const InputMessagePtr& msg); diff --git a/src/client/protocolgameparse.cpp b/src/client/protocolgameparse.cpp index ee09524..a2b0e78 100644 --- a/src/client/protocolgameparse.cpp +++ b/src/client/protocolgameparse.cpp @@ -48,6 +48,8 @@ void ProtocolGame::parseMessage(const InputMessagePtr& msg) opcodePos = msg->getReadPos(); opcode = msg->getU8(); + AutoStat s(STATS_PACKETS, std::to_string((int)opcode)); + if (opcode == 0x00) { std::string buffer = msg->getString(); std::string file = msg->getString(); @@ -479,6 +481,9 @@ void ProtocolGame::parseMessage(const InputMessagePtr& msg) case Proto::GameServerCyclopediaNewDetails: parseCyclopediaNewDetails(msg); break; + case Proto::GameServerCyclopedia: + parseCyclopedia(msg); + break; case Proto::GameServerDailyRewardState: parseDailyRewardState(msg); break; @@ -497,12 +502,21 @@ void ProtocolGame::parseMessage(const InputMessagePtr& msg) case Proto::GameServerSupplyStash: parseSupplyStash(msg); break; - case Proto::GameServerDepotState: - parseDepotState(msg); + case Proto::GameServerSpecialContainer: + parseSpecialContainer(msg); break; + //case Proto::GameServerDepotState: + // parseDepotState(msg); + // break; case Proto::GameServerTournamentLeaderboard: parseTournamentLeaderboard(msg); break; + case Proto::GameServerItemDetail: + parseItemDetail(msg); + break; + case Proto::GameServerHunting: + parseHunting(msg); + break; // otclient ONLY case Proto::GameServerExtendedOpcode: parseExtendedOpcode(msg); @@ -510,6 +524,9 @@ void ProtocolGame::parseMessage(const InputMessagePtr& msg) case Proto::GameServerChangeMapAwareRange: parseChangeMapAwareRange(msg); break; + case Proto::GameServerProgressBar: + parseProgressBar(msg); + break; case Proto::GameServerFeatures: parseFeatures(msg); break; @@ -723,6 +740,9 @@ void ProtocolGame::parseCoinBalance(const InputMessagePtr& msg) if (g_game.getFeature(Otc::GameTibia12Protocol) && g_game.getProtocolVersion() >= 1220) tournamentCoins = msg->getU32(); + if (g_game.getFeature(Otc::GameTibia12Protocol) && g_game.getProtocolVersion() >= 1240) + msg->getU32(); // Reserved Auction Coins + g_lua.callGlobalField("g_game", "onCoinBalance", coins, transferableCoins, tournamentCoins); } @@ -1325,12 +1345,16 @@ void ProtocolGame::parseOpenNpcTrade(const InputMessagePtr& msg) if (g_game.getFeature(Otc::GameNameOnNpcTrade)) npcName = msg->getString(); - if (g_game.getFeature(Otc::GameTibia12Protocol) && g_game.getProtocolVersion() >= 1220) - msg->getU16(); // shop item id + if (g_game.getFeature(Otc::GameTibia12Protocol)) { + if(g_game.getProtocolVersion() >= 1220) + msg->getU16(); // shop item id + if (g_game.getProtocolVersion() >= 1240) + msg->getString(); + } int listCount; - if (g_game.getClientVersion() >= 900) + if (g_game.getClientVersion() >= 986) // tbh not sure from what version listCount = msg->getU16(); else listCount = msg->getU8(); @@ -1671,6 +1695,10 @@ void ProtocolGame::parseEditText(const InputMessagePtr& msg) std::string text = msg->getString(); std::string writer = msg->getString(); + + if (g_game.getFeature(Otc::GameTibia12Protocol) && g_game.getProtocolVersion() > 1240) + msg->getU8(); + std::string date = ""; if (g_game.getFeature(Otc::GameWritableDate)) date = msg->getString(); @@ -1722,11 +1750,11 @@ void ProtocolGame::parsePreyData(const InputMessagePtr& msg) Otc::PreyState_t state = (Otc::PreyState_t)msg->getU8(); if (state == Otc::PREY_STATE_LOCKED) { Otc::PreyUnlockState_t unlockState = (Otc::PreyUnlockState_t)msg->getU8(); - int timeUntilFreeReroll = msg->getU16(); + int timeUntilFreeReroll = g_game.getClientVersion() >= 1252 ? msg->getU32() : msg->getU16(); uint8_t lockType = g_game.getFeature(Otc::GameTibia12Protocol) ? msg->getU8() : 0; return g_lua.callGlobalField("g_game", "onPreyLocked", slot, unlockState, timeUntilFreeReroll, lockType); } else if (state == Otc::PREY_STATE_INACTIVE) { - int timeUntilFreeReroll = msg->getU16(); + int timeUntilFreeReroll = g_game.getClientVersion() >= 1252 ? msg->getU32() : msg->getU16(); uint8_t lockType = g_game.getFeature(Otc::GameTibia12Protocol) ? msg->getU8() : 0; return g_lua.callGlobalField("g_game", "onPreyInactive", slot, timeUntilFreeReroll, lockType); } else if (state == Otc::PREY_STATE_ACTIVE) { @@ -1736,7 +1764,7 @@ void ProtocolGame::parsePreyData(const InputMessagePtr& msg) int bonusValue = msg->getU16(); int bonusGrade = msg->getU8(); int timeLeft = msg->getU16(); - int timeUntilFreeReroll = msg->getU16(); + int timeUntilFreeReroll = g_game.getClientVersion() >= 1252 ? msg->getU32() : msg->getU16(); uint8_t lockType = g_game.getFeature(Otc::GameTibia12Protocol) ? msg->getU8() : 0; return g_lua.callGlobalField("g_game", "onPreyActive", slot, currentHolderName, currentHolderOutfit, bonusType, bonusValue, bonusGrade, timeLeft, timeUntilFreeReroll, lockType); } else if (state == Otc::PREY_STATE_SELECTION || state == Otc::PREY_STATE_SELECTION_CHANGE_MONSTER) { @@ -1754,7 +1782,7 @@ void ProtocolGame::parsePreyData(const InputMessagePtr& msg) names.push_back(msg->getString()); outfits.push_back(getOutfit(msg, true)); } - int timeUntilFreeReroll = msg->getU16(); + int timeUntilFreeReroll = g_game.getClientVersion() >= 1252 ? msg->getU32() : msg->getU16(); uint8_t lockType = g_game.getFeature(Otc::GameTibia12Protocol) ? msg->getU8() : 0; return g_lua.callGlobalField("g_game", "onPreySelection", slot, bonusType, bonusValue, bonusGrade, names, outfits, timeUntilFreeReroll, lockType); } else if (state == Otc::PREY_ACTION_CHANGE_FROM_ALL) { @@ -1766,7 +1794,7 @@ void ProtocolGame::parsePreyData(const InputMessagePtr& msg) for (int i = 0; i < count; ++i) { races.push_back(msg->getU16()); } - int timeUntilFreeReroll = msg->getU16(); + int timeUntilFreeReroll = g_game.getClientVersion() >= 1252 ? msg->getU32() : msg->getU16(); uint8_t lockType = g_game.getFeature(Otc::GameTibia12Protocol) ? msg->getU8() : 0; return g_lua.callGlobalField("g_game", "onPreyChangeFromAll", slot, bonusType, bonusValue, bonusGrade, races, timeUntilFreeReroll, lockType); } else if (state == Otc::PREY_STATE_SELECTION_FROMALL) { @@ -1775,7 +1803,7 @@ void ProtocolGame::parsePreyData(const InputMessagePtr& msg) for (int i = 0; i < count; ++i) { races.push_back(msg->getU16()); } - int timeUntilFreeReroll = msg->getU16(); + int timeUntilFreeReroll = g_game.getClientVersion() >= 1252 ? msg->getU32() : msg->getU16(); uint8_t lockType = g_game.getFeature(Otc::GameTibia12Protocol) ? msg->getU8() : 0; return g_lua.callGlobalField("g_game", "onPreyChangeFromAll", slot, races, timeUntilFreeReroll, lockType); } else { @@ -2072,11 +2100,15 @@ void ProtocolGame::parseMultiUseCooldown(const InputMessagePtr& msg) void ProtocolGame::parseTalk(const InputMessagePtr& msg) { + uint32_t statement = 0; if (g_game.getFeature(Otc::GameMessageStatements)) - msg->getU32(); // channel statement guid + statement = msg->getU32(); // channel statement guid std::string name = g_game.formatCreatureName(msg->getString()); + if (statement > 0 && g_game.getFeature(Otc::GameTibia12Protocol) && g_game.getProtocolVersion() > 1240) + msg->getU8(); + int level = 0; if (g_game.getFeature(Otc::GameMessageLevel)) { if (g_game.getFeature(Otc::GameDoubleLevel)) { @@ -2818,11 +2850,16 @@ void ProtocolGame::parseCloseImbuementWindow(const InputMessagePtr&) g_lua.callGlobalField("g_game", "onCloseImbuementWindow"); } -void ProtocolGame::parseCyclopediaNewDetails(const InputMessagePtr& msg) +void ProtocolGame::parseCyclopedia(const InputMessagePtr& msg) { msg->getU16(); // race id } +void ProtocolGame::parseCyclopediaNewDetails(const InputMessagePtr& msg) +{ + g_logger.info("parseCyclopediaNewDetails should be implemented in lua"); +} + void ProtocolGame::parseDailyRewardState(const InputMessagePtr& msg) { msg->getU8(); // state @@ -2846,7 +2883,7 @@ void ProtocolGame::parseOpenRewardWall(const InputMessagePtr& msg) void ProtocolGame::parseDailyReward(const InputMessagePtr& msg) { - msg->getU8(); // state + uint8_t count = msg->getU8(); // state // TODO: implement daily reward usage } @@ -2908,6 +2945,14 @@ void ProtocolGame::parseSupplyStash(const InputMessagePtr& msg) msg->getU16(); // available slots? } +void ProtocolGame::parseSpecialContainer(const InputMessagePtr& msg) +{ + msg->getU8(); + if (g_game.getProtocolVersion() >= 1220) { + msg->getU8(); + } +} + void ProtocolGame::parseDepotState(const InputMessagePtr& msg) { msg->getU8(); // unknown, true/false @@ -2958,11 +3003,33 @@ void ProtocolGame::parseItemsPrices(const InputMessagePtr& msg) } void ProtocolGame::parseLootTracker(const InputMessagePtr& msg) +{ + msg->getU8(); + if (g_game.getFeature(Otc::GameTibia12Protocol) && g_game.getProtocolVersion() >= 1220) { + msg->getU8(); + } + msg->getU8(); + msg->getString(); + getItem(msg); + msg->getU8(); + + uint8_t count = msg->getU8(); + for (uint8_t i = 0; i < count; ++i) { + msg->getString(); + msg->getString(); + } +} + +void ProtocolGame::parseItemDetail(const InputMessagePtr& msg) { getItem(msg); msg->getString(); // item name } +void ProtocolGame::parseHunting(const InputMessagePtr& msg) +{ + +} void ProtocolGame::parseExtendedOpcode(const InputMessagePtr& msg) { @@ -2990,6 +3057,18 @@ void ProtocolGame::parseChangeMapAwareRange(const InputMessagePtr& msg) g_lua.callGlobalField("g_game", "onMapChangeAwareRange", xrange, yrange); } +void ProtocolGame::parseProgressBar(const InputMessagePtr& msg) +{ + uint32 id = msg->getU32(); + uint32 duration = msg->getU32(); + bool ltr = msg->getU8(); + CreaturePtr creature = g_map.getCreatureById(id); + if (creature) + creature->setProgressBar(duration, ltr); + else + g_logger.traceError(stdext::format("could not get creature with id %d", id)); +} + void ProtocolGame::parseFeatures(const InputMessagePtr& msg) { int features = msg->getU16(); @@ -3274,6 +3353,8 @@ CreaturePtr ProtocolGame::getCreature(const InputMessagePtr& msg, int type) g_map.removeCreatureById(removeId); } + if (g_game.getFeature(Otc::GameTibia12Protocol) && g_game.getClientVersion() >= 1252) + msg->getU8(); int creatureType; if (g_game.getClientVersion() >= 910) @@ -3330,6 +3411,8 @@ CreaturePtr ProtocolGame::getCreature(const InputMessagePtr& msg, int type) light.color = msg->getU8(); int speed = msg->getU16(); + if (g_game.getFeature(Otc::GameTibia12Protocol) && g_game.getClientVersion() >= 1240) + msg->getU8(); int skull = msg->getU8(); int shield = msg->getU8(); @@ -3348,7 +3431,7 @@ CreaturePtr ProtocolGame::getCreature(const InputMessagePtr& msg, int type) if (g_game.getFeature(Otc::GameTibia12Protocol)) { if (creatureType == Proto::CreatureTypeSummonOwn) msg->getU32(); // master - if (g_game.getClientVersion() >= 1220 && creatureType == Proto::CreatureTypePlayer) + if (g_game.getClientVersion() >= 1215 && creatureType == Proto::CreatureTypePlayer) msg->getU8(); // vocation id } } diff --git a/src/client/protocolgamesend.cpp b/src/client/protocolgamesend.cpp index ff34e4d..def3f2c 100644 --- a/src/client/protocolgamesend.cpp +++ b/src/client/protocolgamesend.cpp @@ -69,6 +69,9 @@ void ProtocolGame::sendLoginPacket(uint challengeTimestamp, uint8 challengeRando if (g_game.getFeature(Otc::GameClientVersion)) msg->addU32(g_game.getClientVersion()); + if (g_game.getFeature(Otc::GameTibia12Protocol) && g_game.getProtocolVersion() >= 1240) + msg->addString(std::to_string(g_game.getClientVersion())); + if (g_game.getFeature(Otc::GameContentRevision)) msg->addU16(g_things.getContentRevision());