diff --git a/commands/misc.py b/commands/misc.py index 9097d16..7c415cc 100755 --- a/commands/misc.py +++ b/commands/misc.py @@ -220,7 +220,7 @@ class CmdSetRummage(CmdSet): class CmdCat(Command): - """Have you cat do some antic. + """Have your cat do some antic. Usage: @@ -333,3 +333,102 @@ class CmdPress(Command): class CmdSetPress(CmdSet): def at_cmdset_creation(self): self.add(CmdPress) + + +class CmdTransform(MuxCommand): + """Transform yourself into an object. + + Usage: + + |gtransform = |n + + Where the 'object' is what you might see when looking around a + room, and 'description' is what is shown when a character 'look's + at you. + + See |guntransform|n. + """ + key = "transform" + alias = ["transmogrify"] + + def func(self): + if self.caller.attributes.get('transformed'): + self.caller.msg(f"You have already transformed into a {self.caller.name}." + f" You need to |guntransform|n first.") + elif not self.lhs: + self.caller.msg("What do you want to transform into?") + elif not self.rhs: + self.caller.msg(f"You need to describe this '{self.lhs}'.") + else: + self.caller.transmogrify(self.lhs, self.rhs) + + +class CmdUntransform(Command): + """ + Untransform yourself from a previous 'transform' command. + + Usage: + + |guntransform|n + """ + key = "untransform" + alias = ["untransmogrify"] + + def func(self): + if self.caller.attributes.get('transformed'): + self.caller.untransmogrify() + else: + self.caller.msg("You are not an object.") + + +class CmdSetTransform(CmdSet): + """ + Transforming set. + """ + def at_cmdset_creation(self): + self.add(CmdTransform) + self.add(CmdUntransform) + + +class CmdPeer(Command): + """ + Closely examine an object past the magic and see if they are a + transformed character, or really the object. + + Usage: + + |gpeer |n + + """ + key = "peer" + alias = ["seek"] + + def func(self): + looker = self.caller + label = self.args.strip() + + if label == "": + looker.msg("What do you want to closely examine?") + return + + thing = looker.search(label) + if thing: + if thing.attributes.get("transformed"): + self.obj.found_someone(thing) + looker.msg(f"Aha! The {label} is not how it appears!|/" + + f"It is really a |c{thing.db.orig_sdesc}|n... " + + thing.db.orig_desc) + thing.msg(f"The {looker.get_display_name(thing)} peers closely at you.|/" + + f"The jig is up! {looker.pronoun_subjective()} knows " + + f"that you are not a {label}.") + else: + self.obj.failed_at_looking() + looker.msg(f"The {label} is what it is...|/" + thing.db.desc) + + +class CmdSetPeer(CmdSet): + """ + Peering set. + """ + def at_cmdset_creation(self): + self.add(CmdPeer) diff --git a/typeclasses/characters.py b/typeclasses/characters.py index 417cc5f..f09adbc 100644 --- a/typeclasses/characters.py +++ b/typeclasses/characters.py @@ -365,6 +365,9 @@ class Character(Object, GenderCharacter, ContribRPCharacter): Can be overridden or appended with an effect. Does the looker affect this? """ + if self.db.transformed: + return self.db.desc + # To replace, temporarily, a description: # @set woman/temp_desc:effect = "They fade away from view." new_desc = self.attributes.get(category="effect", @@ -384,6 +387,9 @@ class Character(Object, GenderCharacter, ContribRPCharacter): '|S has') + post_desc) + def at_look(self, target, **kwargs): + return "Whacka whacka!" + def at_pre_move(self, destination, *args, **kwargs): """ Called by self.move_to when trying to move somewhere. If this returns @@ -416,7 +422,8 @@ class Character(Object, GenderCharacter, ContribRPCharacter): logger.warn(f"Found tethered, {thing} to #{to}, but that needs to be 'tethered:id(num) where num is the ID# of an object/place.") # Tell the room and any puppets here that we are leaving: - self.location.other_leave(self) + if not self.location.is_typeclass(Character): + self.location.other_leave(self) for puppet in self.puppets_here(): puppet.other_leave(self) @@ -424,7 +431,8 @@ class Character(Object, GenderCharacter, ContribRPCharacter): def at_post_move(self, past_location, move_type="move", **kwargs): super().at_post_move(past_location, move_type) - self.location.other_arrive(self) + if not self.location.is_typeclass(Character): + self.location.other_arrive(self) for puppet in self.puppets_here(): puppet.other_arrive(self) @@ -531,6 +539,32 @@ class Character(Object, GenderCharacter, ContribRPCharacter): self.db.reappear_msg.split(';;'), self.db.appear_delay or 2) + def transmogrify(self, name, description): + """ + Convert the appearance of this character into an object. + """ + self.db.orig_name = self.name + self.db.orig_desc = self.db.desc + self.db.orig_sdesc = self.sdesc.get() + + self.announce_action(f"Octarine sparks fly, as $you() $conj(transform) into a {name}.") + + self.db.transformed = True + self.name = name + self.sdesc.add(name) + self.db.desc = description + + def untransmogrify(self): + """ + Undo the conversion of this character from an object. + """ + self.name = self.db.orig_name + self.sdesc.add(self.db.orig_sdesc) + self.db.desc = self.db.orig_desc + self.db.transformed = False + + self.announce_action(f"Octarine sparks glint, as $you() $conj(transform) back into $pron(your) old form.") + # Hooks to the puppets and storycubes: def puppets_here(self): diff --git a/typeclasses/rooms.py b/typeclasses/rooms.py index 249ddce..351236e 100644 --- a/typeclasses/rooms.py +++ b/typeclasses/rooms.py @@ -5,11 +5,15 @@ Rooms are simple containers that has no location of their own. """ +from collections import defaultdict from datetime import datetime from re import match +from django.utils.translation import gettext as _ + from evennia import utils from evennia.utils import gametime, logger, delay +from evennia.utils.utils import iter_to_str from evennia.contrib.grid.extended_room import ExtendedRoom from evennia.contrib.rpg.rpsystem import ContribRPRoom @@ -39,6 +43,7 @@ def articlize_character(character, capitalize=False): else: return f"{article} " + character + def captialize_characters(characters): """ Capitalizes and prefixes an article on string of characters. @@ -96,7 +101,8 @@ class Room(ObjectParent, ExtendedRoom, ContribRPRoom, Listener): return "|n" def get_display_characters(self, looker, *args, **kwargs): - chars = self.contents_get(content_type="character") + chars = [c for c in self.contents_get(content_type="character") + if not c.attributes.get('transformed')] vchars = self.filter_visible(chars, looker, **kwargs) num_chars = len(vchars) @@ -129,16 +135,31 @@ class Room(ObjectParent, ExtendedRoom, ContribRPRoom, Listener): return '' def get_display_things(self, looker, *args, **kwargs): - std_msg = super().get_display_things(looker, pose=False) - if std_msg: - return std_msg.replace('|wYou see:|n', 'You notice') + '.' - else: - return '' + """ + Replace + """ + things = self.filter_visible(self.contents_get(content_type="object"), + looker, **kwargs) + tchars = [c for c in self.contents_get(content_type="character") + if c.attributes.get('transformed')] - def msg_contents(self, message, exclude=None, from_obj=None, mapping=None, + grouped_things = defaultdict(list) + for thing in things + tchars: + grouped_things[thing.get_display_name(looker, **kwargs)].append(thing) + + thing_names = [] + for thingname, thinglist in sorted(grouped_things.items()): + nthings = len(thinglist) + thing = thinglist[0] + singular, plural = thing.get_numbered_name(nthings, looker, key=thingname) + thing_names.append(singular if nthings == 1 else plural) + thing_names = iter_to_str(thing_names, endsep=_(", and")) + return _(f"You notice {thing_names}." if thing_names else "") + + def msg_contents(self, text, exclude=None, from_obj=None, mapping=None, raise_funcparse_errors=False, **kwargs): - message = fix_msg(message) - super().msg_contents(message, exclude, from_obj, mapping, + text = fix_msg(text) + super().msg_contents(text, exclude, from_obj, mapping, raise_funcparse_errors) diff --git a/typeclasses/things.py b/typeclasses/things.py index c8bd504..9b43606 100755 --- a/typeclasses/things.py +++ b/typeclasses/things.py @@ -10,6 +10,7 @@ from evennia.prototypes.spawner import spawn from evennia.utils import (logger, delay, iter_to_str, int2str, search) +from evennia.utils.search import search_channel from commands.misc import (CmdSetPuddle, CmdSetStick, @@ -17,7 +18,8 @@ from commands.misc import (CmdSetPuddle, CmdSetSmoke, CmdSetRummage, CmdSetDice, - CmdSetPress) + CmdSetPress, + CmdSetTransform, CmdSetPeer) from commands.consumables import CmdSetMakeConsumable from commands.wizards import CmdSetWand from utils.word_list import routput, choices, paragraph @@ -1066,3 +1068,74 @@ class Teleporter(Object): self.db.destination, move_type="traverse") else: announce_seq(time_to_delay, victim, self.db.fail_msg) + + +class MagicRing(Object): + """ + Ring that can change someone into an object. + """ + def at_object_creation(self): + self.cmdset.add_default(CmdSetTransform) + +class MagicMonocle(Object): + """ + Monocle that sees past the 'transform' magic. + """ + def at_object_creation(self): + """ + Sets the scoreboard as well as the 'peer' command. + """ + self.cmdset.add_default(CmdSetPeer) + self.reset_counts() + + def found_someone(self, found): + """ + Store a set as an attribute with 'found'. + """ + the_set = self.attributes.get('found', set()) + the_set.add(found) + self.attributes.add('found', the_set) + + def failed_at_looking(self): + """ +nv Increment the failures on the scoreboard. + """ + self.db.failed = self.db.failed + 1 + + def reset_counts(self): + """ + Resets the scoreboard to 0. + """ + self.attributes.add('found', set()) + self.attributes.add('failed', 0) + + def report_score(self, seeker=None): + """ + Reports the score for the object to the room. + """ + if not seeker: + seeker = self.location + + room = seeker.location + name = seeker.sdesc.get() + room.msg_contents(f"The score for the seeker, {name} is:|/" + + f" - Found: {len(self.db.found)}|/" + + f" - Misses: {self.db.failed}") + + +class GlobalAlarmClock(Object): + """ + Clock that announces timers on a public channel. + """ + def at_object_creation(self): + """ + Sets up the attributes. + """ + self.db.channel = "Hide and Seek Game" + + def start_timer(self, seconds=30, start=None, finish=None): + chan = search_channel(self.db.channel).first() + if chan: + if start: + chan.msg(start) + delay(seconds, chan.msg, finish)