summaryrefslogtreecommitdiff
path: root/plugins
diff options
context:
space:
mode:
authorLivio Recchia <recchialivio@libero.it>2020-02-10 23:06:34 +0100
committerLivio Recchia <recchialivio@libero.it>2020-02-10 23:06:34 +0100
commit9a13903a2f7d3a65fdf15a65fb59cccd622e2066 (patch)
tree9403b7dff39eb5e5d7fa0f79efb69b496add4c4b /plugins
parent11cc316b74d5f3f283413a33e7693b314741aa4a (diff)
downloadmanachat-9a13903a2f7d3a65fdf15a65fb59cccd622e2066.tar.gz
manachat-9a13903a2f7d3a65fdf15a65fb59cccd622e2066.tar.bz2
manachat-9a13903a2f7d3a65fdf15a65fb59cccd622e2066.tar.xz
manachat-9a13903a2f7d3a65fdf15a65fb59cccd622e2066.zip
Initial commit
Diffstat (limited to 'plugins')
-rw-r--r--plugins/README.txt23
-rw-r--r--plugins/__init__.py51
-rw-r--r--plugins/__init__.pycbin0 -> 1925 bytes
-rw-r--r--plugins/autofollow.py39
-rw-r--r--plugins/autofollow.pycbin0 -> 1266 bytes
-rw-r--r--plugins/autospell.py57
-rw-r--r--plugins/autospell.pycbin0 -> 1802 bytes
-rw-r--r--plugins/battlebot.py227
-rw-r--r--plugins/battlebot.pycbin0 -> 5768 bytes
-rw-r--r--plugins/chatbot.py71
-rw-r--r--plugins/chatbot.pycbin0 -> 2591 bytes
-rw-r--r--plugins/chatlogfile.py109
-rw-r--r--plugins/chatlogfile.pycbin0 -> 3908 bytes
-rw-r--r--plugins/guildbot/__init__.py18
-rw-r--r--plugins/guildbot/__init__.pycbin0 -> 763 bytes
-rw-r--r--plugins/guildbot/create_db.sql23
-rw-r--r--plugins/guildbot/guilddb.py153
-rw-r--r--plugins/guildbot/guilddb.pycbin0 -> 6575 bytes
-rw-r--r--plugins/guildbot/handlers.py326
-rw-r--r--plugins/guildbot/handlers.pycbin0 -> 10189 bytes
-rw-r--r--plugins/lazytree.py285
-rw-r--r--plugins/lazytree.pycbin0 -> 9158 bytes
-rw-r--r--plugins/manaboy.py1390
-rw-r--r--plugins/manaboy.pycbin0 -> 38016 bytes
-rw-r--r--plugins/msgqueue.py76
-rw-r--r--plugins/msgqueue.pycbin0 -> 2424 bytes
-rw-r--r--plugins/notify.py99
-rw-r--r--plugins/notify.pycbin0 -> 2803 bytes
-rw-r--r--plugins/npc.py135
-rw-r--r--plugins/npc.pycbin0 -> 4036 bytes
-rw-r--r--plugins/restapi.py149
-rw-r--r--plugins/shop.py551
-rw-r--r--plugins/shop.pycbin0 -> 12766 bytes
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
new file mode 100644
index 0000000..4469184
--- /dev/null
+++ b/plugins/__init__.pyc
Binary files differ
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
new file mode 100644
index 0000000..159fe47
--- /dev/null
+++ b/plugins/autofollow.pyc
Binary files differ
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
new file mode 100644
index 0000000..362aab6
--- /dev/null
+++ b/plugins/autospell.pyc
Binary files differ
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
new file mode 100644
index 0000000..c48b64d
--- /dev/null
+++ b/plugins/battlebot.pyc
Binary files differ
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
new file mode 100644
index 0000000..b327a9e
--- /dev/null
+++ b/plugins/chatbot.pyc
Binary files differ
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
new file mode 100644
index 0000000..c6fad9b
--- /dev/null
+++ b/plugins/chatlogfile.pyc
Binary files differ
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
new file mode 100644
index 0000000..f10c5b9
--- /dev/null
+++ b/plugins/guildbot/__init__.pyc
Binary files differ
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
new file mode 100644
index 0000000..9de8f66
--- /dev/null
+++ b/plugins/guildbot/guilddb.pyc
Binary files differ
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
new file mode 100644
index 0000000..a109f0c
--- /dev/null
+++ b/plugins/guildbot/handlers.pyc
Binary files differ
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
new file mode 100644
index 0000000..b7bb800
--- /dev/null
+++ b/plugins/lazytree.pyc
Binary files differ
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
new file mode 100644
index 0000000..a712707
--- /dev/null
+++ b/plugins/manaboy.pyc
Binary files differ
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
new file mode 100644
index 0000000..5814be1
--- /dev/null
+++ b/plugins/msgqueue.pyc
Binary files differ
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
new file mode 100644
index 0000000..db13ee8
--- /dev/null
+++ b/plugins/notify.pyc
Binary files differ
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
new file mode 100644
index 0000000..b0dec22
--- /dev/null
+++ b/plugins/npc.pyc
Binary files differ
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
new file mode 100644
index 0000000..aa39471
--- /dev/null
+++ b/plugins/shop.pyc
Binary files differ