summaryrefslogtreecommitdiff
path: root/plugins/shop.py
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/shop.py')
-rw-r--r--plugins/shop.py551
1 files changed, 551 insertions, 0 deletions
diff --git a/plugins/shop.py b/plugins/shop.py
new file mode 100644
index 0000000..44c5db8
--- /dev/null
+++ b/plugins/shop.py
@@ -0,0 +1,551 @@
+import os
+import time
+import logging
+from collections import OrderedDict
+import net.mapserv as mapserv
+import chatbot
+import logicmanager
+import status
+import badge
+from net.inventory import get_item_index
+from net.trade import reset_trade_state
+from utils import encode_str, extends
+from itemdb import item_name
+from playerlist import PlayerList
+from chat import send_whisper as whisper
+
+
+__all__ = [ 'PLUGIN', 'init', 'shoplog', 'buying', 'selling' ]
+
+#nobuy = ['Manatauro', 'ManatauroMage', 'Manatauro Shop', 'kytty']
+nobuy = ['kirito123']
+
+PLUGIN = {
+ 'name': 'shop',
+ 'requires': ('chatbot',),
+ 'blocks': (),
+ 'default_config' : {
+ 'timeout' : 60,
+ 'shoplist_txt' : 'shoplist.txt',
+ 'admins_file' : 'shopAdmins.txt'
+ }
+}
+
+shoplog = logging.getLogger('ManaChat.Shop')
+trade_timeout = 60
+shop_admins = None
+
+
+class s:
+ player = ''
+ mode = ''
+ item_id = 0
+ amount = 0
+ price = 0
+ index = 0
+ start_time = 0
+
+
+buying = OrderedDict([
+# (621, (5000, 1)), # Eyepatch
+# (640, (1450, 100)), # Iron Ore
+# (4001, (650, 300)), # Coal
+])
+
+selling = OrderedDict([
+# (535, (100, 50)), # Red Apple
+# (640, (1750, 100)), # Iron Ore
+])
+
+
+def cleanup():
+ s.player = ''
+ s.mode = ''
+ s.item_id = 0
+ s.amount = 0
+ s.price = 0
+ s.index = 0
+ s.start_time = 0
+
+
+# =========================================================================
+def selllist(nick, message, is_whisper, match):
+ if not is_whisper:
+ return
+ if nick in nobuy:
+ mapserv.cmsg_chat_message("Special prize for you, " + nick + "!")
+ time.sleep(5)
+ # ~ return
+ # Support for 4144's shop (Sell list)
+ data = '\302\202B1'
+
+ for id_, (price, amount) in selling.iteritems():
+ index = get_item_index(id_)
+ if index < 0:
+ continue
+
+ _, curr_amount = mapserv.player_inventory[index]
+ amount = min(curr_amount, amount)
+ if nick in nobuy:
+ price=price*100
+
+ data += encode_str(id_, 2)
+ data += encode_str(price, 4)
+ data += encode_str(amount, 3)
+
+ whisper(nick, data)
+
+
+def buylist(nick, message, is_whisper, match):
+ if not is_whisper:
+ return
+
+ # Support for 4144's shop (Sell list)
+ data = '\302\202S1'
+
+ for id_, (price, amount) in buying.iteritems():
+ index = get_item_index(id_)
+ if index > 0:
+ _, curr_amount = mapserv.player_inventory[index]
+ amount -= curr_amount
+
+ try:
+ can_afford = mapserv.player_money / price
+ except ZeroDivisionError:
+ can_afford = 10000000
+
+ amount = min(can_afford, amount)
+
+ if amount <= 0:
+ continue
+
+ data += encode_str(id_, 2)
+ data += encode_str(price, 4)
+ data += encode_str(amount, 3)
+
+ whisper(nick, data)
+
+
+def sellitem(nick, message, is_whisper, match):
+ if not is_whisper:
+ return
+
+ item_id = amount = 0
+
+ # FIXME: check if amount=0 or id=0
+ try:
+ item_id = int(match.group(1))
+ # price = int(match.group(2))
+ amount = int(match.group(3))
+ if item_id < 1 or amount < 1:
+ raise ValueError
+ except ValueError:
+ whisper(nick, "usage: !sellitem ID PRICE AMOUNT")
+ return
+
+ if s.player:
+ whisper(nick, "I am currently trading with someone")
+ return
+
+ player_id = mapserv.beings_cache.findId(nick)
+ if player_id < 0:
+ whisper(nick, "I don't see you nearby")
+ return
+
+ if item_id not in buying:
+ whisper(nick, "I don't buy that")
+ return
+
+ real_price, max_amount = buying[item_id]
+
+ index = get_item_index(item_id)
+ if index > 0:
+ _, curr_amount = mapserv.player_inventory[index]
+ max_amount -= curr_amount
+
+ if amount > max_amount:
+ whisper(nick, "I don't need that many")
+ return
+
+ total_price = real_price * amount
+ if total_price > mapserv.player_money:
+ whisper(nick, "I can't afford it")
+ return
+
+ s.player = nick
+ s.mode = 'buy'
+ s.item_id = item_id
+ s.amount = amount
+ s.price = total_price
+ s.index = index
+ s.start_time = time.time()
+
+ mapserv.cmsg_trade_request(player_id)
+
+
+def buyitem(nick, message, is_whisper, match):
+ if not is_whisper:
+ return
+ item_id = amount = 0
+
+ # FIXME: check if amount=0 or id=0
+ try:
+ item_id = int(match.group(1))
+ # price = int(match.group(2))
+ amount = int(match.group(3))
+ if item_id < 1 or amount < 1:
+ raise ValueError
+ except ValueError:
+ whisper(nick, "usage: !buyitem ID PRICE AMOUNT")
+ return
+
+ if s.player:
+ whisper(nick, "I am currently trading with someone")
+ return
+
+ player_id = mapserv.beings_cache.findId(nick)
+ if player_id < 0:
+ whisper(nick, "I don't see you nearby")
+ return
+
+ if item_id not in selling:
+ whisper(nick, "I don't sell that")
+ return
+
+ real_price, max_amount = selling[item_id]
+
+ index = get_item_index(item_id)
+ if index > 0:
+ _, curr_amount = mapserv.player_inventory[index]
+ max_amount = min(max_amount, curr_amount)
+ else:
+ max_amount = 0
+
+ if amount > max_amount:
+ whisper(nick, "I don't have that many")
+ return
+
+ total_price = real_price * amount
+
+ s.player = nick
+ s.mode = 'sell'
+ s.item_id = item_id
+ s.amount = amount
+ s.price = total_price
+ s.index = index
+ s.start_time = time.time()
+
+ mapserv.cmsg_trade_request(player_id)
+
+
+def retrieve(nick, message, is_whisper, match):
+ if not is_whisper:
+ return
+
+ if shop_admins is None:
+ return
+
+ if not shop_admins.check_player(nick):
+ return
+
+ item_id = amount = 0
+
+ try:
+ item_id = int(match.group(1))
+ amount = int(match.group(2))
+ if amount < 1:
+ raise ValueError
+ except ValueError:
+ whisper(nick, "usage: !retrieve ID AMOUNT (ID=0 for money)")
+ return
+
+ if s.player:
+ whisper(nick, "I am currently trading with someone")
+ return
+
+ player_id = mapserv.beings_cache.findId(nick)
+ if player_id < 0:
+ whisper(nick, "I don't see you nearby")
+ return
+
+ index = max_amount = 0
+
+ if item_id == 0:
+ max_amount = mapserv.player_money
+ else:
+ index = get_item_index(item_id)
+ if index > 0:
+ max_amount = mapserv.player_inventory[index][1]
+
+ if amount > max_amount:
+ whisper(nick, "I don't have that many")
+ return
+
+ s.player = nick
+ s.mode = 'retrieve'
+ s.item_id = item_id
+ s.amount = amount
+ s.index = index
+ s.start_time = time.time()
+
+ mapserv.cmsg_trade_request(player_id)
+
+
+def invlist(nick, message, is_whisper, match):
+ if not is_whisper:
+ return
+
+ if shop_admins is None:
+ return
+
+ if not shop_admins.check_player(nick):
+ return
+
+ ls = status.invlists(50)
+ for l in ls:
+ whisper(nick, l)
+
+
+def zeny(nick, message, is_whisper, match):
+ if not is_whisper:
+ return
+
+ if shop_admins is None:
+ return
+
+ if not shop_admins.check_player(nick):
+ return
+
+ whisper(nick, 'I have {} GP'.format(mapserv.player_stats[20]))
+
+
+# =========================================================================
+@extends('smsg_trade_request')
+def trade_request(data):
+ shoplog.info("Trade request from %s", data.nick)
+ mapserv.cmsg_trade_response(False)
+ selllist(data.nick, '', True, None)
+
+
+@extends('smsg_trade_response')
+def trade_response(data):
+ code = data.code
+
+ if code == 0:
+ shoplog.info("%s is too far", s.player)
+ whisper(s.player, "You are too far, please come closer")
+ mapserv.cmsg_trade_cancel_request() # NOTE: do I need it?
+ cleanup()
+
+ elif code == 3:
+ shoplog.info("%s accepts trade", s.player)
+ if s.mode == 'sell':
+ mapserv.cmsg_trade_item_add_request(s.index, s.amount)
+ mapserv.cmsg_trade_add_complete()
+ elif s.mode == 'buy':
+ mapserv.cmsg_trade_item_add_request(0, s.price)
+ mapserv.cmsg_trade_add_complete()
+ elif s.mode == 'retrieve':
+ mapserv.cmsg_trade_item_add_request(s.index, s.amount)
+ mapserv.cmsg_trade_add_complete()
+ else:
+ shoplog.error("Unknown shop state: %s", s.mode)
+ mapserv.cmsg_trade_cancel_request()
+ cleanup()
+
+ elif code == 4:
+ shoplog.info("%s cancels trade", s.player)
+ cleanup()
+
+ else:
+ shoplog.info("Unknown TRADE_RESPONSE code %d", code)
+ cleanup()
+
+
+@extends('smsg_trade_item_add')
+def trade_item_add(data):
+ item_id, amount = data.id, data.amount
+
+ shoplog.info("%s adds %d %s", s.player, amount, item_name(item_id))
+
+ if item_id == 0:
+ return
+
+ if s.mode == 'sell':
+ whisper(s.player, "I accept only GP")
+ mapserv.cmsg_trade_cancel_request()
+ cleanup()
+
+ elif s.mode == 'buy':
+ if s.item_id != item_id or s.amount != amount:
+ whisper(s.player, "You should give me {} {}".format(
+ s.amount, item_name(s.item_id)))
+ mapserv.cmsg_trade_cancel_request()
+ cleanup()
+
+ elif s.mode == 'retrieve':
+ pass
+
+ else:
+ shoplog.error("Unknown shop state: %s", s.mode)
+ mapserv.cmsg_trade_cancel_request()
+ cleanup()
+
+
+@extends('smsg_trade_item_add_response')
+def trade_item_add_response(data):
+ code = data.code
+ amount = data.amount
+
+ if code == 0:
+ if amount > 0:
+ item_id, _ = mapserv.trade_state['items_give'][-1]
+ shoplog.info("I add to trade %d %s", amount, item_name(item_id))
+
+ elif code == 1:
+ shoplog.info("%s is overweight", s.player)
+ whisper(s.player, "You are overweight")
+ mapserv.cmsg_trade_cancel_request()
+ cleanup()
+
+ elif code == 2:
+ shoplog.info("%s has no free slots", s.player)
+ whisper(s.player, "You don't have free slots")
+ mapserv.cmsg_trade_cancel_request()
+ cleanup()
+
+ else:
+ shoplog.error("Unknown ITEM_ADD_RESPONSE code: ", code)
+ mapserv.cmsg_trade_cancel_request()
+ cleanup()
+
+
+@extends('smsg_trade_cancel')
+def trade_cancel(data):
+ shoplog.error("Trade with %s canceled", s.player)
+ cleanup()
+
+
+@extends('smsg_trade_ok')
+def trade_ok(data):
+ who = data.who
+
+ if who == 0:
+ return
+
+ shoplog.info("Trade OK: %s", s.player)
+
+ if s.mode == 'sell':
+ zeny_get = mapserv.trade_state['zeny_get']
+ if zeny_get >= s.price:
+ mapserv.cmsg_trade_ok()
+ else:
+ whisper(s.player, "Your offer makes me sad")
+ mapserv.cmsg_trade_cancel_request()
+ cleanup()
+
+ elif s.mode == 'buy':
+ items_get = {}
+ for item_id, amount in mapserv.trade_state['items_get']:
+ try:
+ items_get[item_id] += amount
+ except KeyError:
+ items_get[item_id] = amount
+
+ if s.item_id in items_get and s.amount == items_get[s.item_id]:
+ mapserv.cmsg_trade_ok()
+ else:
+ whisper(s.player, "You should give me {} {}".format(
+ s.amount, item_name(s.item_id)))
+ mapserv.cmsg_trade_cancel_request()
+ cleanup()
+
+ elif s.mode == 'retrieve':
+ mapserv.cmsg_trade_ok()
+
+ else:
+ shoplog.error("Unknown shop state: %s", s.mode)
+ mapserv.cmsg_trade_cancel_request()
+ cleanup()
+
+
+@extends('smsg_trade_complete')
+def trade_complete(data):
+ if s.mode == 'sell':
+ shoplog.info("Trade with %s completed. I sold %d %s for %d GP",
+ s.player, s.amount, item_name(s.item_id),
+ mapserv.trade_state['zeny_get'])
+ elif s.mode == 'buy':
+ shoplog.info("Trade with %s completed. I bought %d %s for %d GP",
+ s.player, s.amount, item_name(s.item_id),
+ mapserv.trade_state['zeny_give'])
+ elif s.mode == 'retrieve':
+ shoplog.info("Trade with %s completed.", s.player)
+ else:
+ shoplog.info("Trade with %s completed. Unknown shop state %s",
+ s.player, s.mode)
+
+ reset_trade_state(mapserv.trade_state)
+
+ cleanup()
+
+
+# =========================================================================
+def shop_logic(ts):
+ if s.start_time > 0:
+ if ts > s.start_time + trade_timeout:
+ shoplog.warning("%s timed out", s.player)
+ mapserv.cmsg_trade_cancel_request()
+
+
+# =========================================================================
+shop_commands = {
+ '!selllist' : selllist,
+ '!buylist' : buylist,
+ '!sellitem (\d+) (\d+) (\d+)' : sellitem,
+ '!buyitem (\d+) (\d+) (\d+)' : buyitem,
+ '!retrieve (\d+) (\d+)' : retrieve,
+ '!invlist' : invlist,
+ '!zeny' : zeny,
+}
+
+
+def load_shop_list(config):
+ global buying
+ global selling
+
+ shoplist_txt = config.get('shop', 'shoplist_txt')
+ if not os.path.isfile(shoplist_txt):
+ shoplog.warning('shoplist file not found : %s', shoplist_txt)
+ return
+
+ with open(shoplist_txt, 'r') as f:
+ for l in f:
+ try:
+ item_id, buy_amount, buy_price, sell_amount, sell_price = \
+ map(int, l.split())
+ if buy_amount > 0:
+ buying[item_id] = buy_price, buy_amount
+ if sell_amount > 0:
+ selling[item_id] = sell_price, sell_amount
+ except ValueError:
+ pass
+
+
+def init(config):
+ for cmd, action in shop_commands.items():
+ chatbot.add_command(cmd, action)
+
+ global trade_timeout
+ global shop_admins
+
+ trade_timeout = config.getint('shop', 'timeout')
+
+ shop_admins_file = config.get('shop', 'admins_file')
+ if os.path.isfile(shop_admins_file):
+ shop_admins = PlayerList(shop_admins_file)
+
+ badge.is_shop = True
+
+ load_shop_list(config)
+ logicmanager.logic_manager.add_logic(shop_logic)