mirror of
https://github.com/evennia/evennia.git
synced 2026-03-28 18:47:16 +01:00
Started implementing the Bot functionality.
This commit is contained in:
parent
3f1a461e29
commit
f9eece9749
8 changed files with 261 additions and 10 deletions
|
|
@ -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
133
src/players/bots.py
Normal 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(
|
||||
|
|
@ -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']
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
"""
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue