diff --git a/data/talkactions/scripts/minimap_scan.lua b/data/talkactions/scripts/minimap_scan.lua new file mode 100644 index 0000000..519fda4 --- /dev/null +++ b/data/talkactions/scripts/minimap_scan.lua @@ -0,0 +1,114 @@ +local distanceBetweenPositionsX = 8 +local distanceBetweenPositionsY = 8 +local addEventDelay = 500 +local teleportsPerEvent = 1 +local maxEventExecutionTime = 2000 + +local function teleportToClosestPosition(player, x, y, z) + -- direct to position + local tile = Tile(x, y, z) + + if not tile or not tile:getGround() or tile:hasFlag(TILESTATE_TELEPORT) or not player:teleportTo(tile:getPosition()) then + for distance = 1, 3 do + -- try to find some close tile + for changeX = -distance, distance, distance do + for changeY = -distance, distance, distance do + tile = Tile(x + changeX, y + changeY, z) + if tile and tile:getGround() and not tile:hasFlag(TILESTATE_TELEPORT) and player:teleportTo(tile:getPosition()) then + return true + end + end + end + end + + return false + end + + return true +end + +local function sendScanProgress(player, minX, maxX, minY, maxY, x, y, z, lastProgress) + local progress = math.floor(((y - minY + (((x - minX) / (maxX - minX)) * distanceBetweenPositionsY)) / (maxY - minY)) * 100) + if progress ~= lastProgress then + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_ORANGE, 'Scan progress: ~' .. progress .. '%') + end + + return progress +end + +local function minimapScan(cid, minX, maxX, minY, maxY, x, y, z, lastProgress) + local player = Player(cid) + + if not player then + --print('Minimap scan stopped - player logged out', cid, minX, maxX, minY, maxY, x, y, z) + return + end + + local scanStartTime = os.mtime() + local teleportsDone = 0 + while true do + if scanStartTime + maxEventExecutionTime < os.mtime() then + lastProgress = sendScanProgress(player, minX, maxX, minY, maxY, x, y, z, lastProgress) + addEvent(minimapScan, addEventDelay, cid, minX, maxX, minY, maxY, x, y, z, lastProgress) + break + end + + x = x + distanceBetweenPositionsX + if x > maxX then + x = minX + y = y + distanceBetweenPositionsY + if y > maxY then + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_ORANGE, 'Scan finished: ' .. os.time()) + --print('Minimap scan complete', player:getName(), minX, maxX, minY, maxY, x, y, z) + break + end + end + + if teleportToClosestPosition(player, x, y, z) then + teleportsDone = teleportsDone + 1 + lastProgress = sendScanProgress(player, minX, maxX, minY, maxY, x, y, z, lastProgress) + + --print('Minimap scan teleport', player:getName(), minX, maxX, minY, maxY, x, y, z, progress, teleportsDone) + if teleportsDone == teleportsPerEvent then + addEvent(minimapScan, addEventDelay, cid, minX, maxX, minY, maxY, x, y, z, progress) + break + end + end + end +end + +local function minimapStart(player, minX, maxX, minY, maxY, x, y, z) + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_ORANGE, 'Scan started: ' .. os.time()) + --print('Minimap scan start', player:getName(), minX, maxX, minY, maxY, x, y, z) + minimapScan(player:getId(), minX, maxX, minY, maxY, minX - 5, minY, z) +end + +function onSay(player, words, param) + if not player:getGroup():getAccess() then + return true + end + + if player:getAccountType() < ACCOUNT_TYPE_GOD then + return false + end + + local positions = param:split(',') + if #positions ~= 5 then + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_ORANGE, 'Command requires 5 parameters: /minimap minX, maxX, minY, maxY, z') + return false + end + + for key, position in pairs(positions) do + local value = tonumber(position) + + if not value then + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_ORANGE, 'Invalid parameter ' .. key .. ': ' .. position) + return false + end + + positions[key] = value + end + + minimapStart(player, positions[1], positions[2], positions[3], positions[4], positions[1] - distanceBetweenPositionsX, positions[3], positions[5]) + return false +end \ No newline at end of file diff --git a/data/talkactions/scripts/znoteshop.lua b/data/talkactions/scripts/znoteshop.lua new file mode 100644 index 0000000..9fbcfe0 --- /dev/null +++ b/data/talkactions/scripts/znoteshop.lua @@ -0,0 +1,128 @@ +-- Znote Shop v1.0 for Znote AAC on TFS 1.1 +function onSay(player, words, param) + local storage = 54073 -- Make sure to select non-used storage. This is used to prevent SQL load attacks. + local cooldown = 15 -- in seconds. + + if player:getStorageValue(storage) <= os.time() then + player:setStorageValue(storage, os.time() + cooldown) + + local type_desc = { + "itemids", + "pending premium (skip)", + "pending gender change (skip)", + "pending character name change (skip)", + "Outfit and addons", + "Mounts", + "Instant house purchase" + } + print("Player: " .. player:getName() .. " triggered !shop talkaction.") + -- Create the query + local orderQuery = db.storeQuery("SELECT `id`, `type`, `itemid`, `count` FROM `znote_shop_orders` WHERE `account_id` = " .. player:getAccountId() .. ";") + local served = false + + -- Detect if we got any results + if orderQuery ~= false then + repeat + -- Fetch order values + local q_id = result.getNumber(orderQuery, "id") + local q_type = result.getNumber(orderQuery, "type") + local q_itemid = result.getNumber(orderQuery, "itemid") + local q_count = result.getNumber(orderQuery, "count") + + print("Processing type "..q_type..": ".. type_desc[q_type]) + + -- ORDER TYPE 1 (Regular item shop products) + if q_type == 1 then + served = true + -- Get wheight + if player:getFreeCapacity() >= ItemType(q_itemid):getWeight(q_count) then + db.query("DELETE FROM `znote_shop_orders` WHERE `id` = " .. q_id .. ";") + player:addItem(q_itemid, q_count) + player:sendTextMessage(MESSAGE_INFO_DESCR, "Congratulations! You have received " .. q_count .. " x " .. ItemType(q_itemid):getName() .. "!") + else + player:sendTextMessage(MESSAGE_STATUS_WARNING, "Need more CAP!") + end + end + + -- ORDER TYPE 5 (Outfit and addon) + if q_type == 5 then + served = true + + local itemid = q_itemid + local outfits = {} + + if itemid > 1000 then + local first = math.floor(itemid/1000) + table.insert(outfits, first) + itemid = itemid - (first * 1000) + end + table.insert(outfits, itemid) + + for _, outfitId in pairs(outfits) do + -- Make sure player don't already have this outfit and addon + if not player:hasOutfit(outfitId, q_count) then + db.query("DELETE FROM `znote_shop_orders` WHERE `id` = " .. q_id .. ";") + player:addOutfit(outfitId) + player:addOutfitAddon(outfitId, q_count) + player:sendTextMessage(MESSAGE_INFO_DESCR, "Congratulations! You have received a new outfit!") + else + player:sendTextMessage(MESSAGE_STATUS_WARNING, "You already have this outfit and addon!") + end + end + end + + -- ORDER TYPE 6 (Mounts) + if q_type == 6 then + served = true + -- Make sure player don't already have this outfit and addon + if not player:hasMount(q_itemid) then + db.query("DELETE FROM `znote_shop_orders` WHERE `id` = " .. q_id .. ";") + player:addMount(q_itemid) + player:sendTextMessage(MESSAGE_INFO_DESCR, "Congratulations! You have received a new mount!") + else + player:sendTextMessage(MESSAGE_STATUS_WARNING, "You already have this mount!") + end + end + + -- ORDER TYPE 7 (Direct house purchase) + if orderType == 7 then + served = true + local house = House(orderItemId) + -- Logged in player is not neccesarily the player that bough the house. So we need to load player from db. + local buyerQuery = db.storeQuery("SELECT `name` FROM `players` WHERE `id` = "..orderCount.." LIMIT 1") + if buyerQuery ~= false then + local buyerName = result.getDataString(buyerQuery, "name") + result.free(buyerQuery) + if house then + db.query("DELETE FROM `znote_shop_orders` WHERE `id` = " .. orderId .. ";") + house:setOwnerGuid(orderCount) + player:sendTextMessage(MESSAGE_INFO_DESCR, "You have successfully bought the house "..house:getName().." on "..buyerName..", be sure to have the money for the rent in the bank.") + print("Process complete. [".. buyerName .."] has recieved house: ["..house:getName().."]") + end + end + end + + + -- Add custom order types here + -- Type 1 is for itemids (Already coded here) + -- Type 2 is for premium (Coded on web) + -- Type 3 is for gender change (Coded on web) + -- Type 4 is for character name change (Coded on web) + -- Type 5 is for character outfit and addon (Already coded here) + -- Type 6 is for mounts (Already coded here) + -- So use type 7+ for custom stuff, like etc packages. + -- if q_type == 7 then + -- end + until not result.next(orderQuery) + result.free(orderQuery) + if not served then + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "You have no orders to process in-game.") + end + else + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "You have no orders.") + end + else + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Can only be executed once every " .. cooldown .. " seconds. Remaining cooldown: " .. player:getStorageValue(storage) - os.time()) + end + return false +end \ No newline at end of file diff --git a/data/talkactions/talkactions.xml b/data/talkactions/talkactions.xml index de95de2..99e591d 100644 --- a/data/talkactions/talkactions.xml +++ b/data/talkactions/talkactions.xml @@ -36,7 +36,8 @@ - + + @@ -49,7 +50,8 @@ - + + diff --git a/src/actions.cpp b/src/actions.cpp index d865e43..e1c4113 100644 --- a/src/actions.cpp +++ b/src/actions.cpp @@ -324,7 +324,14 @@ bool Actions::useItem(Player* player, const Position& pos, uint8_t index, Item* player->stopWalk(); if (isHotkey) { - showUseHotkeyMessage(player, item, player->getItemTypeCount(item->getID(), (!item->getFluidType() ? -1 : item->getSubType()))); + uint32_t count = 0; + if (item->isRune()) { + count = player->getRuneCount(item->getID()); + } else { + count = player->getItemTypeCount(item->getID(), (!item->getFluidType() ? -1 : item->getSubType())); + } + + showUseHotkeyMessage(player, item, count); } ReturnValue ret = internalUseItem(player, pos, index, item, isHotkey); @@ -354,7 +361,15 @@ bool Actions::useItemEx(Player* player, const Position& fromPos, const Position& } if (isHotkey) { - showUseHotkeyMessage(player, item, player->getItemTypeCount(item->getID(), (!item->getFluidType() ? -1 : item->getSubType()))); + uint32_t count = 0; + if (item->isRune()) { + count = player->getRuneCount(item->getID()); + } + else { + count = player->getItemTypeCount(item->getID(), (!item->getFluidType() ? -1 : item->getSubType())); + } + + showUseHotkeyMessage(player, item, count); } if (!action->executeUse(player, item, fromPos, action->getTarget(player, creature, toPos, toStackPos), toPos, isHotkey)) { diff --git a/src/player.cpp b/src/player.cpp index 5e847de..7335037 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -2540,6 +2540,31 @@ uint32_t Player::getItemTypeCount(uint16_t itemId, int32_t subType /*= -1*/) con return count; } +// 7.8 mechanics to count runes by charges requires different logic to be implemented +uint32_t Player::getRuneCount(uint16_t itemId) const +{ + uint32_t count = 0; + for (int32_t i = CONST_SLOT_FIRST; i <= CONST_SLOT_LAST; i++) { + Item* item = inventory[i]; + if (!item) { + continue; + } + + if (item->getID() == itemId) { + count += item->getCharges(); + } + + if (Container* container = item->getContainer()) { + for (ContainerIterator it = container->iterator(); it.hasNext(); it.advance()) { + if ((*it)->getID() == itemId) { + count += (*it)->getCharges(); + } + } + } + } + return count; +} + bool Player::removeItemOfType(uint16_t itemId, uint32_t amount, int32_t subType, bool ignoreEquipped/* = false*/) const { if (amount == 0) { diff --git a/src/player.h b/src/player.h index 0ca4979..7a47cf3 100644 --- a/src/player.h +++ b/src/player.h @@ -972,6 +972,7 @@ class Player final : public Creature, public Cylinder size_t getFirstIndex() const final; size_t getLastIndex() const final; uint32_t getItemTypeCount(uint16_t itemId, int32_t subType = -1) const final; + uint32_t getRuneCount(uint16_t itemId) const; std::map& getAllItemTypeCount(std::map& countMap) const final; Thing* getThing(size_t index) const final;