yatta-invisible-market.lua

-- Build up a list of items for sale.
-- Each is assigned to a keyword which is what a player must say to buy the item.
-- Each also supports the following properties:
--   vnum: Object vnum of item
--   cost: Cost in coins
--   slot: Number 1-4 for picking stock. It'll pick one from slot 1, one from slot 2, etc.
--   previewMsg: What Yatta says when you ask about an item
-- Optional properties:
--   count: If buying this should give the player multiple of an item, this is that number. Defaults to 1.
--   buyName: The name of the item shown when Yatta mentions it at purchase time. Defaults to "One <item short name>"
local items = {
  -- Spreading this one out for readability, but subsequent items are squished onto fewer lines to save space.
  ["scope"] = {
    vnum = 36651,
    cost = 5,
    slot = 1,
    previewMsg = "Ah, the scope eh? Perfect for a sniper! That'll be 5 coins."
  },
  ["superconductor"] = {vnum = 36652, cost = 5, slot = 1,
    previewMsg = "You some sorta engineer? No matter. My contact tells me that Relby makes the best. A bargain at 5 coins."},
  ["battery"] = {vnum = 36653, cost = 5, slot = 2,
    previewMsg = "Yeah, I get it. Everyone needs batteries. This power pack is something else though. But it's all yours for 5 coins."},
  ["crystal"] = {vnum = 36654, cost = 5, slot = 2,
    previewMsg = "Crystal... yeah... I got some. Just uh.. don't ask where it came from. That'll be 10 coins."},
  ["sweetblossom"] = {vnum = 36657, count = 10, cost = 5, slot = 3, buyName = "Ten vials of sweetblossom",
    previewMsg = "Suhweeeeeeet blossom. That's the stuff. Hit a little harder, feel a little stronger? Give ya 10 vials for 5 coins."},
  ["gunship blueprints"] = {vnum = 32018, cost = 250, slot = 4, buyName = "One set of gunship blueprints",
    previewMsg = "You ain't seen nothing 'til you seen these babies bust out their payload! Gunship blueprint'll be 250 coins."},
}

-- We can define a local function to be used inside this mob's progs so we don't have to copy/paste the code
local function stockCheck(self, ch, keyword)
  -- We set a global variable with a value like "<battery> <gunship blueprints>" to mark what's in stock.
  -- Each item is wrapped in <...> so that we could differetiate between "battery" and "fancy battery".
  if not LOTJ.getGlobalVar("im.stock", ""):find("<"..keyword..">") then
    self:sayTo(ch, "Hah! Nice try, but I'm sold outta that fer now. Check back next week.")
    return false
  end
  return true
end

self:onGive(function(self, obj, ch)
  -- If they gave her something other than what she wants, drop it.
  -- This sort of thing could be used for any mob taking specific items to drop non-matches.
  if obj:getVNum() ~= 36641 then
    self:sayTo(ch, "What's this, hmm? I don't want your junk!")
    self:drop(obj)
    return
  end

  -- Whenever someone gives her a coin, increment their im.coinBalance variable and then destroy the coin.
  -- Because this is tracked as a variable, there's no need to do the original behavior where only one player
  -- can be making a purchase at a time, and no need to know what they plan to buy ahead of time.
  ch:setVar("im.coinBalance", ch:getVar("im.coinBalance", 0)+1)
  self:emote("&cflicks the coin with a long fingernail and listens to it. She says, \"Yeah, that's a real one. "..
    "I have you down for "..ch:getVar("im.coinBalance").." of these.\"")
  obj:toRoom(4)
end)


