summaryrefslogblamecommitdiff
path: root/plugins/shop.py
blob: 8c234fe4cb1096bb4a7c2a8c829fe897cb18b703 (plain) (tree)
1
2
3
4
5
6
7
8
9








                                   
             

                                        
                                                   



                                        
                                                              
          






                             

                                               




                                            



                                                                  

                  




                                                        
 


                                                        
 

                                                             
 































                                      
                                                                           
 


                                               




                                                                                  
                                       



                                                              


                                         












                                                        
                          





                                              






                                                          
 






















                                                            
                          
                       



                                               

                               
 
                          













                                                         



                                                                                                             
              
 






































                                                        

                               

                        

                          











                                                        



                                                                                                             







































                                                        

                               





                                          
                          
 




















































                                                                     
                          
 


















                                                                           





                                                        



                               


                                   

                    
                          
 
































                                                                  



                                   
                          
 





























                                                                       



                                   
                          
 

































                                                                            



                                   

                  
                          
 








































                                                                       



                                   
                          
 













































                                                                           

                                                               





                                                                           
                                            



















                                                              



                                                                           



                                                    
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 = 1
# 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)