Started implementing the Bot functionality.

This commit is contained in:
Griatch 2014-02-19 00:13:05 +01:00
parent 3f1a461e29
commit f9eece9749
8 changed files with 261 additions and 10 deletions

View file

@ -3,7 +3,7 @@ Default Typeclass for Comms.
See objects.objects for more information on Typeclassing.
"""
from src.comms import Msg, TempMsg, ChannelDB
from src.comms import Msg, TempMsg
from src.typeclasses.typeclass import TypeClass
from src.utils import logger
from src.utils.utils import make_iter
@ -14,8 +14,8 @@ class Channel(TypeClass):
This is the base class for all Comms. Inherit from this to create different
types of communication channels.
"""
def __init__(self, dbobj):
super(Channel, self).__init__(dbobj)
# helper methods, for easy overloading
def channel_prefix(self, msg=None, emit=False):
"""
@ -107,6 +107,7 @@ class Channel(TypeClass):
"""
Run at channel creation.
"""
pass
def pre_join_channel(self, joiner):
"""
@ -132,6 +133,7 @@ class Channel(TypeClass):
"""
Run right after an object or player leaves a channel.
"""
pass
def pre_send_message(self, msg):
"""
@ -146,6 +148,7 @@ class Channel(TypeClass):
"""
Run after a message is sent to the channel.
"""
pass
def at_init(self):
"""
@ -155,6 +158,7 @@ class Channel(TypeClass):
in some way after being created but also after each server
restart or reload.
"""
pass
def distribute_message(self, msg, online=False):
"""
@ -165,7 +169,9 @@ class Channel(TypeClass):
for player in self.dbobj.db_subscriptions.all():
player = player.typeclass
try:
player.msg(msg.message, from_obj=msg.senders)
# note our addition of the from_channel keyword here. This could be checked
# by a custom player.msg() to treat channel-receives differently.
player.msg(msg.message, from_obj=msg.senders, from_channel=self)
except AttributeError:
try:
player.to_external(msg.message,

133
src/players/bots.py Normal file
View file

@ -0,0 +1,133 @@
"""
Bots are a special child typeclasses of
Player that are controlled by the server.
"""
from src.players.player import Player
from src.scripts.script import Script
from src.commands.command import Command
from src.commands.cmdset import CmdSet
from src.commands.cmdhandler import CMD_NOMATCH
_SESSIONS = None
class BotStarter(Script):
"""
This non-repeating script has the
sole purpose of kicking its bot
into gear when it is initialized.
"""
def at_script_creation(self):
self.key = "botstarter"
self.desc = "kickstarts bot"
self.persistent = True
self.db.started = False
def at_start(self):
"Kick bot into gear"
if not self.db.started:
self.obj.start()
self.db.started = False
def at_server_reload(self):
"""
If server reloads we don't need to start the bot again,
the Portal resync will do that for us.
"""
self.db.started = True
def at_server_shutdown(self):
"Make sure we are shutdown"
self.db.started = False
class CmdBotListen(Command):
"""
This is a catch-all command that absorbs
all input coming into the bot through its
session and pipes it into its execute_cmd
method.
"""
key = CMD_NOMATCH
def func(self):
text = self.cmdname + self.args
self.obj.execute_cmd(text, sessid=self.sessid)
class BotCmdSet(CmdSet):
"Holds the BotListen command"
key = "botcmdset"
def at_cmdset_creation(self):
self.add(CmdBotListen())
class Bot(Player):
"""
A Bot will start itself when the server
starts (it will generally not do so
on a reload - that will be handled by the
normal Portal session resync)
"""
def at_player_creation(self):
"""
Called when the bot is first created. It sets
up the cmdset and the botstarter script
"""
self.cmdset.add_default(BotCmdSet)
script_key = "botstarter_%s" % self.key
self.scripts.add(BotStarter, key=script_key)
self.is_bot = True
def start(self):
"""
This starts the bot, usually by connecting
to a protocol.
"""
pass
def msg(self, text=None, from_obj=None, sessid=None, **kwargs):
"""
Evennia -> outgoing protocol
"""
pass
def execute_cmd(self, raw_string, sessid=None):
"""
Incoming protocol -> Evennia
"""
pass
class IRCBot(Bot):
"""
Bot for handling IRC connections
"""
def start(self):
"Start by telling the portal to start a new session"
global _SESSIONS
if not _SESSIONS:
from src.server.sessionhandler import SESSIONS as _SESSIONS
# instruct the server and portal to create a new session
_SESSIONS.start_bot_session("src.server.portal.irc.IRCClient", self.id)
def connect_to_channel(self, channelname):
"""
Connect the bot to an Evennia channel
"""
pass
def msg(self, text=None, **kwargs):
"""
Takes text from connected channel (only)
"""
if "from_channel" in kwargs and text:
# a channel receive. This is the only one we deal with
channel = kwargs.pop("from_channel")
ckey = channel.key
text = "[%s] %s" % (ckey, text)
self.dbobj.msg(text=text)
def execute_cmd(

View file

@ -0,0 +1,91 @@
# -*- coding: utf-8 -*-
from south.utils import datetime_utils as datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding field 'PlayerDB.db_is_bot'
db.add_column(u'players_playerdb', 'db_is_bot',
self.gf('django.db.models.fields.BooleanField')(default=False),
keep_default=False)
def backwards(self, orm):
# Deleting field 'PlayerDB.db_is_bot'
db.delete_column(u'players_playerdb', 'db_is_bot')
models = {
u'auth.group': {
'Meta': {'object_name': 'Group'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
u'auth.permission': {
'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
u'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
u'players.playerdb': {
'Meta': {'object_name': 'PlayerDB'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'db_attributes': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['typeclasses.Attribute']", 'null': 'True', 'symmetrical': 'False'}),
'db_cmdset_storage': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}),
'db_date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'db_is_bot': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'db_is_connected': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'db_key': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
'db_lock_storage': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'db_tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['typeclasses.Tag']", 'null': 'True', 'symmetrical': 'False'}),
'db_typeclass_path': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
u'typeclasses.attribute': {
'Meta': {'object_name': 'Attribute'},
'db_attrtype': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '16', 'null': 'True', 'blank': 'True'}),
'db_category': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '128', 'null': 'True', 'blank': 'True'}),
'db_date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'db_key': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
'db_lock_storage': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'db_model': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '32', 'null': 'True', 'blank': 'True'}),
'db_strvalue': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'db_value': ('src.utils.picklefield.PickledObjectField', [], {'null': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
},
u'typeclasses.tag': {
'Meta': {'unique_together': "(('db_key', 'db_category'),)", 'object_name': 'Tag', 'index_together': "(('db_key', 'db_category'),)"},
'db_category': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True', 'db_index': 'True'}),
'db_data': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'db_key': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'db_index': 'True'}),
'db_model': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'db_index': 'True'}),
'db_tagtype': ('django.db.models.fields.CharField', [], {'max_length': '16', 'null': 'True', 'db_index': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
}
}
complete_apps = ['players']

