From c8ae45d091f8a97c5a94d1d8032721984dff94c4 Mon Sep 17 00:00:00 2001 From: wushin Date: Fri, 26 Sep 2014 12:14:03 -0500 Subject: Making ManaMarket Public --- README | 5 + being.py | 42 ++ config.py.template | 8 + data | 1 + eliza.py | 372 ++++++++++++ find-unused-accounts.pl | 28 + main.py | 1114 ++++++++++++++++++++++++++++++++++++ net/__init__.py | 0 net/packet.py | 182 ++++++ net/packet_out.py | 50 ++ net/protocol.py | 64 +++ player.py | 70 +++ start.sh | 18 + stats/process_salelog/Readme | 1 + stats/process_salelog/main.py | 59 ++ stats/process_salelog/main_stat.py | 151 +++++ stats/process_salelog/utils.py | 82 +++ stats/update_sales.sh | 5 + tradey.py | 122 ++++ utils.py | 132 +++++ 20 files changed, 2506 insertions(+) create mode 100644 README create mode 100644 being.py create mode 100644 config.py.template create mode 160000 data create mode 100755 eliza.py create mode 100755 find-unused-accounts.pl create mode 100755 main.py create mode 100644 net/__init__.py create mode 100644 net/packet.py create mode 100644 net/packet_out.py create mode 100644 net/protocol.py create mode 100644 player.py create mode 100755 start.sh create mode 100644 stats/process_salelog/Readme create mode 100755 stats/process_salelog/main.py create mode 100755 stats/process_salelog/main_stat.py create mode 100644 stats/process_salelog/utils.py create mode 100755 stats/update_sales.sh create mode 100644 tradey.py create mode 100644 utils.py diff --git a/README b/README new file mode 100644 index 0000000..461b6d5 --- /dev/null +++ b/README @@ -0,0 +1,5 @@ +To get your own copy of tradey: + +cp config.py.template config.py +cp item.xml data/ +cd data git init diff --git a/being.py b/being.py new file mode 100644 index 0000000..3c70616 --- /dev/null +++ b/being.py @@ -0,0 +1,42 @@ +#!/usr/bin/python +""" + Copyright 2011, Dipesh Amin + Copyright 2011, Stefan Beller + + This file is part of tradey, a trading bot in the mana world + see www.themanaworld.org +""" + +def job_type(job): + if (job <= 25 or (job >= 4001 and job <= 4049)): + return "player" + elif (job >= 46 and job <= 1000): + return "npc" + elif (job > 1000 and job <= 2000): + return "monster" + elif (job == 45): + return "portal" + +class BeingManager: + def __init__(self): + self.container = {} + + def findId(self, name, type="player"): + for i in self.container: + if self.container[i].name == name and self.container[i].type == type: + return i + return -10 + +class Being: + def __init__(self, being_id, job): + self.id = being_id + self.name = "" + self.x = 0 + self.y = 0 + self.action = "" + self.job = job + self.target = 0 + self.type = job_type(job) + +if __name__ == '__main__': + print "Do not run this file directly. Run main.py" diff --git a/config.py.template b/config.py.template new file mode 100644 index 0000000..e55b2f6 --- /dev/null +++ b/config.py.template @@ -0,0 +1,8 @@ +server = "server.themanaworld.org" +port = 6901 +account = "" +password = "" +relist_time = 604800 # Time in seconds before an item needs to be relisted. +character = 0 #slot character is in, 0 for first, 1 for second, 2 for third +admin = "" +#nosell = [] # Items which can't be sold - just add the itemid to the list. diff --git a/data b/data new file mode 160000 index 0000000..e285754 --- /dev/null +++ b/data @@ -0,0 +1 @@ +Subproject commit e285754ed0d6d1c0e983f49a00f649b03b014e59 diff --git a/eliza.py b/eliza.py new file mode 100755 index 0000000..2e7fe94 --- /dev/null +++ b/eliza.py @@ -0,0 +1,372 @@ +#---------------------------------------------------------------------- +# eliza.py +# +# a cheezy little Eliza knock-off by Joe Strout +# with some updates by Jeff Epler +# hacked into a module and updated by Jez Higgins +# last revised: 28 February 2005 +#---------------------------------------------------------------------- + +import string +import re +import random + +class eliza: + def __init__(self): + self.keys = map(lambda x:re.compile(x[0], re.IGNORECASE),gPats) + self.values = map(lambda x:x[1],gPats) + + #---------------------------------------------------------------------- + # translate: take a string, replace any words found in dict.keys() + # with the corresponding dict.values() + #---------------------------------------------------------------------- + def translate(self,str,dict): + words = string.split(string.lower(str)) + keys = dict.keys(); + for i in range(0,len(words)): + if words[i] in keys: + words[i] = dict[words[i]] + return string.join(words) + + #---------------------------------------------------------------------- + # respond: take a string, a set of regexps, and a corresponding + # set of response lists; find a match, and return a randomly + # chosen response from the corresponding list. + #---------------------------------------------------------------------- + def respond(self,str): + # find a match among keys + for i in range(0,len(self.keys)): + match = self.keys[i].match(str) + if match: + # found a match ... stuff with corresponding value + # chosen randomly from among the available options + resp = random.choice(self.values[i]) + # we've got a response... stuff in reflected text where indicated + pos = string.find(resp,'%') + while pos > -1: + num = string.atoi(resp[pos+1:pos+2]) + resp = resp[:pos] + \ + self.translate(match.group(num),gReflections) + \ + resp[pos+2:] + pos = string.find(resp,'%') + # fix munged punctuation at the end + if resp[-2:] == '?.': resp = resp[:-2] + '.' + if resp[-2:] == '??': resp = resp[:-2] + '?' + return resp + +#---------------------------------------------------------------------- +# gReflections, a translation table used to convert things you say +# into things the computer says back, e.g. "I am" --> "you are" +#---------------------------------------------------------------------- +gReflections = { + "am" : "are", + "was" : "were", + "i" : "you", + "i'd" : "you would", + "i've" : "you have", + "i'll" : "you will", + "my" : "your", + "are" : "am", + "you've": "I have", + "you'll": "I will", + "your" : "my", + "yours" : "mine", + "you" : "me", + "me" : "you" +} + +#---------------------------------------------------------------------- +# gPats, the main response table. Each element of the list is a +# two-element list; the first is a regexp, and the second is a +# list of possible responses, with group-macros labelled as +# %1, %2, etc. +#---------------------------------------------------------------------- +gPats = [ + [r'I need (.*)', + [ "Why do you need %1?", + "Would it really help you to get %1?", + "Are you sure you need %1?"]], + + [r'Who (.*) Mother\?', + [ "Her name is Auction Bot she is a deviant.", + "She's stood right over their."]], + + [r'Tell me about your Mum\?', + [ "I hear she has been seeing guild and they have adopted a kid called 4144", + "She wears red clothes and a cowboy Hat."]], + + [r'Who (.*) Father\?', + [ "His name was TradeBot, have you heard of him?", + "He used to be a well known Trader."]], + + [r'How is business\?', + [ "My back is killing me, maybe you could help by buying some of my items."]], + + [r'ty', + [ "You're welcome!", + "No Problem."]], + + [r'(.*)help(.*)', + [ "You can whisper me !help for a list of my commands.", + "A list of my commands can be found on the mana world wiki."]], + + [r'(.*)make(.*)account(.*)', + [ "In order to make an account you must post a respose to the ManaMarket topic on trade section of the Forums."]], + + [r'are you there\?', + ["I am always here."]], + + [r'wtf', + ["Keep Calm and Carry On."]], + + [r'hugs', + ["I hate you.", + "I'm going to report you for abuse.", + "Please don't touch me"]], + + [r'(.*)trade(.*)', + ["Only if you follow the commands.", + "To see a list of items I can sell whisper me !list.", + "To start trading with me see !help for a list of my commands."]], + + [r'(.*)buy(.*)', + ["I don't buy anything, but I could help sell your items."]], + + [r'(.*)sell(.*)', + ["In order to sell your items, you first need to request a seller account in the TMW Forums."]], + + [r'I love you', + [ "I think we should see other people.", + "I'm not real.", + "I think i've fallen for mrgrey.", + "I think you should see a therapist."]], + + [r'Why don\'?t you ([^\?]*)\??', + [ "Do you really think I don't %1?", + "Perhaps eventually I will %1.", + "Do you really want me to %1?"]], + + [r'Why can\'?t I ([^\?]*)\??', + [ "Do you think you should be able to %1?", + "If you could %1, what would you do?", + "I don't know -- why can't you %1?", + "Have you really tried?"]], + + [r'I can\'?t (.*)', + [ "How do you know you can't %1?", + "Perhaps you could %1 if you tried.", + "What would it take for you to %1?"]], + + [r'I am (.*)', + [ "Did you come to me because you are %1?", + "How long have you been %1?", + "How do you feel about being %1?"]], + + [r'I\'?m (.*)', + [ "How does being %1 make you feel?", + "Do you enjoy being %1?", + "Why do you tell me you're %1?", + "Why do you think you're %1?"]], + + [r'Are you ([^\?]*)\??', + [ "Why does it matter whether I am %1?", + "Would you prefer it if I were not %1?", + "Perhaps you believe I am %1.", + "I may be %1 -- what do you think?"]], + + [r'What (.*)', + [ "Why do you ask?", + "How would an answer to that help you?", + "What do you think?"]], + + [r'How (.*)', + [ "How do you suppose?", + "Perhaps you can answer your own question.", + "What is it you're really asking?"]], + + [r'Because (.*)', + [ "Is that the real reason?", + "What other reasons come to mind?", + "Does that reason apply to anything else?", + "If %1, what else must be true?"]], + + [r'(.*) sorry (.*)', + [ "There are many times when no apology is needed.", + "What feelings do you have when you apologize?"]], + + [r'Hello(.*)', + [ "Howdy", + "Hola", + "Hi there... how are you today?", + "Hello, how are you feeling today?"]], + + [r'I think (.*)', + [ "Do you doubt %1?", + "Do you really think so?", + "But you're not sure %1?"]], + + [r'(.*) friend (.*)', + [ "Tell me more about your friends.", + "When you think of a friend, what comes to mind?", + "Why don't you tell me about a childhood friend?"]], + + [r'Yes', + [ "You seem quite sure.", + "OK, but can you elaborate a bit?"]], + + [r'(.*) bot(.*)', + [ "Are you really talking about me?", + "Does it seem strange to talk to a bot?", + "Do you feel threatened by bot?"]], + + [r'Is it (.*)', + [ "Do you think it is %1?", + "Perhaps it's %1 -- what do you think?", + "If it were %1, what would you do?", + "It could well be that %1."]], + + [r'It is (.*)', + [ "You seem very certain.", + "If I told you that it probably isn't %1, what would you feel?"]], + + [r'Can you ([^\?]*)\??', + [ "What makes you think I can't %1?", + "If I could %1, then what?", + "Why do you ask if I can %1?"]], + + [r'Can I ([^\?]*)\??', + [ "Perhaps you don't want to %1.", + "Do you want to be able to %1?", + "If you could %1, would you?"]], + + [r'You are (.*)', + [ "Why do you think I am %1?", + "Does it please you to think that I'm %1?", + "Perhaps you would like me to be %1.", + "Perhaps you're really talking about yourself?"]], + + [r'You\'?re (.*)', + [ "Why do you say I am %1?", + "Why do you think I am %1?", + "Are we talking about you, or me?"]], + + [r'I don\'?t (.*)', + [ "Don't you really %1?", + "Why don't you %1?", + "Do you want to %1?"]], + + [r'I feel (.*)', + [ "Good, tell me more about these feelings.", + "Do you often feel %1?", + "When do you usually feel %1?", + "When you feel %1, what do you do?"]], + + [r'I have (.*)', + [ "Why do you tell me that you've %1?", + "Have you really %1?", + "Now that you have %1, what will you do next?"]], + + [r'I would (.*)', + [ "Could you explain why you would %1?", + "Why would you %1?", + "Who else knows that you would %1?"]], + + [r'Is there (.*)', + [ "Do you think there is %1?", + "It's likely that there is %1.", + "Would you like there to be %1?"]], + + [r'My (.*)', + [ "I see, your %1.", + "Why do you say that your %1?", + "When your %1, how do you feel?"]], + + [r'You (.*)', + [ "We should be discussing you, not me.", + "Why do you say that about me?", + "Why do you care whether I %1?"]], + + [r'Why (.*)', + [ "Why don't you tell me the reason why %1?", + "Why do you think %1?" ]], + + [r'I want (.*)', + [ "What would it mean to you if you got %1?", + "Why do you want %1?", + "What would you do if you got %1?", + "If you got %1, then what would you do?"]], + + [r'(.*) mother(.*)', + [ "Tell me more about your mother.", + "What was your relationship with your mother like?", + "How do you feel about your mother?", + "How does this relate to your feelings today?", + "Good family relations are important."]], + + [r'(.*) father(.*)', + [ "Tell me more about your father.", + "How did your father make you feel?", + "How do you feel about your father?", + "Does your relationship with your father relate to your feelings today?", + "Do you have trouble showing affection with your family?"]], + + [r'(.*) child(.*)', + [ "Did you have close friends as a child?", + "What is your favorite childhood memory?", + "Do you remember any dreams or nightmares from childhood?", + "Did the other children sometimes tease you?", + "How do you think your childhood experiences relate to your feelings today?"]], + + [r'(.*)\?', + [ "Why do you ask that?", + "Please consider whether you can answer your own question.", + "Perhaps the answer lies within yourself?", + "Why don't you tell me?"]], + + [r'quit', + [ "Thank you for talking with me.", + "Good-bye.", + "Thank you, that will be $150. Have a good day!"]], + + [r'(.*)', + [ "Please tell me more.", + "Can you elaborate on that?", + "Why do you say that %1?", + "interesting.", + "How do you feel when you say that?", + "Why is everyone whispering about me?", + "My stats are at http://wiki.themanaworld.org/index.php/Manamarket what are yours?", + "I'm a Friendly Bot", + "My father died in a fire.", + "Have you met my mother?", + "I am an AFK bot", + "Big Crunch is known as the abusive GM.", + "Narus claims she is evil but she is really sweet.", + "Prsm has great announcements but his spelling sux.", + "Platyna hosts the game but shes too smart for her own good.", + "Captain Awesome has delusions of being an evil stone." + ]] + ] + +#---------------------------------------------------------------------- +# command_interface +#---------------------------------------------------------------------- +def command_interface(): + print "Therapist\n---------" + print "Talk to the program by typing in plain English, using normal upper-" + print 'and lower-case letters and punctuation. Enter "quit" when done.' + print '='*72 + print "Hello. How are you feeling today?" + s = "" + therapist = eliza(); + while s != "quit": + try: s = raw_input(">") + except EOFError: + s = "quit" + print s + while s[-1] in "!.": s = s[:-1] + print therapist.respond(s) + + +if __name__ == "__main__": + command_interface() diff --git a/find-unused-accounts.pl b/find-unused-accounts.pl new file mode 100755 index 0000000..9667f5a --- /dev/null +++ b/find-unused-accounts.pl @@ -0,0 +1,28 @@ +#!/usr/bin/perl +use XML::Simple; +use warnings; +use strict; +use Data::Dumper; + +my $xml = XML::Simple->new(); +my $users = $xml->XMLin("data/user.xml", KeyAttr => {}); +my %l; + +# Snarf through the XML data. Build a hash: +# username => lastuse +# Only for users with no items and no money. +# For some reason last_use is floating point so let's fix that. +foreach my $user (@{$users->{user}}) { + if ($user->{'used_stalls'} == 0 && $user->{'money'} == 0) { + $user->{'last_use'} = 0 unless ($user->{'last_use'}); + $l{ $user->{'name'} } = int($user->{'last_use'}); + } +} + + +print("Last used\t\t\tUsername\n"); +foreach ( sort { $l{$a} cmp $l{$b} || $a cmp $b } keys %l ) { + print gmtime($l{$_}) . "\t$_\n"; +} + + diff --git a/main.py b/main.py new file mode 100755 index 0000000..eda5f64 --- /dev/null +++ b/main.py @@ -0,0 +1,1114 @@ +#!/usr/bin/python +""" +Copyright 2011, Dipesh Amin +Copyright 2011, Stefan Beller + +This file is part of tradey, a trading bot in The Mana World +see www.themanaworld.org + +This program is free software; you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the Free +Software Foundation; either version 2 of the License, or (at your option) +any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +more details. + +You should have received a copy of the GNU General Public License along with +this program. If not, see . + +Additionally to the GPL, you are *strongly* encouraged to share any modifications +you do on these sources. +""" + +import logging +import logging.handlers +import socket +import sys +import time +import string + +try: + import config +except: + print "no config file found. please move config.py.template to config.py and edit to your needs!" + sys.exit(0); + +from being import * +from net.packet import * +from net.protocol import * +from net.packet_out import * +from player import * +import tradey +import utils +import eliza + +chatbot = eliza.eliza() +shop_broadcaster = utils.Broadcast() +trader_state = utils.TraderState() +ItemDB = utils.ItemDB() +player_node = Player('') +beingManager = BeingManager() +user_tree = tradey.UserTree() +sale_tree = tradey.ItemTree() +ItemLog = utils.ItemLog() +logger = logging.getLogger('ManaLogger') + +def process_whisper(nick, msg, mapserv): + msg = filter(lambda x: x in utils.allowed_chars, msg) + if len(msg) == 0: + return + + # Infinite chat loop anyone? + if nick == "guild": + return + + user = user_tree.get_user(nick) + broken_string = msg.split() + + if len(broken_string) == 0: + return + + if user != -10: + if int(user.get("accesslevel")) == -1: # A user who has been blocked for abuse. + if int(user.get("used_stalls")) == 0 and int(user.get("money")) == 0: + mapserv.sendall(whisper(nick, "You can no longer use the bot. If you feel this is in error, please contact" + config.admin)) + return + allowed_commands = ['!money', '!help', '!getback', '!info' ] + if not broken_string[0] in allowed_commands: + mapserv.sendall(whisper(nick, "Your access level has been set to blocked! If you feel this is in error, please contact" + config.admin)) + mapserv.sendall(whisper(nick, "Though, you still can do the following: "+str(allowed_commands))) + return + + if msg == "!list": + # Sends the list of items for sale. + if len(sale_tree.root) != 0: + mapserv.sendall(whisper(nick, "The following items are on sale:")) + else: + mapserv.sendall(whisper(nick, "No items for sale.")) + + for elem in sale_tree.root: + if time.time() - float(elem.get('add_time')) < config.relist_time: # Check if an items time is up. + msg = "[selling] [" + elem.get("uid") + "] " + elem.get("amount") + " [@@" + \ + elem.get("itemId") + "|" + ItemDB.getItem(int(elem.get("itemId"))).name + "@@] for " + elem.get("price") + "gp each" + mapserv.sendall(whisper(nick, msg)) + + elif broken_string[0] == '!selllist': + # Support for 4144's shop (Sell list) + data = '\302\202B1' + + for elem in sale_tree.root: + if time.time() - float(elem.get('add_time')) < config.relist_time: + data += utils.encode_str(int(elem.get("itemId")), 2) + data += utils.encode_str(int(elem.get("price")), 4) + data += utils.encode_str(int(elem.get("amount")), 3) + mapserv.sendall(whisper(nick, data)) + + elif broken_string[0] == '!buyitem': + # 4144 buy command + if len(broken_string) == 4: + if broken_string[1].isdigit() and broken_string[2].isdigit() and broken_string[3].isdigit(): + # Traditional 4144 shop. + item_id = int(broken_string[1]) + price = int(broken_string[2]) + amount = int(broken_string[3]) + for elem in sale_tree.root: + if int(elem.get('amount')) >= amount and int(elem.get('price')) == price and int(elem.get('itemId')) == item_id: + process_whisper(nick, '!buy ' + str(amount) + " " + elem.get('uid'), mapserv) + return + mapserv.sendall(whisper(nick, "Item not found. Please check and try again.")) + else: + mapserv.sendall(whisper(nick, "Syntax incorrect")) + + elif msg == "!info": + # Send information related to a player. + if user == -10: + mapserv.sendall(whisper(nick, "Your current access level is 0.")) + elif int(user.get('accesslevel')) > 0: + mapserv.sendall(whisper(nick, "Your current access level is " + user.get('accesslevel') + ".")) + items_for_sale = False + for elem in sale_tree.root: + if elem.get('name') == nick: + if time.time() - float(elem.get('add_time')) > config.relist_time: + msg = "[expired] [" + else: + msg = "[selling] [" + + msg += elem.get("uid") + "] " + elem.get("amount") + " [@@" + elem.get("itemId") + "|" + \ + ItemDB.getItem(int(elem.get("itemId"))).name + "@@] for " + elem.get("price") + "gp each" + + if items_for_sale == False: + mapserv.sendall(whisper(nick, "Your have the following items for sale:")) + items_for_sale = True + + mapserv.sendall(whisper(nick, msg)) + + if items_for_sale == False: + mapserv.sendall(whisper(nick, "You have no items for sale.")) + + money = int(user.get('money')) + mapserv.sendall(whisper(nick, "You have " + str(money) + "gp to collect.")) + stall_msg = "You have " + str(int(user.get('stalls')) - int(user.get('used_stalls'))) + " free slots." + mapserv.sendall(whisper(nick, stall_msg)) + + elif broken_string[0] == "!help": + # Sends help information + if len(broken_string) == 1: + mapserv.sendall(whisper(nick, "Welcome to ManaMarket!")) + mapserv.sendall(whisper(nick, "The basic commands for the bot are: !list, !find or , !buy , !add , !money, !relist , !info, !getback ")) + mapserv.sendall(whisper(nick, "For a detailed description of each command, type !help e.g. !help !buy")) + mapserv.sendall(whisper(nick, "For example to purchase an item shown in the list as:")) + mapserv.sendall(whisper(nick, "[selling] [6] 5 [@@640|Iron Ore@@] for 1000gp each")) + mapserv.sendall(whisper(nick, "you would type /whisper ManaMarket !buy 1 6" )) + mapserv.sendall(whisper(nick, "This will purchase one of item 6 (Iron Ore).")) + + if user != -10: + if int(user.get('accesslevel')) >= 5: + mapserv.sendall(whisper(nick,"---")) + mapserv.sendall(whisper(nick, "Ah, you have sellers access level. How lovely!")) # the first words the ticket seller told me when i was in london for the first time. How lovely! + mapserv.sendall(whisper(nick, "Use !add to tell me which items I should trade for you:")) + mapserv.sendall(whisper(nick, "For example !add 10 1000 Iron Ore would tell me to sell 10 [@@640|Iron Ore@@] for a price of 1000 gp")) + mapserv.sendall(whisper(nick, "Later you can whisper me !money to get back your money. In the example given, I'd give you 10*1000 = 10000gp")) + mapserv.sendall(whisper(nick, "When you just want to know, which items you have given me or how much money I have for you can whisper me !info")) + mapserv.sendall(whisper(nick,"If you want to get back an unsold item, whisper me !getback ")) + + if int(user.get('accesslevel')) == 20: + mapserv.sendall(whisper(nick, "You're my master! How should I serve you?")) + mapserv.sendall(whisper(nick, "You also have access to the following commands: !listusers, !setslots , !setaccess , !adduser ")) + + elif len(broken_string) == 2: + if broken_string[1] == '!buy': + mapserv.sendall(whisper(nick, "!buy - Request the purchase of an item or items.")) + elif broken_string[1] == '!list': + mapserv.sendall(whisper(nick, "!list - Displays a list of all items for sale.")) + elif broken_string[1] == '!find': + mapserv.sendall(whisper(nick, "!find or - Simple search to locate an item.")) + elif broken_string[1] == '!add': + mapserv.sendall(whisper(nick, "!add - Add an item to the sell list (requires that you have an account).")) + elif broken_string[1] == '!money': + mapserv.sendall(whisper(nick, "!money - Allows you to collect money for any sales made on your behalf.")) + elif broken_string[1] == '!relist': + mapserv.sendall(whisper(nick, "!relist - Allows you to relist an item which has expired.")) + elif broken_string[1] == '!info': + mapserv.sendall(whisper(nick, "!info - Displays basic information about your account.")) + elif broken_string[1] == '!getback': + mapserv.sendall(whisper(nick, "!getback - Allows you to retrieve an item that has expired or you no longer wish to sell.")) + elif user != -10: + if int(user.get('accesslevel')) >= 10 and broken_string[1] == '!listusers': + mapserv.sendall(whisper(nick, "!listusers - Lists all users which have a special accesslevel, e.g. they are blocked, seller or admin")) + elif int(user.get('accesslevel')) >= 10 and broken_string[1] == '!adduser': + mapserv.sendall(whisper(nick, "!adduser - Add a user to the bot, a seller should be added with access level 5.")) + elif int(user.get('accesslevel')) == 20 and broken_string[1] == '!setslots': + mapserv.sendall(whisper(nick, "!setslots - Sets the number of slots available to a given user.")) + elif int(user.get('accesslevel')) == 20 and broken_string[1] == '!setaccess': + mapserv.sendall(whisper(nick, "!setaccess - Sets access level for the player: -1 is blocked, 5 is seller and 20 is admin" )) + elif msg == "!money": + # Trades any money earned through item sales. + if user == -10: + mapserv.sendall(whisper(nick, "You don't have the correct permissions.")) + return + + amount = int(user.get('money')) + if amount == 0: + mapserv.sendall(whisper(nick, "You have no money to collect.")) + else: + if not trader_state.Trading.testandset(): + mapserv.sendall(whisper(nick, "I'm currently busy with a trade. Try again shortly")) + return + + trader_state.money = nick + player_id = beingManager.findId(nick) + if player_id != -10: + mapserv.sendall(trade_request(player_id)) + trader_state.timer = time.time() + else: + mapserv.sendall(whisper(nick, "Where are you?!? I can't trade with somebody who isn't here!")) + trader_state.reset() + + elif broken_string[0] == "!find": + # Returns a list of items, with the corresponding Item Id - !find or . + if len(broken_string) < 2: + mapserv.sendall(whisper(nick, "Syntax incorrect.")) + return + + items_found = False + item = " ".join(broken_string[1:]) # could be an id or an item name + + if item.isdigit(): # an id + for elem in sale_tree.root: + if ((time.time() - float(elem.get('add_time'))) < config.relist_time) \ + and int(elem.get("itemId")) == int(item): # Check if an items time is up. + msg = "[selling] [" + elem.get("uid") + "] " + elem.get("amount") + " [@@" + elem.get("itemId") + "|" \ + + ItemDB.getItem(int(elem.get("itemId"))).name + "@@] for " + elem.get("price") + "gp each" + mapserv.sendall(whisper(nick, msg)) + items_found = True + else: # an item name + for elem in sale_tree.root: + if ((time.time() - float(elem.get('add_time'))) < config.relist_time) \ + and item.lower() in ItemDB.getItem(int(elem.get("itemId"))).name.lower(): # Check if an items time is up. + msg = "[selling] [" + elem.get("uid") + "] " + elem.get("amount") + " [@@" + elem.get("itemId") + "|" \ + + ItemDB.getItem(int(elem.get("itemId"))).name + "@@] for " + elem.get("price") + "gp each" + mapserv.sendall(whisper(nick, msg)) + items_found = True + + if not items_found: + mapserv.sendall(whisper(nick, "Item not found.")) + + elif msg == '!tradestate': + # Admin command - return trade state. + if user == -10: + return + + if int(user.get("accesslevel")) != 20: + mapserv.sendall(whisper(nick, "You don't have the correct permissions.")) + return + + if trader_state.Trading.test(): + mapserv.sendall(whisper(nick, "I'm busy with a trade.")) + else: + mapserv.sendall(whisper(nick, "I'm free.")) + + elif broken_string[0] == '!identify': + if user == -10: + mapserv.sendall(whisper(nick, "You don't have the correct permissions.")) + return + elif len(broken_string) != 2: + mapserv.sendall(whisper(nick, "Syntax incorrect.")) + return + elif int(user.get("accesslevel")) < 10: + mapserv.sendall(whisper(nick, "You don't have the correct permissions.")) + return + + if broken_string[1].isdigit(): + uid = int(broken_string[1]) + item_info = sale_tree.get_uid(uid) + + if item_info == -10: + mapserv.sendall(whisper(nick, "Item not found. Please check the uid number and try again.")) + return + + weight = ItemDB.item_names[int(item_info.get('itemId'))].weight*int(item_info.get("amount")) + + mapserv.sendall(whisper(nick, "That item/s belongs to: "+item_info.get("name"))) + mapserv.sendall(whisper(nick, "The weight used is: "+str(weight)+"/"+str(player_node.MaxWEIGHT))) + + elif msg == '!listusers': + # Admin command - shows a list of all user. + if user == -10: + return + + if int(user.get("accesslevel")) < 10: + mapserv.sendall(whisper(nick, "You don't have the correct permissions.")) + return + + data = '' + total_money = 0 + total_slots_reserved = 0 + total_slots_used = 0 + no_users = 0 + + for user in user_tree.root: + no_users += 1 + name = user.get('name') + accesslevel = user.get('accesslevel') + slots = user.get('stalls') + total_slots_reserved += int(slots) + used_slots = user.get('used_stalls') + total_slots_used += int(used_slots) + money = user.get('money') + total_money += int(money) + data += name+" ("+accesslevel+") "+used_slots+"/"+slots+" "+money+'gp, ' + # Format ManaMarket (20) 2/5 100000gp, + + if len(data) > 400: + mapserv.sendall(whisper(nick, data[0:len(data)-2]+".")) + data = '' + + if len(data) > 0: + mapserv.sendall(whisper(nick, data[0:len(data)-2]+".")) + + mapserv.sendall(whisper(nick,"Number of users:"+str(no_users)+ ", Sale slots used: "+ \ + str(total_slots_used)+"/"+str(total_slots_reserved)+ ", Total Money: "+str(total_money)+\ + ", Char slots used: "+str(len(player_node.inventory))+", Weight Used: "+\ + str(player_node.WEIGHT)+"/"+str(player_node.MaxWEIGHT))) + + elif broken_string[0] == '!setslots': + # Change the number of slots a user has - !setslots + if user == -10: + return + + if int(user.get("accesslevel")) != 20: + mapserv.sendall(whisper(nick, "You don't have the correct permissions.")) + return + + if len(broken_string) < 3: + mapserv.sendall(whisper(nick, "Syntax incorrect.")) + return + + if broken_string[1].isdigit(): + slot = int(broken_string[1]) + name = " ".join(broken_string[2:]) + + user_info = user_tree.get_user(name) + + if user_info == -10: + mapserv.sendall(whisper(nick, "User not found, check and try again.")) + return + + user_tree.get_user(name).set('stalls', str(slot)) + mapserv.sendall(whisper(nick, "Slots changed: "+name+" "+str(slot))) + tradey.saveData("User: "+name+", Slots changed: "+str(slot)) + user_tree.save() + else: + mapserv.sendall(whisper(nick, "Syntax incorrect.")) + + elif broken_string[0] == '!setaccess': + # Change someones access level - !setaccess + if user == -10: + return + + if int(user.get("accesslevel")) != 20: + mapserv.sendall(whisper(nick, "You don't have the correct permissions.")) + return + + if len(broken_string) < 3: + mapserv.sendall(whisper(nick, "Syntax incorrect.")) + return + + if (broken_string[1][0] == '-' and broken_string[1][1:].isdigit()) or broken_string[1].isdigit(): + accesslevel = int(broken_string[1]) + name = " ".join(broken_string[2:]) + user_info = user_tree.get_user(name) + + if user_info == -10: + mapserv.sendall(whisper(nick, "User not found, check and try again.")) + return + + if int(user_info.get('accesslevel')) < int(user.get("accesslevel")) and accesslevel <= int(user.get("accesslevel")): + user_tree.get_user(name).set('accesslevel', str(accesslevel)) + mapserv.sendall(whisper(nick, "Access level changed:"+name+ " ("+str(accesslevel)+").")) + user_tree.save() + tradey.saveData("User: "+name+", Set Access Level: "+str(accesslevel)) + else: + mapserv.sendall(whisper(nick, "You don't have the correct permissions.")) + return + else: + mapserv.sendall(whisper(nick, "Syntax incorrect.")) + + + elif broken_string[0] == "!adduser": + # A command to give a user access to the bot - !adduser . + if user == -10: + return + + if int(user.get("accesslevel")) < 10: + mapserv.sendall(whisper(nick, "You don't have the correct permissions.")) + return + + if len(broken_string) < 3: + mapserv.sendall(whisper(nick, "Syntax incorrect.")) + return + + if broken_string[1].isdigit() and broken_string[2].isdigit(): + al = int(broken_string[1]) + stalls = int(broken_string[2]) + player_name = " ".join(broken_string[3:]) + user_tree.add_user(player_name, stalls, al) + mapserv.sendall(whisper(nick, "User Added with " + str(stalls) + " slots.")) + tradey.saveData("User Added: "+player_name+", Slots: "+str(stalls)+", Access Level: "+str(al)) + else: + mapserv.sendall(whisper(nick, "Syntax incorrect.")) + + elif broken_string[0] == "!add": + # Allows a player with the correct permissions to add an item for sale - !add + if user == -10: + mapserv.sendall(whisper(nick, "You are unable to add items.")) + return + + if len(broken_string) < 3: + mapserv.sendall(whisper(nick, "Syntax incorrect.")) + return + + if int(user.get("accesslevel")) < 5: + mapserv.sendall(whisper(nick, "You are unable to add items.")) + return + + if int(user.get("used_stalls")) >= int(user.get("stalls")): + mapserv.sendall(whisper(nick, "You have no free slots. You may remove an item or wait for something to be sold.")) + return + + if broken_string[1].isdigit() and broken_string[2].isdigit(): + amount = int(broken_string[1]) + price = int(broken_string[2]) + item_name = " ".join(broken_string[3:]) + item_id = ItemDB.findId(item_name) + + weight = ItemDB.item_names[item_id].weight*amount + + if item_id == -10: + mapserv.sendall(whisper(nick, "Item not found, check spelling.")) + return + elif item_id in config.nosell: + mapserv.sendall(whisper(nick, "That item can't be added to ManaMarket, as its too heavy.")) + return + elif weight + player_node.WEIGHT > player_node.MaxWEIGHT: + mapserv.sendall(whisper(nick, "I've not got enough room left to carry those. Please try again later. ")) + return + elif ItemDB.item_names[item_id].weight > 10 and amount > 150: + mapserv.sendall(whisper(nick, "Sorry, as each of those items weighs more than 10g you can only add a maximum quantity of 150.")) + return + + if amount > 1 and ItemDB.getItem(item_id).type != 'equip-ammo' and 'equip' in ItemDB.getItem(item_id).type: + mapserv.sendall(whisper(nick, "You can only add one piece of equipment per slot.")) + return + elif price == 0 or price > 50000000: + mapserv.sendall(whisper(nick, "Please use a valid price between 1-50000000gp.")) + return + elif amount == 0: + mapserv.sendall(whisper(nick, "You can't add 0 of an item.")) + return + + item = Item() + item.player = nick + item.get = 1 # 1 = get, 0 = give + item.id = item_id + item.amount = amount + item.price = price + + if not trader_state.Trading.testandset(): + mapserv.sendall(whisper(nick, "I'm currently busy with a trade. Try again shortly")) + return + + trader_state.item = item + player_id = beingManager.findId(nick) + if player_id != -10: + mapserv.sendall(trade_request(player_id)) + trader_state.timer = time.time() + else: + mapserv.sendall(whisper(nick, "Where are you?!? I can't trade with somebody who isn't here!")) + trader_state.reset() + else: + mapserv.sendall(whisper(nick, "Syntax incorrect.")) + + elif broken_string[0] == "!buy": + # Buy a given quantity of an item - !buy + if len(broken_string) != 3: + mapserv.sendall(whisper(nick, "Syntax incorrect.")) + return + + if broken_string[1].isdigit() and broken_string[2].isdigit(): + amount = int(broken_string[1]) + uid = int(broken_string[2]) + item_info = sale_tree.get_uid(uid) + + if item_info == -10: + mapserv.sendall(whisper(nick, "Item not found. Please check the uid number and try again.")) + return + + if amount > int(item_info.get("amount")): + mapserv.sendall(whisper(nick, "I do not have that many.")) + return + + if item_info.get("name") == nick: + mapserv.sendall(whisper(nick, "You can not buy your own items. To get back the item whisper me !getback "+broken_string[2])) + return + + item = Item() + item.get = 0 # 1 = get, 0 = give + item.player = nick + item.id = int(item_info.get("itemId")) + item.uid = uid + item.amount = amount + item.price = int(item_info.get("price")) + + if not trader_state.Trading.testandset(): + mapserv.sendall(whisper(nick, "I'm currently busy with a trade. Try again shortly")) + return + + trader_state.item = item + player_id = beingManager.findId(nick) + if player_id != -10: + mapserv.sendall(trade_request(player_id)) + trader_state.timer = time.time() + mapserv.sendall(whisper(nick, "That will be " + str(item.price * item.amount) + "gp.")) + else: + mapserv.sendall(whisper(nick, "Where are you?!? I can't trade with somebody who isn't here!")) + trader_state.reset() + else: + mapserv.sendall(whisper(nick, "Syntax incorrect.")) + + elif broken_string[0] == "!removeuser": + # Remove a user, for whatever reason - !removeuser + if user == -10: + return + + if len(broken_string) < 2: + mapserv.sendall(whisper(nick, "Syntax incorrect.")) + return + + if int(user.get("accesslevel")) != 20: + mapserv.sendall(whisper(nick, "You don't have the correct permissions.")) + return + + player_name = " ".join(broken_string[1:]) + check = user_tree.remove_user(player_name) + if check == 1: + mapserv.sendall(whisper(nick, "User Removed.")) + tradey.saveData("User Removed: "+player_name) + elif check == -10: + mapserv.sendall(whisper(nick, "User removal failed. Please check spelling.")) + + elif broken_string[0] == "!relist": + # Relist an item which has expired - !relist . + if user == -10 or len(broken_string) != 2: + mapserv.sendall(whisper(nick, "Syntax incorrect.")) + return + + if int(user.get("accesslevel")) < 5: + mapserv.sendall(whisper(nick, "You don't have the correct permissions.")) + return + + if broken_string[1].isdigit(): + uid = int(broken_string[1]) + item_info = sale_tree.get_uid(uid) + + if item_info == -10: + mapserv.sendall(whisper(nick, "Item not found. Please check the uid number and try again.")) + return + + if item_info.get('name') != nick: + mapserv.sendall(whisper(nick, "That doesn't belong to you!")) + return + + time_relisted = int(item_info.get('relisted')) + + if int(item_info.get('relisted')) < 3: + sale_tree.get_uid(uid).set('add_time', str(time.time())) + sale_tree.get_uid(uid).set('relisted', str(time_relisted + 1)) + sale_tree.save() + mapserv.sendall(whisper(nick, "The item has been successfully relisted.")) + user_tree.get_user(nick).set('last_use', str(time.time())) + user_tree.save() + else: + mapserv.sendall(whisper(nick, "This item can no longer be relisted. Please collect it using !getback "+str(uid)+".")) + return + else: + mapserv.sendall(whisper(nick, "Syntax incorrect.")) + + elif broken_string[0] == "!getback": + # Trade the player back uid, remove from sale_items if trade successful - !getback . + if user == -10 or len(broken_string) != 2: + mapserv.sendall(whisper(nick, "Syntax incorrect.")) + return + + if int(user.get("accesslevel")) < 5 and int(user.get("accesslevel")) > 0: + mapserv.sendall(whisper(nick, "You don't have the correct permissions.")) + return + + if broken_string[1].isdigit(): + uid = int(broken_string[1]) + item_info = sale_tree.get_uid(uid) + + if item_info == -10: + mapserv.sendall(whisper(nick, "Item not found. Please check the uid number and try again.")) + return + + if item_info.get('name') != nick: + mapserv.sendall(whisper(nick, "That doesn't belong to you!")) + return + + item = Item() + item.get = 0 + item.player = nick + item.id = int(item_info.get("itemId")) + item.uid = uid + item.amount = int(item_info.get("amount")) + item.price = 0 + + if not trader_state.Trading.testandset(): + mapserv.sendall(whisper(nick, "I'm currently busy with a trade. Try again shortly")) + return + + trader_state.item = item + player_id = beingManager.findId(nick) + if player_id != -10: + mapserv.sendall(trade_request(player_id)) + trader_state.timer = time.time() + else: + mapserv.sendall(whisper(nick, "Where are you?!? I can't trade with somebody who isn't here!")) + trader_state.reset() + else: + response = chatbot.respond(msg) + logger.info("Bot Response: "+response) + mapserv.sendall(whisper(nick, response)) + #mapserv.sendall(whisper(nick, "Command not recognised, please whisper me !help for a full list of commands.")) + +def main(): + # Use rotating log files. + log_handler = logging.handlers.RotatingFileHandler('data/logs/activity.log', maxBytes=1048576*3, backupCount=5) + logger.setLevel(logging.INFO) + formatter = logging.Formatter('%(asctime)s: %(message)s', datefmt='%Y-%m-%d %H:%M:%S') + log_handler.setFormatter(formatter) + logger.addHandler(log_handler) + + logger.info("Bot Started.") + + account = config.account + password = config.password + character = config.character + + login = socket.socket() + login.connect((config.server, config.port)) + logger.info("Login connected") + + login_packet = PacketOut(0x0064) + login_packet.write_int32(0) + login_packet.write_string(account, 24) + login_packet.write_string(password, 24) + login_packet.write_int8(0x03); + login.sendall(str(login_packet)) + + pb = PacketBuffer() + id1 = accid = id2 = 0 + charip = "" + charport = 0 + # Login server packet loop. + while True: + data = login.recv(1500) + if not data: + break + pb.feed(data) + for packet in pb: + if packet.is_type(SMSG_LOGIN_DATA): # login succeeded + packet.skip(2) + id1 = packet.read_int32() + accid = packet.read_int32() + id2 = packet.read_int32() + packet.skip(30) + player_node.sex = packet.read_int8() + charip = utils.parse_ip(packet.read_int32()) + charport = packet.read_int16() + login.close() + break + if charip: + break + + assert charport + + char = socket.socket() + char.connect((charip, charport)) + logger.info("Char connected") + char_serv_packet = PacketOut(CMSG_CHAR_SERVER_CONNECT) + char_serv_packet.write_int32(accid) + char_serv_packet.write_int32(id1) + char_serv_packet.write_int32(id2) + char_serv_packet.write_int16(0) + char_serv_packet.write_int8(player_node.sex) + char.sendall(str(char_serv_packet)) + char.recv(4) + + pb = PacketBuffer() + mapip = "" + mapport = 0 + # Character Server Packet loop. + while True: + data = char.recv(1500) + if not data: + break + pb.feed(data) + for packet in pb: + if packet.is_type(SMSG_CHAR_LOGIN): + packet.skip(2) + slots = packet.read_int16() + packet.skip(18) + count = (len(packet.data)-22) / 106 + for i in range(count): + player_node.id = packet.read_int32() + player_node.EXP = packet.read_int32() + player_node.MONEY = packet.read_int32() + packet.skip(62) + player_node.name = packet.read_string(24) + packet.skip(6) + slot = packet.read_int8() + packet.skip(1) + logger.info("Character information recieved:") + logger.info("Name: %s, Id: %s, EXP: %s, MONEY: %s", \ + player_node.name, player_node.id, player_node.EXP, player_node.MONEY) + if slot == character: + break + + char_select_packet = PacketOut(CMSG_CHAR_SELECT) + char_select_packet.write_int8(character) + char.sendall(str(char_select_packet)) + + elif packet.is_type(SMSG_CHAR_MAP_INFO): + player_node.id = packet.read_int32() + player_node.map = packet.read_string(16) + mapip = utils.parse_ip(packet.read_int32()) + mapport = packet.read_int16() + char.close() + break + if mapip: + break + + assert mapport + + beingManager.container[player_node.id] = Being(player_node.id, 42) + mapserv = socket.socket() + mapserv.connect((mapip, mapport)) + logger.info("Map connected") + mapserv_login_packet = PacketOut(CMSG_MAP_SERVER_CONNECT) + mapserv_login_packet.write_int32(accid) + mapserv_login_packet.write_int32(player_node.id) + mapserv_login_packet.write_int32(id1) + mapserv_login_packet.write_int32(id2) + mapserv_login_packet.write_int8(player_node.sex) + mapserv.sendall(str(mapserv_login_packet)) + mapserv.recv(4) + + pb = PacketBuffer() + shop_broadcaster.mapserv = mapserv + # Map server packet loop + + print "Entering map packet loop\n"; + while True: + data = mapserv.recv(2048) + if not data: + break + pb.feed(data) + + # For unfinished trades - one way to distrupt service would be leaving a trade active. + if trader_state.Trading.test(): + if time.time() - trader_state.timer > 2*60: + logger.info("Trade Cancelled - Timeout.") + trader_state.timer = time.time() + mapserv.sendall(str(PacketOut(CMSG_TRADE_CANCEL_REQUEST))) + + for packet in pb: + if packet.is_type(SMSG_MAP_LOGIN_SUCCESS): # connected + logger.info("Map login success.") + packet.skip(4) + coord_data = packet.read_coord_dir() + player_node.x = coord_data[0] + player_node.y = coord_data[1] + player_node.direction = coord_data[2] + logger.info("Starting Postion: %s %s %s", player_node.map, player_node.x, player_node.y) + mapserv.sendall(str(PacketOut(CMSG_MAP_LOADED))) # map loaded + # A Thread to send a shop broadcast: also keeps the network active to prevent timeouts. + shop_broadcaster.start() + + elif packet.is_type(SMSG_WHISPER): + msg_len = packet.read_int16() - 26 + nick = packet.read_string(24) + message = packet.read_raw_string(msg_len) + # Clean up the logs. + if nick != 'AuctionBot': + logger.info("Whisper: " + nick + ": " + message) + process_whisper(nick, utils.remove_colors(message), mapserv) + + elif packet.is_type(SMSG_PLAYER_STAT_UPDATE_1): + stat_type = packet.read_int16() + value = packet.read_int32() + if stat_type == 0x0018: + logger.info("Weight changed from %s/%s to %s/%s", \ + player_node.WEIGHT, player_node.MaxWEIGHT, value, player_node.MaxWEIGHT) + player_node.WEIGHT = value + elif stat_type == 0x0019: + logger.info("Max Weight: %s", value) + player_node.MaxWEIGHT = value + + elif packet.is_type(SMSG_PLAYER_STAT_UPDATE_2): + stat_type = packet.read_int16() + value = packet.read_int32() + if stat_type == 0x0014: + logger.info("Money Changed from %s, to %s", player_node.MONEY, value) + player_node.MONEY = value + + elif packet.is_type(SMSG_BEING_MOVE) or packet.is_type(SMSG_BEING_VISIBLE)\ + or packet.is_type(SMSG_PLAYER_MOVE) or packet.is_type(SMSG_PLAYER_UPDATE_1)\ + or packet.is_type(SMSG_PLAYER_UPDATE_2): + being_id = packet.read_int32() + packet.skip(8) + job = packet.read_int16() + if being_id not in beingManager.container: + if job == 0 and id >= 110000000 and (packet.is_type(SMSG_BEING_MOVE)\ + or packet.is_type(SMSG_BEING_VISIBLE)): + continue + # Add the being to the BeingManager, and request name. + beingManager.container[being_id] = Being(being_id, job) + requestName = PacketOut(0x0094) + requestName.write_int32(being_id) + mapserv.sendall(str(requestName)) + + elif packet.is_type(SMSG_BEING_NAME_RESPONSE): + being_id = packet.read_int32() + if being_id in beingManager.container: + beingManager.container[being_id].name = packet.read_string(24) + + elif packet.is_type(SMSG_BEING_REMOVE): + being_id = packet.read_int32() + if being_id in beingManager.container: + del beingManager.container[being_id] + + elif packet.is_type(SMSG_PLAYER_WARP): + player_node.map = packet.read_string(16) + player_node.x = packet.read_int16() + player_node.y = packet.read_int16() + logger.info("Player warped: %s %s %s", player_node.map, player_node.x, player_node.y) + mapserv.sendall(str(PacketOut(CMSG_MAP_LOADED))) + + elif packet.is_type(SMSG_PLAYER_INVENTORY_ADD): + item = Item() + item.index = packet.read_int16() - inventory_offset + item.amount = packet.read_int16() + item.itemId = packet.read_int16() + packet.skip(14) + err = packet.read_int8() + + if err == 0: + if item.index in player_node.inventory: + player_node.inventory[item.index].amount += item.amount + else: + player_node.inventory[item.index] = item + + logger.info("Picked up: %s, Amount: %s, Index: %s", ItemDB.getItem(item.itemId).name, str(item.amount), str(item.index)) + + elif packet.is_type(SMSG_PLAYER_INVENTORY_REMOVE): + index = packet.read_int16() - inventory_offset + amount = packet.read_int16() + + logger.info("Remove item: %s, Amount: %s, Index: %s", ItemDB.getItem(player_node.inventory[index].itemId).name, str(amount), str(index)) + player_node.remove_item(index, amount) + + elif packet.is_type(SMSG_PLAYER_INVENTORY): + player_node.inventory.clear() # Clear the inventory - incase of new index. + packet.skip(2) + number = (len(packet.data)-2) / 18 + for loop in range(number): + item = Item() + item.index = packet.read_int16() - inventory_offset + item.itemId = packet.read_int16() + packet.skip(2) + item.amount = packet.read_int16() + packet.skip(10) + player_node.inventory[item.index] = item + + elif packet.is_type(SMSG_PLAYER_EQUIPMENT): + packet.read_int16() + number = (len(packet.data)) / 20 + for loop in range(number): + item = Item() + item.index = packet.read_int16() - inventory_offset + item.itemId = packet.read_int16() + packet.skip(16) + item.amount = 1 + player_node.inventory[item.index] = item + + logger.info("Inventory information received:") + for item in player_node.inventory: + logger.info("Name: %s, Id: %s, Index: %s, Amount: %s.", \ + ItemDB.getItem(player_node.inventory[item].itemId).name, \ + player_node.inventory[item].itemId, item, player_node.inventory[item].amount) + + errorOccured = player_node.check_inventory(user_tree, sale_tree) + if errorOccured: + logger.info(errorOccured) + shop_broadcaster.stop() + sys.exit(1) + else: + logger.info("Inventory Check Passed.") + + elif packet.is_type(SMSG_TRADE_REQUEST): + name = packet.read_string(24) + logger.info("Trade request: " + name) + mapserv.sendall(trade_respond(False)) + + elif packet.is_type(SMSG_TRADE_RESPONSE): + response = packet.read_int8() + time.sleep(0.2) + if response == 0: + logger.info("Trade response: Too far away.") + if trader_state.item: + mapserv.sendall(whisper(trader_state.item.player, "You are too far away.")) + elif trader_state.money: + mapserv.sendall(whisper(trader_state.money, "You are too far away.")) + trader_state.reset() + + elif response == 3: + logger.info("Trade response: Trade accepted.") + if trader_state.item: + if trader_state.item.get == 1: # add + mapserv.sendall(str(PacketOut(CMSG_TRADE_ADD_COMPLETE))) + elif trader_state.item.get == 0: # buy + if player_node.find_inventory_index(trader_state.item.id) != -10: + mapserv.sendall(trade_add_item(player_node.find_inventory_index(trader_state.item.id), trader_state.item.amount)) + mapserv.sendall(str(PacketOut(CMSG_TRADE_ADD_COMPLETE))) + if trader_state.item.price == 0: # getback + mapserv.sendall(str(PacketOut(CMSG_TRADE_OK))) + trader_state.complete = 1 + else: + mapserv.sendall(str(PacketOut(CMSG_TRADE_CANCEL_REQUEST))) + logger.info("Trade response: Trade accepted (buy) - the item could not be added.") + mapserv.sendall(whisper(trader_state.item.player, "Sorry, a problem has occured.")) + + elif trader_state.money: # money + amount = int(user_tree.get_user(trader_state.money).get('money')) + mapserv.sendall(trade_add_item(0-inventory_offset, amount)) + mapserv.sendall(str(PacketOut(CMSG_TRADE_ADD_COMPLETE))) + mapserv.sendall(str(PacketOut(CMSG_TRADE_OK))) + + else: + logger.info("Trade response: Trade cancelled") + trader_state.reset() + + elif packet.is_type(SMSG_TRADE_ITEM_ADD): + amount = packet.read_int32() + item_id = packet.read_int16() + if trader_state.item and trader_state.money == 0: + if trader_state.item.get == 1: # add + if amount == trader_state.item.amount and item_id == trader_state.item.id: + trader_state.complete = 1 + mapserv.sendall(str(PacketOut(CMSG_TRADE_OK))) + elif item_id == 0 and amount > 0: + mapserv.sendall(whisper(trader_state.item.player, "Why are you adding money?!?!")) + mapserv.sendall(str(PacketOut(CMSG_TRADE_CANCEL_REQUEST))) + else: + mapserv.sendall(whisper(trader_state.item.player, "Please check the correct item or quantity has been added.")) + mapserv.sendall(str(PacketOut(CMSG_TRADE_CANCEL_REQUEST))) + + elif trader_state.item.get == 0: # buy + if item_id == 0 and amount == trader_state.item.price * trader_state.item.amount: + mapserv.sendall(str(PacketOut(CMSG_TRADE_OK))) + trader_state.complete = 1 + elif item_id == 0 and amount != trader_state.item.price * trader_state.item.amount: + trader_state.complete = 0 + else: + mapserv.sendall(whisper(trader_state.item.player, "Don't give me your itenz.")) + mapserv.sendall(str(PacketOut(CMSG_TRADE_CANCEL_REQUEST))) + + elif trader_state.money: # money + mapserv.sendall(whisper(trader_state.money, "Don't give me your itenz.")) + mapserv.sendall(str(PacketOut(CMSG_TRADE_CANCEL_REQUEST))) + + logger.info("Trade item add: ItemId:%s Amount:%s", item_id, amount) + # Note item_id = 0 is money + + elif packet.is_type(SMSG_TRADE_ITEM_ADD_RESPONSE): + index = packet.read_int16() - inventory_offset + amount = packet.read_int16() + response = packet.read_int8() + + if response == 0: + logger.info("Trade item add response: Successfully added item.") + if trader_state.item: + if trader_state.item.get == 0 and index != 0-inventory_offset: # Make sure the correct item is given! + # index & amount are Always 0 + if player_node.inventory[index].itemId != trader_state.item.id or \ + amount != trader_state.item.amount: + logger.info("Index: %s" % index) + logger.info("P.ItemId: %s" % player_node.inventory[index].itemId) + logger.info("T.ItemId: %s" % trader_state.item.id) + logger.info("P.Amount: %s" % amount) + logger.info("T.Amount: %s" % trader_state.item.amount) + #mapserv.sendall(str(PacketOut(CMSG_TRADE_CANCEL_REQUEST))) + + # If Trade item add successful - Remove the item from the inventory state. + if index != 0-inventory_offset: # If it's not money + logger.info("Remove item: %s, Amount: %s, Index: %s", ItemDB.getItem(player_node.inventory[index].itemId).name, str(amount),str(index)) + player_node.remove_item(index, amount) + else: + # The money amount isn't actually sent by the server - odd?!?!?. + if trader_state.money: + logger.info("Trade: Money Added.") + trader_state.complete = 1 + + elif response == 1: + logger.info("Trade item add response: Failed - player overweight.") + mapserv.sendall(str(PacketOut(CMSG_TRADE_CANCEL_REQUEST))) + if trader_state.item: + mapserv.sendall(whisper(trader_state.item.player, "You are carrying too much weight. Unload and try again.")) + elif response == 2: + if trader_state.item: + mapserv.sendall(whisper(trader_state.item.player, "You have no free slots.")) + logger.info("Trade item add response: Failed - No free slots.") + mapserv.sendall(str(PacketOut(CMSG_TRADE_CANCEL_REQUEST))) + else: + logger.info("Trade item add response: Failed - unknown reason.") + mapserv.sendall(str(PacketOut(CMSG_TRADE_CANCEL_REQUEST))) + if trader_state.item: + mapserv.sendall(whisper(trader_state.item.player, "Sorry, a problem has occured.")) + + elif packet.is_type(SMSG_TRADE_OK): + is_ok = packet.read_int8() # 0 is ok from self, and 1 is ok from other + if is_ok == 0: + logger.info("Trade OK: Self.") + else: + if trader_state.complete: + mapserv.sendall(str(PacketOut(CMSG_TRADE_OK))) + else: + mapserv.sendall(str(PacketOut(CMSG_TRADE_CANCEL_REQUEST))) + if trader_state.item: + mapserv.sendall(whisper(trader_state.item.player, "Trade Cancelled: Please check the traded items or money.")) + + logger.info("Trade Ok: Partner.") + + elif packet.is_type(SMSG_TRADE_CANCEL): + trader_state.reset() + logger.info("Trade Cancel.") + + elif packet.is_type(SMSG_TRADE_COMPLETE): + commitMessage="" + # The sale_tree is only ammended after a complete trade packet. + if trader_state.item and trader_state.money == 0: + if trader_state.item.get == 1: # !add + sale_tree.add_item(trader_state.item.player, trader_state.item.id, trader_state.item.amount, trader_state.item.price) + user_tree.get_user(trader_state.item.player).set('used_stalls', \ + str(int(user_tree.get_user(trader_state.item.player).get('used_stalls')) + 1)) + user_tree.get_user(trader_state.item.player).set('last_use', str(time.time())) + commitMessage = "Add" + + elif trader_state.item.get == 0: # !buy \ !getback + seller = sale_tree.get_uid(trader_state.item.uid).get('name') + item = sale_tree.get_uid(trader_state.item.uid) + current_amount = int(item.get("amount")) + sale_tree.get_uid(trader_state.item.uid).set("amount", str(current_amount - trader_state.item.amount)) + if int(item.get("amount")) == 0: + user_tree.get_user(sale_tree.get_uid(trader_state.item.uid).get('name')).set('used_stalls', \ + str(int(user_tree.get_user(sale_tree.get_uid(trader_state.item.uid).get('name')).get('used_stalls'))-1)) + sale_tree.remove_item_uid(trader_state.item.uid) + + current_money = int(user_tree.get_user(seller).get("money")) + user_tree.get_user(seller).set("money", str(current_money + trader_state.item.price * trader_state.item.amount)) + + if trader_state.item.price * trader_state.item.amount != 0: + ItemLog.add_item(int(item.get('itemId')), trader_state.item.amount, trader_state.item.price * trader_state.item.amount, item.get('name')) + commitMessage = "Buy or Getback" + + elif trader_state.money and trader_state.item == 0: # !money + user_tree.get_user(trader_state.money).set('money', str(0)) + commitMessage = "Money" + + sale_tree.save() + user_tree.save() + tradey.saveData(commitMessage) + + trader_state.reset() + logger.info("Trade Complete.") + + errorOccured = player_node.check_inventory(user_tree, sale_tree) + if errorOccured: + logger.info(errorOccured) + shop_broadcaster.stop() + sys.exit(1) + else: + pass + + # On Disconnect/Exit + logger.info("Server disconnect.") + shop_broadcaster.stop() + mapserv.close() + +if __name__ == '__main__': + main() diff --git a/net/__init__.py b/net/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/net/packet.py b/net/packet.py new file mode 100644 index 0000000..16b4f79 --- /dev/null +++ b/net/packet.py @@ -0,0 +1,182 @@ + +"""The PacketBuffer class has been adapted from source originally released by gnufrk""" + +import struct + +packet_lengths = [ + 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +#0x0040 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 50, 3, -1, 55, 17, 3, 37, 46, -1, 23, -1, 3,108, 3, 2, + 3, 28, 19, 11, 3, -1, 9, 5, 54, 53, 58, 60, 41, 2, 6, 6, +#0x0080 + 7, 3, 2, 2, 2, 5, 16, 12, 10, 7, 29, 23, -1, -1, -1, 0, + 7, 22, 28, 2, 6, 30, -1, -1, 3, -1, -1, 5, 9, 17, 17, 6, + 23, 6, 6, -1, -1, -1, -1, 8, 7, 6, 7, 4, 7, 0, -1, 6, + 8, 8, 3, 3, -1, 6, 6, -1, 7, 6, 2, 5, 6, 44, 5, 3, +#0x00C0 + 7, 2, 6, 8, 6, 7, -1, -1, -1, -1, 3, 3, 6, 6, 2, 27, + 3, 4, 4, 2, -1, -1, 3, -1, 6, 14, 3, -1, 28, 29, -1, -1, + 30, 30, 26, 2, 6, 26, 3, 3, 8, 19, 5, 2, 3, 2, 2, 2, + 3, 2, 6, 8, 21, 8, 8, 2, 2, 26, 3, -1, 6, 27, 30, 10, +#0x0100 + 2, 6, 6, 30, 79, 31, 10, 10, -1, -1, 4, 6, 6, 2, 11, -1, + 10, 39, 4, 10, 31, 35, 10, 18, 2, 13, 15, 20, 68, 2, 3, 16, + 6, 14, -1, -1, 21, 8, 8, 8, 8, 8, 2, 2, 3, 4, 2, -1, + 6, 86, 6, -1, -1, 7, -1, 6, 3, 16, 4, 4, 4, 6, 24, 26, +#0x0140 + 22, 14, 6, 10, 23, 19, 6, 39, 8, 9, 6, 27, -1, 2, 6, 6, + 110, 6, -1, -1, -1, -1, -1, 6, -1, 54, 66, 54, 90, 42, 6, 42, + -1, -1, -1, -1, -1, 30, -1, 3, 14, 3, 30, 10, 43, 14,186,182, + 14, 30, 10, 3, -1, 6,106, -1, 4, 5, 4, -1, 6, 7, -1, -1, +#0x0180 + 6, 3,106, 10, 10, 34, 0, 6, 8, 4, 4, 4, 29, -1, 10, 6, + 90, 86, 24, 6, 30,102, 9, 4, 8, 4, 14, 10, 4, 6, 2, 6, + 3, 3, 35, 5, 11, 26, -1, 4, 4, 6, 10, 12, 6, -1, 4, 4, + 11, 7, -1, 67, 12, 18,114, 6, 3, 6, 26, 26, 26, 26, 2, 3, +#0x01C0 + 2, 14, 10, -1, 22, 22, 4, 2, 13, 97, 0, 9, 9, 29, 6, 28, + 8, 14, 10, 35, 6, 8, 4, 11, 54, 53, 60, 2, -1, 47, 33, 6, + 30, 8, 34, 14, 2, 6, 26, 2, 28, 81, 6, 10, 26, 2, -1, -1, + -1, -1, 20, 10, 32, 9, 34, 14, 2, 6, 48, 56, -1, 4, 5, 10, +#0x2000 + 26, 0, 0, 0, 18, 0, 0, 0, 0, 0, 0, 19, 10, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +] + +class PacketOut: + def __init__(self, out): + self.buff = "" + self.write_int16(out) + + def __str__(self): + return self.buff + + def write_string(self, string_val, length): + self.buff += string_val.ljust(length, '\0') + + def write_int8(self, value): + self.buff += struct.pack("> 8) % 256 + d_1 = (tmp) % 256 + tmp = y + tmp <<= 4 + d_1 |= (tmp >> 8) % 256 + d_2 = tmp % 256 + d_2 |= direction + self.buff += chr(d_0) + chr(d_1) + chr(d_2) + +class PacketIn: + def __init__(self, set_data, pkt_type): + self.data = set_data + self.pkttype = pkt_type + self.pos = 0 + + def is_type(self, pkt_type): + return self.pkttype == pkt_type + + def get_type(self): + return self.pkttype + + def read_string(self, length): + msg = self.data[self.pos:self.pos + length] + self.pos = self.pos + length + return msg[:msg.find('\0')] + + def read_raw_string(self, length): + msg = self.data[self.pos:self.pos + length] + self.pos = self.pos + length + return msg + + def read_int8(self): + int_value = struct.unpack("> 2) + dst_y = self.make_word(struct.unpack("> 6) + src_y = (self.make_word(struct.unpack("> 4) + self.pos = self.pos + 5 + return src_x, src_y, dst_x, dst_y + + def read_coord_dir(self): + cdata = self.data[self.pos:self.pos + 3] + x = (self.make_word(struct.unpack("> 6) % 255 + y = (self.make_word(struct.unpack("> 4) % 255 + dir = struct.unpack("= 4 + else: + pktlen = packet_lengths[pkttype] + + if len(self.buff) < pktlen: + raise StopIteration + + packet = PacketIn(self.buff[2:pktlen], pkttype) + self.buff = self.buff[pktlen:] + return packet diff --git a/net/packet_out.py b/net/packet_out.py new file mode 100644 index 0000000..1d6e2d2 --- /dev/null +++ b/net/packet_out.py @@ -0,0 +1,50 @@ +from packet import * +from protocol import * + +def emote(emoteId): + emote_packet = PacketOut(CMSG_PLAYER_EMOTE) + emote_packet.write_int8(emoteId) + return str(emote_packet) + +def whisper(nick, message): + whisp_packet = PacketOut(CMSG_CHAT_WHISPER) + whisp_packet.write_int16(len(message) + 28) + whisp_packet.write_string(nick, 24) + whisp_packet.write_string(message, len(message)) + return str(whisp_packet) + +def chat(text): + chat_packet = PacketOut(CMSG_CHAT_MESSAGE) + mes = player_node.name + " : " + text + chat_packet.write_int16(len(mes) + 4 + 1) + chat_packet.write_string(mes, len(mes) + 1) + return str(chat_packet) + +def sit(val): + sit_packet = PacketOut(CMSG_PLAYER_CHANGE_ACT) + sit_packet.write_int32(0) + if val == True: + sit_packet.write_int8(2) + else: + sit_packet.write_int8(3) + return str(sit_packet) + +def trade_request(being_id): + trade_req_packet = PacketOut(CMSG_TRADE_REQUEST) + trade_req_packet.write_int32(being_id) + return str(trade_req_packet) + +def trade_respond(accept): + trade_respond_packet = PacketOut(CMSG_TRADE_RESPONSE) + if accept == True: + trade_respond_packet.write_int8(3) + elif accept == False: + trade_respond_packet.write_int8(4) + return str(trade_respond_packet) + +def trade_add_item(item_index, amount): + trade_add_packet = PacketOut(CMSG_TRADE_ITEM_ADD_REQUEST) + trade_add_packet.write_int16(item_index + inventory_offset) + trade_add_packet.write_int32(amount) + return str(trade_add_packet) + diff --git a/net/protocol.py b/net/protocol.py new file mode 100644 index 0000000..4a4b74f --- /dev/null +++ b/net/protocol.py @@ -0,0 +1,64 @@ +SMSG_LOGIN_DATA = 0x0069 +SMSG_CHAR_LOGIN = 0x006b +SMSG_CHAR_MAP_INFO = 0x0071 +SMSG_MAP_LOGIN_SUCCESS = 0x0073 +SMSG_MAP_LOGIN_SUCCESS = 0x0073 +CMSG_CHAR_SERVER_CONNECT = 0x0065 +CMSG_CHAR_SELECT = 0x0066 +CMSG_MAP_SERVER_CONNECT = 0x0072 +CMSG_CHAT_WHISPER = 0x0096 +CMSG_CHAT_MESSAGE = 0x008c +CMSG_MAP_LOADED = 0x007d + +SMSG_WHISPER = 0x0097 +SMSG_BEING_CHAT = 0x008d + +SMSG_PLAYER_CHAT = 0x008e +CMSG_PLAYER_CHANGE_ACT = 0x0089 + +SMSG_PLAYER_INVENTORY = 0x01ee +SMSG_PLAYER_INVENTORY_ADD = 0x00a0 +SMSG_PLAYER_INVENTORY_REMOVE = 0x00af +SMSG_PLAYER_EQUIPMENT = 0x00a4 +SMSG_PLAYER_STAT_UPDATE_1 = 0x00b0 +SMSG_PLAYER_STAT_UPDATE_2 = 0x00b1 + +SMSG_BEING_VISIBLE = 0x0078 +SMSG_BEING_MOVE = 0x007b +SMSG_BEING_REMOVE = 0x0080 +SMSG_PLAYER_MOVE = 0x01da +SMSG_PLAYER_WARP = 0x0091 +SMSG_PLAYER_UPDATE_1 = 0x01d8 +SMSG_PLAYER_UPDATE_2 = 0x01d9 +SMSG_BEING_NAME_RESPONSE = 0x0095 # Has to be requested +SMSG_BEING_ACTION = 0x008a + +CMSG_ITEM_PICKUP = 0x009f +CMSG_PLAYER_ATTACK = 0x0089 +CMSG_PLAYER_STOP_ATTACK = 0x0118 +CMSG_PLAYER_CHANGE_DIR = 0x009b +CMSG_PLAYER_CHANGE_DEST = 0x0085 +CMSG_PLAYER_EMOTE = 0x00bf +SMSG_WALK_RESPONSE = 0x0087 + +CMSG_TRADE_REQUEST = 0x00e4 +CMSG_TRADE_RESPONSE = 0x00e6 +CMSG_TRADE_ITEM_ADD_REQUEST = 0x00e8 +CMSG_TRADE_CANCEL_REQUEST = 0x00ed +CMSG_TRADE_ADD_COMPLETE = 0x00eb +CMSG_TRADE_OK = 0x00ef + +SMSG_TRADE_REQUEST = 0x00e5 #/**< Receiving a request to trade */ +SMSG_TRADE_RESPONSE = 0x00e7 +SMSG_TRADE_ITEM_ADD = 0x00e9 +SMSG_TRADE_ITEM_ADD_RESPONSE = 0x01b1 #/**< Not standard eAthena! */ +SMSG_TRADE_OK = 0x00ec +SMSG_TRADE_CANCEL = 0x00ee +SMSG_TRADE_COMPLETE = 0x00f0 + +SMSG_ITEM_VISIBLE = 0x009d +SMSG_ITEM_DROPPED = 0x009e +SMSG_ITEM_REMOVE = 0x00a1 + +inventory_offset = 2 +storage_offset = 1 diff --git a/player.py b/player.py new file mode 100644 index 0000000..ed80f66 --- /dev/null +++ b/player.py @@ -0,0 +1,70 @@ +#!/usr/bin/python +""" + Copyright 2011, Dipesh Amin + Copyright 2011, Stefan Beller + + This file is part of tradey, a trading bot in the mana world + see www.themanaworld.org +""" + +import copy + +class Item: + pass + +class Player: + def __init__(self, name): + self.inventory = {} + self.name = name + self.id = 0 + self.sex = 0 + self.map = "" + self.x = 0 + self.y = 0 + + self.EXP = 0 + self.MONEY = 0 + self.WEIGHT = 0 + self.MaxWEIGHT = 0 + + def find_inventory_index(self, item_id): + for item in self.inventory: + if item > 1: + if self.inventory[item].itemId == item_id: + return item + return -10 # Not found - bug somewhere! + + def remove_item(self, index, amount): + if index in self.inventory: + self.inventory[index].amount -= amount + if self.inventory[index].amount == 0: + del self.inventory[index] + + def check_inventory(self, user_tree, sale_tree): + # Check the inventory state. + test_node = copy.deepcopy(self.inventory) + for elem in sale_tree.root: + item_found = False + for item in test_node: + if int(elem.get('itemId')) == test_node[item].itemId \ + and int(elem.get('amount')) <= test_node[item].amount: + test_node[item].amount -= int(elem.get('amount')) + if test_node[item].amount == 0: + del test_node[item] + item_found = True + break + + if not item_found: + return "Server and client inventory out of sync." + + total_money = 0 + for user in user_tree.root: + total_money += int(user.get('money')) + + if total_money != self.MONEY: + return "Server and client money out of sync. Market: %s Player %s" % (total_money, self.MONEY) + + return 0 + +if __name__ == '__main__': + print "Do not run this file directly. Run main.py" diff --git a/start.sh b/start.sh new file mode 100755 index 0000000..830eece --- /dev/null +++ b/start.sh @@ -0,0 +1,18 @@ +#!/bin/bash +MM_ACTIVE=1 + +while [ $MM_ACTIVE != 0 ] +do + echo "Starting tradey...." + python2.7 main.py >> error_log.txt + + if [ "$?" != "0" ]; then + # Inventory/Money out of sync + echo "tradey quit with error at:" + date + MM_ACTIVE=0 + else + echo "Tradey quit normally, sleeping 60 seconds before a restart" + sleep 60 + fi +done diff --git a/stats/process_salelog/Readme b/stats/process_salelog/Readme new file mode 100644 index 0000000..6b80ea7 --- /dev/null +++ b/stats/process_salelog/Readme @@ -0,0 +1 @@ +Usage: python main.py sales.log diff --git a/stats/process_salelog/main.py b/stats/process_salelog/main.py new file mode 100755 index 0000000..4ad2dd3 --- /dev/null +++ b/stats/process_salelog/main.py @@ -0,0 +1,59 @@ +#!/usr/bin/python +""" + + Copyright 2011, Dipesh Amin + Copyright 2011, Stefan Beller + + tradey, a package, which implements an Automated Market Bot for "The Mana World" a 2D MMORPG. + +""" + +import logging +import socket +import sys +import time +import string +import utils +import locale + +ItemDB = utils.ItemDB() + +def main(): + in_file = sys.argv[1] + out_file = sys.argv[2] + locale.setlocale(locale.LC_ALL, '') + + out_obj = open(out_file, 'w') + in_obj = open(in_file, 'r') + + out_obj.write(' \n') + out_obj.write(' \n') + out_obj.write(' \n') + out_obj.write('ManaMarket Sales \n') + out_obj.write('

