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;