-- Set up all speech triggers by looping through the items list.
-- Because we assign each to a keyword in the items variable, we can get that keyword here.
-- "item" represents the object assigned to that keyword, with the vnum, cost, etc properties.
for keyword, item in pairs(items) do

  -- Set a trigger for someone saying just <keyword> ("battery", etc)
  -- This will give them the item's description and cost, but do nothing else.
  self:onSpeech(keyword, function(self, ch, fullText)
    -- We don't want to trigger when someone says "battery" in a longer sentence,
    -- so return early if they said more than just the text trigger.
    if fullText ~= keyword then return end -- Exact match only

    -- Use our stock check function defined above. If it returned false, it's not in stock and we should return now
    if not stockCheck(self, ch, keyword) then return end

    -- Say the previewMsg from the item settings
    self:sayTo(ch, item.previewMsg)
    ch:echoAt("&z[&WI&wnvisible Market&z] To purchase this item, give all of the required coins to Yatta then '&ksay confirm "..keyword.."&k'.")
  end)

  -- Set a trigger for someone saying "confirm <keyword>". This will complete a purchase.
  self:onSpeech("confirm "..keyword, function(self, ch, fullText)
    if fullText ~= "confirm "..keyword then return end -- Exact match only
    if not stockCheck(self, ch, keyword) then return end

    -- See if we've recorded them giving her enough coins by looking up the variable we use to track it
    local coinBalance = ch:getVar("im.coinBalance", 0)
    if coinBalance < item.cost then
      self:emote("&ccounts the coins in her palm with a dissatisfied grunt, \"Whatcha tryin' to pull here? That ain't enough coins!\"")
      return
    end

    local buyName = item.buyName or "One "..keyword
    self:emote("&cgrins greedily, quickly closing her fingers around the coins, \""..buyName..", comin' right up!\"")

    -- We declare the "obj" variable so that we can later access one of the purchased items to get its name.
    local obj = nil
    local count = item.count or 1
    -- This code generically handles single or multiple items by going through a loop up to <count> times.
    for i=1,count do
      obj = Object.invoke(item.vnum)
      -- Put the object directly in the character's inventory. Note that this won't show an action
      -- message like "player gets a battery", so it's up to us to echo an appropriate message.
      obj:toChar(ch)
    end

    -- Subtract the cost from their coinBalance tracker
    ch:setVar("im.coinBalance", coinBalance - item.cost)
    if count == 1 then
      self:echo("{1} &cdisappears into the vault and returns, sans coins but with {2}&c, which she hands over to {3}.", self, obj, ch)
    else
      self:echo("{1} &cdisappears into the vault and returns, sans coins but with "..count.." of {2}&c, which she hands over to {3}.", self, obj, ch)
    end
    self:emote("&creturns her attention to the room and hollers, \"Next!\"")
  end)
end

-- This is some funky date math to find the beginning of the next week.
local function secondsUntilNextSundayMidnight()
  local expTime = LOTJ.time()
  -- Keep adding a day to the time until it lands on a Sunday
  while LOTJ.date("*t", expTime).wday > 1 do
    expTime = expTime + (24*60*60) -- Add number of seconds in a day
  end

  -- Reconstruct the expiration time using the year/month/day of the Sunday we found, but hour 0 so we get midnight.
  local expDate = LOTJ.date("*t", expTime)
  expTime = LOTJ.time({year=expDate.year, month=expDate.month, day=expDate.day, hour = 0})

  -- The difference between that Sunday at midnight and the current time will be the number of seconds to wait.
  return expTime - LOTJ.time()
end

-- If necessary, update the stock. This uses an expiring variable to ensure it happens once per week.
self:onRandom(100, function(self)
  -- If our im.stockTimer variable hasn't expired yet, do nothing.
  if LOTJ.getGlobalVar("im.stockTimer") then return end

  -- For each slot 1-4, find all items in that slot and pick one of them to stock.
  local stockString = ""
  for slot = 1, 4 do
    local slotOptions = {}
    for keyword, item in pairs(items) do
      if item.slot == slot then table.insert(slotOptions, keyword) end
    end
    if slot > 1 then stockString = stockString.." " end
    stockString = stockString.."<"..slotOptions[math.random(#slotOptions)]..">"
  end

  self:force("immchat This week's Invisible Market stock is: "..stockString)
  LOTJ.setGlobalVar("im.stock", stockString)

  -- Set the timer variable to expire next Sunday at midnight so new stock will be picked then.
  LOTJ.setGlobalVar("im.stockTimer", true, secondsUntilNextSundayMidnight())
end)
generated by LDoc 1.5.0 Last updated 2024-10-22 16:05:00