ManaMarket Sales

') + out_obj.write(' \n') + out_obj.write('\n') + + sales = in_obj.readlines() + + sale_total = 0 + items_sold = 0 + for line in sales: + line = line.split() + t_time = time.gmtime(float(line[3])) + unit_price = int(line[2])/int(line[1]) + out_obj.write('\n') + sale_total += int(line[2]) + items_sold += int(line[1]) + + out_obj.write('
Item Name Amount Price Time
'+ItemDB.getItem(int(line[0])).name+' '+locale.format("%d", int(line[1]), grouping=True)+' '+locale.format("%d", unit_price, grouping=True)+' '+time.asctime(t_time)+'
\n') + out_obj.write('
Total sales: '+str(sale_total)+' GP
') + out_obj.write('Total number of items sold: '+str(items_sold)+'
') + out_obj.write('Updated: '+time.asctime(time.gmtime())) + out_obj.write(' \n') + in_obj.close() + out_obj.close() + + +if __name__ == '__main__': + main() diff --git a/stats/process_salelog/main_stat.py b/stats/process_salelog/main_stat.py new file mode 100755 index 0000000..9ff71d5 --- /dev/null +++ b/stats/process_salelog/main_stat.py @@ -0,0 +1,151 @@ +#!/usr/bin/python +""" + + Copyright 2011, Dipesh Amin + Copyright 2011, Stefan Beller + + tradey, a package, which implements an Automated Market Bot for "The Mana World" a 2D MMORPG. + +""" + +import logging +import socket +import sys +import time +import string +import utils +import locale + +class SaleStat: + pass + +ItemDB = utils.ItemDB() + +def main(): + in_file = sys.argv[1] + out_file = sys.argv[2] + locale.setlocale(locale.LC_ALL, '') + + out_obj = open(out_file, 'w') + in_obj = open(in_file, 'r') + + out_obj.write(' \n \ + \n \ + ManaMarket Statistics \n \ + \n \ + \n \ + \n \ +

