def test_disable_stale_removal(self): # manual removal of stale tasks. timedelay = self.timedelay _TASK_HANDLER.stale_timeout = 0 for pers in (False, True): t = utils.delay(timedelay, dummy_func, self.char1.dbref, persistent=pers) t.cancel() self.assertFalse(t.active()) _TASK_HANDLER.clock.advance(timedelay) # make time pass if pers: self.assertTrue(t.get_id() in _TASK_HANDLER.to_save) self.assertTrue(t.get_id() in _TASK_HANDLER.tasks) # Make task handler's now time, after the stale timeout _TASK_HANDLER._now = datetime.now() + timedelta(seconds=_TASK_HANDLER.stale_timeout + timedelay + 1) t2 = utils.delay(timedelay, dummy_func, self.char1.dbref) if pers: self.assertTrue(t.get_id() in _TASK_HANDLER.to_save) self.assertTrue(t.get_id() in _TASK_HANDLER.tasks) self.assertEqual(self.char1.ndb.dummy_var, False) # manual removal should still work _TASK_HANDLER.clean_stale_tasks() # cleanup of stale tasks in in the save method if pers: self.assertFalse(t.get_id() in _TASK_HANDLER.to_save) self.assertFalse(t.get_id() in _TASK_HANDLER.tasks) _TASK_HANDLER.clear()
def test_server_restart(self): # emulate a server restart timedelay = self.timedelay utils.delay(timedelay, dummy_func, self.char1.dbref, persistent=True) _TASK_HANDLER.clear(False) # remove all tasks from task handler, do not save this change. _TASK_HANDLER.clock.advance(timedelay) # advance twisted reactor time past callback time self.assertEqual(self.char1.ndb.dummy_var, False) # task has not run _TASK_HANDLER.load() # load persistent tasks from database. _TASK_HANDLER.create_delays() # create new deffered instances from persistent tasks _TASK_HANDLER.clock.advance(timedelay) # Clock must advance to trigger, even if past timedelay self.assertEqual(self.char1.ndb.dummy_var, 'dummy_func ran')
def _progressive_cmd_run(cmd, generator, response=None): """ Progressively call the command that was given in argument. Used when `yield` is present in the Command's `func()` method. Args: cmd (Command): the command itself. generator (GeneratorType): the generator describing the processing. reponse (str, optional): the response to send to the generator. Raises: ValueError: If the func call yields something not identifiable as a time-delay or a string prompt. Note: This function is responsible for executing the command, if the func() method contains 'yield' instructions. The yielded value will be accessible at each step and will affect the process. If the value is a number, just delay the execution of the command. If it's a string, wait for the user input. """ global _GET_INPUT if not _GET_INPUT: from evennia.utils.evmenu import get_input as _GET_INPUT try: if response is None: value = next(generator) else: value = generator.send(response) except StopIteration: # duplicated from cmdhandler._run_command, to have these # run in the right order while staying inside the deferred cmd.at_post_cmd() if cmd.save_for_next: # store a reference to this command, possibly # accessible by the next command. cmd.caller.ndb.last_cmd = copy(cmd) else: cmd.caller.ndb.last_cmd = None else: if isinstance(value, (int, float)): utils.delay(value, _progressive_cmd_run, cmd, generator) elif isinstance(value, str): _GET_INPUT(cmd.caller, value, _process_input, cmd=cmd, generator=generator) else: raise ValueError( "unknown type for a yielded value in command: {}".format( type(value)))
def at_start(self): """Set up the event system when starting. Note that this hook is called every time the server restarts (including when it's reloaded). This hook performs the following tasks: - Create temporarily stored events. - Generate locals (individual events' namespace). - Load eventfuncs, including user-defined ones. - Re-schedule tasks that aren't set to fire anymore. - Effectively connect the handler to the main script. """ self.ndb.events = {} for typeclass, name, variables, help_text, custom_call, custom_add in EVENTS: self.add_event(typeclass, name, variables, help_text, custom_call, custom_add) # Generate locals self.ndb.current_locals = {} self.ndb.fresh_locals = {} addresses = ["evennia.contrib.ingame_python.eventfuncs"] addresses.extend( getattr(settings, "EVENTFUNCS_LOCATIONS", ["world.eventfuncs"])) for address in addresses: if pypath_to_realpath(address): self.ndb.fresh_locals.update(all_from_module(address)) # Restart the delayed tasks now = datetime.now() for task_id, definition in tuple(self.db.tasks.items()): future, obj, event_name, locals = definition seconds = (future - now).total_seconds() if seconds < 0: seconds = 0 delay(seconds, complete_task, task_id) # Place the script in the CallbackHandler from evennia.contrib.ingame_python import typeclasses CallbackHandler.script = self DefaultObject.callbacks = typeclasses.EventObject.callbacks # Create the channel if non-existent try: self.ndb.channel = ChannelDB.objects.get(db_key="everror") except ChannelDB.DoesNotExist: self.ndb.channel = create_channel( "everror", desc="Event errors", locks="control:false();listen:perm(Builders);send:false()", )
def __init__(self, delay_in_seconds, callback=None, retval=None): """ Arguments: delay_in_seconds (int or float) - delay until callback is fired callback (func) - callback to call, if any, after delay elapses retval (any, or None) - any arguments to return or input to to callback """ if retval: self.deferred = delay(delay_in_seconds, callback, retval) else: self.deferred = delay(delay_in_seconds, callback)
def _do_flee(st_remaining, character, _, args): """Implements the 'flee' combat command.""" ch = character.ndb.combat_handler # fleeing takes two subturns if st_remaining > 0: # first subturn: messaging only ch.combat_msg( "{actor} looks about frantically for an escape route.", actor=character) else: # second subturn: skill check and resolution min_range = ch.get_min_range(character) if min_range == 'melee': # very difficult target_num = 9 elif min_range == 'reach': # moderately difficult target_num = 6 else: # min_range == 'ranged' # easiest target_num = 4 ok = skill_check(character.skills.escape.actual, target_num) if ok: # successfully escaped ch.combat_msg( "{actor} seizes the opportunity to escape the fight!", actor=character) ch.remove_character(character) character.msg("{actor} knows they are safe, for a time...".format( actor=character.get_display_name(character))) # prevent re-attack for a time character.ndb.no_attack = True safe_time = 2 * (character.skills.escape + d_roll('1d6')) def enable_attack(): del character.ndb.no_attack character.msg("{actor} feels somehow more vulnerable than just a moment ago.".format( actor=character.get_display_name(character))) utils.delay(safe_time, enable_attack) else: # failed to escape ch.combat_msg( "{actor} tries to escape, but is boxed in.", actor=character) return 1 * COMBAT_DELAY
def _do_flee(st_remaining, character, _, args): """Implements the 'flee' combat command.""" ch = character.ndb.combat_handler # fleeing takes two subturns if st_remaining > 0: # first subturn: messaging only ch.combat_msg("{actor} looks about frantically for an escape route.", actor=character) else: # second subturn: skill check and resolution min_range = ch.get_min_range(character) if min_range == 'melee': # very difficult target_num = 9 elif min_range == 'reach': # moderately difficult target_num = 6 else: # min_range == 'ranged' # easiest target_num = 4 ok = skill_check(character.skills.escape.actual, target_num) if ok: # successfully escaped ch.combat_msg( "{actor} seizes the opportunity to escape the fight!", actor=character) ch.remove_character(character) character.msg("{actor} knows they are safe, for a time...".format( actor=character.get_display_name(character))) # prevent re-attack for a time character.ndb.no_attack = True safe_time = 2 * (character.skills.escape + d_roll('1d6')) def enable_attack(): del character.ndb.no_attack character.msg( "{actor} feels somehow more vulnerable than just a moment ago." .format(actor=character.get_display_name(character))) utils.delay(safe_time, enable_attack) else: # failed to escape ch.combat_msg("{actor} tries to escape, but is boxed in.", actor=character) return 1 * COMBAT_DELAY
def connectionMade(self): """ This is called when the connection is first established. """ # initialize the session self.line_buffer = "" client_address = self.transport.client client_address = client_address[0] if client_address else None # this number is counted down for every handshake that completes. # when it reaches 0 the portal/server syncs their data self.handshakes = 8 # suppress-go-ahead, naws, ttype, mccp, mssp, msdp, gmcp, mxp self.init_session(self.protocol_key, client_address, self.factory.sessionhandler) self.protocol_flags["ENCODING"] = settings.ENCODINGS[ 0] if settings.ENCODINGS else 'utf-8' # add this new connection to sessionhandler so # the Server becomes aware of it. self.sessionhandler.connect(self) # change encoding to ENCODINGS[0] which reflects Telnet default encoding # suppress go-ahead self.sga = suppress_ga.SuppressGA(self) # negotiate client size self.naws = naws.Naws(self) # negotiate ttype (client info) # Obs: mudlet ttype does not seem to work if we start mccp before ttype. /Griatch self.ttype = ttype.Ttype(self) # negotiate mccp (data compression) - turn this off for wireshark analysis self.mccp = Mccp(self) # negotiate mssp (crawler communication) self.mssp = mssp.Mssp(self) # oob communication (MSDP, GMCP) - two handshake calls! self.oob = telnet_oob.TelnetOOB(self) # mxp support self.mxp = Mxp(self) from evennia.utils.utils import delay # timeout the handshakes in case the client doesn't reply at all delay(2, callback=self.handshake_done, timeout=True) # TCP/IP keepalive watches for dead links self.transport.setTcpKeepAlive(1) # The TCP/IP keepalive is not enough for some networks; # we have to complement it with a NOP keep-alive. self.protocol_flags["NOPKEEPALIVE"] = True self.nop_keep_alive = None self.toggle_nop_keepalive()
def at_start(self): """Set up the event system when starting. Note that this hook is called every time the server restarts (including when it's reloaded). This hook performs the following tasks: - Create temporarily stored events. - Generate locals (individual events' namespace). - Load eventfuncs, including user-defined ones. - Re-schedule tasks that aren't set to fire anymore. - Effectively connect the handler to the main script. """ self.ndb.events = {} for typeclass, name, variables, help_text, custom_call, custom_add in EVENTS: self.add_event(typeclass, name, variables, help_text, custom_call, custom_add) # Generate locals self.ndb.current_locals = {} self.ndb.fresh_locals = {} addresses = ["evennia.contrib.ingame_python.eventfuncs"] addresses.extend(getattr(settings, "EVENTFUNCS_LOCATIONS", ["world.eventfuncs"])) for address in addresses: if pypath_to_realpath(address): self.ndb.fresh_locals.update(all_from_module(address)) # Restart the delayed tasks now = datetime.now() for task_id, definition in tuple(self.db.tasks.items()): future, obj, event_name, locals = definition seconds = (future - now).total_seconds() if seconds < 0: seconds = 0 delay(seconds, complete_task, task_id) # Place the script in the CallbackHandler from evennia.contrib.ingame_python import typeclasses CallbackHandler.script = self DefaultObject.callbacks = typeclasses.EventObject.callbacks # Create the channel if non-existent try: self.ndb.channel = ChannelDB.objects.get(db_key="everror") except ChannelDB.DoesNotExist: self.ndb.channel = create_channel("everror", desc="Event errors", locks="control:false();listen:perm(Builders);send:false()")
def connectionMade(self): """ This is called when the connection is first established. """ # initialize the session self.line_buffer = "" client_address = self.transport.client client_address = client_address[0] if client_address else None # this number is counted down for every handshake that completes. # when it reaches 0 the portal/server syncs their data self.handshakes = 8 # suppress-go-ahead, naws, ttype, mccp, mssp, msdp, gmcp, mxp self.init_session(self.protocol_key, client_address, self.factory.sessionhandler) self.protocol_flags["ENCODING"] = settings.ENCODINGS[0] if settings.ENCODINGS else 'utf-8' # add this new connection to sessionhandler so # the Server becomes aware of it. self.sessionhandler.connect(self) # change encoding to ENCODINGS[0] which reflects Telnet default encoding # suppress go-ahead self.sga = suppress_ga.SuppressGA(self) # negotiate client size self.naws = naws.Naws(self) # negotiate ttype (client info) # Obs: mudlet ttype does not seem to work if we start mccp before ttype. /Griatch self.ttype = ttype.Ttype(self) # negotiate mccp (data compression) - turn this off for wireshark analysis self.mccp = Mccp(self) # negotiate mssp (crawler communication) self.mssp = mssp.Mssp(self) # oob communication (MSDP, GMCP) - two handshake calls! self.oob = telnet_oob.TelnetOOB(self) # mxp support self.mxp = Mxp(self) from evennia.utils.utils import delay # timeout the handshakes in case the client doesn't reply at all delay(2, callback=self.handshake_done, timeout=True) # TCP/IP keepalive watches for dead links self.transport.setTcpKeepAlive(1) # The TCP/IP keepalive is not enough for some networks; # we have to complement it with a NOP keep-alive. self.protocol_flags["NOPKEEPALIVE"] = True self.nop_keep_alive = None self.toggle_nop_keepalive()
def at_say(self, speaker, message): """ Called on this object if an object inside this object speaks. The string returned from this method is the final form of the speech. Args: speaker (Object): The object speaking. message (str): The words spoken. Returns: The message to be said (str) or None. Notes: You should not need to add things like 'you say: ' or similar here, that should be handled by the say command before this. """ allow = self.callbacks.call("can_say", speaker, self, message, parameters=message) if not allow: return message = self.callbacks.get_variable("message") # Call the event "can_say" of other characters in the location for present in [o for o in self.contents if isinstance( o, DefaultCharacter) and o is not speaker]: allow = present.callbacks.call("can_say", speaker, present, message, parameters=message) if not allow: return message = present.callbacks.get_variable("message") # We force the next event to be called after the message # This will have to change when the Evennia API adds new hooks delay(0, self.callbacks.call, "say", speaker, self, message, parameters=message) for present in [o for o in self.contents if isinstance( o, DefaultCharacter) and o is not speaker]: delay(0, present.callbacks.call, "say", speaker, present, message, parameters=message) return message
def test_active(self): timedelay = self.timedelay t = utils.delay(timedelay, dummy_func, self.char1.dbref) self.assertTrue(_TASK_HANDLER.active(t.get_id())) self.assertTrue(t.active()) _TASK_HANDLER.clock.advance(timedelay) # make time pass self.assertFalse(_TASK_HANDLER.active(t.get_id())) self.assertFalse(t.active())
def test_short_deferred_call(self): # wait for deferred to call with a very short time timedelay = .1 for pers in (False, True): t = utils.delay(timedelay, dummy_func, self.char1.dbref, persistent=pers) self.assertTrue(t.active()) _TASK_HANDLER.clock.advance(timedelay) # make time pass self.assertEqual(self.char1.ndb.dummy_var, 'dummy_func ran') self.assertFalse(t.exists()) self.char1.ndb.dummy_var = False
def test_do_task(self): # call the task early with do_task for pers in (True, False): t = utils.delay(self.timedelay, dummy_func, self.char1.dbref, persistent=pers) # call the task early to test Task.call and TaskHandler.call_task result = t.do_task() self.assertTrue(result) self.assertEqual(self.char1.ndb.dummy_var, 'dummy_func ran') self.assertFalse(t.exists()) self.char1.ndb.dummy_var = False
def test_call_early(self): # call a task early with call for pers in (True, False): t = utils.delay(self.timedelay, dummy_func, self.char1.dbref, persistent=pers) result = t.call() self.assertTrue(result) self.assertEqual(self.char1.ndb.dummy_var, 'dummy_func ran') self.assertTrue(t.exists()) self.assertTrue(t.active()) self.char1.ndb.dummy_var = False
def connectionMade(self): """ This is called when the connection is first established. """ # initialize the session self.iaw_mode = False self.no_lb_mode = False client_address = self.transport.client # this number is counted down for every handshake that completes. # when it reaches 0 the portal/server syncs their data self.handshakes = 7 # naws, ttype, mccp, mssp, msdp, gmcp, mxp self.init_session("telnet", client_address, self.factory.sessionhandler) # negotiate client size self.naws = naws.Naws(self) # negotiate ttype (client info) # Obs: mudlet ttype does not seem to work if we start mccp before ttype. /Griatch self.ttype = ttype.Ttype(self) # negotiate mccp (data compression) - turn this off for wireshark analysis self.mccp = Mccp(self) # negotiate mssp (crawler communication) self.mssp = mssp.Mssp(self) # oob communication (MSDP, GMCP) - two handshake calls! self.oob = telnet_oob.TelnetOOB(self) # mxp support self.mxp = Mxp(self) # keepalive watches for dead links self.transport.setTcpKeepAlive(1) self.transport.setTcpNoDelay(True) # add this new connection to sessionhandler so # the Server becomes aware of it. self.sessionhandler.connect(self) # timeout the handshakes in case the client doesn't reply at all from evennia.utils.utils import delay delay(2, callback=self.handshake_done, retval=True) # set up a keep-alive self.keep_alive = LoopingCall(self._write, IAC + NOP) self.keep_alive.start(30, now=False)
def set_task(self, seconds, obj, callback_name): """ Set and schedule a task to run. Args: seconds (int, float): the delay in seconds from now. obj (Object): the typecalssed object connected to the event. callback_name (str): the callback's name. Notes: This method allows to schedule a "persistent" task. 'utils.delay' is called, but a copy of the task is kept in the event handler, and when the script restarts (after reload), the differed delay is called again. The dictionary of locals is frozen and will be available again when the task runs. This feature, however, is limited by the database: all data cannot be saved. Lambda functions, class methods, objects inside an instance and so on will not be kept in the locals dictionary. """ now = datetime.now() delta = timedelta(seconds=seconds) # Choose a free task_id used_ids = list(self.db.tasks.keys()) task_id = 1 while task_id in used_ids: task_id += 1 # Collect and freeze current locals locals = {} for key, value in self.ndb.current_locals.items(): try: dbserialize(value) except TypeError: continue else: locals[key] = value self.db.tasks[task_id] = (now + delta, obj, callback_name, locals) delay(seconds, complete_task, task_id)
def test_remove(self): timedelay = self.timedelay for pers in (False, True): t = utils.delay(timedelay, dummy_func, self.char1.dbref, persistent=pers) self.assertTrue(t.active()) success = t.remove() self.assertTrue(success) self.assertFalse(t.active()) self.assertFalse(t.exists()) _TASK_HANDLER.clock.advance(timedelay) # make time pass self.assertEqual(self.char1.ndb.dummy_var, False)
def test_auto_stale_task_removal(self): # automated removal of stale tasks. timedelay = self.timedelay for pers in (False, True): t = utils.delay(timedelay, dummy_func, self.char1.dbref, persistent=pers) t.cancel() self.assertFalse(t.active()) _TASK_HANDLER.clock.advance(timedelay) # make time pass if pers: self.assertTrue(t.get_id() in _TASK_HANDLER.to_save) self.assertTrue(t.get_id() in _TASK_HANDLER.tasks) # Make task handler's now time, after the stale timeout _TASK_HANDLER._now = datetime.now() + timedelta(seconds=_TASK_HANDLER.stale_timeout + timedelay + 1) # add a task to test automatic removal t2 = utils.delay(timedelay, dummy_func, self.char1.dbref) if pers: self.assertFalse(t.get_id() in _TASK_HANDLER.to_save) self.assertFalse(t.get_id() in _TASK_HANDLER.tasks) self.assertEqual(self.char1.ndb.dummy_var, False) _TASK_HANDLER.clear()
def portal_connect(self, portalsessiondata): """ Called by Portal when a new session has connected. Creates a new, unlogged-in game session. Args: portalsessiondata (dict): a dictionary of all property:value keys defining the session and which is marked to be synced. """ delayed_import() global _ServerSession, _AccountDB, _ScriptDB sess = _ServerSession() sess.sessionhandler = self sess.load_sync_data(portalsessiondata) sess.at_sync() # validate all scripts _ScriptDB.objects.validate() self[sess.sessid] = sess if sess.logged_in and sess.uid: # Session is already logged in. This can happen in the # case of auto-authenticating protocols like SSH or # webclient's session sharing account = _AccountDB.objects.get_account_from_uid(sess.uid) if account: # this will set account.is_connected too self.login(sess, account, force=True) return else: sess.logged_in = False sess.uid = None # show the first login command # this delay is necessary notably for Mudlet, which will fail on the connection screen # unless the MXP protocol has been negotiated. Unfortunately this may be too short for some # networks, the symptom is that < and > are not parsed by mudlet on first connection. delay(0.3, self._run_cmd_login, sess)
def _progressive_cmd_run(cmd, generator, response=None): """ Progressively call the command that was given in argument. Used when `yield` is present in the Command's `func()` method. Args: cmd (Command): the command itself. generator (GeneratorType): the generator describing the processing. reponse (str, optional): the response to send to the generator. Raises: ValueError: If the func call yields something not identifiable as a time-delay or a string prompt. Note: This function is responsible for executing the command, if the func() method contains 'yield' instructions. The yielded value will be accessible at each step and will affect the process. If the value is a number, just delay the execution of the command. If it's a string, wait for the user input. """ global _GET_INPUT if not _GET_INPUT: from evennia.utils.evmenu import get_input as _GET_INPUT try: if response is None: value = generator.next() else: value = generator.send(response) except StopIteration: pass else: if isinstance(value, (int, float)): utils.delay(value, _progressive_cmd_run, cmd, generator) elif isinstance(value, basestring): _GET_INPUT(cmd.caller, value, _process_input, cmd=cmd, generator=generator) else: raise ValueError("unknown type for a yielded value in command: {}".format(type(value)))
def portal_connect(self, portalsessiondata): """ Called by Portal when a new session has connected. Creates a new, unlogged-in game session. Args: portalsessiondata (dict): a dictionary of all property:value keys defining the session and which is marked to be synced. """ delayed_import() global _ServerSession, _AccountDB, _ScriptDB sess = _ServerSession() sess.sessionhandler = self sess.load_sync_data(portalsessiondata) sess.at_sync() # validate all scripts _ScriptDB.objects.validate() self[sess.sessid] = sess if sess.logged_in and sess.uid: # Session is already logged in. This can happen in the # case of auto-authenticating protocols like SSH or # webclient's session sharing account = _AccountDB.objects.get_account_from_uid(sess.uid) if account: # this will set account.is_connected too self.login(sess, account, force=True) return else: sess.logged_in = False sess.uid = None # show the first login command, may delay slightly to allow # the handshakes to finish. delay(_DELAY_CMD_LOGINSTART, self._run_cmd_login, sess)
def test_pause_unpause(self): # remove a canceled task timedelay = self.timedelay for pers in (False, True): t = utils.delay(timedelay, dummy_func, self.char1.dbref, persistent=pers) self.assertTrue(t.active()) t.pause() self.assertTrue(t.paused) t.unpause() self.assertFalse(t.paused) self.assertEqual(self.char1.ndb.dummy_var, False) t.pause() _TASK_HANDLER.clock.advance(timedelay) # make time pass self.assertEqual(self.char1.ndb.dummy_var, False) t.unpause() self.assertEqual(self.char1.ndb.dummy_var, 'dummy_func ran') self.char1.ndb.dummy_var = False
def at_say(self, speaker, message): """ Called on this object if an object inside this object speaks. The string returned from this method is the final form of the speech. Args: speaker (Object): The object speaking. message (str): The words spoken. Returns: The message to be said (str) or None. Notes: You should not need to add things like 'you say: ' or similar here, that should be handled by the say command before this. """ allow = self.callbacks.call("can_say", speaker, self, message, parameters=message) if not allow: return message = self.callbacks.get_variable("message") # Call the event "can_say" of other characters in the location for present in [ o for o in self.contents if isinstance(o, DefaultCharacter) and o is not speaker ]: allow = present.callbacks.call("can_say", speaker, present, message, parameters=message) if not allow: return message = present.callbacks.get_variable("message") # We force the next event to be called after the message # This will have to change when the Evennia API adds new hooks delay(0, self.callbacks.call, "say", speaker, self, message, parameters=message) for present in [ o for o in self.contents if isinstance(o, DefaultCharacter) and o is not speaker ]: delay(0, present.callbacks.call, "say", speaker, present, message, parameters=message) return message
def process_next_action(combat_handler): """ Callback that handles processing combat actions Args: combat_handler: instance of a combat handler Returns: None Based on the combat handler's data, this callback selects an appropriate `_do_*` function to execute the combat action. These handlers are all called with the signature: `_do_action(subturns_remaining, character, target, args)` Each `_do_*` function should return a time delay in seconds before the next call to `process_next_action` should be run. """ turn_actions = combat_handler.db.turn_actions turn_order = combat_handler.db.turn_order actor_idx = combat_handler.db.actor_idx delay = 0 if actor_idx >= len(turn_order): # finished a subturn # reset counters and see who is dodging during the next action combat_handler.ndb.actions_taken = defaultdict(int) combat_handler.db.actor_idx = actor_idx = 0 for dbref, char in combat_handler.db.characters.items(): if char.nattributes.has('dodging'): del char.ndb.dodging action_count = 0 # set the dodging nattribute on any characters # with 'dodge' as their action for action, _, _, duration in combat_handler.db.turn_actions[dbref]: if action == 'dodge': char.ndb.dodging = True break action_count += duration if action_count >= 1: break # and increment the subturn combat_handler.db.subturn += 1 if combat_handler.db.subturn > ACTIONS_PER_TURN: # turn is over; notify the handler to start the next combat_handler.begin_turn() return current_charid = turn_order[actor_idx] if combat_handler.ndb.actions_taken[current_charid] < 1: action, character, target, duration = \ turn_actions[current_charid].popleft() combat_handler.ndb.actions_taken[current_charid] += duration args = action.split('/')[1:] # we decrement the duration for this turn duration -= 1 if duration > 0: # action takes more than one subturn; return it to the queue turn_actions[current_charid].append( (action, character, target, duration) ) combat_handler.db.actor_idx += 1 action_func = '_do_{}'.format(action.split('/')[0]) # action_func receives the number of subturns remaining in the action delay = globals()[action_func](duration, character, target, args) else: combat_handler.db.actor_idx += 1 if len(combat_handler.db.characters) < 2: combat_handler.stop() else: utils.delay(delay, process_next_action, combat_handler)
def func(self): """Implement function using the Msg methods""" char = self.character sent_messages = Msg.objects.get_messages_by_sender(char, exclude_channel_messages=True) recd_messages = Msg.objects.get_messages_by_receiver(char) if 'last' in self.switches: self.mail_check() if sent_messages: recv = ', '.join('%s%s|n' % (obj.STYLE, obj.key) for obj in sent_messages[-1].receivers) self.msg("You last mailed |w%s|n: |w%s" % (recv, sent_messages[-1].message)) else: self.msg("You haven't mailed anyone yet.") self.mail_check() return if 'check' in self.switches: if not self.mail_check(): if not ('silent' in self.switches and 'quiet' in self.switches): self.msg('Your %s mailbox has no new mail.' % char.location.get_display_name(self.character)) if not self.args or not self.rhs: mail = sent_messages + recd_messages mail.sort(lambda x, y: cmp(x.db_date_created, y.db_date_created)) number = 5 if self.args: try: number = int(self.args) except ValueError: self.msg("Usage: mail [<character> = msg]") return if len(mail) > number: mail_last = mail[-number:] else: mail_last = mail template = "|w%s|n |w%s|n to |w%s|n: %s" mail_last = "\n ".join(template % (utils.datetime_format(mail.date_created), ', '.join('%s' % obj.get_display_name(self.character) for obj in mail.senders), ', '.join(['%s' % obj.get_display_name(self.character) for obj in mail.receivers]), mail.message) for mail in mail_last) if mail_last: string = "Your latest letters:\n %s" % mail_last else: string = "You haven't mailed anyone yet." self.msg(string) char.nattributes.remove('new_mail') # Removes the notice. return # Send mode if not self.lhs: if sent_messages: # If no recipients provided, receivers = sent_messages[-1].receivers # default to sending to the last character mailed. else: self.msg("Who do you want to mail?") return else: # Build a list of comma-delimited recipients. receivers = self.lhslist rec_objs = [] received = [] r_strings = [] for receiver in set(receivers): if isinstance(receiver, basestring): c_obj = char.search(receiver, global_search=True, exact=True) elif hasattr(receiver, 'location'): c_obj = receiver else: self.msg("Who do you want to mail?") return if c_obj: if not c_obj.access(char, 'mail'): r_strings.append("You are not able to mail %s." % c_obj) continue rec_objs.append(c_obj) if not rec_objs: self.msg("No one found to mail.") return message = self.rhs.strip() if message.startswith(':'): # Format as pose if message begins with a : message = "%s%s|n %s" % (char.STYLE, char.key, message.strip(':')) create.create_message(char, message, receivers=rec_objs) def letter_delivery(): # c_obj.msg('%s %s' % (header, message)) c_obj.msg('|/A letter has arrived in %s%s|n mailbox for you.|/' % (c_obj.home.STYLE, c_obj.home.key)) for c_obj in rec_objs: # Notify character of mail delivery. received.append('%s%s|n' % (c_obj.STYLE, c_obj.key)) if hasattr(c_obj, 'sessions') and not c_obj.sessions.count(): r_strings.append("|r%s|n is currently asleep, and won't read the letter until later." % received[-1]) c_obj.ndb.new_mail = True else: # Tell the receiving characters about receiving a letter if they are online. utils.delay(20, callback=letter_delivery) if r_strings: self.msg("\n".join(r_strings)) stamp_count = len(rec_objs) stamp_plural = 'a stamp' if stamp_count == 1 else '%i stamps' % stamp_count self.msg('Mail delivery costs %s.' % stamp_plural) char.location.msg_contents('|g%s|n places %s on an envelope and slips it into the %s%s|n mailbox.' % (char.key, stamp_plural, char.location.STYLE, char.location.key)) self.msg("Your letter to %s will be delivered soon. You wrote: %s" % (', '.join(received), message)) self.mail_check()
def _do_strike(st_remaining, character, target, args): """Implements the 'strike' combat command.""" ch = character.ndb.combat_handler if character.db.position != 'STANDING' and 'end' not in args: # if you are in any wrestling position other # than free standing, all you can do is wrestle to break free return _do_wrestle(character, target, ['break']) # confirm the target is still in combat, if target.id not in ch.db.characters: character.msg("{defender} has left combat.".format( defender=target.get_display_name(character))) return 0.2 * COMBAT_DELAY # is within range, attack_range = ch.get_range(character, target) if attack_range != 'melee': ch.combat_msg( "{actor} is too far away from {target} to strike them.", actor=character, target=target) return 0.2 * COMBAT_DELAY # and has at least one free hand strikes = sum(1 if character.equip.get(slot) is None else 0 for slot in character.db.slots if slot.startswith('wield')) if strikes <= 0: ch.combat_msg( "{actor} goes to punch, but does not have a free hand.", actor=character) return 0.2 * COMBAT_DELAY # check whether the target is dodging dodging = target.nattributes.has('dodging') if dodging: # attacker must take the worse of two standard rolls atk_roll = min((std_roll(), std_roll())) else: atk_roll = std_roll() damage = (atk_roll + character.traits.ATKU) - target.traits.DEF if damage > 0: if dodging: ch.combat_msg( "{actor} tries to dodge, but", actor=target ) if any((arg.startswith('s') for arg in args)): # 'subdue': deduct stamina instead of health if target.traits.SP >= damage: target.traits.SP.current -= damage status = 'dmg_sp' else: # if we don't have enough SP, damages HP instead damage = damage - target.traits.SP target.traits.SP.current = 0 target.traits.HP.current -= damage status = 'dmg_hp' else: target.traits.HP.current -= damage status = 'dmg_hp' else: if dodging: status = 'dodged' else: status = 'missed' messages = { 'dmg_hp': "{actor} strikes {target} savagely with their fist.", 'dmg_sp': "{actor} strikes {target} hard in the chest, and {target} " "staggers from the blow.", 'dodged': "{target} dodges a punch from {actor}.", 'missed': "{actor} attempts to punch {target} and misses." } ch.combat_msg( messages[status], actor=character, target=target) # handle Power Points if atk_roll > 0: # this attack has earned PPs character.traits.PP.current += atk_roll if character.traits.PP.actual > 0: # handle equipment abilities pass if target.traits.HP.actual <= 0: # target has died resolve_death(character, target, ch) if 'end' not in args: if strikes > 1: # we have two free hands; do a second strike args.append('end') utils.delay(0.5 * COMBAT_DELAY, _do_strike, 0, character, target, args) return 1 * COMBAT_DELAY
def test_called(self): timedelay = self.timedelay t = utils.delay(timedelay, dummy_func, self.char1.dbref) self.assertFalse(t.called) _TASK_HANDLER.clock.advance(timedelay) # make time pass self.assertTrue(t.called)
def password(caller, input): """Ask the user to enter the password to this account. This is assuming the user exists (see 'create_username' and 'create_password'). This node "loops" if needed: if the user specifies a wrong password, offers the user to try again or to go back by entering 'b'. If the password is correct, then login. """ input = input.strip() text = "" options = ({ "key": "_default", "desc": "Enter your password.", "goto": "password", }, ) # Check the password account = caller.db._account # If the account is locked, the user has to wait (maximum # 3 seconds) before retrying if account.db._locked: text = "|gPlease wait, you cannot enter your password yet.|n" return text, options bans = ServerConfig.objects.conf("server_bans") banned = bans and (any(tup[0] == account.name.lower() for tup in bans) \ or any(tup[2].match(caller.address) for tup in bans if tup[2])) if not account.check_password(input): text = dedent(""" |rIncorrect password.|n Type |wb|n to go back to the login screen. Or wait 3 seconds before trying a new password. """.strip("\n")) # Loops on the same node, lock for 3 seconds account.db._locked = True delay(3, _wrong_password, account) options = ( { "key": "b", "desc": "Go back to the login screen.", "goto": "pre_start", }, { "key": "_default", "desc": "Enter your password again.", "goto": "password", }, ) elif banned: # This is a banned IP or name string = dedent(""" |rYou have been banned and cannot continue from here.|n If you feel this ban is in error, please email an admin. """.strip("\n")) caller.msg(string) caller.sessionhandler.disconnect(caller, "Good bye! Disconnecting...") else: # The password is correct, we can log into the account. caller.msg(echo=True) if not account.email: # Redirects to the node to set an email address text = _text_email_address(account) options = ({ "key": "_default", "desc": "Enter your email address.", "goto": "email_address", }, ) elif not account.db.valid: # Redirects to the node for the validation code text = "Enter your 4-digit validation code." options = ({ "key": "_default", "desc": "Enter your validation code.", "goto": "validate_account", }, ) else: _login(caller, account) text = "" options = _options_choose_characters(account) if not account.db._playable_characters: options = ({ "key": "_default", "desc": "Enter your new character's first name.", "goto": "create_first_name", }, ) return text, options
def func(self): """Performs the summon, accounting for in-world conditions.""" char = self.character loc = char.location account = self.account args = self.args lhs, rhs = self.lhs, self.rhs # opt = self.switches # TODO - add code to make the switches work. if char and char.ndb.currently_moving: account.msg("You can not summon while moving. (|rstop|n, then try again.)") return if not args: char.msg("Could not find target to summon. Usage: summon <character or NPC>") return session_list = SESSIONS.get_sessions() target = [] for session in session_list: if not (session.logged_in and session.get_puppet()): continue puppet = session.get_puppet() if lhs.lower() in puppet.get_display_name(char, plain=True).lower(): target.append(puppet) if len(target) < 1: char.msg("Error: character not found.") return if len(target) > 1: # Too many partial matches, try exact matching. char.msg("Error: character not found.") return else: target[0].msg("You are being summoned to %s " % loc.get_display_name(target[0]) + "by %s" % char.get_display_name(target[0]) + ". A portal should appear soon that will take you to " + "%s." % char.get_display_name(target[0])) char.msg("You begin to summon a portal between %s" % target[0].get_display_name(char) + " and your location.") # Check the pool for objects to use. Filter a list of objects tagged "pool" by those located in None. obj_pool = [each for each in evennia.search_tag('pool', category='portal') if not each.location] print('Object pool total: %i' % len(obj_pool)) if len(obj_pool) < 2: char.msg('Portals are currently out of stock or in use elsewhere.') return portal_enter, portal_exit = obj_pool[-2:] def open_portal(): """Move inflatable portals into place.""" portal_enter.move_to(target[0].location) portal_exit.move_to(loc) delay(10, callback=open_portal) # 10 seconds later, the portal (exit pair) appears. def close_portal(): """Remove and store inflatable portals in Nothingness.""" for each in portal_enter.contents: each.move_to(target[0].location) for each in portal_exit.contents: each.move_to(loc) portal_enter.location.msg_contents("|r%s|n vanishes into |222Nothingness|n." % portal_enter) portal_exit.location.msg_contents("|r%s|n vanishes into |222Nothingness|n." % portal_exit) portal_enter.move_to(None, to_none=True) portal_exit.move_to(None, to_none=True) delay(180, callback=close_portal) # Wait, then move portal objects to the portal pool in Nothingness
def at_traverse(self, traversing_object, target_location, **kwargs): """ This implements the actual traversal. The traverse lock has already been checked (in the Exit command) at this point. Args: traversing_object (Object): Object traversing us. target_location (Object): Where target is going. Returns: bool: True if the traverse is allowed to happen """ currently_moving = traversing_object.ndb.currently_moving if currently_moving and not currently_moving.called: currently_moving.cancel() traversing_object.db.travel_direction = None itemcoordinates = self.location.wilderness.db.itemcoordinates current_coordinates = itemcoordinates[traversing_object] new_coordinates = get_new_coordinates(current_coordinates, self.key) if not self.at_traverse_coordinates( traversing_object, current_coordinates, new_coordinates ): return False if not traversing_object.at_before_move(None): return False string = "{object} va verso {direction}." mapping = {"object": traversing_object, "direction": self.key} room = traversing_object.location room.msg_contents(string, exclude=(traversing_object,), mapping=mapping) script = WildernessScript.objects.get( db_key=traversing_object.db.last_wilderness ) if not script.is_valid_coordinates(new_coordinates): self.at_failed_traverse(traversing_object) return False def move_callback(): self.location.wilderness.move_obj(traversing_object, new_coordinates) opposite = { "nord": "sud", "nordest": "sudovest", "est": "ovest", "sudest": "nordovest", "sud": "nord", "sudovest": "nordest", "ovest": "est", "nordovest": "sudest", } string = "{object} arriva da {direction}." mapping = {"object": traversing_object, "direction": opposite[self.key]} room = traversing_object.location room.msg_contents(string, exclude=(traversing_object,), mapping=mapping) traversing_object.at_after_move(None) move_speed = traversing_object.db.move_speed or "cammina" move_delay = slow_exit.MOVE_DELAY.get(move_speed, 2) if self.location.db.water: move_speed = "nuota" move_delay = slow_exit.MOVE_DELAY.get(move_speed, 2) if kwargs.get("forced"): traversing_object.msg("\n|cLa corrente ti spinge verso %s.|n" % self.key) move_delay = 2 else: traversing_object.msg( "Inizi a %s verso %s." % (slow_exit.SPEED_VERBS[move_speed], self.key) ) deferred = utils.delay(move_delay, move_callback) traversing_object.ndb.currently_moving = deferred return True
def _do_strike(st_remaining, character, target, args): """Implements the 'strike' combat command.""" ch = character.ndb.combat_handler if character.db.position != 'STANDING' and 'end' not in args: # if you are in any wrestling position other # than free standing, all you can do is wrestle to break free return _do_wrestle(character, target, ['break']) # confirm the target is still in combat, if target.id not in ch.db.characters: character.msg("{defender} has left combat.".format( defender=target.get_display_name(character))) return 0.2 * COMBAT_DELAY # is within range, attack_range = ch.get_range(character, target) if attack_range != 'melee': ch.combat_msg("{actor} is too far away from {target} to strike them.", actor=character, target=target) return 0.2 * COMBAT_DELAY # and has at least one free hand strikes = sum(1 if character.equip.get(slot) is None else 0 for slot in character.db.slots if slot.startswith('wield')) if strikes <= 0: ch.combat_msg("{actor} goes to punch, but does not have a free hand.", actor=character) return 0.2 * COMBAT_DELAY # check whether the target is dodging dodging = target.nattributes.has('dodging') if dodging: # attacker must take the worse of two standard rolls atk_roll = min((std_roll(), std_roll())) else: atk_roll = std_roll() damage = (atk_roll + character.traits.ATKU) - target.traits.DEF if damage > 0: if dodging: ch.combat_msg("{actor} tries to dodge, but", actor=target) if any((arg.startswith('s') for arg in args)): # 'subdue': deduct stamina instead of health if target.traits.SP >= damage: target.traits.SP.current -= damage status = 'dmg_sp' else: # if we don't have enough SP, damages HP instead damage = damage - target.traits.SP target.traits.SP.current = 0 target.traits.HP.current -= damage status = 'dmg_hp' else: target.traits.HP.current -= damage status = 'dmg_hp' else: if dodging: status = 'dodged' else: status = 'missed' messages = { 'dmg_hp': "{actor} strikes {target} savagely with their fist.", 'dmg_sp': "{actor} strikes {target} hard in the chest, and {target} " "staggers from the blow.", 'dodged': "{target} dodges a punch from {actor}.", 'missed': "{actor} attempts to punch {target} and misses." } ch.combat_msg(messages[status], actor=character, target=target) # handle Power Points if atk_roll > 0: # this attack has earned PPs character.traits.PP.current += atk_roll if character.traits.PP.actual > 0: # handle equipment abilities pass if target.traits.HP.actual <= 0: # target has died resolve_death(character, target, ch) if 'end' not in args: if strikes > 1: # we have two free hands; do a second strike args.append('end') utils.delay(0.5 * COMBAT_DELAY, _do_strike, 0, character, target, args) return 1 * COMBAT_DELAY
def func(self): """ Performs the summon, accounting for in-world conditions. join: Implies one way portal to target summon: Implies one way portal to summoner meet: Implies two-way portal, both can meet. """ char = self.character cmd = self.cmdstring loc = char.location account = self.account args = self.args lhs, rhs = self.lhs, self.rhs opt = self.switches message_private = ' in a private room that does not allow portals to form.' if char and char.ndb.currently_moving: account.msg( "You can not open a portal while moving. (|lcstop|lt|rStop|n|le, then try again.)" ) return if not args and 'vanish' not in opt: char.msg('Usage: {} <character or NPC>'.format(cmd)) return session_list = SESSIONS.get_sessions() target = [] # Check for private flag on source room. It must be controlled by summoner if private. if loc.tags.get('private', category='flags') and not loc.access(char, 'control'): char.msg('You are' + message_private) return # Check object pool filtered by tagged "pool" and located in None. obj_pool = [ each for each in evennia.search_tag('pool', category='portal') if not each.location ] print('Object pool total: %i' % len(obj_pool)) if len(obj_pool) < 2: char.msg('Portals are currently out of stock or in use elsewhere.') return portal_enter, portal_exit = obj_pool[-2:] for session in session_list: if not (session.logged_in and session.get_puppet()): continue puppet = session.get_puppet() if lhs.lower() in puppet.get_display_name(char, plain=True).lower(): target.append(puppet) if len(target) < 1: char.msg("Specific character name not found.") return elif len(target) > 1: # Too many partial matches, try exact matching. char.msg("Unique character name not found.") return first_target = target[0] target = list(set(target)) # Remove duplicate character sessions target = first_target # Check for private flag on destination room. If so, check for in/out locks. there = target.location if there and there.tags.get( 'private', category='flags') and not there.access(char, 'control'): char.msg('Destination of portal is' + message_private) return # Check if A can walk to B, or B to A depending on meet or summon, # because sometimes a portal might not be needed. meet_message = 'You are being invited to meet {summoner} in {loc}.' join_message = 'You are being joined by {summoner} from {loc}.' summon_message = 'You are being summoned to {loc} by {summoner}.' message = meet_message if 'meet' in cmd else ( summon_message if 'summon' in cmd else join_message) loc_name = loc.get_display_name(target) target_name = target.get_display_name(char) char_name = char.get_display_name(target) target.msg(message.format(summoner=char_name, loc=loc_name)) target.msg('A portal should appear soon.') char.msg("You begin to open a portal connecting %s" % target_name + " and your location.") def open_portal(): """Move inflatable portals into place.""" # If in or out, join or summon, lock portals, depending. enter_lock, exit_lock = 'all()', 'all()' if 'only' in opt: enter_lock = 'id({}) OR id({})'.format(target.id, char.id) exit_lock = 'id({}) OR id({})'.format(target.id, char.id) if 'in' in opt or 'join' in cmd: enter_lock = 'none()' if 'out' in opt or 'summon' in cmd: exit_lock = 'none()' portal_enter.locks.add('enter:' + enter_lock) portal_exit.locks.add('enter:' + exit_lock) quiet = True if ('quiet' in opt or 'silent' in opt) else False portal_enter.move_to(target.location, quiet=quiet) if quiet: target.msg('{} quietly appears in {}.'.format( portal_enter.get_display_name(target), loc_name)) char.msg('{} quietly appears in {}.'.format( portal_exit.get_display_name(char), loc.get_display_name(char))) portal_exit.move_to(loc, quiet=quiet) delay(10, callback=open_portal ) # 10 seconds later, the portal (exit pair) appears. def close_portal(): """Remove and store inflatable portals in Nothingness.""" vanish_message = '|r{}|n vanishes into ' + settings.NOTHINGNESS + '.' for every in portal_enter.contents: every.move_to(target.location) for every in portal_exit.contents: every.move_to(loc) # if not quiet: if portal_enter.location: portal_enter.location.msg_contents( vanish_message.format(portal_enter)) portal_enter.move_to(None, to_none=True) # , quiet=quiet) if portal_exit.location: portal_exit.location.msg_contents( vanish_message.format(portal_exit)) portal_exit.move_to(None, to_none=True) # , quiet=quiet) delay( 180, callback=close_portal ) # Wait, then move portal objects to the portal pool in Nothingness
def process_next_action(combat_handler): """ Callback that handles processing combat actions Args: combat_handler: instance of a combat handler Returns: None Based on the combat handler's data, this callback selects an appropriate `_do_*` function to execute the combat action. These handlers are all called with the signature: `_do_action(subturns_remaining, character, target, args)` Each `_do_*` function should return a time delay in seconds before the next call to `process_next_action` should be run. """ turn_actions = combat_handler.db.turn_actions turn_order = combat_handler.db.turn_order actor_idx = combat_handler.db.actor_idx delay = 0 if actor_idx >= len(turn_order): # finished a subturn # reset counters and see who is dodging during the next action combat_handler.ndb.actions_taken = defaultdict(int) combat_handler.db.actor_idx = actor_idx = 0 for dbref, char in combat_handler.db.characters.items(): if char.nattributes.has('dodging'): del char.ndb.dodging action_count = 0 # set the dodging nattribute on any characters # with 'dodge' as their action for action, _, _, duration in combat_handler.db.turn_actions[ dbref]: if action == 'dodge': char.ndb.dodging = True break action_count += duration if action_count >= 1: break # and increment the subturn combat_handler.db.subturn += 1 if combat_handler.db.subturn > ACTIONS_PER_TURN: # turn is over; notify the handler to start the next combat_handler.begin_turn() return current_charid = turn_order[actor_idx] if combat_handler.ndb.actions_taken[current_charid] < 1: action, character, target, duration = \ turn_actions[current_charid].popleft() combat_handler.ndb.actions_taken[current_charid] += duration args = action.split('/')[1:] # we decrement the duration for this turn duration -= 1 if duration > 0: # action takes more than one subturn; return it to the queue turn_actions[current_charid].append( (action, character, target, duration)) combat_handler.db.actor_idx += 1 action_func = '_do_{}'.format(action.split('/')[0]) # action_func receives the number of subturns remaining in the action delay = globals()[action_func](duration, character, target, args) else: combat_handler.db.actor_idx += 1 if len(combat_handler.db.characters) < 2: combat_handler.stop() else: utils.delay(delay, process_next_action, combat_handler)
def func(self): """Performs the summon, accounting for in-world conditions.""" char = self.character loc = char.location account = self.account args = self.args lhs, rhs = self.lhs, self.rhs # opt = self.switches # TODO - add code to make the switches work. if char and char.ndb.currently_moving: account.msg( "You can not summon while moving. (|rstop|n, then try again.)") return if not args: char.msg( "Could not find target to summon. Usage: summon <character or NPC>" ) return session_list = SESSIONS.get_sessions() target = [] for session in session_list: if not (session.logged_in and session.get_puppet()): continue puppet = session.get_puppet() if lhs.lower() in puppet.get_display_name(char, plain=True).lower(): target.append(puppet) if len(target) < 1: char.msg("Error: character not found.") return if len(target) > 1: # Too many partial matches, try exact matching. char.msg("Error: character not found.") return else: target[0].msg( "You are being summoned to %s " % loc.get_display_name(target[0]) + "by %s" % char.get_display_name(target[0]) + ". A portal should appear soon that will take you to " + "%s." % char.get_display_name(target[0])) char.msg("You begin to summon a portal between %s" % target[0].get_display_name(char) + " and your location.") # Check the pool for objects to use. Filter a list of objects tagged "pool" by those located in None. obj_pool = [ each for each in evennia.search_tag('pool', category='portal') if not each.location ] print('Object pool total: %i' % len(obj_pool)) if len(obj_pool) < 2: char.msg('Portals are currently out of stock or in use elsewhere.') return portal_enter, portal_exit = obj_pool[-2:] def open_portal(): """Move inflatable portals into place.""" portal_enter.move_to(target[0].location) portal_exit.move_to(loc) delay(10, callback=open_portal ) # 10 seconds later, the portal (exit pair) appears. def close_portal(): """Remove and store inflatable portals in Nothingness.""" for each in portal_enter.contents: each.move_to(target[0].location) for each in portal_exit.contents: each.move_to(loc) portal_enter.location.msg_contents( "|r%s|n vanishes into |222Nothingness|n." % portal_enter) portal_exit.location.msg_contents( "|r%s|n vanishes into |222Nothingness|n." % portal_exit) portal_enter.move_to(None, to_none=True) portal_exit.move_to(None, to_none=True) delay( 180, callback=close_portal ) # Wait, then move portal objects to the portal pool in Nothingness
def _idle_spawner(self): """ This method is called when it is time for the spawning to cease. It then attempts to start spawning again once the wait period is over. """ utils.delay(30, self.start_spawner)
def set_roundtime(owner): now = time.time() delay(2, unbusy, owner, persistent=True) owner.db.busy = True owner.db.roundtime = now
def func(self): """Implement function using the Msg methods""" char = self.character sent_messages = Msg.objects.get_messages_by_sender( char, exclude_channel_messages=True) recd_messages = Msg.objects.get_messages_by_receiver(char) if 'last' in self.switches: self.mail_check() if sent_messages: recv = ', '.join('%s%s|n' % (obj.STYLE, obj.key) for obj in sent_messages[-1].receivers) self.msg("You last mailed |w%s|n: |w%s" % (recv, sent_messages[-1].message)) else: self.msg("You haven't mailed anyone yet.") self.mail_check() return if 'check' in self.switches: if not self.mail_check(): if not ('silent' in self.switches and 'quiet' in self.switches): self.msg('Your %s mailbox has no new mail.' % char.location.get_display_name(self.character)) if not self.args or not self.rhs: mail = sent_messages + recd_messages mail.sort(lambda x, y: cmp(x.db_date_created, y.db_date_created)) number = 5 if self.args: try: number = int(self.args) except ValueError: self.msg("Usage: mail [<character> = msg]") return if len(mail) > number: mail_last = mail[-number:] else: mail_last = mail template = "|w%s|n |w%s|n to |w%s|n: %s" mail_last = "\n ".join( template % (utils.datetime_format(mail.date_created), ', '.join( '%s' % obj.get_display_name(self.character) for obj in mail.senders), ', '.join([ '%s' % obj.get_display_name(self.character) for obj in mail.receivers ]), mail.message) for mail in mail_last) if mail_last: string = "Your latest letters:\n %s" % mail_last else: string = "You haven't mailed anyone yet." self.msg(string) char.nattributes.remove('new_mail') # Removes the notice. return # Send mode if not self.lhs: if sent_messages: # If no recipients provided, receivers = sent_messages[ -1].receivers # default to sending to the last character mailed. else: self.msg("Who do you want to mail?") return else: # Build a list of comma-delimited recipients. receivers = self.lhslist rec_objs = [] received = [] r_strings = [] for receiver in set(receivers): if isinstance(receiver, basestring): c_obj = char.search(receiver, global_search=True, exact=True) elif hasattr(receiver, 'location'): c_obj = receiver else: self.msg("Who do you want to mail?") return if c_obj: if not c_obj.access(char, 'mail'): r_strings.append("You are not able to mail %s." % c_obj) continue rec_objs.append(c_obj) if not rec_objs: self.msg("No one found to mail.") return message = self.rhs.strip() if message.startswith( ':'): # Format as pose if message begins with a : message = "%s%s|n %s" % (char.STYLE, char.key, message.strip(':')) create.create_message(char, message, receivers=rec_objs) def letter_delivery(): # c_obj.msg('%s %s' % (header, message)) c_obj.msg('|/A letter has arrived in %s%s|n mailbox for you.|/' % (c_obj.home.STYLE, c_obj.home.key)) for c_obj in rec_objs: # Notify character of mail delivery. received.append('%s%s|n' % (c_obj.STYLE, c_obj.key)) if hasattr(c_obj, 'sessions') and not c_obj.sessions.count(): r_strings.append( "|r%s|n is currently asleep, and won't read the letter until later." % received[-1]) c_obj.ndb.new_mail = True else: # Tell the receiving characters about receiving a letter if they are online. utils.delay(20, callback=letter_delivery) if r_strings: self.msg("\n".join(r_strings)) stamp_count = len(rec_objs) stamp_plural = 'a stamp' if stamp_count == 1 else '%i stamps' % stamp_count self.msg('Mail delivery costs %s.' % stamp_plural) char.location.msg_contents( '|g%s|n places %s on an envelope and slips it into the %s%s|n mailbox.' % (char.key, stamp_plural, char.location.STYLE, char.location.key)) self.msg("Your letter to %s will be delivered soon. You wrote: %s" % (', '.join(received), message)) self.mail_check()