View file

@ -95,6 +95,8 @@ class PlayerDB(TypedObject, AbstractUser):
# database storage of persistant cmdsets.
db_cmdset_storage = models.CharField('cmdset', max_length=255, null=True,
help_text="optional python path to a cmdset class. If creating a Character, this will default to settings.CMDSET_CHARACTER.")
# marks if this is a "virtual" bot player object
db_is_bot = models.BooleanField(default=False, verbose_name="is_bot", help_text="Used to identify irc/imc2/rss bots")
# Database manager
objects = manager.PlayerManager()

View file

@ -482,7 +482,7 @@ class Script(ScriptBase):
def at_stop(self):
"""
Called whenever when it's time for this script to stop
(either because is_valid returned False or )
(either because is_valid returned False or it runs out of iterations)
"""
pass

View file

@ -473,7 +473,7 @@ class AMPProtocol(amp.AMP):
# set a flag in case we are about to shut down soon
self.factory.server_restart_mode = True
elif operation == SCONN: # server_force_connection (for irc/imc2 etc)
portal_sessionhandler.server_connect(data)
portal_sessionhandler.server_connect(**data)
else:
raise Exception("operation %(op)s not recognized." % {'op': operation})
return {}

View file

@ -69,7 +69,7 @@ class PortalSessionHandler(SessionHandler):
operation=PDISCONN)
def server_connect(self, protocol_class_path):
def server_connect(self, protocol_path="", uid=None):
"""
Called by server to force the initialization of a new
protocol instance. Server wants this instance to get
@ -83,12 +83,12 @@ class PortalSessionHandler(SessionHandler):
in a property sessionhandler. It must have a
connectionMade() method, responsible for configuring
itself and then calling self.sessionhandler.connect(self)
like any other protocol.
like any other newly connected protocol.
"""
global _MOD_IMPORT
if not _MOD_IMPORT:
from src.utils.utils import variable_from_module as _MOD_IMPORT
path, clsname = protocol_class_path.rsplit(".", 1)
path, clsname = protocol_path.rsplit(".", 1)
cls = _MOD_IMPORT(path, clsname)
protocol = cls()
protocol.sessionhandler = self

View file

@ -37,6 +37,7 @@ SDISCONN = chr(5) # server session disconnect
SDISCONNALL = chr(6) # server session disconnect all
SSHUTD = chr(7) # server shutdown
SSYNC = chr(8) # server session sync
SCONN = chr(9) # server portal connection (for bots)
# i18n
from django.utils.translation import ugettext as _
@ -256,6 +257,25 @@ class ServerSessionHandler(SessionHandler):
# announce the reconnection
self.announce_all(_(" ... Server restarted."))
# server-side access methods
def start_bot_session(self, protocol_path, uid):
"""
This method allows the server-side to force the Portal to create
a new bot session using the protocol specified by protocol_path,
which should be the full python path to the class, including the
class name, like "src.server.portal.irc.IRCClient".
The new session will use the supplied player-bot uid to
initiate an already logged-in connection. The Portal will
treat this as a normal connection and henceforth so will the
Server.
"""
data = {"protocol_path":protocol_path,
"uid":uid}
self.server.amp_protocol.call_remote_PortalAdmin(0,
operation=SCONN,
data=data)
def portal_shutdown(self):
"""
Called by server when shutting down the portal.
@ -263,7 +283,6 @@ class ServerSessionHandler(SessionHandler):
self.server.amp_protocol.call_remote_PortalAdmin(0,
operation=SSHUTD,
data="")
# server-side access methods
def login(self, session, player, testmode=False):
"""