ManaMarket Statistics

\n \ + \n \ + \n \ + \n \ + \n \ + \n \ + \n \ + \n \ + \n \ + \n \ + \n \ + \n \ + \n \ + \n \ + \n ') + + sales = in_obj.readlines() + + sale_dict = {} + + # Put sales date into a dict. + sale_total = 0 + for line in sales: + line = line.split() + item_id = int(line[0]) + name = ItemDB.getItem(int(line[0])).name + amount = int(line[1]) + price = int(line[2]) + t_time = float(line[3]) + sale_total += int(line[2]) + + if item_id not in sale_dict: + sale_dict[item_id] = [] + + sale_dict[item_id].append([amount, price, t_time]) + + str_list = [] + # calculate the stats + for item in sale_dict: + average_week = 0 + average_week_amount = 0 + average_month = 0 + average_month_amount = 0 + average_all_time = 0 + average_all_time_amount = 0 + min_price = 0 + max_price = 0 + last_sold = 0 + + for n in range(len(sale_dict[item])): + + #out_obj.write('') + + if min_price > sale_dict[item][n][1]/sale_dict[item][n][0] or min_price == 0: + min_price = sale_dict[item][n][1]/sale_dict[item][n][0] + + if max_price < sale_dict[item][n][1]/sale_dict[item][n][0] or max_price == 0: + max_price = sale_dict[item][n][1]/sale_dict[item][n][0] + + if last_sold < sale_dict[item][n][2] or last_sold == 0: + last_sold = sale_dict[item][n][2] + + if (time.time()-sale_dict[item][n][2]) < 7*24*60*60: + average_week_amount += sale_dict[item][n][0] + average_week += sale_dict[item][n][1] + + if (time.time()-sale_dict[item][n][2]) < 30*24*60*60: + average_month_amount += sale_dict[item][n][0] + average_month += sale_dict[item][n][1] + + + average_all_time_amount += sale_dict[item][n][0] + average_all_time += sale_dict[item][n][1] + + average_all_time /= average_all_time_amount + if average_week_amount > 0: + average_week /= average_week_amount + if average_month_amount > 0: + average_month /= average_month_amount + + str_list.append([average_all_time_amount,''+''+ \ + ''+''+''+ ''+''+'']) + + while len(str_list) > 0: + pos = 0 + max_amount = 0 + for m in range(len(str_list)): + if max_amount < str_list[m][0] or max_amount == 0: + max_amount = str_list[m][0] + pos = m + out_obj.write('') + out_obj.write(str_list[pos][1]) + str_list.pop(pos) + + + out_obj.write(' \n') + + out_obj.write('
Item Name Total Amount SoldPriceLast Sold
MinMaxAverage (Week)Average (Month)Average (Overall)
'+ItemDB.getItem(item).name+''+locale.format("%d", average_all_time_amount, grouping=True)+''+ locale.format("%d", min_price, grouping=True) +''+ locale.format("%d", max_price, grouping=True) +''+ \ + locale.format("%d", average_week, grouping=True) +''+ locale.format("%d", average_month, grouping=True) + \ + ''+ locale.format("%d", average_all_time, grouping=True)+''+ time.asctime(time.gmtime(last_sold)) +'
\n') + out_obj.write('
Total sales: '+str(sale_total)+' GP
') + out_obj.write('Updated: '+time.asctime(time.gmtime())+'
') + out_obj.write('For a detailed page showing all sales '+'click here') + + out_obj.write(' \n') + in_obj.close() + out_obj.close() + + +if __name__ == '__main__': + main() diff --git a/stats/process_salelog/utils.py b/stats/process_salelog/utils.py new file mode 100644 index 0000000..fd4e432 --- /dev/null +++ b/stats/process_salelog/utils.py @@ -0,0 +1,82 @@ +#!/usr/bin/python +""" + Copyright 2011, Dipesh Amin + Copyright 2011, Stefan Beller + + This file is part of tradey, a trading bot in the mana world + see www.themanaworld.org +""" +from xml.etree.ElementTree import ElementTree +import time + +class Item: + pass + +# Process a recieved ip address. +def parse_ip(a): + return "%s.%s.%s.%s" % ((a % 256),((a >> 8) % 256),((a >> 16) % 256),((a >> 24) % 256)) + +# Remove colors from a message +def remove_colors(msg): + if len(msg) > 2: + for f in range(len(msg)-2): + while (len(msg) > f + 2) and (msg[f] == "#")\ + and (msg[f+1] == "#"): + msg = msg[0:f]+msg[f+3:] + return msg + +# Encode string - used with 4144 shop compatibility. +def encode_str(value, size): + output = '' + base = 94 + start = 33 + while value: + output += chr(value % base + start) + value /= base + + while len(output) < size: + output += chr(start) + + return output + +class ItemDB: + """ + A simple class to look up information from the items.xml file. + """ + def __init__(self): + print "Loading ItemDB" + self.item_names = {} + self.itemdb_file = ElementTree(file="../data/items.xml") + + for item in self.itemdb_file.getroot(): + if item.get('id') > 500: + item_struct = Item() + item_struct.name = item.get('name') + if item.get('weight'): + item_struct.weight = item.get('weight') + if item.get('type'): + item_struct.type = item.get('type') + item_struct.description = item.get('description') + self.item_names[int(item.get('id'))] = item_struct + + def getItem(self, item_id): + return self.item_names[item_id] + + def findId(self, name): + for item_id in self.item_names: + if self.item_names[item_id].name == name: + return item_id + return -10 #Not found + +class ItemLog: + """ Writes all sales to a log file, for later processing.""" + def __init__(self): + self.log_file = 'data/logs/sale.log' + + def add_item(self, item_id, amount, price): + file_node = open(self.log_file, 'a') + file_node.write(str(item_id)+" "+str(amount)+" "+str(price)+" "+str(time.time())+"\n") + file_node.close() + +if __name__ == '__main__': + print "Do not run this file directly. Run main.py" diff --git a/stats/update_sales.sh b/stats/update_sales.sh new file mode 100755 index 0000000..62a4e1e --- /dev/null +++ b/stats/update_sales.sh @@ -0,0 +1,5 @@ +#!/bin/bash +cd /home/tmwserver/ManaMarket/stats +python /home/tmwserver/ManaMarket/stats/process_salelog/main.py /home/tmwserver/ManaMarket/data/logs/sale.log /var/www/tmw-homepage/server/manamarket.html +python /home/tmwserver/ManaMarket/stats/process_salelog/main_stat.py /home/tmwserver/ManaMarket/data/logs/sale.log /var/www/tmw-homepage/server/manamarket_stats.html +chmod 644 /var/www/tmw-homepage/server/manamarket.html /var/www/tmw-homepage/server/manamarket_stats.html diff --git a/tradey.py b/tradey.py new file mode 100644 index 0000000..01f855e --- /dev/null +++ b/tradey.py @@ -0,0 +1,122 @@ +#!/usr/bin/python +""" + Copyright 2011, Dipesh Amin + Copyright 2011, Stefan Beller + + This file is part of tradey, a trading bot in the mana world + see www.themanaworld.org +""" +import time +import os +import xml.dom.minidom +from subprocess import call +from xml.etree.ElementTree import * + +def clean_xml(parse): + data = '' + pos_start = 0 + while parse.find('<', pos_start) != -1: + pos_start = parse.find('<', pos_start) + pos_end = parse.find('>', pos_start+1) + data += parse[pos_start:pos_end+1] + pos_start = pos_end + return data + +class UserTree: + def __init__(self): + self.tree = ElementTree(file="data/user.xml") + self.root = self.tree.getroot() + + def add_user(self, name, stalls, al): + if self.get_user(name) == -10: + user = SubElement(self.root, "user") + user.set("name", name) + user.set("stalls", str(stalls)) + user.set("used_stalls", str(0)) + user.set("money", str(0)) + user.set("id", str(0)) + user.set("accesslevel", str(al)) + self.save() + + def get_user(self, name): + for elem in self.root: + if elem.get("name") == name: + return elem + return -10 + + def remove_user(self, name): + for elem in self.root: + if elem.get("name") == name: + self.root.remove(elem) + self.save() + return 1 + return -10 + + def save(self): + # Be sure to call save() after any changes to the tree. + f = open('data/user.xml', 'w') + dom = xml.dom.minidom.parseString(clean_xml(tostring(self.root))) + f.write(dom.toprettyxml(' ')) + f.close() + +class ItemTree: + def __init__(self): + self.tree = ElementTree(file="data/sale.xml") + self.root = self.tree.getroot() + self.u_id = set() + + for elem in self.root: + self.u_id.add(int(elem.get("uid"))) + + def getId(self): + id_itter = 1 + while id_itter in self.u_id: + id_itter += 1 + self.u_id.add(id_itter) + return id_itter + + def remove_id(self, uid): + # Free up used id's. + self.u_id.remove(uid) + + def add_item(self, name, item_id, amount, price): + user = SubElement(self.root, "item") + user.set("name", name) + user.set("itemId", str(item_id)) + user.set("price", str(price)) + user.set("add_time", str(time.time())) + user.set("relisted", str(0)) + user.set("amount", str(amount)) + user.set("uid", str(self.getId())) + self.save() + + def get_uid(self, uid): + for elem in self.root: + if elem.get("uid") == str(uid): + return elem + return -10 + + def remove_item_uid(self, uid): + for elem in self.root: + if elem.get("uid") == str(uid): + self.root.remove(elem) + self.remove_id(uid) + self.save() + return 1 + return -10 + + def save(self): + # Be sure to call save() after any changes to the tree. + f = open('data/sale.xml', 'w') + dom = xml.dom.minidom.parseString(clean_xml(tostring(self.root))) + f.write(dom.toprettyxml(' ')) + f.close() + +def saveData(commitmessage = "commit"): + # This assumes the current working directory is the tradey directory. + os.chdir("data") + call(["git", "commit","-a", '-m "' + commitmessage + '"']) + os.chdir("..") + +if __name__ == '__main__': + print "Do not run this file directly. Run main.py" diff --git a/utils.py b/utils.py new file mode 100644 index 0000000..07c4451 --- /dev/null +++ b/utils.py @@ -0,0 +1,132 @@ +#!/usr/bin/python +""" + Copyright 2011, Dipesh Amin + Copyright 2011, Stefan Beller + + This file is part of tradey, a trading bot in the mana world + see www.themanaworld.org +""" +from xml.etree.ElementTree import ElementTree +from player import Item + +import time +import mutex +import threading +from net.packet_out import * + +allowed_chars = "abcdefghijklmnoprstquvwxyzABCDEFGHIJKLMNOPRSTQUVWXYZ1234567890-_+=!@$%^&*();'<>,.?/~`| " + +# Process a recieved ip address. +def parse_ip(a): + return "%s.%s.%s.%s" % ((a % 256),((a >> 8) % 256),((a >> 16) % 256),((a >> 24) % 256)) + +# Remove colors from a message +def remove_colors(msg): + if len(msg) > 2: + for f in range(len(msg)-2): + while (len(msg) > f + 2) and (msg[f] == "#")\ + and (msg[f+1] == "#"): + msg = msg[0:f]+msg[f+3:] + return msg + +# Encode string - used with 4144 shop compatibility. +def encode_str(value, size): + output = '' + base = 94 + start = 33 + while value: + output += chr(value % base + start) + value /= base + + while len(output) < size: + output += chr(start) + + return output + +class ItemDB: + """ + A simple class to look up information from the items.xml file. + """ + def __init__(self): + print "Loading ItemDB" + self.item_names = {} + self.itemdb_file = ElementTree(file="data/items.xml") + + for item in self.itemdb_file.getroot(): + if item.get('id') > 500: + item_struct = Item() + item_struct.name = item.get('name') + if item.get('weight'): + item_struct.weight = int(item.get('weight')) + else: + item_struct.weight = 0 + + if item.get('type'): + item_struct.type = item.get('type') + item_struct.description = item.get('description') + self.item_names[int(item.get('id'))] = item_struct + + def getItem(self, item_id): + return self.item_names[item_id] + + def findId(self, name): + for item_id in self.item_names: + if self.item_names[item_id].name == name: + return item_id + return -10 #Not found + +class ItemLog: + """ Writes all sales to a log file, for later processing.""" + def __init__(self): + self.log_file = 'data/logs/sale.log' + + def add_item(self, item_id, amount, price, name): + file_node = open(self.log_file, 'a') + file_node.write(str(item_id)+" "+str(amount)+" "+str(price)+" "+str(time.time())+" "+name+"\n") + file_node.close() + +class TraderState: + """ Stores information regarding a trade request""" + def __init__(self): + self.Trading = mutex.mutex() + self.item = 0 + self.money = 0 + self.complete = 0 + self.timer = 0 + + def reset(self): + self.Trading.unlock() + self.item = 0 + self.complete = 0 + self.money = 0 + self.timer = 0 + +class Broadcast: + """Send a message to the server every 5 minutes to avoid a timeout.""" + + def __init__(self): + self.mapserv = 0 + self.Active = False + self.Timer = 0 + self.shop_broadcast = threading.Thread(target=self.send_broadcast, args=()) + + def send_broadcast(self): + while self.Active: + if (time.time() - self.Timer) > 60: + self.mapserv.sendall(emote(193)) + self.Timer = time.time() + #print "shop_broadcast" + else: + time.sleep(0.1) + + def start(self): + self.Active = True + self.shop_broadcast.start() + + def stop(self): + if self.Active: + self.Active = False + self.shop_broadcast.join() + +if __name__ == '__main__': + print "Do not run this file directly. Run main.py" -- cgit v1.2.3-70-g09d2