diff options
author | Livio Recchia <recchialivio@libero.it> | 2020-02-10 23:06:34 +0100 |
---|---|---|
committer | Livio Recchia <recchialivio@libero.it> | 2020-02-10 23:06:34 +0100 |
commit | 9a13903a2f7d3a65fdf15a65fb59cccd622e2066 (patch) | |
tree | 9403b7dff39eb5e5d7fa0f79efb69b496add4c4b /plugins | |
parent | 11cc316b74d5f3f283413a33e7693b314741aa4a (diff) | |
download | manachat-9a13903a2f7d3a65fdf15a65fb59cccd622e2066.tar.gz manachat-9a13903a2f7d3a65fdf15a65fb59cccd622e2066.tar.bz2 manachat-9a13903a2f7d3a65fdf15a65fb59cccd622e2066.tar.xz manachat-9a13903a2f7d3a65fdf15a65fb59cccd622e2066.zip |
Initial commit
Diffstat (limited to 'plugins')
33 files changed, 3782 insertions, 0 deletions
diff --git a/plugins/README.txt b/plugins/README.txt new file mode 100644 index 0000000..8324b3d --- /dev/null +++ b/plugins/README.txt @@ -0,0 +1,23 @@ +This directory contains plugins for ManaChat. + +To autoload the plugin, in the [Plugins] section of manachat.ini add + +[Plugins] +... +pluginname = 1 +... + +The plugin and it's dependencies will be autoloaded. +The plugin must export a variable PLUGIN, and the function init() + +PLUGIN = { + 'name' : 'PluginName' # not really used atm + 'requires' : (plugin1, plugin2, ...) # list of required plugins + 'blocks' : (plugin3, plugin4, ...) # list of incompatible plugins +} + +def init(config): # config is ConfigParser instance + # ... plugin initialisation code ... + pass + +See 'shop.py' as an example. diff --git a/plugins/__init__.py b/plugins/__init__.py new file mode 100644 index 0000000..0e40be9 --- /dev/null +++ b/plugins/__init__.py @@ -0,0 +1,51 @@ +import logging +import sys + +sys.path.insert(0, "plugins") +debuglog = logging.getLogger("ManaChat.Debug") + +plugins_loaded = [] + + +class PluginError(Exception): + pass + + +def load_plugin(config, plugin_name): + if plugin_name in plugins_loaded: + return + + plugin = __import__(plugin_name) + + for p in plugin.PLUGIN['blocks']: + if p in plugins_loaded: + raise PluginError("{} blocks {}".format(p, plugin_name)) + + for p in plugin.PLUGIN['requires']: + if p not in plugins_loaded: + load_plugin(config, p) + + this = sys.modules[__name__] + setattr(this, plugin_name, plugin) + + # filling the gaps in config + if not config.has_section(plugin_name): + config.add_section(plugin_name) + + default_config = plugin.PLUGIN.setdefault('default_config', {}) + for option, value in default_config.iteritems(): + if not config.has_option(plugin_name, option): + config.set(plugin_name, option, str(value)) + + plugin.init(config) + plugins_loaded.append(plugin_name) + debuglog.info('Plugin %s loaded', plugin_name) + + +def load_plugins(config): + for pn in config.options('Plugins'): + if config.getboolean('Plugins', pn): + try: + load_plugin(config, pn) + except ImportError as e: + debuglog.error('Error loading plugin %s: %s', pn, e) diff --git a/plugins/__init__.pyc b/plugins/__init__.pyc Binary files differnew file mode 100644 index 0000000..4469184 --- /dev/null +++ b/plugins/__init__.pyc diff --git a/plugins/autofollow.py b/plugins/autofollow.py new file mode 100644 index 0000000..ff34bde --- /dev/null +++ b/plugins/autofollow.py @@ -0,0 +1,39 @@ +import net.mapserv as mapserv +import commands +from utils import extends +from loggers import debuglog + + +__all__ = [ 'PLUGIN', 'init', 'follow' ] + + +PLUGIN = { + 'name': 'autofollow', + 'requires': (), + 'blocks': (), +} + +follow = '' + + +@extends('smsg_player_move') +def player_move(data): + if follow: + b = mapserv.beings_cache[data.id] + if b.name == follow: + mapserv.cmsg_player_change_dest(data.coor_pair.dst_x, + data.coor_pair.dst_y) + + +def follow_cmd(_, player): + '''Follow given player, or disable following (if no arg)''' + global follow + follow = player + if player: + debuglog.info('Following %s', player) + else: + debuglog.info('Not following anyone') + + +def init(config): + commands.commands['follow'] = follow_cmd diff --git a/plugins/autofollow.pyc b/plugins/autofollow.pyc Binary files differnew file mode 100644 index 0000000..159fe47 --- /dev/null +++ b/plugins/autofollow.pyc diff --git a/plugins/autospell.py b/plugins/autospell.py new file mode 100644 index 0000000..30d0ec6 --- /dev/null +++ b/plugins/autospell.py @@ -0,0 +1,57 @@ +import re +import time +import commands +import net.mapserv as mapserv +from loggers import debuglog +from logicmanager import logic_manager + +__all__ = [ 'PLUGIN', 'init' ] + +PLUGIN = { + 'name': 'autospell', + 'requires': (), + 'blocks': (), + 'default_config' : {} +} + +as_re = re.compile(r'(\d+) (\d+) (.+)') +times = 0 +delay = 0 +spell = '' +next_ts = 0 + + +def cmd_autospell(_, arg): + '''Cast a spell multiple times automatically +/autospell TIMES DELAY SPELL''' + global times, delay, spell, next_ts + m = as_re.match(arg) + if m: + times = int(m.group(1)) + delay = int(m.group(2)) + spell = m.group(3) + else: + debuglog.warning("Usage: /autospell TIMES DELAY SPELL") + return + + next_ts = time.time() + delay + + +def cmd_stopspell(*unused): + '''Stop casting autospells''' + global times, delay, spell, next_ts + times = delay = next_ts = 0 + + +def autospell_logic(ts): + global times, next_ts + if times > 0 and ts >= next_ts: + times -= 1 + next_ts = ts + delay + mapserv.cmsg_chat_message(spell) + + +def init(config): + commands.commands['autospell'] = cmd_autospell + commands.commands['stopspell'] = cmd_stopspell + logic_manager.add_logic(autospell_logic) diff --git a/plugins/autospell.pyc b/plugins/autospell.pyc Binary files differnew file mode 100644 index 0000000..362aab6 --- /dev/null +++ b/plugins/autospell.pyc diff --git a/plugins/battlebot.py b/plugins/battlebot.py new file mode 100644 index 0000000..5cba467 --- /dev/null +++ b/plugins/battlebot.py @@ -0,0 +1,227 @@ +import time +import net.mapserv as mapserv +import net.charserv as charserv +import net.stats as stats +import walkto +import logicmanager +import commands +from net.common import distance +from net.inventory import get_item_index +from utils import extends +from loggers import debuglog +from actor import find_nearest_being + + +__all__ = [ 'PLUGIN', 'init', + 'hp_healing_ids', 'hp_heal_at', 'mp_healing_ids', 'mp_heal_at', + 'auto_attack', 'auto_pickup', 'auto_heal_self', + 'auto_heal_others' ] + + +PLUGIN = { + 'name': 'battlebot', + 'requires': (), + 'blocks': (), +} + +target = None +# last_time_attacked = 0 +aa_next_time = 0 + +hp_healing_ids = [ 535, 541 ] +hp_heal_at = 0.3 +hp_is_healing = False +hp_prev_value = 0 + +mp_healing_ids = [ 826 ] +mp_heal_at = 0.5 +mp_is_healing = False +mp_prev_value = 0 + +players_taken_damage = {} +player_damage_heal = 300 + +aa_monster_types = [] + +auto_pickup = True +auto_attack = False +auto_heal_self = False +auto_heal_others = False + + +@extends('smsg_being_action') +def being_action(data): + # global last_time_attacked + global aa_next_time + + if data.type in (0, 10): + + if data.src_id == charserv.server.account: + # last_time_attacked = time.time() + aa_next_time = time.time() + 5.0 + + if (auto_heal_others and + data.dst_id != charserv.server.account and + data.dst_id in mapserv.beings_cache and + mapserv.beings_cache[data.dst_id].type == 'player'): + + players_taken_damage[data.dst_id] = players_taken_damage.get( + data.dst_id, 0) + data.damage + + if players_taken_damage[data.dst_id] >= player_damage_heal: + mapserv.cmsg_chat_message("#inma {}".format( + mapserv.beings_cache[data.dst_id].name)) + players_taken_damage[data.dst_id] = 0 + + +@extends('smsg_item_dropped') +@extends('smsg_item_visible') +def flooritem_appears(data): + if not auto_pickup: + return + + item = mapserv.floor_items[data.id] + px = mapserv.player_pos['x'] + py = mapserv.player_pos['y'] + + if distance(px, py, item.x, item.y) > 3: + return + + walkto.walkto_and_action(item, 'pickup') + + +@extends('smsg_player_status_change') +def player_status_change(data): + global hp_is_healing + if data.id == charserv.server.account: + if data.effect == 256: + hp_is_healing = True + elif data.effect == 0: + hp_is_healing = False + + +@extends('smsg_player_stat_update_x') +def player_stat_update(data): + if not auto_heal_self: + return + + global hp_prev_value, mp_prev_value + + if data.type == stats.HP: + max_hp = mapserv.player_stats.get(stats.MAX_HP, 0) + if data.stat_value < max_hp * hp_heal_at and not hp_is_healing: + healing_found = False + for item_id in hp_healing_ids: + index = get_item_index(item_id) + if index > 0: + healing_found = True + debuglog.info("Consuming %d", item_id) + mapserv.cmsg_player_inventory_use(index, item_id) + break + if not healing_found: + debuglog.info("Low health, but no HP healing item found") + + hp_prev_value = data.stat_value + + elif data.type == stats.MP: + max_mp = mapserv.player_stats.get(stats.MAX_MP, 0) + if data.stat_value < max_mp * mp_heal_at and not mp_is_healing: + healing_found = False + for item_id in mp_healing_ids: + index = get_item_index(item_id) + if index > 0: + healing_found = True + debuglog.info("Consuming %d", item_id) + mapserv.cmsg_player_inventory_use(index, item_id) + break + + if not healing_found: + debuglog.info("Low mana, but no MP healing item found") + + mp_prev_value = data.stat_value + + +@extends('smsg_being_remove') +def being_remove(data): + global target + if target is not None and target.id == data.id: + target = None + aa_next_time = time.time() + 5.0 + + +def battlebot_logic(ts): + + if not auto_attack: + return + + global target + # global last_time_attacked + global aa_next_time + + if ts < aa_next_time: + return + + if target is None: + if walkto.state: + return + + target = find_nearest_being(type='monster', + ignored_ids=walkto.unreachable_ids, + allowed_jobs=aa_monster_types) + if target is not None: + # last_time_attacked = time.time() + aa_next_time = time.time() + 5.0 + walkto.walkto_and_action(target, 'attack') + + elif ts > aa_next_time: + walkto.walkto_and_action(target, 'attack') + + +def startbot(_, arg): + '''Start autoattacking and autolooting''' + global auto_attack + global auto_pickup + global aa_monster_types + auto_attack = True + auto_pickup = True + try: + aa_monster_types = map(int, arg.split()) + except ValueError: + aa_monster_types = [] + + +def stopbot(cmd, _): + '''Stop battlebot''' + global auto_attack + global auto_pickup + global auto_heal_self + global auto_heal_others + global target + auto_attack = False + auto_pickup = False + auto_heal_self = False + auto_heal_others = False + if target is not None: + mapserv.cmsg_player_stop_attack() + target = None + + +def debugbot(cmd, _): + px = mapserv.player_pos['x'] + py = mapserv.player_pos['y'] + target_info = '<no_target>' + if target is not None: + target_info = '{} at ({},{})'.format(target.name, target.x, target.y) + debuglog.info('target = %s | player at (%d, %d)', target_info, px, py) + + +bot_commands = { + 'startbot' : startbot, + 'stopbot' : stopbot, + 'debugbot' : debugbot, +} + + +def init(config): + logicmanager.logic_manager.add_logic(battlebot_logic) + commands.commands.update(bot_commands) diff --git a/plugins/battlebot.pyc b/plugins/battlebot.pyc Binary files differnew file mode 100644 index 0000000..c48b64d --- /dev/null +++ b/plugins/battlebot.pyc diff --git a/plugins/chatbot.py b/plugins/chatbot.py new file mode 100644 index 0000000..e26928d --- /dev/null +++ b/plugins/chatbot.py @@ -0,0 +1,71 @@ +import re +import random +import types +import net.mapserv as mapserv +from utils import extends +import chat + + +__all__ = [ 'PLUGIN', 'init', 'answer', 'add_command', 'remove_command' ] + + +PLUGIN = { + 'name': 'chatbot', + 'requires': (), + 'blocks': (), +} + +commands = {} + + +def answer_info(nick, message, is_whisper, match): + if is_whisper: + chat.send_whisper(nick, "This is an experimental bot.") + +def answer_random(nick, message, is_whisper, answers): + resp = random.choice(answers) + if is_whisper: + chat.send_whisper(nick, resp) + else: + mapserv.cmsg_chat_message(resp) + +def answer(nick, message, is_whisper): + try: + for regex, action in commands.iteritems(): + match = regex.match(message) + if match: + if isinstance(action, types.ListType): + answer_random(nick, message, is_whisper, action) + elif isinstance(action, types.FunctionType): + action(nick, message, is_whisper, match) + else: + raise ValueError("must be either list or function") + except: + answer_random(nick, message, is_whisper, action) + +@extends('smsg_being_chat') +def being_chat(data): + idx = data.message.find(' : ') + if idx > -1: + nick = data.message[:idx] + message = data.message[idx + 3:] + answer(nick, message, False) + + +@extends('smsg_whisper') +def got_whisper(data): + nick, message = data.nick, data.message + answer(nick, message, True) + + +def add_command(cmd, action): + cmd_re = re.compile(cmd) + commands[cmd_re] = action + +def remove_command(cmd): + cmd_re = re.compile(cmd) + commands.remove(cmd_re) + +def init(config): + add_command('!info', answer_info) + add_command('!random', ['asd', 'Ciao!']) diff --git a/plugins/chatbot.pyc b/plugins/chatbot.pyc Binary files differnew file mode 100644 index 0000000..b327a9e --- /dev/null +++ b/plugins/chatbot.pyc diff --git a/plugins/chatlogfile.py b/plugins/chatlogfile.py new file mode 100644 index 0000000..7d97c8c --- /dev/null +++ b/plugins/chatlogfile.py @@ -0,0 +1,109 @@ +import os +import logging + +import net.mapserv as mapserv +from loggers import chatlog +from utils import extends + + +__all__ = [ 'PLUGIN', 'init', 'ChatLogHandler' ] + + +PLUGIN = { + 'name': 'chatlogfile', + 'requires': (), + 'blocks': (), + 'default_config' : {'chatlog_dir': 'chatlogs'} +} + + +class ChatLogHandler(logging.Handler): + + def __init__(self, chat_log_dir): + logging.Handler.__init__(self, 0) + self.chat_log_dir = chat_log_dir + self.loggers = {} + if not os.path.exists(self.chat_log_dir): + os.makedirs(self.chat_log_dir) + self._count = 0 + + def emit(self, record): + try: + user = record.user + except AttributeError: + return + + user = ''.join(map(lambda c: c if c.isalnum() else '_', user)) + + if user in self.loggers: + logger = self.loggers[user] + else: + logger = chatlog.getChild(user) + self.loggers[user] = logger + handler = logging.FileHandler(os.path.join( + self.chat_log_dir, user + ".txt")) + logger.addHandler(handler) + + self._count += 1 + logger.count = self._count + + if len(self.loggers) > 5: + min_count = self._count + old_user = '' + for usr, lgr in self.loggers.items(): + if lgr.count < min_count: + old_user = user + min_count = lgr.count + self.loggers[old_user].handlers[0].close() + del self.loggers[old_user] + + message = self.format(record) + logger.info(message) + + +def log(message, user='General'): + chatlog.info(message, extra={'user': user}) + + +@extends('smsg_being_chat') +def being_chat(data): + log(data.message) + + +@extends('smsg_player_chat') +def player_chat(data): + message = data.message + log(message) + + +@extends('smsg_whisper') +def got_whisper(data): + nick, message = data.nick, data.message + m = "[{} ->] {}".format(nick, message) + log(m, nick) + + +@extends('smsg_whisper_response') +def send_whisper_result(data): + if data.code == 0: + m = "[-> {}] {}".format(mapserv.last_whisper['to'], + mapserv.last_whisper['msg']) + log(m, mapserv.last_whisper['to']) + + +@extends('smsg_party_chat') +def party_chat(data): + nick = mapserv.party_members.get(data.id, str(data.id)) + msg = data.message + m = "[Party] {} : {}".format(nick, msg) + log(m, "Party") + + +def init(config): + chatlog_dir = config.get('chatlogfile', 'chatlog_dir') + + clh = ChatLogHandler(chatlog_dir) + clh.setFormatter(logging.Formatter("[%(asctime)s] %(message)s", + datefmt="%Y-%m-%d %H:%M:%S")) + chatlog.addHandler(clh) + chatlog.setLevel(logging.INFO) diff --git a/plugins/chatlogfile.pyc b/plugins/chatlogfile.pyc Binary files differnew file mode 100644 index 0000000..c6fad9b --- /dev/null +++ b/plugins/chatlogfile.pyc diff --git a/plugins/guildbot/__init__.py b/plugins/guildbot/__init__.py new file mode 100644 index 0000000..0e4ffd5 --- /dev/null +++ b/plugins/guildbot/__init__.py @@ -0,0 +1,18 @@ +import handlers +from guilddb import GuildDB +from net.onlineusers import OnlineUsers + +PLUGIN = { + 'name': 'guildbot', + 'requires': (), + 'blocks': (), +} + +__all__ = ['PLUGIN', 'init'] + + +def init(config): + handlers.online_users = OnlineUsers(config.get('Other', 'online_txt_url'), + refresh_hook=handlers.online_list_update) + handlers.online_users.start() + handlers.db = GuildDB(config.get('GuildBot', 'dbfile')) diff --git a/plugins/guildbot/__init__.pyc b/plugins/guildbot/__init__.pyc Binary files differnew file mode 100644 index 0000000..f10c5b9 --- /dev/null +++ b/plugins/guildbot/__init__.pyc diff --git a/plugins/guildbot/create_db.sql b/plugins/guildbot/create_db.sql new file mode 100644 index 0000000..bdf1d76 --- /dev/null +++ b/plugins/guildbot/create_db.sql @@ -0,0 +1,23 @@ +pragma foreign_keys=on; + +create table if not exists GUILDS( + ID integer primary key not null, + NAME text[25] not null unique, + CREATED datetime default current_timestamp, + MOTD text[100]); + +create table if not exists PLAYERS( + ID integer primary key not null, + NAME text[25] not null unique, + LASTSEEN datetime, + GUILD_ID integer, + ACCESS integer default 0, + foreign key(GUILD_ID) references GUILDS(ID)); + +insert into GUILDS(NAME) values('Crew of Red Corsair'); +insert into GUILDS(NAME) values('Phoenix Council'); + +insert into PLAYERS(NAME, GUILD_ID, ACCESS) values('Travolta', 1, 10); +insert into PLAYERS(NAME, GUILD_ID, ACCESS) values('Rill', 1, 10); +insert into PLAYERS(NAME, GUILD_ID, ACCESS) values('veryape', 2, 10); + diff --git a/plugins/guildbot/guilddb.py b/plugins/guildbot/guilddb.py new file mode 100644 index 0000000..a720b49 --- /dev/null +++ b/plugins/guildbot/guilddb.py @@ -0,0 +1,153 @@ +import logging +import sys +import sqlite3 + + +class GuildDB: + + def __init__(self, dbfile): + self._dbfile = dbfile + self.logger = logging.getLogger('ManaChat.Guild') + self.db, self.cur = self._open_sqlite_db(dbfile) + + # self.cur.execute('PRAGMA foreign_keys = ON') + self.cur.execute('create table if not exists GUILDS(\ + ID integer primary key,\ + NAME text[25] not null unique,\ + CREATED datetime default current_timestamp,\ + MOTD text[100])') + self.cur.execute('create table if not exists PLAYERS(\ + ID integer primary key,\ + NAME text[25] not null unique,\ + LASTSEEN date,\ + GUILD_ID integer,\ + ACCESS integer not null default -10,\ + SHOWINFO boolean not null default 0,\ + foreign key(GUILD_ID) references GUILDS(ID))') + self.db.commit() + + def __del__(self): + try: + self.db.close() + except Exception: + pass + + def _open_sqlite_db(self, dbfile): + """ + Open sqlite db, and return tuple (connection, cursor) + """ + try: + db = sqlite3.connect(dbfile) + cur = db.cursor() + except sqlite3.Error, e: + self.logger.error("sqlite3 error: %s", e.message) + sys.exit(1) + return db, cur + + def guild_create(self, name): + self.cur.execute('insert into GUILDS(NAME) values(?)', (name,)) + self.db.commit() + if self.cur.rowcount: + self.logger.info('Created guild "%s"', name) + return True + else: + self.logger.info('Error creating guild "%s"', name) + return False + + def guild_delete(self, name): + self.cur.execute('select ID from GUILDS where name = ?', (name,)) + row = self.cur.fetchone() + if row: + guild_id = row[0] + self.cur.execute('delete from GUILDS where name=?', (name,)) + self.cur.execute('update PLAYERS set GUILD_ID = NULL, \ + ACCESS = -10, where GUILD_ID = ?', (guild_id,)) + self.db.commit() + self.logger.info('Deleted guild "%s"', name) + return True + else: + self.logger.error('Guild "%s" not found', name) + return False + + def guild_set_motd(self, name, motd): + self.cur.execute('update GUILD set MOTD = ? where NAME = ?', + (motd, name)) + self.db.commit() + if self.cur.rowcount: + self.logger.info('Guild "%s" MOTD: %s', name, motd) + return True + else: + self.logger.error('Error setting MOTD for guild: %s', name) + return False + + def player_info(self, name): + query = '''select GUILDS.ID,GUILDS.NAME,ACCESS + from PLAYERS join GUILDS + on PLAYERS.GUILD_ID = GUILDS.ID + where PLAYERS.NAME = ?''' + self.cur.execute(query, (name,)) + return self.cur.fetchone() + + def player_get_access(self, name, guild_name=''): + query = 'select ACCESS from PLAYERS where NAME = ?' + self.cur.execute(query, (name, guild_name)) + row = self.cur.fetchone() + if row: + return row[0] + else: + # self.logger.warning('player %s not found', name) + return -10 + + def player_set_access(self, name, access_level): + query = '''update table PLAYERS set ACCESS = ? + where name = ?''' + self.cur.execute(query, (name, access_level)) + self.db.commit() + + def player_join_guild(self, player, guild, access=0): + self.cur.execute('select ID from GUILDS where NAME = ?', (guild,)) + guild_info = self.cur.fetchone() + if guild_info: + guild_id = guild_info[0] + else: + self.logger.error('Guild "%s" not found', guild) + return False + + query = '''update or ignore PLAYERS + set GUILD_ID = ?, ACCESS = ? + where NAME = ?''' + self.cur.execute(query, (guild_id, access)) + + query = '''insert or ignore into + PLAYERS(NAME, GUILD_ID, ACCESS) + values(?, ?, ?)''' + self.cur.execute(query, (player, guild_id, access)) + + self.db.commit() + + self.logger.info('Added player "%s" to guild "%s"', + player, guild) + return True + + def player_set_showinfo(self, player, si=True): + query = '''update table PLAYERS set SHOWINFO = ? + where name = ?''' + self.cur.execute(query, (player, si)) + self.db.commit() + + def guild_remove_player(self, player_name): + query = '''update PLAYERS set GUILD_ID = NULL, ACCESS = -10 + where NAME = ?''' + self.cur.execute(query, (player_name,)) + self.db.commit() + + def all_players_same_guild(self, player_name): + query = '''select NAME from PLAYERS + where GUILD_ID = (select GUILD_ID from PLAYERS + where NAME = ?)''' + return self.cur.fetchall(query, (player_name,)) + + def all_players_any_guild(self): + query = '''select NAME from PLAYERS + where ACCESS >= 0''' + return self.cur.fetchall(query) diff --git a/plugins/guildbot/guilddb.pyc b/plugins/guildbot/guilddb.pyc Binary files differnew file mode 100644 index 0000000..9de8f66 --- /dev/null +++ b/plugins/guildbot/guilddb.pyc diff --git a/plugins/guildbot/handlers.py b/plugins/guildbot/handlers.py new file mode 100644 index 0000000..6d032b5 --- /dev/null +++ b/plugins/guildbot/handlers.py @@ -0,0 +1,326 @@ +from chat import send_whisper +from utils import extends +from commands import parse_player_name + + +online_users = None +db = None +max_msg_len = 200 +pending_invitations = {} + + +def ignore(): + pass + + +def listonline(nick, _): + curr_msg = '' + online = online_users.online_users + + for prow in db.all_players_same_guild(nick): + p = prow[0] + if p in online: + if len(curr_msg + ', ' + p) > max_msg_len: + send_whisper(nick, curr_msg) + curr_msg = p + else: + curr_msg = curr_msg + ', ' + p + + send_whisper(nick, curr_msg) + + +def leave(nick, _): + info = db.player_info(nick) + broadcast(nick, '"{}" left the guild'.format(nick), True) + db.guild_remove_player(nick) + send_whisper(nick, 'You left guild {}'.format(info[1])) + + +def showinfo(nick, _): + db.player_set_showinfo(nick, True) + send_whisper(nick, "Information messages are visible") + + +def hideinfo(nick, _): + db.player_set_showinfo(nick, False) + send_whisper(nick, "Information messages are hidden") + + +def status(nick, _): + _, guild, access = db.player_info(nick) + send_whisper(nick, 'Player:{}, Guild:{}, Access:{}'.format( + nick, guild, access)) + + +# FIXME: not finished +def invite(nick, player): + if not player: + send_whisper(nick, "Usage: !invite Player") + return + + pinfo = db.player_info(player) + if pinfo and pinfo[0]: + send_whisper(nick, '"{}" is already a member of guild "{}"'.format( + player, pinfo[1])) + return + + online = online_users.online_users + if player not in online: + send_whisper(nick, '"{}" is not online'.format(player)) + return + + _, guild, _ = db.player_info(nick) + invite_msg = ('You have been invited to the "{}" guild chat. ' + 'If you would like to accept this invitation ' + 'please reply "yes" and if not then "no"').format(guild) + send_whisper(player, invite_msg) + # FIXME: what if player is offline? online_list can be outdated + pending_invitations[player] = guild + + +def remove(nick, player): + if not player: + send_whisper(nick, "Usage: !remove Player") + return + + pinfo = db.player_info(player) + if not pinfo: + send_whisper(nick, '{} is not in any guild'.format(player)) + return + + gid, _, _ = db.player_info(nick) + if gid != pinfo[0]: + send_whisper(nick, '{} is not in your guild'.format(player)) + return + + broadcast(player, '{} was removed from your guild'.format(player), True) + db.guild_remove_player(player) + send_whisper(nick, 'You were removed from "{}" guild'.format(pinfo[1])) + + +def setmotd(nick, motd): + guild = db.player_info(nick)[1] + db.setmotd(guild, motd) + broadcast(nick, 'MOTD: ' + motd) + + +def removemotd(nick, _): + guild = db.player_info(nick)[1] + db.setmotd(guild, '') + broadcast(nick, 'MOTD removed') + + +def setaccess(nick, params): + try: + si = params.index(" ") + lvl = int(params[:si]) + player = params[si + 1:] + if len(player) < 4: + raise ValueError + except ValueError: + send_whisper(nick, "Usage: !setaccess Level Player") + return + + gid, guild_name, access = db.player_info(nick) + gidp, _, accessp = db.player_info(player) + + if gid != gidp: + send_whisper(nick, '{} is not in your guild "{}"'.format( + player, guild_name)) + return + + if access <= accessp: + send_whisper(nick, "You cannot set access level for {}".format( + player)) + return + + db.player_set_access(player, lvl) + send_whisper(nick, "Player: {}, access level: {}".format( + player, lvl)) + + +def disband(nick, _): + _, guild, _ = db.player_info(nick) + if db.guild_delete(guild): + send_whisper(nick, 'Deleted guild "{}"'.format(guild)) + else: + send_whisper(nick, 'Error deleting guild "{}"'.format(guild)) + + +def addguild(nick, params): + usage = 'Usage: !addguild Leader Guild (note: Leader can be quoted)' + if not params: + send_whisper(nick, usage) + return + + leader, guild = parse_player_name(params) + + if len(leader) < 4 or len(guild) < 4: + send_whisper(nick, usage) + return + + if db.guild_create(guild): + send_whisper(nick, 'Created guild "{}", leader is "{}"'.format( + guild, leader)) + else: + send_whisper(nick, "Error creating guild") + + +def removeguild(nick, guild_name): + if not guild_name: + send_whisper(nick, "Usage: !removeguild Guild") + return + + if db.guild_delete(guild_name): + send_whisper(nick, 'Deleted guild "{}"'.format(guild_name)) + else: + send_whisper(nick, 'Guild not found: "{}"'.format(guild_name)) + + +def globalmsg(nick, msg): + if not msg: + send_whisper(nick, "Usage: !global Message") + return + + online = online_users.online_users + for prow in db.all_players_any_guild(): + pname = prow[0] + if pname in online: + send_whisper(pname, msg) + + +def joinguild(nick, guild): + if not guild: + send_whisper(nick, "Usage: !joinguild Guild") + return + + if db.player_join_guild(nick, guild, 20): + send_whisper(nick, 'You joined guild "{}"'.format(guild)) + else: + send_whisper(nick, 'Guild "{}" does not exist'.format(guild)) + + +def showhelp(nick, _): + access = db.player_get_access(nick) + curr_line = '' + + for cmd, (lvl, _, hlp) in commands.iteritems(): + if access < lvl: + continue + + if hlp[0] == '+': + help_s = '!' + cmd + ' ' + hlp[1:] + else: + help_s = '!' + cmd + ' -- ' + hlp + + if len(curr_line + '; ' + help_s) > max_msg_len: + send_whisper(nick, curr_line) + curr_line = help_s + else: + curr_line = curr_line + '; ' + help_s + + if curr_line: + send_whisper(nick, curr_line) + + +commands = { + "help": (-10, showhelp, "show help"), + "info": (0, status, "display guild information"), + "listonline": (0, listonline, "list online players"), + "leave": (0, leave, "leave your guild"), + "showinfo": (0, showinfo, "verbose notifications"), + "hideinfo": (0, hideinfo, "quiet notifications"), + "invite": (5, invite, "+Player -- invite player to guild"), + "remove": (5, remove, "+Player -- remove player from guild"), + "setmotd": (5, setmotd, "+MOTD -- set MOTD"), + "removemotd": (5, removemotd, "remove MOTD"), + "setaccess": (10, setaccess, "+Level Player -- set access level"), + "disband": (10, disband, "disband your guild"), + "addguild": (20, addguild, "+Leader GuildName -- add guild"), + "removeguild": (20, removeguild, "+GuildName -- remove guild"), + "global": (20, globalmsg, "+Message -- global message"), + "joinguild": (20, joinguild, "+GuildName -- join a guild"), +} + + +def exec_command(nick, cmdline): + end = cmdline.find(" ") + if end < 0: + cmd = cmdline[1:] + arg = "" + else: + cmd = cmdline[1:end] + arg = cmdline[end + 1:] + + if cmd in commands: + lvl, func, _ = commands[cmd] + access = db.player_get_access(nick) + + if access < lvl: + send_whisper(nick, 'That command is fobidden for you!') + else: + func(nick, arg) + + else: + send_whisper(nick, 'Command !{} not found. Try !help'.format(cmd)) + + +def player_joining(player, guild): + db.player_join_guild(player, guild) + broadcast(player, '{} joined your guild'.format(player), True) + + +def broadcast(nick, msg, exclude_nick=False): + """ + Broadcast message for all players that belong the same guild as nick. + """ + n = 0 + for prec in db.all_players_same_guild(nick): + if exclude_nick and prec[0] == nick: + continue + n += 1 + send_whisper(prec[0], '{} : {}'.format(nick, msg)) + + if n == 0: + send_whisper(nick, "You don't belong to any guild") + + +def online_list_update(curr, prev): + for p in curr - prev: + ginfo = db.player_info(p) + if ginfo is not None: + if ginfo[0] is not None: + allp = set(db.all_players_same_guild(p)) + n = len(allp.intersection(curr)) + send_whisper(p, + 'Welcome to {}! ({} Members are online)'.format( + ginfo[1], n)) + broadcast(p, '{} is now Online'.format(p), True) + + for p in prev - curr: + broadcast(p, '{} is now Offline'.format(p), True) + + +@extends('smsg_whisper') +def got_whisper(data): + nick, message = data.nick, data.message + + if len(message) < 1: + return + + if message[0] == '!': + exec_command(nick, message) + else: + if nick in pending_invitations: + # TODO: inform message + if message.lower() == 'yes': + player_joining(nick, pending_invitations[nick]) + del pending_invitations[nick] + + else: + broadcast(nick, message) + + +@extends('smsg_whisper_response') +def send_whisper_result(data): + pass diff --git a/plugins/guildbot/handlers.pyc b/plugins/guildbot/handlers.pyc Binary files differnew file mode 100644 index 0000000..a109f0c --- /dev/null +++ b/plugins/guildbot/handlers.pyc diff --git a/plugins/lazytree.py b/plugins/lazytree.py new file mode 100644 index 0000000..64efda0 --- /dev/null +++ b/plugins/lazytree.py @@ -0,0 +1,285 @@ +# -*- coding: utf-8 -*- +import random +import chatbot +from commands import general_chat, send_whisper + + +__all__ = [ 'PLUGIN', 'init' ] + +PLUGIN = { + 'name': 'lazytree', + 'requires': ('chatbot',), + 'blocks': (), +} + + +# ----------------------------------------------------------------------------- +greetings = { + "Hi {0}!" : 4, + "Hey {0}" : 3, + "Yo {0}" : 2, + "{0}!!!!" : 1, + "{0}!!!" : 1, + "{0}!!" : 1, + "Hello {0}" : 5, + "Hello {0}!" : 5, + "Welcome back {0}" : 3, + "Hello {0}! You are looking lovely today!" : 1, + "{0} is back!!" : 1, + "Hello and welcome to the Aperture Science \ +computer-aided enrichment center." : 1, +} + +drop_items = [ + "a bomb", "a bowl of petunias", "a cake", "a candy", "a chocobo", + "a coin", "a cookie", "a drunken pirate", "a freight train", + "a fruit", "a mouboo", "an angry cat", + "an angry polish spelling of a rare element with the atomic number 78", + "an anvil", "an apple", "an iten", "a magic eightball", "a GM", + "a whale", "an elephant", "a piano", "a piece of moon rock", "a pin", + "a rock", "a tub", "a wet mop", "some bass", "Voldemort", "a sandworm", + "a princess", "a prince", "an idea", "Luvia", "a penguin", + "The Hitchhiker's Guide to the Galaxy", +] + +dropping_other = [ + "Hu hu hu.. {0} kicked me!", + "Ouch..", + "Ouchy..", + "*drops dead*", + "*sighs*", + "Leaf me alone.", + "Stop it! I doesn't drop branches, try the Druid tree for once!", +] + +dropping_special = { + "ShaiN2" : "*drops a nurse on {0}*", + "Shainen" : "*drops a nurse on {0}*", + "Silent Dawn" : "*drops a box of chocolate on {0}*", + "veryape" : "*drops a chest of rares on {0}*", + "veryapeGM" : "*drops a chest of rares on {0}*", + "Ginaria" : "*drops a bluepar on {0}*", + "Rift Avis" : "*drops an acorn on {0}*", +} + +die_answers = [ + "*drops a bomb on {0}'s head*", + "*drops a bowl of petunias on {0}'s head*", + "*drops a drunken pirate on {0}'s head*", + "*drops a freight train on {0}'s head*", + "*drops a mouboo on {0}'s head*", + "*drops an angry cat on {0}'s head*", + "*drops an angry polish spelling of a rare element with \ +the atomic number 78 on {0}'s head*", + "*drops an iten on {0}'s head*", + "*drops a piano on {0}'s head*", + "*drops a piece of moon rock on {0}'s head*", + "*drops Voldemort on {0}'s head*", + "*drops dead*", + "*sighs*", + "Avada Kedavra!", + "Make me!", + "Never!!", + "You die, {0}!", + "You die, {0}!", + "You die, {0}!", + "You die, {0}!", + "No!", + "In a minute..", + "Suuure... I'll get right on it", +] + +healme_answers = [ + "Eat an apple, they're good for you.", + "If I do it for you, then I have to do it for everybody.", + "Oh, go drink a potion or something.", + "Whoops! I lost my spellbook.", + "no mana", +] + +whoami_answers = [ + "An undercover GM.", + "An exiled GM.", + "I'm not telling you!", + "I'm a bot! I'll be level 99 one day! Mwahahahaaha!!!111!", + "Somebody said I'm a Chinese copy of Confused Tree", + "I am your evil twin.", + "I don't remember anything after I woke up! What happened to me?", + "I don't know. Why am I here??", + "Who are you?", + "On the 8th day, God was bored and said 'There will be bots'. \ +So here I am.", + "♪ I'm your hell, I'm your dream, I'm nothing in between ♪♪", + "♪♪ Aperture Science. We do what we must, because.. we can ♪", + "I'm just a reincarnation of a copy.", +] + +joke_answers = [ + "How did the tree get drunk? On root beer.", + "Do you think I'm lazy?", + "I miss Confused Tree :(", + "I miss CrazyTree :(", + "I'm not telling you!", + "*sighs*", + "If I do it for you, then I have to do it for everybody.", + "What did the beaver say to the tree? It's been nice gnawing you.", + "What did the little tree say to the big tree? Leaf me alone.", + "What did the tree wear to the pool party? Swimming trunks.", + "What do trees give to their dogs? Treets.", + "What do you call a tree that only eats meat? Carniforous.", + "What do you call a tree who's always envious? Evergreen.", + "What is the tree's least favourite month? Sep-timber!", + "What kind of tree can fit into your hand? A palm-tree.", + "What was the tree's favorite subject in school? Chemistree.", + "Why did the leaf go to the doctor? It was feeling green.", + "Why doesn't the tree need sudo? Because it has root.", + "Why was the cat afraid of the tree? Because of its bark.", + "Why was the tree executed? For treeson.", + "How do trees get on the internet? They log in.", + "Why did the pine tree get into trouble? Because it was being knotty.", + "Did you hear the one about the oak tree? It's a corn-y one!", + "What do you call a blonde in a tree with a briefcase? Branch Manager.", + "How is an apple like a lawyer? They both look good hanging from a tree.", + "Why did the sheriff arrest the tree? Because its leaves rustled.", + "I'm to tired, ask someone else.", + "If you are trying to get me to tell jokes you are barking \ +up the wrong tree!", + "You wodden think they were funny anyhow. Leaf me alone!", + "What is brown and sticky? A stick.", +] + +burn_answers = [ + "*curses {0} and dies %%c*", + "Help! I'm on fire!", + "Oh hot.. hot hot!", + "*is glowing*", + "*is flaming*", + "ehemm. where are firefighters? I need them now!", + "*is so hot!*", +] + +noidea_answers = [ + "what?", "what??", "whatever", "hmm...", "huh?", "*yawns*", + "Wait a minute..", "What are you talking about?", + "Who are you?", "What about me?", + "I don't know what you are talking about", + "Excuse me?", "very interesting", "really?", + "go on...", "*scratches its leafy head*", + "*feels a disturbance in the force*", + "*senses a disturbance in the force*", + "*humming*", "I'm bored..", "%%j", "%%U", "%%[", +] + +pain_answers = [ "Ouch..", "Ouchy..", "Argh..", "Eckk...", "*howls*", + "*screams*", "*groans*", "*cries*", "*faints*", "%%k", + "Why.. What did I do to you? %%i" ] + +hurt_actions = [ "eat", "shoot", "pluck", "torture", "slap", "poison", + "break", "stab", "throw" ] + +ignored_players = [] +tree_admins = [ 'Livio' ] + + +# ----------------------------------------------------------------------------- +def say_greeting(nick, _, is_whisper, match): + if is_whisper: + return + + if nick in ignored_players: + return + + total_weight = 0 + for w in greetings.itervalues(): + total_weight += w + + random_weight = random.randint(0, total_weight) + total_weight = 0 + random_greeting = 'Hi {0}' + for g, w in greetings.iteritems(): + if total_weight >= random_weight: + random_greeting = g + break + total_weight += w + + general_chat(random_greeting.format(nick)) + + +def drop_on_head(nick, _, is_whisper, match): + if is_whisper: + return + + if nick in ignored_players: + return + + answer = 'yeah' + if nick in dropping_special: + answer = dropping_special[nick] + else: + r = random.randint(0, len(drop_items) + len(dropping_other)) + if r < len(drop_items): + answer = "*drops {} on {}'s head*".format(drop_items[r], nick) + else: + answer = random.choice(dropping_other) + + general_chat(answer.format(nick)) + + +def answer_threat(nick, _, is_whisper, match): + if is_whisper: + return + + if nick in ignored_players: + return + + answer = random.choice(die_answers) + general_chat(answer.format(nick)) + + +# ----------------------------------------------------------------------------- +def admin_additem(nick, _, is_whisper, match): + if not is_whisper: + return + + if nick not in tree_admins: + return + + item = match.group(1) + if item not in drop_items: + drop_items.append(item) + + send_whisper(nick, "Added item '{}' to drop list".format(item)) + + +def admin_addjoke(nick, _, is_whisper, match): + if not is_whisper: + return + + if nick not in tree_admins: + return + + joke = match.group(1) + if joke not in joke_answers: + joke_answers.append(joke) + + send_whisper(nick, "Added joke") + + +# ----------------------------------------------------------------------------- +tree_commands = { + r'^(hello|hi|hey|heya|hiya|yo) (livio|liviobot)' : say_greeting, + r'^(hello|hi|hey|heya|hiya) (all|everybody|everyone)$' : say_greeting, + r'\*?((shake|kick)s?) (livio|liviobot)' : drop_on_head, + r'(die|\*?((nuke|kill)s?)) (livio|liviobot)' : answer_threat, + r'^tell (.*)joke([ ,]{1,2})tree' : joke_answers, + r'^heal me([ ,]{1,2})tree' : healme_answers, + r'^(who|what) are you([ ,]{1,3})tree' : whoami_answers, + r'^!additem (.*)' : admin_additem, + r'^!addjoke (.*)' : admin_addjoke, + r'\*(burn(s?)) (livio|liviobot)' : burn_answers, + r'\*?(' + '|'.join(hurt_actions) + ')s? (livio|liviobot)' : pain_answers, +} + + +def init(config): + chatbot.commands.update(tree_commands) diff --git a/plugins/lazytree.pyc b/plugins/lazytree.pyc Binary files differnew file mode 100644 index 0000000..b7bb800 --- /dev/null +++ b/plugins/lazytree.pyc diff --git a/plugins/manaboy.py b/plugins/manaboy.py new file mode 100644 index 0000000..127ddd0 --- /dev/null +++ b/plugins/manaboy.py @@ -0,0 +1,1390 @@ +# -*- coding: utf-8 -*- +import time +import net.mapserv as mapserv +import net.charserv as charserv +import net.stats as stats +import commands +import walkto +import logicmanager +import status +import plugins +import itemdb +import random +from collections import deque +from net.inventory import get_item_index, get_storage_index +from utils import extends +from actor import find_nearest_being +from chat import send_whisper as whisper + +from net.onlineusers import OnlineUsers + +__all__ = [ 'PLUGIN', 'init' ] + +def preloadArray(nfile): + try: + file = open(nfile, "r") + array=[] + for x in file.readlines(): + x = x.replace("\n", "") + x = x.replace("\r", "") + array.append(x) + file.close() + return array + except: + print "preloadArray: File " + nfile + " not found!" + +joke_answers = preloadArray("bot/jokes.txt") +ignored_players = preloadArray("bot/ignored.txt") +disliked_players = preloadArray("bot/disliked.txt") +admins = preloadArray("bot/admins.txt") +friends = preloadArray("bot/friends.txt") + +# ====================== XCOM ============= +XCOMList = preloadArray("bot/XCOM.txt") +XCOMServerStatInterested = [] #List of nicks interested in server status change +XCOMBroadcastPrefix = "##B##G " + + +def online_list_update(curr,prev): + for x in curr: + found = False + for y in prev: + if x==y: found = True + if found == False: #detected change + for nicks in XCOMList: #For every XCOM user... + if nicks in online_users.online_users: #That's online... + if nicks in XCOMServerStatInterested: #If XCOM player is interested + if x in XCOMList: #An XCOM user connected? + XCOMDelay() #Share its status + whisper(nicks, "##W" + x + " is now online [XCOM]") + else: #Is a regular server player + if x not in XCOMList: + XCOMDelay() #Share its status + whisper(nicks, "##W" + x + " is now online") + + for x in prev: + found = False + for y in curr: + if x==y: found = True + if found == False: + for nicks in XCOMList: #For every XCOM user... + if nicks in online_users.online_users: #That's online... + if nicks in XCOMServerStatInterested: #If XCOM player is interested + if x in XCOMList: #An XCOM user connected? + XCOMDelay() #Share its status + whisper(nicks, "##L" + x + " is now offline [XCOM]") + else: #Is a regular server player + if x not in XCOMList: + XCOMDelay() #Share its status + whisper(nicks, "##L" + x + " is now offline") + +online_users = OnlineUsers(online_url=' https://server.themanaworld.org/online-old.txt', update_interval=20, refresh_hook=online_list_update) + +def XCOMOnlineList(nick, message, is_whisper, match): + XCOMDelay() + msg="" + for nicks in XCOMList: + if nicks in online_users.online_users: + msg = msg + nicks + " | " + XCOMDelay() + whisper(nick, msg) + +def XCOMPrintStat(): + pOnline=0 + xOnline=0 + for p in online_users.online_users: + pOnline=pOnline+1 + if p in XCOMList: + xOnline=xOnline+1 + return "%(xOnline)d/%(pOnline)d"%{"pOnline": pOnline, "xOnline": xOnline,} + +def XCOMDelay(): + time.sleep(0.1) + +def XCOMBroadcast(message): + for nicks in XCOMList: + if nicks in online_users.online_users: + if nicks not in ignored_players: + XCOMDelay() + whisper(nicks, message) + +def XCOMCommunicate(nick, message, is_whisper, match): + if not is_whisper: + return + if nick in ignored_players: + return #or say something + if message[0]=="!": + return + if message.startswith("*AFK*:"): # AFK bug workaround + return + if nick in XCOMList: + for nicks in XCOMList: + if nicks in online_users.online_users: + if nick==nicks: + pass + else: + XCOMDelay() + whisper(nicks, "##B##LXCOM[" + XCOMPrintStat() + "]##l " + nick + ": ##b" + message) + else: + whisper(nick, XCOMBroadcastPrefix + "XCOM is not enabled (Use !xcon)") + +def XCOMSilentInvite(nick, message, is_whisper, match): + XCOMDelay() + if not is_whisper: + return + if nick in ignored_players: + return #or say something + if nick in admins: + XCOMList.append(match.group(1)) + if match.group(1) not in ignored_players: + whisper(nick, "##W--- " + nick + " silently invited " + match.group(1) + " on XCOM ---") + else: + whisper(nick, "##W" + match.group(1) + " has been ignored by bot and cannot be added to XCOM.") + +def XCOMInvite(nick, message, is_whisper, match): + XCOMDelay() + if not is_whisper: + return + if nick in ignored_players: + return #or say something + if nick in admins: + XCOMList.append(match.group(1)) + XCOMBroadcast("##W--- " + nick + " (Admin) invited " + match.group(1) + " on XCOM ---" + XCOMBroadcastPrefix + match.group(1) + " XCOM enabled! Use !xcoff to disable, use !xclist to see XCOM online list") + else: + if nick in ignored_players: + whisper(nick, "You cannot invite banned players.") + else: + whisper(match.group(1), "##W--- " + nick + " invited you on XCOM --- Answer !xcon to join.") + XCOMDelay() + whisper(nick, "Invited " + match.group(1) + " to join XCOM. Waiting for his/her reply...") + +def XCOMEnable(nick, message, is_whisper, match): + XCOMDelay() + #accept only whispers + if not is_whisper: + return + if nick in ignored_players: + return #or say something + #search array + if nick in XCOMList: + whisper(nick, XCOMBroadcastPrefix + nick + " XCOM already enabled") + else: + XCOMList.append(nick) + XCOMBroadcast("##W--- " + nick + " is online on XCOM ---" + XCOMBroadcastPrefix + nick + " XCOM enabled! Use !xcoff or !xcom off to disable, use !xclist to see XCOM online list") + +def XCOMDisable(nick, message, is_whisper, match): + XCOMDelay() + #accept only whispers + if not is_whisper: + return + if nick in ignored_players: + return #or say something + #search array + if nick in XCOMList: + XCOMBroadcast("##L--- " + nick + " disabled XCOM ---") + XCOMList.remove(nick) + else: + whisper(nick, XCOMBroadcastPrefix + nick + " XCOM already disabled") + +def XCOMServerInterestEnable(nick, message, is_whisper, match): + XCOMDelay() + #accept only whispers + if not is_whisper: + return + if nick in ignored_players: + return #or say something + #search array + if nick in XCOMList: + whisper(nick, XCOMBroadcastPrefix + "Server online status notifications enabled!") + XCOMServerStatInterested.append(nick) + +def XCOMServerInterestDisable(nick, message, is_whisper, match): + XCOMDelay() + #accept only whispers + if not is_whisper: + return + if nick in ignored_players: + return #or say something + #search array + if nick in XCOMList: + whisper(nick, XCOMBroadcastPrefix + "Server online status notifications disabled!") + XCOMServerStatInterested.remove(nick) + +def XCOMBan(nick, message, is_whisper, match): + XCOMDelay() + #accept only whispers + if not is_whisper: + return + if nick in admins: + #search array + if match.group(1) in ignored_players: + whisper(nick, "Already banned.") + else: + ignored_players.append(match.group(1)) + XCOMList.remove(match.group(1)) + #FIXME array need to be saved!!! + XCOMBroadcast(XCOMBroadcastPrefix + match.group(1) + " is now banned from XCOM") + else: + whisper(nick, "Admins only.") + +def XCOMUnBan(nick, message, is_whisper, match): + XCOMDelay() + #accept only whispers + if not is_whisper: + return + if nick in admins: + #search array + if match.group(1) in ignored_players: + XCOMList.append(match.group(1)) + ignored_players.remove(match.group(1)) + #FIXME array need to be saved!!! + XCOMBroadcast(XCOMBroadcastPrefix + match.group(1) + " is now unbanned from XCOM") + whisper(match.group(1), "You are now unbanned from XCOM. Don't make it happen again.") + else: + whisper(nick, "Already banned.") + else: + whisper(nick, "Admins only.") + +# ============================================= + +greetings = { + "Hi {0}!" : 4, + "Hey {0}" : 3, + "Yo {0}" : 2, + "{0}!!!!" : 1, + "{0}!!!" : 1, + "{0}!!" : 1, + "Hello {0}!!!" : 5, + "Hello {0}!" : 5, + "Welcome back {0}!" : 3, + "Hello {0}! You are looking lovely today!" : 1, + "Hello {0}! I'm the bot that you can trust: I want your money!" : 1, + "{0} is back!!" : 1, + "Hello and welcome to the Aperture Science \ +computer-aided enrichment center." : 1, +} + +drop_items = [ + "a bomb", "a bowl of petunias", "a cake", "a candy", "a chocobo", + "a coin", "a cookie", "a drunken pirate", "a freight train", + "a fruit", "a mouboo", "an angry cat", + "an angry polish spelling of a rare element with the atomic number 78", + "an anvil", "an apple", "an iten", "a magic eightball", "a GM", + "a whale", "an elephant", "a piano", "a piece of moon rock", "a pin", + "a rock", "a tub", "a wet mop", "some bass", "Voldemort", "a sandworm", + "a princess", "a prince", "an idea", "Luvia", "a penguin", + "The Hitchhiker's Guide to the Galaxy", +] + +dropping_other = [ + "Hu hu hu.. {0} kicked me!", + "Ouch..", + "Ouchy..", + "*drops dead*", + "*sighs*", + "Leave me alone.", + "Whoa, dammit!", +] + +explain_sentences = { + "livio" : "He created Liviobot.", + "party" : "Is a group of players with their chat tab and they can share exp, items and HP status. See [[@@https://wiki.themanaworld.org/index.php/Legacy:Party_Skill|Party Wiki@@] for more informations.", +} + +dropping_special = { + "ShaiN2" : "*drops a nurse on {0}*", + "Shainen" : "*drops a nurse on {0}*", + "Silent Dawn" : "*drops a box of chocolate on {0}*", + "veryape" : "*drops a chest of rares on {0}*", + "veryapeGM" : "*drops a chest of rares on {0}*", + "Ginaria" : "*drops a bluepar on {0}*", + "Rift Avis" : "*drops an acorn on {0}*", +} + +die_answers = [ + "Avada Kedavra!", + "Make me!", + "Never!!", + "You die, {0}!", + "You die, {0}!", + "You die, {0}!", + "You die, {0}!", + "No!", + "In a minute..", + "Suuure... I'll get right on it", +] + +healme_answers = [ + "Eat an apple, they're good for you.", + "If I do it for you, then I have to do it for everybody.", + "Oh, go drink a potion or something.", + "Whoops! I lost my spellbook.", + "No mana!", +] + +whoami_answers = [ + "An undercover GM.", + "An exiled GM.", + "I'm not telling you!", + "I'm a bot! I'll be level 135 one day! Mwahahahaaha!!!111!", + "Somebody said I'm a Chinese copy of Confused Tree", + "I am your evil twin.", + "I don't remember anything after I woke up! What happened to me?", + "I don't know. Why am I here??", + "Who are you?", + "On the 8th day, God was bored and said 'There will be bots'. \ +So here I am.", + "♪ I'm your hell, I'm your dream, I'm nothing in between ♪♪", + "♪♪ Aperture Science. We do what we must, because.. we can ♪", + "I'm just a reincarnation of a copy.", +] + +burn_answers = [ + "*curses {0} and dies %%c*", + "Help! I'm on fire!", + "Oh hot.. hot hot!", + "*is glowing*", + "*is flaming*", + "ehemm. where are firefighters? I need them now!", + "*is so hot!*", +] + +noidea_answers = [ + "What?", "What??", "Whatever...", "Hmm...", "Huh?", "*yawns*", + "Wait a minute...", "What are you talking about?", + "Who are you?", "What about me?", + "I don't know what you are talking about", + "Excuse me?", "Very interesting", "Really?", + "Go on...", "*Scratches its leafy head*", + "*feels a disturbance in the force*", + "*senses a disturbance in the force*", + "*humming*", "I'm bored..", "%%j", "%%U", "%%[", +] + +pain_answers = [ "Ouch..", "Ouchy..", "Argh..", "Eckk...", "*howls*", + "*screams*", "*groans*", "*cries*", "*faints*", "%%k", + "Why.. What did I do to you? %%i" ] + +hurt_actions = [ "eat", "shoot", "pluck", "torture", "slap", "poison", + "break", "stab", "throw", "drown" ] + +like_answers = [ + "Yay it's", + "You are the sunshine in this beautiful land", + "Can't do this because I like you", +] + +dislike_answers = [ + "Oh, no! It's you!", + "Go away!!!", + "I don't want to see!", + "Your face makes onions cry.", + "You look like I need another drink…", + "Mayas were right...", +] + +bye_answers = [ + "See you soon!", + "Come back anytime!!!", + "See ya!", + "Hope to see you again!", + "More beer for me." +] + + +dislikebye_answers = [ + "Finally!", + "Go away!!!", + "Don't come back!", + "Whew...", + "I need another drink…" +] + +attack_answers = [ + "Attack!!!", + "Whoa, dammit!", + "Alright!!!", + "I have some pain to deliver.", + "Fire at will!!!", + "...I'm all out of gum.", + "His name is: JOHN CENA!!!", + "Target acquired!", + "Puah!", + "Grr!!!", + "Eat this!", + "Ha!", + "Come on!", + "Party time!!!", + "I will burn you down.", + "The show begins...", + "I'm better than makeup artists, prepare yourself!!!", +] + +notattack_answers = [ + "Nope!", + "*picking his nose*", + "Do it yourself.", + "Meh.", + "I will attack you instead.", + "What about my reward?", +] + +story_introductions = [ + "I was with", + "Yesterday I got bored and called", + "With none else around I've asked", +] + +story_action_fail = [ + "failed at it", + "stomped on the soul menhir", + "slipped on a terranite ore", + "got interrupted by phone call", +] + +# FIXME Unused +story_actions = [ + "jumping on", + "speaking with", + "attacking", + "poking", + "playing cards", +] + +# ----------------------------------------------------------------------------- +def say_greeting(nick, _, is_whisper, match): + if is_whisper: + return + + if nick in ignored_players: + return + + total_weight = 0 + for w in greetings.itervalues(): + total_weight += w + + random_weight = random.randint(0, total_weight) + total_weight = 0 + random_greeting = 'Hi {0}' + for g, w in greetings.iteritems(): + if total_weight >= random_weight: + random_greeting = g + break + total_weight += w + if nick in disliked_players: + mapserv.cmsg_chat_message(random.choice(dislike_answers)) + else: + mapserv.cmsg_chat_message(random_greeting.format(nick)) + time.sleep(1) + +def say_goodbye(nick, _, is_whisper, match): + if is_whisper: + return + + if nick in ignored_players: + return + + total_weight = 0 + for w in greetings.itervalues(): + total_weight += w + + random_weight = random.randint(0, total_weight) + total_weight = 0 + random_greeting = 'Hi {0}' + for g, w in greetings.iteritems(): + if total_weight >= random_weight: + random_greeting = g + break + total_weight += w + if nick in disliked_players: + mapserv.cmsg_chat_message(random.choice(dislikebye_answers)) + else: + mapserv.cmsg_chat_message(random.choice(bye_answers)) + time.sleep(1) + + +def drop_on_head(nick, _, is_whisper, match): + if is_whisper: + return + + if nick in ignored_players: + return + + answer = 'yeah' + if nick in dropping_special: + answer = dropping_special[nick] + else: + r = random.randint(0, len(drop_items) + len(dropping_other)) + if r < len(drop_items): + answer = "*drops {} on {}'s head*".format(drop_items[r], nick) + else: + answer = random.choice(dropping_other) + + mapserv.cmsg_chat_message(answer.format(nick)) + + +def answer_threat(nick, _, is_whisper, match): + if is_whisper: + return + + if nick in ignored_players: + return + + answer = random.choice(die_answers) + mapserv.cmsg_chat_message(answer.format(nick)) + + +# ----------------------------------------------------------------------------- +def admin_additem(nick, _, is_whisper, match): + if not is_whisper: + return + + if nick not in tree_admins: + return + + item = match.group(1) + if item not in drop_items: + drop_items.append(item) + + send_whisper(nick, "Added item '{}' to drop list".format(item)) + + +def admin_addjoke(nick, _, is_whisper, match): + if not is_whisper: + return + if nick in ignored_players: + return + if nick not in tree_admins: + return + + joke = match.group(1) + if joke not in joke_answers: + joke_answers.append(joke) + + send_whisper(nick, "Added joke") + + +# ----------------------------------------------------------------------------- + +PLUGIN = { + 'name': 'manaboy', + 'requires': ('chatbot', 'npc', 'autofollow'), + 'blocks': (), +} + +npcdialog = { + 'start_time': -1, + 'program': [], +} + +_times = { + 'follow': 0, + 'where' : 0, + 'status' : 0, + 'inventory' : 0, + 'say' : 0, + 'zeny' : 0, + 'storage' : 0, +} + +allowed_drops = [535, 719, 513, 727, 729, 869] +allowed_sells = [531, 521, 522, 700, 1201] + +npc_owner = '' +history = deque(maxlen=10) +storage_is_open = False +bugs = deque(maxlen=100) + + +def set_npc_owner(nick): + global npc_owner + # if plugins.npc.npc_id < 0: + npc_owner = nick + + +@extends('smsg_being_remove') +def bot_dies(data): + if data.id == charserv.server.account: + mapserv.cmsg_player_respawn() + + +@extends('smsg_player_chat') +def player_chat(data): + if not npc_owner: + return + + whisper(npc_owner, data.message) + + +@extends('smsg_npc_message') +@extends('smsg_npc_choice') +@extends('smsg_npc_close') +@extends('smsg_npc_next') +@extends('smsg_npc_int_input') +@extends('smsg_npc_str_input') +def npc_activity(data): + npcdialog['start_time'] = time.time() + + +@extends('smsg_npc_message') +def npc_message(data): + if not npc_owner: + return + + npc = mapserv.beings_cache.findName(data.id) + m = '[npc] {} : {}'.format(npc, data.message) + whisper(npc_owner, m) + + +@extends('smsg_npc_choice') +def npc_choice(data): + if not npc_owner: + return + + choices = filter(lambda s: len(s.strip()) > 0, + data.select.split(':')) + + whisper(npc_owner, '[npc][select] (use !input <number> to select)') + for i, s in enumerate(choices): + whisper(npc_owner, ' {}) {}'.format(i + 1, s)) + + +@extends('smsg_npc_int_input') +@extends('smsg_npc_str_input') +def npc_input(data): + if not npc_owner: + return + + t = 'number' + if plugins.npc.input_type == 'str': + t = 'string' + + whisper(npc_owner, '[npc][input] (use !input <{}>)'.format(t)) + + +@extends('smsg_storage_status') +def storage_status(data): + global storage_is_open + storage_is_open = True + _times['storage'] = time.time() + if npc_owner: + whisper(npc_owner, '[storage][{}/{}]'.format( + data.used, data.max_size)) + + +@extends('smsg_storage_items') +def storage_items(data): + if not npc_owner: + return + + items_s = [] + for item in data.storage: + s = itemdb.item_name(item.id, True) + if item.amount > 1: + s = str(item.amount) + ' ' + s + items_s.append(s) + + for l in status.split_names(items_s): + whisper(npc_owner, l) + + +@extends('smsg_storage_equip') +def storage_equipment(data): + if not npc_owner: + return + + items_s = [] + for item in data.equipment: + s = itemdb.item_name(item.id, True) + items_s.append(s) + + for l in status.split_names(items_s): + whisper(npc_owner, l) + + +@extends('smsg_storage_close') +def storage_close(data): + global storage_is_open + storage_is_open = False + _times['storage'] = 0 + + +@extends('smsg_player_arrow_message') +def arrow_message(data): + if npc_owner: + if data.code == 0: + whisper(npc_owner, "Equip arrows") + + +def cmd_where(nick, message, is_whisper, match): + if not is_whisper: + return + if nick in ignored_players: + return + msg = status.player_position() + whisper(nick, msg) + + +def cmd_goto(nick, message, is_whisper, match): + if not is_whisper: + return + if nick in ignored_players: + return + if nick not in admins: + return + try: + x = int(match.group(1)) + y = int(match.group(2)) + except ValueError: + return + + set_npc_owner(nick) + plugins.autofollow.follow = '' + mapserv.cmsg_player_change_dest(x, y) + + +def cmd_goclose(nick, message, is_whisper, match): + if not is_whisper: + return + if nick in ignored_players: + return + if nick not in admins: + return + + x = mapserv.player_pos['x'] + y = mapserv.player_pos['y'] + + if message.startswith('!left'): + x -= 1 + elif message.startswith('!right'): + x += 1 + elif message.startswith('!up'): + y -= 1 + elif message.startswith('!down'): + y += 1 + + set_npc_owner(nick) + plugins.autofollow.follow = '' + mapserv.cmsg_player_change_dest(x, y) + + +def cmd_pickup(nick, message, is_whisper, match): + if not is_whisper: + return + if nick in ignored_players: + return + if nick not in admins: + return + commands.pickup() + + +def cmd_drop(nick, message, is_whisper, match): + if not is_whisper: + return + if nick in ignored_players: + return + if nick not in admins: + return + + try: + amount = int(match.group(1)) + item_id = int(match.group(2)) + except ValueError: + return + + if nick not in admins: + if item_id not in allowed_drops: + return + + index = get_item_index(item_id) + if index > 0: + mapserv.cmsg_player_inventory_drop(index, amount) + + +def cmd_item_action(nick, message, is_whisper, match): + if not is_whisper: + return + if nick in ignored_players: + return + if nick not in admins: + return + try: + itemId = int(match.group(1)) + except ValueError: + return + + index = get_item_index(itemId) + if index <= 0: + return + + if message.startswith('!equip'): + mapserv.cmsg_player_equip(index) + elif message.startswith('!unequip'): + mapserv.cmsg_player_unequip(index) + elif message.startswith('!use'): + mapserv.cmsg_player_inventory_use(index, itemId) + + +def cmd_emote(nick, message, is_whisper, match): + if not is_whisper: + return + if nick in ignored_players: + return + if nick not in admins: + return + try: + emote = int(match.group(1)) + except ValueError: + return + + mapserv.cmsg_player_emote(emote) + + +def cmd_attack(nick, message, is_whisper, match): +# if not is_whisper: +# return + if nick in ignored_players: + return + if nick not in admins: + mapserv.cmsg_chat_message(random.choice(notattack_answers)) + return + + target_s = match.group(1) + + try: + target = mapserv.beings_cache[int(target_s)] + except (ValueError, KeyError): + target = find_nearest_being(name=target_s, + ignored_ids=walkto.unreachable_ids) + + if target in ignored_players: + return + + if target is not None: + set_npc_owner(nick) + plugins.autofollow.follow = '' + if target_s=="Bee": + mapserv.cmsg_chat_message("Forget it " + nick + "!!!") + elif target_s=="Pink Flower": + mapserv.cmsg_chat_message("Yeah, I love those.") + elif target_s=="Squirrel": + mapserv.cmsg_chat_message("Die, you rodent!!!") + mapserv.cmsg_player_emote(5) + walkto.walkto_and_action(target, 'attack', mapserv.player_attack_range) + time.sleep(5) + mapserv.cmsg_chat_message("Go to squirrel's heaven.") + elif target_s in friends: + mapserv.cmsg_chat_message(random.choice(like_answers)+ " " + target_s + "!") + time.sleep(5) + mapserv.cmsg_player_emote(32) + else: + mapserv.cmsg_chat_message(random.choice(attack_answers)) + time.sleep(1) + walkto.walkto_and_action(target, 'attack', mapserv.player_attack_range) + time.sleep(5) + mapserv.cmsg_chat_message(random.choice(attack_answers)) + else: + mapserv.cmsg_chat_message(random.choice(noidea_answers)) + +def cmd_come(nick, message, is_whisper, match): + if nick in ignored_players: + return + if nick not in admins: + mapserv.cmsg_chat_message(random.choice(notattack_answers)) + return + + target_s = match.group(1) + + try: + target = mapserv.beings_cache[int(nick)] + except (ValueError, KeyError): + target = find_nearest_being(name=nick, + ignored_ids=walkto.unreachable_ids) + + if target is not None: + set_npc_owner(nick) + plugins.autofollow.follow = '' + walkto.walkto_and_action(target, '', mapserv.player_attack_range) + mapserv.cmsg_chat_message(random.choice(attack_answers)) + else: + mapserv.cmsg_chat_message(random.choice(noidea_answers)) + +def say_explain(nick, msg, is_whisper, match): + if is_whisper: + return + + if nick in ignored_players: + return + + if msg.split(' ',1)[1].lower() in explain_sentences: + mapserv.cmsg_chat_message(explain_sentences[msg.split(' ',1)[1].lower()]) + mapserv.cmsg_player_emote(3) + else: + mapserv.cmsg_chat_message(random.choice(noidea_answers)) +# mapserv.cmsg_chat_message(msg.split(' ',1)[1].lower()) + +def say_think(nick, msg, is_whisper, match): + if is_whisper: + return + + if nick in ignored_players: + return + random_weight = random.randint(0, 2) + if random_weight == 0: + mapserv.cmsg_chat_message(random.choice(noidea_answers)) + if random_weight == 1: + mapserv.cmsg_chat_message("Maybe " + nick + " " + random.choice(hurt_actions) + " " + msg.split(' ')[-1][:-1]+"?") + if random_weight == 2: + mapserv.cmsg_chat_message(nick + " I have to check the wiki.") +# mapserv.cmsg_chat_message(msg.split(' ')[-1][:-1]) + +def make_story(self): + return "asd" + +def say_story(nick, msg, is_whisper, match): + if nick in ignored_players: + return + # ~ random_weight = random.randint(0, 2) + # ~ if random_weight == 0: + # ~ mapserv.cmsg_chat_message(random.choice(noidea_answers)) + # ~ if random_weight == 1: + # ~ mapserv.cmsg_chat_message("Maybe " + nick + " " + random.choice(hurt_actions) + " " + msg.split(' ')[-1][:-1]+"?") + # ~ if random_weight == 2: + # ~ mapserv.cmsg_chat_message(nick + " I have to check the wiki.") + players = [] + for being in mapserv.beings_cache.itervalues(): + if ((being.type == 'player' or being.type == 'npc') and len(being.name) > 1): + players.append(being.name) + monsters = ["monster"] + for being in mapserv.beings_cache.itervalues(): + if being.type == 'monster' and len(being.name) > 1: + monsters.append(being.name) + mapserv.cmsg_chat_message(random.choice(story_introductions) + " " + random.choice(players) + " to " + random.choice(hurt_actions) + " a " + random.choice(monsters) + " with " + random.choice(drop_items) + " but " + random.choice(story_action_fail) +" and said: \"" + random.choice(pain_answers) + "\". Then the " + random.choice(monsters) + " said: \"" + random.choice(noidea_answers) + "\". But " + random.choice(players) +" replied: \"" + random.choice(attack_answers) + "\"") + #mapserv.cmsg_chat_message() + +# Doesn't work. +def cmd_say(nick, message, is_whisper, match): + if not is_whisper: + return + if nick in ignored_players: + return + if nick not in admins: + return + set_npc_owner(nick) + msg = message.group(1) + mapserv.cmsg_chat_message(msg) + + +def cmd_sit(nick, message, is_whisper, match): + if not is_whisper: + return + if nick in ignored_players: + return + plugins.autofollow.follow = '' + mapserv.cmsg_player_change_act(0, 2) + + +def cmd_turn(nick, message, is_whisper, match): + if not is_whisper: + return + if nick in ignored_players: + return + commands.set_direction('', message[6:]) + + +def cmd_follow(nick, message, is_whisper, match): + if not is_whisper: + return + if nick in ignored_players: + return + if nick not in admins: + return + if plugins.autofollow.follow == nick: + plugins.autofollow.follow = '' + else: + set_npc_owner(nick) + plugins.autofollow.follow = nick + + +def cmd_lvlup(nick, message, is_whisper, match): + if not is_whisper: + return + if nick in ignored_players: + return + if nick not in admins: + return + stat = match.group(1).lower() + stats = {'str': 13, 'agi': 14, 'vit': 15, + 'int': 16, 'dex': 17, 'luk': 18} + + skills = {'mallard': 45, 'brawling': 350, 'speed': 352, + 'astral': 354, 'raging': 355, 'resist': 353} + + if stat in stats: + mapserv.cmsg_stat_update_request(stats[stat], 1) + elif stat in skills: + mapserv.cmsg_skill_levelup_request(skills[stat]) + +#FIXME it fails: leads bot to spam +def cmd_invlist(nick, message, is_whisper, match): + if not is_whisper: + return + if nick in ignored_players: + return + if nick not in admins: + return + ls = status.invlists(50) + for l in ls: + whisper(nick, l) + time.delay(2) + +#FIXME it fails +def cmd_inventory(nick, message, is_whisper, match): + if not is_whisper: + return + if nick in ignored_players: + return + if nick not in admins: + return + ls = status.invlists2(255) + for l in ls: + whisper(nick, l) + time.delay(2) + + +def cmd_status(nick, message, is_whisper, match): + if not is_whisper: + return + if nick in ignored_players: + return + all_stats = ('stats', 'hpmp', 'weight', 'points', + 'zeny', 'attack', 'skills') + + sr = status.stats_repr(*all_stats) + whisper(nick, ' | '.join(sr.values())) + + +def cmd_zeny(nick, message, is_whisper, match): + if not is_whisper: + return + if nick in ignored_players: + return + if nick not in admins: + return + whisper(nick, 'I have {} GP'.format(mapserv.player_stats[stats.MONEY])) + + +def cmd_nearby(nick, message, is_whisper, match): + if not is_whisper: + return + if nick in ignored_players: + return + btype = message[8:] + if btype.endswith('s'): + btype = btype[:-1] + + ls = status.nearby(btype) + for l in ls: + whisper(nick, l) + + +def cmd_talk2npc(nick, message, is_whisper, match): + if not is_whisper: + return + if nick in ignored_players: + return + if nick not in admins: + return + npc_s = match.group(1).strip() + jobs = [] + name = '' + try: + jobs = [int(npc_s)] + except ValueError: + name = npc_s + + b = find_nearest_being(name=name, type='npc', allowed_jobs=jobs) + if b is None: + whisper(nick, '[error] NPC not found: {}'.format(npc_s)) + return + + set_npc_owner(nick) + plugins.autofollow.follow = '' + plugins.npc.npc_id = b.id + mapserv.cmsg_npc_talk(b.id) + + +def cmd_input(nick, message, is_whisper, match): + if not is_whisper: + return + if nick in ignored_players: + return + if nick not in admins: + return + plugins.npc.cmd_npcinput('', match.group(1)) + + +def cmd_close(nick, message, is_whisper, match): + if not is_whisper: + return + if nick in ignored_players: + return + if nick not in admins: + return + if storage_is_open: + reset_storage() + else: + plugins.npc.cmd_npcclose() + + +def cmd_history(nick, message, is_whisper, match): + if not is_whisper: + return + if nick in ignored_players: + return + if nick not in admins: + return + for user, cmd in history: + whisper(nick, '{} : {}'.format(user, cmd)) + + +def cmd_store(nick, message, is_whisper, match): + if not is_whisper: + return + if nick in ignored_players: + return + if nick not in admins: + return + if not storage_is_open: + return + + try: + amount = int(match.group(1)) + item_id = int(match.group(2)) + except ValueError: + return + + index = get_item_index(item_id) + if index > 0: + mapserv.cmsg_move_to_storage(index, amount) + + +def cmd_retrieve(nick, message, is_whisper, match): + if not is_whisper: + return + if nick in ignored_players: + return + if nick not in admins: + return + if not storage_is_open: + return + + try: + amount = int(match.group(1)) + item_id = int(match.group(2)) + except ValueError: + return + + index = get_storage_index(item_id) + if index > 0: + mapserv.cmsg_move_from_storage(index, amount) + + +def cmd_sell(nick, message, is_whisper, match): + if not is_whisper: + return + if nick in ignored_players: + return + try: + amount = int(match.group(1)) + item_id = int(match.group(2)) + npc_s = match.group(3).strip() + except ValueError: + return + + if item_id not in allowed_sells: + return + + index = get_item_index(item_id) + if index < 0: + return + + jobs = [] + name = '' + try: + jobs = [int(npc_s)] + except ValueError: + name = npc_s + + b = find_nearest_being(name=name, type='npc', allowed_jobs=jobs) + if b is None: + return + + mapserv.cmsg_npc_buy_sell_request(b.id, 1) + mapserv.cmsg_npc_sell_request(index, amount) + + +def cmd_help(nick, message, is_whisper, match): + if not is_whisper: + return + if nick in ignored_players: + return + + m = ('[@@https://forums.themanaworld.org/viewtopic.php?f=12&t=19673|Forum@@]' + '[@@https://bitbucket.org/rumly111/manachat|Sources@@] ' + 'Try !commands for list of commands') + whisper(nick, m) + + +def cmd_commands(nick, message, is_whisper, match): + if not is_whisper: + return + if nick in ignored_players: + return + + c = [] + for cmd in manaboy_commands: + if cmd.startswith('!('): + br = cmd.index(')') + c.extend(cmd[2:br].split('|')) + elif cmd.startswith('!'): + c.append(cmd[1:].split()[0]) + + c.sort() + whisper(nick, ', '.join(c)) + + +def cmd_report_bug(nick, message, is_whisper, match): + if not is_whisper: + return + if nick in ignored_players: + return + + bug_s = match.group(1) + bugs.append((nick, bug_s)) + whisper(nick, 'Thank you for your bug report') + + +def cmd_check_bugs(nick, message, is_whisper, match): + if not is_whisper: + return + if nick in ignored_players: + return + + if nick not in admins: + return + + for user, bug in bugs: + whisper(nick, '{} : {}'.format(user, bug)) + + bugs.clear() + + +def reset_storage(): + mapserv.cmsg_storage_close() + mapserv.cmsg_npc_list_choice(plugins.npc.npc_id, 6) + + +# ========================================================================= +def manaboy_logic(ts): + + def reset(): + global npc_owner + npc_owner = '' + npcdialog['start_time'] = -1 + plugins.npc.cmd_npcinput('', '6') + # plugins.npc.cmd_npcclose() + + if storage_is_open and ts > _times['storage'] + 150: + reset_storage() + + if npcdialog['start_time'] <= 0: + return + + if not storage_is_open and ts > npcdialog['start_time'] + 30.0: + reset() + +# ========================================================================= +manaboy_commands = { + '!where' : cmd_where, + '!goto (\d+) (\d+)' : cmd_goto, + '!(left|right|up|down)' : cmd_goclose, + '!pickup' : cmd_pickup, + '!drop (\d+) (\d+)' : cmd_drop, + '!equip (\d+)' : cmd_item_action, + '!unequip (\d+)' : cmd_item_action, + '!use (\d+)' : cmd_item_action, + '!emote (\d+)' : cmd_emote, + '!attack (.+)' : cmd_attack, + '!say ((@|#).+)' : cmd_say, + '!sit' : cmd_sit, + '!turn' : cmd_turn, + '!follow' : cmd_follow, + '!lvlup (\w+)' : cmd_lvlup, + '!inventory' : cmd_inventory, + '!invlist' : cmd_invlist, + '!status' : cmd_status, + '!zeny' : cmd_zeny, + '!nearby' : cmd_nearby, + '!talk2npc (.+)' : cmd_talk2npc, + '!input (.+)' : cmd_input, + '!close' : cmd_close, + '!store (\d+) (\d+)' : cmd_store, + '!retrieve (\d+) (\d+)' : cmd_retrieve, + '!sell (\d+) (\d+) (.+)' : cmd_sell, + '!(help|info)' : cmd_help, + '!commands' : cmd_commands, + '!history' : cmd_history, + '!bug (.+)' : cmd_report_bug, + '!bugs' : cmd_check_bugs, + '!xcon' : XCOMEnable, + '!xcom' : XCOMEnable, + '!xcoff' : XCOMDisable, + '!xcom off' : XCOMDisable, + '!xclist' : XCOMOnlineList, + '!xci (.*)' : XCOMInvite, + '!xcsi (.*)' : XCOMSilentInvite, + '!xcb (.*)' : XCOMBan, + '!xcu (.*)' : XCOMUnBan, + '!xcsion' : XCOMServerInterestEnable, + '!xcsioff' : XCOMServerInterestDisable, + r'(.*)' : XCOMCommunicate, + r'^(?i)explain (.*)': say_explain, + r'^(?i)(hello|hi|hey|heya|hiya|yo) (?i)(livio|liviobot)' : say_greeting, + r'^(?i)(hello|hi|hey|heya|hiya) (?i)(all|everybody|everyone)(.*)' : say_greeting, + r'\*(?i)?((shake|kick)s?) (?i)(livio|liviobot)' : drop_on_head, + r'\*(?i)?(bye|cya|gtg)' : say_goodbye, + r'(?i)(die|go away|\*?((nuke|kill)s?)) (?i)(livio|liviobot)' : answer_threat, + r'^(?i)(livio|liviobot) (?i)Will (.*)' : noidea_answers, + r'^(?i)heal me([ ,]{1,2})(livio|liviobot)' : healme_answers, + r'^(?i)(who|what) are you([ ,]{1,3})(livio|liviobot)' : whoami_answers, + r'^!additem (.*)' : admin_additem, + r'^!addjoke (.*)' : admin_addjoke, + r'\*(?i)?(burn(s?)) (livio|liviobot)' : burn_answers, + r'\*(?i)?(come) (livio|liviobot)' : cmd_come, + r'\*(?i)?(' + '|'.join(hurt_actions) + ')s?(?i)(livio|liviobot)' : pain_answers, + r'^(?i)what do you think about(.*)' : say_think, + '!story': say_story, + '!joke' : joke_answers, +} + + +def chatbot_answer_mod(func): + '''modifies chatbot.answer to remember last 10 commands''' + + def mb_answer(nick, message, is_whisper): + if is_whisper: + history.append((nick, message)) + return func(nick, message, is_whisper) + + return mb_answer + +def init(config): + + online_users.start() + + for cmd, action in manaboy_commands.items(): + plugins.chatbot.add_command(cmd, action) + plugins.chatbot.answer = chatbot_answer_mod(plugins.chatbot.answer) + + logicmanager.logic_manager.add_logic(manaboy_logic) diff --git a/plugins/manaboy.pyc b/plugins/manaboy.pyc Binary files differnew file mode 100644 index 0000000..a712707 --- /dev/null +++ b/plugins/manaboy.pyc diff --git a/plugins/msgqueue.py b/plugins/msgqueue.py new file mode 100644 index 0000000..25fe2b0 --- /dev/null +++ b/plugins/msgqueue.py @@ -0,0 +1,76 @@ +from collections import deque +from loggers import debuglog +from logicmanager import logic_manager + + +__all__ = [ 'PLUGIN', 'init', 'delayed_functions', 'reload_function' ] + + +PLUGIN = { + 'name': 'msgqueue', + 'requires': (), + 'blocks': (), +} + + +reloaded_functions = {} +event_queue = deque() +_times = { 'next_event' : 0 } + +delayed_functions = { + # 'net.mapserv.cmsg_chat_whisper': 7.5, + # 'net.mapserv.cmsg_chat_message': 3.5, +} + + +def delayed_function(func_name, delay): + + def func(*args, **kwargs): + call = (delay, reloaded_functions[func_name], args, kwargs) + event_queue.append(call) + + return func + + +def reload_function(name, delay): + + def recurs_import(name): + m = __import__(name) + for n in name.split('.')[1:]: + m = getattr(m, n) + return m + + ss = name.rsplit('.', 1) + + if len(ss) == 1: + ss.insert(0, 'net.mapserv') + name = 'net.mapserv.' + name + + try: + module = recurs_import(ss[0]) + func_name = ss[1] + reloaded_functions[name] = getattr(module, func_name) + setattr(module, func_name, delayed_function(name, delay)) + debuglog.debug('function %s wrapped with delay %d', name, delay) + + except Exception as e: + debuglog.error('error wrapping function %s: %s', name, e) + + +def msgq_logic(ts): + if len(event_queue): + if ts > _times['next_event']: + delay, func, args, kwargs = event_queue.popleft() + _times['next_event'] = ts + delay + func(*args, **kwargs) + + +def init(config): + section = PLUGIN['name'] + for option in config.options(section): + delayed_functions[option] = config.getfloat(section, option) + + for func_name, delay in delayed_functions.iteritems(): + reload_function(func_name, delay) + + logic_manager.add_logic(msgq_logic) diff --git a/plugins/msgqueue.pyc b/plugins/msgqueue.pyc Binary files differnew file mode 100644 index 0000000..5814be1 --- /dev/null +++ b/plugins/msgqueue.pyc diff --git a/plugins/notify.py b/plugins/notify.py new file mode 100644 index 0000000..ace4e0b --- /dev/null +++ b/plugins/notify.py @@ -0,0 +1,99 @@ + +import os +import re +from kivy.app import App +from kivy.core.audio import SoundLoader +from plyer import notification +import net.mapserv as mapserv +from utils import extends + + +__all__ = [ 'PLUGIN', 'init', 'timeout', 'guard_words' ] + +timeout = 5000 +guard_words = ["test1", "illia", "eyepatch"] +sound = None + + +PLUGIN = { + 'name': 'notify', + 'requires': (), + 'blocks': (), + 'default_config' : { + 'notif_timeout': 7000, + 'notif_sound' : True + } +} + + +def notify(title, message, use_regex): + bNotify = False + if use_regex: + for regex in guard_words: + if regex.search(message): + bNotify = True + break + else: + bNotify = True + + if bNotify: + app = App.get_running_app() + icon = os.path.join(app.directory, app.icon) + notification.notify(title=title, message=message, + timeout=timeout, + app_name=app.get_application_name(), + app_icon=icon) + if sound is not None: + sound.play() + + +@extends('smsg_being_chat') +def being_chat(data): + app = App.get_running_app() + if app.root_window.focus: + return + + notify('General', data.message, True) + + +@extends('smsg_whisper') +def got_whisper(data): + app = App.get_running_app() + if app.root_window.focus: + return + + nick, message = data.nick, data.message + + notify(nick, message, nick == 'guild') + + +@extends('smsg_party_chat') +def party_chat(data): + app = App.get_running_app() + if app.root_window.focus: + return + + nick = mapserv.party_members.get(data.id, str(data.id)) + message = data.message + m = "{} : {}".format(nick, message) + + notify('Party', m, True) + + +def init(config): + global timeout + global sound + global guard_words + + gw = [] + for w in guard_words: + gw.append(re.compile(w, re.IGNORECASE)) + + gw.append(re.compile(config.get('Player', 'charname'), + re.IGNORECASE)) + guard_words = gw + + timeout = config.getint('notify', 'notif_timeout') + + if config.getboolean('notify', 'notif_sound'): + sound = SoundLoader.load('newmessage.wav') diff --git a/plugins/notify.pyc b/plugins/notify.pyc Binary files differnew file mode 100644 index 0000000..db13ee8 --- /dev/null +++ b/plugins/notify.pyc diff --git a/plugins/npc.py b/plugins/npc.py new file mode 100644 index 0000000..5013562 --- /dev/null +++ b/plugins/npc.py @@ -0,0 +1,135 @@ +import net.mapserv as mapserv +from commands import commands, must_have_arg +from loggers import debuglog +from utils import extends +from actor import find_nearest_being + + +__all__ = [ 'PLUGIN', 'init', 'autonext', 'npc_id', 'input_type' ] + + +PLUGIN = { + 'name': 'npc', + 'requires': (), + 'blocks': (), +} + +npc_id = -1 +autonext = True +input_type = '' + + +@extends('smsg_npc_message') +def npc_message(data): + npc = mapserv.beings_cache.findName(data.id) + m = '[npc] {} : {}'.format(npc, data.message) + debuglog.info(m) + + +@extends('smsg_npc_choice') +def npc_choice(data): + global npc_id + global input_type + npc_id = data.id + input_type = 'select' + choices = filter(lambda s: len(s.strip()) > 0, + data.select.split(':')) + debuglog.info('[npc][select]') + for i, s in enumerate(choices): + debuglog.info(' %d) %s', i + 1, s) + + +@extends('smsg_npc_close') +def npc_close(data): + if autonext: + global npc_id + npc_id = -1 + mapserv.cmsg_npc_close(data.id) + else: + debuglog.info('[npc][close]') + + +@extends('smsg_npc_next') +def npc_next(data): + if autonext: + mapserv.cmsg_npc_next_request(data.id) + else: + debuglog.info('[npc][next]') + + +@extends('smsg_npc_int_input') +def npc_int_input(data): + global input_type + input_type = 'int' + debuglog.info('[npc][input] Enter number:') + + +@extends('smsg_npc_str_input') +def npc_str_input(data): + global input_type + input_type = 'str' + debuglog.info('[npc][input] Enter string:') + + +@must_have_arg +def cmd_npctalk(_, arg): + global npc_id + jobs = [] + name = '' + try: + jobs = [int(arg)] + except ValueError: + name = arg + + b = find_nearest_being(name=name, type='npc', allowed_jobs=jobs) + + if b is not None: + npc_id = b.id + mapserv.cmsg_npc_talk(npc_id) + else: + debuglog.warning("NPC %s not found", arg) + + +def cmd_npcclose(*unused): + global npc_id + if npc_id > -1: + mapserv.cmsg_npc_close(npc_id) + npc_id = -1 + + +def cmd_npcnext(*unused): + if npc_id > -1: + mapserv.cmsg_npc_next_request(npc_id) + + +@must_have_arg +def cmd_npcinput(_, arg): + if npc_id < 0: + return + + global input_type + + if input_type in ('int', 'select'): + try: + n = int(arg) + except ValueError, e: + debuglog.error(e.message) + return + + if input_type == 'int': + mapserv.cmsg_npc_int_response(npc_id, n) + + elif input_type == 'str': + mapserv.cmsg_npc_str_response(npc_id, arg) + + elif input_type == 'select': + mapserv.cmsg_npc_list_choice(npc_id, n) + + input_type = '' + + +def init(config): + commands['talk'] = cmd_npctalk + commands['close'] = cmd_npcclose + commands['next'] = cmd_npcnext + commands['input'] = cmd_npcinput diff --git a/plugins/npc.pyc b/plugins/npc.pyc Binary files differnew file mode 100644 index 0000000..b0dec22 --- /dev/null +++ b/plugins/npc.pyc diff --git a/plugins/restapi.py b/plugins/restapi.py new file mode 100644 index 0000000..5b1d0eb --- /dev/null +++ b/plugins/restapi.py @@ -0,0 +1,149 @@ +import asyncore +import asynchat +import socket +import logging +import json +from collections import deque +from BaseHTTPServer import BaseHTTPRequestHandler + +try: + from cStringIO import StringIO +except ImportError: + from StringIO import StringIO + +import commands + +__all__ = [ 'PLUGIN', 'init' ] + +PLUGIN = { + 'name': 'restapi', + 'requires': (), + 'blocks': (), + 'default_config' : { + 'rest_ip': '127.0.0.1', + 'rest_port': '8971', + } +} + +last_id = 0 +log_history = deque(maxlen=200) + + +def collect_messages(since_id): + ss = [] + for m_id, m_text in log_history: + if m_id >= since_id: + ss.append({'id': m_id, 'message': m_text}) + + return json.dumps(ss) + + +class RequestHandler(asynchat.async_chat, BaseHTTPRequestHandler): + + protocol_version = "HTTP/1.1" + + def __init__(self, conn, addr, server): + asynchat.async_chat.__init__(self, conn) + self.client_address = addr + self.connection = conn + self.server = server + self.set_terminator('\r\n\r\n') + self.rfile = StringIO() + self.wfile = StringIO() + self.found_terminator = self.handle_request_line + + def collect_incoming_data(self, data): + """Collect the data arriving on the connexion""" + self.rfile.write(data) + + def prepare_POST(self): + """Prepare to read the request body""" + bytesToRead = int(self.headers.getheader('Content-Length')) + # set terminator to length (will read bytesToRead bytes) + self.set_terminator(bytesToRead) + self.rfile = StringIO() + # control will be passed to a new found_terminator + self.found_terminator = self.handle_post_data + + def handle_post_data(self): + """Called when a POST request body has been read""" + self.rfile.seek(0) + self.do_POST() + self.finish() + + def handle_request_line(self): + """Called when the http request line and headers have been received""" + self.rfile.seek(0) + self.raw_requestline = self.rfile.readline() + self.parse_request() + + if self.command == 'GET': + self.do_GET() + self.finish() + elif self.command == 'POST': + self.prepare_POST() + else: + self.send_error(501) + + def finish(self): + data = self.wfile.getvalue() + self.push(data) + self.close_when_done() + + def do_GET(self): + try: + since_id = int(self.path[1:]) + except ValueError: + self.send_error(400) + return + + response = collect_messages(since_id) + self.send_response(200) + self.send_header("Content-type", "application/json") + self.end_headers() + self.wfile.write(response) + + def do_POST(self): + cmd = self.rfile.getvalue() + commands.process_line(cmd) + self.send_response(200) + + +class Server(asyncore.dispatcher): + + def __init__(self, ip, port, handler): + asyncore.dispatcher.__init__(self) + self.handler = handler + self.create_socket(socket.AF_INET, socket.SOCK_STREAM) + self.set_reuse_addr() + self.bind((ip, port)) + self.listen(5) + + def handle_accept(self): + try: + conn, addr = self.accept() + except: + self.log_info('handle_accept() error', 'warning') + return + + self.handler(conn, addr, self) + + +class RestDebugLogHandler(logging.Handler): + def emit(self, record): + global last_id + msg = self.format(record) + last_id += 1 + log_history.append((last_id, msg)) + + +def init(config): + debuglog = logging.getLogger("ManaChat.Debug") + dbgh = RestDebugLogHandler() + dbgh.setFormatter(logging.Formatter("[%(asctime)s] %(message)s", + datefmt="%H:%M:%S")) + debuglog.addHandler(dbgh) + + ip = config.get(PLUGIN['name'], 'rest_ip') + port = config.getint(PLUGIN['name'], 'rest_port') + Server(ip, port, RequestHandler) diff --git a/plugins/shop.py b/plugins/shop.py new file mode 100644 index 0000000..44c5db8 --- /dev/null +++ b/plugins/shop.py @@ -0,0 +1,551 @@ +import os +import time +import logging +from collections import OrderedDict +import net.mapserv as mapserv +import chatbot +import logicmanager +import status +import badge +from net.inventory import get_item_index +from net.trade import reset_trade_state +from utils import encode_str, extends +from itemdb import item_name +from playerlist import PlayerList +from chat import send_whisper as whisper + + +__all__ = [ 'PLUGIN', 'init', 'shoplog', 'buying', 'selling' ] + +#nobuy = ['Manatauro', 'ManatauroMage', 'Manatauro Shop', 'kytty'] +nobuy = ['kirito123'] + +PLUGIN = { + 'name': 'shop', + 'requires': ('chatbot',), + 'blocks': (), + 'default_config' : { + 'timeout' : 60, + 'shoplist_txt' : 'shoplist.txt', + 'admins_file' : 'shopAdmins.txt' + } +} + +shoplog = logging.getLogger('ManaChat.Shop') +trade_timeout = 60 +shop_admins = None + + +class s: + player = '' + mode = '' + item_id = 0 + amount = 0 + price = 0 + index = 0 + start_time = 0 + + +buying = OrderedDict([ +# (621, (5000, 1)), # Eyepatch +# (640, (1450, 100)), # Iron Ore +# (4001, (650, 300)), # Coal +]) + +selling = OrderedDict([ +# (535, (100, 50)), # Red Apple +# (640, (1750, 100)), # Iron Ore +]) + + +def cleanup(): + s.player = '' + s.mode = '' + s.item_id = 0 + s.amount = 0 + s.price = 0 + s.index = 0 + s.start_time = 0 + + +# ========================================================================= +def selllist(nick, message, is_whisper, match): + if not is_whisper: + return + if nick in nobuy: + mapserv.cmsg_chat_message("Special prize for you, " + nick + "!") + time.sleep(5) + # ~ return + # Support for 4144's shop (Sell list) + data = '\302\202B1' + + for id_, (price, amount) in selling.iteritems(): + index = get_item_index(id_) + if index < 0: + continue + + _, curr_amount = mapserv.player_inventory[index] + amount = min(curr_amount, amount) + if nick in nobuy: + price=price*100 + + data += encode_str(id_, 2) + data += encode_str(price, 4) + data += encode_str(amount, 3) + + whisper(nick, data) + + +def buylist(nick, message, is_whisper, match): + if not is_whisper: + return + + # Support for 4144's shop (Sell list) + data = '\302\202S1' + + for id_, (price, amount) in buying.iteritems(): + index = get_item_index(id_) + if index > 0: + _, curr_amount = mapserv.player_inventory[index] + amount -= curr_amount + + try: + can_afford = mapserv.player_money / price + except ZeroDivisionError: + can_afford = 10000000 + + amount = min(can_afford, amount) + + if amount <= 0: + continue + + data += encode_str(id_, 2) + data += encode_str(price, 4) + data += encode_str(amount, 3) + + whisper(nick, data) + + +def sellitem(nick, message, is_whisper, match): + if not is_whisper: + return + + item_id = amount = 0 + + # FIXME: check if amount=0 or id=0 + try: + item_id = int(match.group(1)) + # price = int(match.group(2)) + amount = int(match.group(3)) + if item_id < 1 or amount < 1: + raise ValueError + except ValueError: + whisper(nick, "usage: !sellitem ID PRICE AMOUNT") + return + + if s.player: + whisper(nick, "I am currently trading with someone") + return + + player_id = mapserv.beings_cache.findId(nick) + if player_id < 0: + whisper(nick, "I don't see you nearby") + return + + if item_id not in buying: + whisper(nick, "I don't buy that") + return + + real_price, max_amount = buying[item_id] + + index = get_item_index(item_id) + if index > 0: + _, curr_amount = mapserv.player_inventory[index] + max_amount -= curr_amount + + if amount > max_amount: + whisper(nick, "I don't need that many") + return + + total_price = real_price * amount + if total_price > mapserv.player_money: + whisper(nick, "I can't afford it") + return + + s.player = nick + s.mode = 'buy' + s.item_id = item_id + s.amount = amount + s.price = total_price + s.index = index + s.start_time = time.time() + + mapserv.cmsg_trade_request(player_id) + + +def buyitem(nick, message, is_whisper, match): + if not is_whisper: + return + item_id = amount = 0 + + # FIXME: check if amount=0 or id=0 + try: + item_id = int(match.group(1)) + # price = int(match.group(2)) + amount = int(match.group(3)) + if item_id < 1 or amount < 1: + raise ValueError + except ValueError: + whisper(nick, "usage: !buyitem ID PRICE AMOUNT") + return + + if s.player: + whisper(nick, "I am currently trading with someone") + return + + player_id = mapserv.beings_cache.findId(nick) + if player_id < 0: + whisper(nick, "I don't see you nearby") + return + + if item_id not in selling: + whisper(nick, "I don't sell that") + return + + real_price, max_amount = selling[item_id] + + index = get_item_index(item_id) + if index > 0: + _, curr_amount = mapserv.player_inventory[index] + max_amount = min(max_amount, curr_amount) + else: + max_amount = 0 + + if amount > max_amount: + whisper(nick, "I don't have that many") + return + + total_price = real_price * amount + + s.player = nick + s.mode = 'sell' + s.item_id = item_id + s.amount = amount + s.price = total_price + s.index = index + s.start_time = time.time() + + mapserv.cmsg_trade_request(player_id) + + +def retrieve(nick, message, is_whisper, match): + if not is_whisper: + return + + if shop_admins is None: + return + + if not shop_admins.check_player(nick): + return + + item_id = amount = 0 + + try: + item_id = int(match.group(1)) + amount = int(match.group(2)) + if amount < 1: + raise ValueError + except ValueError: + whisper(nick, "usage: !retrieve ID AMOUNT (ID=0 for money)") + return + + if s.player: + whisper(nick, "I am currently trading with someone") + return + + player_id = mapserv.beings_cache.findId(nick) + if player_id < 0: + whisper(nick, "I don't see you nearby") + return + + index = max_amount = 0 + + if item_id == 0: + max_amount = mapserv.player_money + else: + index = get_item_index(item_id) + if index > 0: + max_amount = mapserv.player_inventory[index][1] + + if amount > max_amount: + whisper(nick, "I don't have that many") + return + + s.player = nick + s.mode = 'retrieve' + s.item_id = item_id + s.amount = amount + s.index = index + s.start_time = time.time() + + mapserv.cmsg_trade_request(player_id) + + +def invlist(nick, message, is_whisper, match): + if not is_whisper: + return + + if shop_admins is None: + return + + if not shop_admins.check_player(nick): + return + + ls = status.invlists(50) + for l in ls: + whisper(nick, l) + + +def zeny(nick, message, is_whisper, match): + if not is_whisper: + return + + if shop_admins is None: + return + + if not shop_admins.check_player(nick): + return + + whisper(nick, 'I have {} GP'.format(mapserv.player_stats[20])) + + +# ========================================================================= +@extends('smsg_trade_request') +def trade_request(data): + shoplog.info("Trade request from %s", data.nick) + mapserv.cmsg_trade_response(False) + selllist(data.nick, '', True, None) + + +@extends('smsg_trade_response') +def trade_response(data): + code = data.code + + if code == 0: + shoplog.info("%s is too far", s.player) + whisper(s.player, "You are too far, please come closer") + mapserv.cmsg_trade_cancel_request() # NOTE: do I need it? + cleanup() + + elif code == 3: + shoplog.info("%s accepts trade", s.player) + if s.mode == 'sell': + mapserv.cmsg_trade_item_add_request(s.index, s.amount) + mapserv.cmsg_trade_add_complete() + elif s.mode == 'buy': + mapserv.cmsg_trade_item_add_request(0, s.price) + mapserv.cmsg_trade_add_complete() + elif s.mode == 'retrieve': + mapserv.cmsg_trade_item_add_request(s.index, s.amount) + mapserv.cmsg_trade_add_complete() + else: + shoplog.error("Unknown shop state: %s", s.mode) + mapserv.cmsg_trade_cancel_request() + cleanup() + + elif code == 4: + shoplog.info("%s cancels trade", s.player) + cleanup() + + else: + shoplog.info("Unknown TRADE_RESPONSE code %d", code) + cleanup() + + +@extends('smsg_trade_item_add') +def trade_item_add(data): + item_id, amount = data.id, data.amount + + shoplog.info("%s adds %d %s", s.player, amount, item_name(item_id)) + + if item_id == 0: + return + + if s.mode == 'sell': + whisper(s.player, "I accept only GP") + mapserv.cmsg_trade_cancel_request() + cleanup() + + elif s.mode == 'buy': + if s.item_id != item_id or s.amount != amount: + whisper(s.player, "You should give me {} {}".format( + s.amount, item_name(s.item_id))) + mapserv.cmsg_trade_cancel_request() + cleanup() + + elif s.mode == 'retrieve': + pass + + else: + shoplog.error("Unknown shop state: %s", s.mode) + mapserv.cmsg_trade_cancel_request() + cleanup() + + +@extends('smsg_trade_item_add_response') +def trade_item_add_response(data): + code = data.code + amount = data.amount + + if code == 0: + if amount > 0: + item_id, _ = mapserv.trade_state['items_give'][-1] + shoplog.info("I add to trade %d %s", amount, item_name(item_id)) + + elif code == 1: + shoplog.info("%s is overweight", s.player) + whisper(s.player, "You are overweight") + mapserv.cmsg_trade_cancel_request() + cleanup() + + elif code == 2: + shoplog.info("%s has no free slots", s.player) + whisper(s.player, "You don't have free slots") + mapserv.cmsg_trade_cancel_request() + cleanup() + + else: + shoplog.error("Unknown ITEM_ADD_RESPONSE code: ", code) + mapserv.cmsg_trade_cancel_request() + cleanup() + + +@extends('smsg_trade_cancel') +def trade_cancel(data): + shoplog.error("Trade with %s canceled", s.player) + cleanup() + + +@extends('smsg_trade_ok') +def trade_ok(data): + who = data.who + + if who == 0: + return + + shoplog.info("Trade OK: %s", s.player) + + if s.mode == 'sell': + zeny_get = mapserv.trade_state['zeny_get'] + if zeny_get >= s.price: + mapserv.cmsg_trade_ok() + else: + whisper(s.player, "Your offer makes me sad") + mapserv.cmsg_trade_cancel_request() + cleanup() + + elif s.mode == 'buy': + items_get = {} + for item_id, amount in mapserv.trade_state['items_get']: + try: + items_get[item_id] += amount + except KeyError: + items_get[item_id] = amount + + if s.item_id in items_get and s.amount == items_get[s.item_id]: + mapserv.cmsg_trade_ok() + else: + whisper(s.player, "You should give me {} {}".format( + s.amount, item_name(s.item_id))) + mapserv.cmsg_trade_cancel_request() + cleanup() + + elif s.mode == 'retrieve': + mapserv.cmsg_trade_ok() + + else: + shoplog.error("Unknown shop state: %s", s.mode) + mapserv.cmsg_trade_cancel_request() + cleanup() + + +@extends('smsg_trade_complete') +def trade_complete(data): + if s.mode == 'sell': + shoplog.info("Trade with %s completed. I sold %d %s for %d GP", + s.player, s.amount, item_name(s.item_id), + mapserv.trade_state['zeny_get']) + elif s.mode == 'buy': + shoplog.info("Trade with %s completed. I bought %d %s for %d GP", + s.player, s.amount, item_name(s.item_id), + mapserv.trade_state['zeny_give']) + elif s.mode == 'retrieve': + shoplog.info("Trade with %s completed.", s.player) + else: + shoplog.info("Trade with %s completed. Unknown shop state %s", + s.player, s.mode) + + reset_trade_state(mapserv.trade_state) + + cleanup() + + +# ========================================================================= +def shop_logic(ts): + if s.start_time > 0: + if ts > s.start_time + trade_timeout: + shoplog.warning("%s timed out", s.player) + mapserv.cmsg_trade_cancel_request() + + +# ========================================================================= +shop_commands = { + '!selllist' : selllist, + '!buylist' : buylist, + '!sellitem (\d+) (\d+) (\d+)' : sellitem, + '!buyitem (\d+) (\d+) (\d+)' : buyitem, + '!retrieve (\d+) (\d+)' : retrieve, + '!invlist' : invlist, + '!zeny' : zeny, +} + + +def load_shop_list(config): + global buying + global selling + + shoplist_txt = config.get('shop', 'shoplist_txt') + if not os.path.isfile(shoplist_txt): + shoplog.warning('shoplist file not found : %s', shoplist_txt) + return + + with open(shoplist_txt, 'r') as f: + for l in f: + try: + item_id, buy_amount, buy_price, sell_amount, sell_price = \ + map(int, l.split()) + if buy_amount > 0: + buying[item_id] = buy_price, buy_amount + if sell_amount > 0: + selling[item_id] = sell_price, sell_amount + except ValueError: + pass + + +def init(config): + for cmd, action in shop_commands.items(): + chatbot.add_command(cmd, action) + + global trade_timeout + global shop_admins + + trade_timeout = config.getint('shop', 'timeout') + + shop_admins_file = config.get('shop', 'admins_file') + if os.path.isfile(shop_admins_file): + shop_admins = PlayerList(shop_admins_file) + + badge.is_shop = True + + load_shop_list(config) + logicmanager.logic_manager.add_logic(shop_logic) diff --git a/plugins/shop.pyc b/plugins/shop.pyc Binary files differnew file mode 100644 index 0000000..aa39471 --- /dev/null +++ b/plugins/shop.pyc |