import os import time import logging from collections import OrderedDict import net.mapserv as mapserv import chatbot import logicmanager import status import badge import random from net.inventory import get_item_index from net.trade import reset_trade_state from utils import encode_str, extends, preloadArray from itemdb import item_name from playerlist import PlayerList from chat import send_whisper as whisper __all__ = [ 'PLUGIN', 'init', 'shoplog', 'buying', 'selling' ] nobuy = [] PLUGIN = { 'name': 'shop', 'requires': ('chatbot',), 'blocks': (), 'default_config' : { 'timeout' : 60, 'shoplist_txt' : 'config/shoplist.txt', 'admins_file' : 'config/shopAdmins.txt' } } shoplog = logging.getLogger('ManaChat.Shop') trade_timeout = 60 time_sleep = 10 # default = 10 # Use at your own risk if you think the default value is too slow. # 0 works too. shop_admins = None ignored_players = preloadArray("config/ignored.txt") # ~ sell_greetings = [ # ~ "Hi {0}! Welcome to my shop!", # ~ ] # ~ buy_greetings = [ # ~ "Okay, {0}! Let's see what you've got to sell!", # ~ ] # ~ sell_greetings = preloadArray("config/sellGreetings.txt") # ~ buy_greetings = preloadArray("config/buyGreetings.txt") 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 ignored_players: return # ~ if lastNickGreeted != nick: # ~ if nick in nobuy: # ~ mapserv.cmsg_chat_message("Special prize for you, " + nick + "!") # ~ time.sleep(time_sleep) # ~ else: # ~ answer = random.choice(sell_greetings) # ~ mapserv.cmsg_chat_message(answer.format(nick)) # ~ lastNickGreeted = nick # ~ 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) time.sleep(time_sleep) whisper(nick, data) def buylist(nick, message, is_whisper, match): if not is_whisper: return if nick in ignored_players: return # ~ if lastNickGreeted != nick: # ~ answer = random.choice(buy_greetings) # ~ mapserv.cmsg_chat_message(answer.format(nick)) # ~ lastNickGreeted = nick # 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) time.sleep(time_sleep) whisper(nick, data) def sellitem(nick, message, is_whisper, match): if not is_whisper: return if nick in ignored_players: return time.sleep(time_sleep) 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: if s.player == nick: whisper(nick, "Hey I'm already trading with you! Don't rush or you may be auto-banned for spam.") else: 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 if nick in ignored_players: return item_id = amount = 0 time.sleep(time_sleep) # 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: if s.player == nick: whisper(nick, "Hey I'm already trading with you! Don't rush or you may be auto-banned for spam.") else: 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 nick in ignored_players: return if shop_admins is None: return if not shop_admins.check_player(nick): return time.sleep(time_sleep) 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 time.sleep(time_sleep) 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])) # ========================================================================= # COMMENTED SECTION DUE TO SPAM TRIGGERING # ~ @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): if s.player in ignored_players: cleanup() return code = data.code time.sleep(time_sleep) 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): if s.player in ignored_players: cleanup() return time.sleep(time_sleep) 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): if s.player in ignored_players: cleanup() return time.sleep(time_sleep) 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): if s.player in ignored_players: cleanup() return who = data.who time.sleep(time_sleep) 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.player in ignored_players: cleanup() return time.sleep(time_sleep) 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) print 'Error: shoplist file not found :' + shoplist_txt os._exit(255) 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()[0:5]) 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) else: shoplog.warning('shop admin file not found : %s', shop_admins_file) print 'Error: shop admin file not found :' + shop_admins_file badge.is_shop = True load_shop_list(config) logicmanager.logic_manager.add_logic(shop_logic)