Character creation system
Also fixed many bugs and inconsistencies. Including: - Whisper works with RP system - Saying /to to non-player character just uses what they wrote - Minor yell bug - Actions on Things (like the puddle) work with RP system
This commit is contained in:
parent
a9e0c24ad9
commit
060abf846c
15 changed files with 665 additions and 87 deletions
|
|
@ -18,8 +18,9 @@ from evennia import default_cmds
|
|||
from evennia.contrib.grid import extended_room
|
||||
from evennia.contrib.game_systems.gendersub import SetGender
|
||||
from evennia.contrib.rpg.rpsystem import RPSystemCmdSet
|
||||
from evennia.contrib.rpg.character_creator.character_creator import ContribChargenCmdSet
|
||||
from commands.sittables import CmdNoSitStand
|
||||
from commands.everyone import CmdTake, CmdThink, CmdSay
|
||||
from commands.everyone import CmdTake, CmdThink, CmdSay, CmdWhisper
|
||||
from commands.wizards import CmdGM
|
||||
|
||||
class CharacterCmdSet(default_cmds.CharacterCmdSet):
|
||||
|
|
@ -37,12 +38,13 @@ class CharacterCmdSet(default_cmds.CharacterCmdSet):
|
|||
"""
|
||||
super().at_cmdset_creation()
|
||||
self.add(CmdNoSitStand)
|
||||
self.add(CmdTake())
|
||||
self.add(CmdThink())
|
||||
self.add(SetGender())
|
||||
self.add(RPSystemCmdSet())
|
||||
self.add(CmdTake)
|
||||
self.add(CmdThink)
|
||||
self.add(SetGender)
|
||||
self.add(RPSystemCmdSet)
|
||||
self.add(extended_room.ExtendedRoomCmdSet)
|
||||
self.add(CmdSay())
|
||||
self.add(CmdSay)
|
||||
self.add(CmdWhisper)
|
||||
self.add(CmdGM)
|
||||
|
||||
|
||||
|
|
@ -64,6 +66,7 @@ class AccountCmdSet(default_cmds.AccountCmdSet):
|
|||
#
|
||||
# any commands you add below will overload the default ones.
|
||||
#
|
||||
self.add(ContribChargenCmdSet)
|
||||
|
||||
|
||||
class UnloggedinCmdSet(default_cmds.UnloggedinCmdSet):
|
||||
|
|
|
|||
|
|
@ -9,30 +9,71 @@ from evennia.commands.default.muxcommand import MuxCommand
|
|||
from evennia.contrib.rpg.rpsystem import send_emote
|
||||
from evennia.utils import iter_to_str, logger
|
||||
|
||||
from utils.word_list import routput
|
||||
from utils.word_list import routput, paragraph
|
||||
|
||||
|
||||
class CmdWhisper(MuxCommand):
|
||||
"""
|
||||
Speak privately as your character to another
|
||||
|
||||
Usage:
|
||||
whisper <character> = <message>
|
||||
whisper <char1>, <char2> = <message>
|
||||
|
||||
Talk privately to one or more characters in your current location, without
|
||||
others in the room being informed.
|
||||
"""
|
||||
key = "whisper"
|
||||
priority = 0
|
||||
locks = "cmd:all()"
|
||||
rhs_split = ("=")
|
||||
|
||||
def func(self):
|
||||
"""
|
||||
Implements the new 'whisper' command.
|
||||
"""
|
||||
if not self.args:
|
||||
self.caller.msg("What are you whispering?")
|
||||
return
|
||||
|
||||
if not self.rhs:
|
||||
self.caller.msg("Usage: whisper <character> = <message>")
|
||||
return
|
||||
|
||||
targets = [self.caller.search(target) for target in split(r" *, *", self.lhs)]
|
||||
full_speech = f"/Me whispers to you, \"{self.rhs}\""
|
||||
send_emote(self.caller, targets, full_speech, msg_type="say", anonymous_add=None, quiet=True)
|
||||
|
||||
to_list = [target.get_display_name(self) for target in targets]
|
||||
full_speech = f"You whisper to {iter_to_str(to_list, endsep='and')}, \"{self.rhs}\""
|
||||
self.caller.msg(full_speech, from_obj=self.caller)
|
||||
|
||||
|
||||
class CmdSay(MuxCommand):
|
||||
"""Say something to the characters in the same area.
|
||||
"""
|
||||
Say something to the characters in the same area.
|
||||
|
||||
Usage:
|
||||
say phrase
|
||||
say/to char1 [char2 ...], phrase
|
||||
say/to char1, [char2 ...] = phrase
|
||||
say[/switches] phrase
|
||||
|
||||
Where switches can be any of the following:
|
||||
|
||||
- yell : To replace 'says' with 'yells'
|
||||
- scream : To replace 'says' with 'screams'
|
||||
- ask : To replace 'says' with 'asks'
|
||||
- to : Takes one or more characters in the same area, and directs the statement to them. Note that others can still hear the statement (see the 'whisper' command).
|
||||
- adverb : Any adverb-like word that ends in '-ly' is added to the say command, for instance:
|
||||
- yell : To replace 'says' with 'yells'
|
||||
- scream : To replace 'says' with 'screams'
|
||||
- ask : To replace 'says' with 'asks'
|
||||
- to : Directs phrase to one or more characters
|
||||
in the same area. Note others can still hear
|
||||
the statement (see the 'whisper' command).
|
||||
- adverb : Any adverb-like word that ends in '-ly'
|
||||
is added to the say command, for instance:
|
||||
|
||||
say/quietly Hi there.
|
||||
say/quietly Hi there.
|
||||
|
||||
Shows as:
|
||||
Shows as:
|
||||
|
||||
You quietly say, "Hi there."
|
||||
You quietly say, "Hi there."
|
||||
"""
|
||||
|
||||
key = "say"
|
||||
|
|
@ -40,13 +81,21 @@ class CmdSay(MuxCommand):
|
|||
priority = 0
|
||||
locks = "cmd:all()"
|
||||
rhs_split = ("=")
|
||||
arg_regex = None
|
||||
|
||||
def func(self):
|
||||
"""
|
||||
Implements the new 'say' command with switches.
|
||||
"""
|
||||
def charname(name):
|
||||
try:
|
||||
results = self.caller.search(name, quiet=True)
|
||||
return results.get_display_name(self)
|
||||
except:
|
||||
return name
|
||||
|
||||
def chars_for_self(chars):
|
||||
return [self.caller.search(c).get_display_name(self) for c in split(r"[ ,]+", chars)]
|
||||
return [charname(c) for c in split(r"[ ,]+", chars)]
|
||||
|
||||
def chars_for_others(chars):
|
||||
return [c if c.startswith('/') else '/'+c for c in split(r"[ ,]+", chars)]
|
||||
|
|
@ -56,12 +105,20 @@ class CmdSay(MuxCommand):
|
|||
return (' to ' if verb == 'say' else ' ') + \
|
||||
iter_to_str(char_lst, endsep='and')
|
||||
|
||||
logger.info(f"CmdSayIt: {self.cmdstring} lhs={self.lhs} switches={self.switches}")
|
||||
# logger.info(f"CmdSayIt: {self.cmdstring} lhs={self.lhs} switches={self.switches}")
|
||||
|
||||
if not self.args:
|
||||
self.caller.msg("Say what?")
|
||||
return
|
||||
|
||||
if 'to' in self.switches and not self.rhs:
|
||||
self.caller.msg(paragraph("""
|
||||
When attempting to say something to one or more
|
||||
characters, use the '=' character to identify what you
|
||||
want to say.
|
||||
"""))
|
||||
return
|
||||
|
||||
if self.rhs:
|
||||
speech = self.rhs
|
||||
else:
|
||||
|
|
@ -76,10 +133,10 @@ class CmdSay(MuxCommand):
|
|||
if switch.endswith('ly'):
|
||||
adverb = switch + ' '
|
||||
|
||||
if 'yell' in self.switches or self.cmdstring == 'yell' or speech.endswith('!'):
|
||||
verb = 'yell'
|
||||
elif 'scream' in self.switches or self.cmdstring == 'scream':
|
||||
if 'scream' in self.switches or self.cmdstring == 'scream':
|
||||
verb = 'scream'
|
||||
elif 'yell' in self.switches or self.cmdstring == 'yell' or speech.endswith('!'):
|
||||
verb = 'yell'
|
||||
elif 'ask' in self.switches or self.cmdstring == 'ask' or speech.endswith('?'):
|
||||
verb = 'ask'
|
||||
else:
|
||||
|
|
@ -96,7 +153,9 @@ class CmdSay(MuxCommand):
|
|||
targets = [item for item in self.caller.location.contents if item != self.caller]
|
||||
to_whom = chars_list(self.lhs, verb) if 'to' in self.switches else ''
|
||||
full_speech = f"/Me {adverb}{verb}s{to_whom}, \"{speech}\""
|
||||
send_emote(self.caller, targets, full_speech, msg_type="say", anonymous_add=None)
|
||||
send_emote(self.caller, targets, full_speech, msg_type="say", anonymous_add=None, quiet=True)
|
||||
|
||||
|
||||
|
||||
|
||||
class CmdThink(Command, NumberedTargetCommand):
|
||||
|
|
@ -130,6 +189,7 @@ class CmdThink(Command, NumberedTargetCommand):
|
|||
else:
|
||||
msg = f"{self.caller.name} . o O ( {thought} )"
|
||||
self.caller.db.thinking_count = (self.caller.db.thinking_count or 0) + 1
|
||||
|
||||
self.caller.location.msg_contents(msg)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -53,6 +53,11 @@ FUNCPARSER_PARSE_OUTGOING_MESSAGES_ENABLED = True
|
|||
SEARCH_MULTIMATCH_REGEX = r"(?P<number>[0-9]+)-(?P<name>[^-]*)(?P<args>.*)"
|
||||
SEARCH_MULTIMATCH_TEMPLATE = " {number}-{name}{aliases}{info}\n"
|
||||
|
||||
CHARGEN_MENU = "world.chargen_menu"
|
||||
AUTO_CREATE_CHARACTER_WITH_ACCOUNT = False
|
||||
AUTO_PUPPET_ON_LOGIN = True
|
||||
MAX_NR_CHARACTERS = 6
|
||||
|
||||
######################################################################
|
||||
# Settings given in secret_settings.py override those in this file.
|
||||
######################################################################
|
||||
|
|
|
|||
|
|
@ -23,9 +23,10 @@ several more options for customizing the Guest account system.
|
|||
"""
|
||||
|
||||
from evennia.accounts.accounts import DefaultAccount, DefaultGuest
|
||||
from evennia.contrib.rpg.character_creator.character_creator import ContribChargenAccount
|
||||
|
||||
|
||||
class Account(DefaultAccount):
|
||||
class Account(ContribChargenAccount):
|
||||
"""
|
||||
An Account is the actual OOC player entity. It doesn't exist in the game,
|
||||
but puppets characters.
|
||||
|
|
@ -135,8 +136,16 @@ class Account(DefaultAccount):
|
|||
- at_post_chnnel_msg(message, channel, senders=None, **kwargs)
|
||||
|
||||
"""
|
||||
def at_post_login(self, session=None, **kwargs):
|
||||
protocol_flags = self.attributes.get("_saved_protocol_flags", {})
|
||||
if session and protocol_flags:
|
||||
session.update_flags(**protocol_flags)
|
||||
|
||||
pass
|
||||
try:
|
||||
self.puppet_object(session, self.db._last_puppet)
|
||||
except RuntimeError:
|
||||
self.msg("Welcome, welcome. Let's begin by creating you a character.")
|
||||
self.execute_cmd("charcreate")
|
||||
|
||||
|
||||
class Guest(DefaultGuest):
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ from re import match
|
|||
|
||||
from evennia.contrib.game_systems.gendersub import GenderCharacter
|
||||
from evennia.contrib.rpg.rpsystem import ContribRPCharacter
|
||||
from evennia.contrib.rpg.rpsystem import send_emote
|
||||
from evennia.prototypes.spawner import spawn
|
||||
from evennia.utils import delay # , logger
|
||||
|
||||
|
|
@ -56,6 +57,8 @@ class Character(Object, GenderCharacter, ContribRPCharacter):
|
|||
See mygame/typeclasses/objects.py for a list of
|
||||
properties and methods available on all Object child classes like this.
|
||||
"""
|
||||
pose = True
|
||||
|
||||
def at_object_creation(self):
|
||||
"called when a character is first created."
|
||||
self.db.tutorstate = 0
|
||||
|
|
@ -105,9 +108,6 @@ class Character(Object, GenderCharacter, ContribRPCharacter):
|
|||
thing = victim.has(to_take)
|
||||
if thing and thing.db.can_take:
|
||||
self.msg(f"You take {thing.key} from {victim.key}.")
|
||||
self.location.msg_contents(
|
||||
f"{self.key} takes {thing.key} from {victim.key}!",
|
||||
exclude=self)
|
||||
thing.move_to(self, quiet=True, use_destination=True)
|
||||
return
|
||||
|
||||
|
|
@ -182,6 +182,13 @@ class Character(Object, GenderCharacter, ContribRPCharacter):
|
|||
# function call.
|
||||
return super().at_look(target)
|
||||
|
||||
def announce_action(self, message):
|
||||
"""
|
||||
Replaces a location's 'msg_contents' with an emote.
|
||||
"""
|
||||
targets = [item for item in self.location.contents if item != self]
|
||||
send_emote(self, targets, f"/Me {message}", msg_type="say", anonymous_add=None, quiet=True)
|
||||
|
||||
def spell_sequence(self, location, messages, time_delay=1):
|
||||
"""
|
||||
Send one or more messages to 'location' with a delay.
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ class Teapot(Object):
|
|||
|
||||
self.db.desc = f"A large, brown teapot full of {desc}."
|
||||
drinker.msg(f"You make a teapot of {desc}.")
|
||||
self.location.msg_contents(f"{drinker.name} makes a pot of tea.", exclude=drinker)
|
||||
drinker.announce_action(f"makes a teapot of {desc}.")
|
||||
|
||||
def do_fill(self, drinker):
|
||||
teatype = self.db.tea_type
|
||||
|
|
|
|||
|
|
@ -168,13 +168,11 @@ class Fire(Pet):
|
|||
if self.db.hunger_level < 5:
|
||||
giver.msg(squish(f"You {get_up} put {adj} wood in the "
|
||||
f"fireplace, and start a fire."))
|
||||
self.location.msg_contents(squish(f"{giver.name} {gets_up} starts a fire."),
|
||||
exclude=giver)
|
||||
giver.announce_action(f"{gets_up} starts a fire.")
|
||||
else:
|
||||
giver.msg(squish(f"You {get_up} put {adj} wood on the "
|
||||
f"fire in the fireplace."))
|
||||
self.location.msg_contents(squish(f"{giver.name} {gets_up} puts {adj} wood on the fire."),
|
||||
exclude=giver)
|
||||
giver.announce_action(f"{gets_up} put wood on the fire.")
|
||||
|
||||
def feed(self, giver, obj=None):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -194,8 +194,7 @@ class Book(Readable):
|
|||
|
||||
def do_read(self, reader):
|
||||
super().do_read(reader)
|
||||
reader.location.msg_contents(routput(f"{reader.name} reads a book. You notice the title, |w{self.db.title}|n."),
|
||||
exclude=reader)
|
||||
reader.announce_action(f"reads |p book. You notice the title, |w{self.db.title}|n.")
|
||||
|
||||
def do_burn(self, reader, drop=False):
|
||||
if isinstance(reader.location, DabblersRoom):
|
||||
|
|
@ -204,7 +203,6 @@ class Book(Readable):
|
|||
Gotta keep the room organized.""")
|
||||
else:
|
||||
reader.msg("You throw the book in the fireplace. It immediately yelps, flaps its pages, and flies back to a section on the shelf.")
|
||||
self.location.msg_contents(routput(f"{reader.name} throws a book in the fireplace! It immediately yelps, flaps it pages, and flies back to a shelf."), exclude=reader)
|
||||
self.delete()
|
||||
else:
|
||||
if not drop:
|
||||
|
|
|
|||
|
|
@ -93,6 +93,7 @@ class Room(ObjectParent, ExtendedRoom, ContribRPRoom):
|
|||
else:
|
||||
return ''
|
||||
|
||||
|
||||
class DabblersRoom(Room):
|
||||
def get_display_desc(self, looker):
|
||||
fire = self.search("fire")
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ TIMEBASE_WEATHER_MSGS = [
|
|||
"The clouds part creating a dazzling sunrise of colors.",
|
||||
"Bird song fills the air with the dawn chorus.",
|
||||
# Cloudy
|
||||
"The clouds accumulate making the morning last.",
|
||||
"The clouds accumulate making the morning last longer.",
|
||||
"The rain slows to a drizzle as the moss begins to glow in the brightening light.",
|
||||
# Rainy
|
||||
"The falling rain intensifies creating vibrating puddles.",
|
||||
|
|
@ -137,7 +137,7 @@ class TimeWeatherRoom(Room):
|
|||
even though we don't actually use them in this example)
|
||||
"""
|
||||
# only update 10 % of the time
|
||||
if random.random() < 0.1:
|
||||
if random.random() < 0.06:
|
||||
msg = choose_weather_message()
|
||||
if self.db.previous_weather != msg:
|
||||
self.db.previous_weather = msg
|
||||
|
|
|
|||
|
|
@ -45,8 +45,7 @@ class Sittable(Object):
|
|||
self.db.sitter = sitter
|
||||
sitter.db.is_sitting = self
|
||||
sitter.msg(self.sit_msg())
|
||||
self.location.msg_contents(f"{sitter.name} sits {adjective} {article} {self.key}.",
|
||||
exclude=sitter)
|
||||
sitter.announce_action(f"sits {adjective} {article} {self.key}.")
|
||||
|
||||
def do_stand(self, stander):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -125,8 +125,8 @@ class Pipe(Object):
|
|||
you_msg = choices(self.db.light_msg or "You pack and light your pipe.")
|
||||
lighter.msg(you_msg)
|
||||
# desc = self.return_appearance()[:1].lower() + self.return_appearance()[1:]
|
||||
other_msg = choices(self.db.light_msg_other or "{0} packs and lights |p pipe.", lighter.name)
|
||||
lighter.location.msg_contents(other_msg, exclude=lighter)
|
||||
other_msg = choices(self.db.light_msg_other or "packs and lights |p pipe.")
|
||||
lighter.announce_action(other_msg)
|
||||
|
||||
def do_puff(self, smoker):
|
||||
pass
|
||||
|
|
@ -163,8 +163,7 @@ class Stick(Object):
|
|||
"This is a <<fun ^ pleasant ^ nice>> <<past-time ^ game>>.",
|
||||
"Maybe we should get a pet <<or beast ^ beastie ^ >> in here who would love to play.",
|
||||
], thrower.name.capitalize()))
|
||||
self.location.msg_contents(f"{thrower.name} throws a stick.",
|
||||
exclude=thrower)
|
||||
thrower.announce_action("throws |p stick.")
|
||||
else:
|
||||
thrower.msg("I think you should be outside or a place with more room before you throw that stick around.")
|
||||
|
||||
|
|
@ -198,10 +197,7 @@ class Puddle(Object):
|
|||
"happy.",
|
||||
"great.",
|
||||
])))
|
||||
|
||||
self.location.msg_contents(f"{player.name} jumps in the puddle.",
|
||||
exclude=player)
|
||||
|
||||
player.announce_action("jumps in the puddle.")
|
||||
|
||||
|
||||
class Knocker_Convo(Enum):
|
||||
|
|
@ -416,8 +412,7 @@ class Knocker(Object):
|
|||
("room", self.db.room_to_msg)
|
||||
])
|
||||
knocker.msg("You grab the ring and knock firmly on the door.")
|
||||
self.msg_contents(f"{knocker.name} grabs the ring and knocks firmly on the door.",
|
||||
exclude=knocker)
|
||||
knocker.announce_action("grabs the ring and knocks firmly on the door.")
|
||||
else:
|
||||
knocker.msg("This door knocker is defective, as it doesn't have a ring to...er, do the knockin'.")
|
||||
|
||||
|
|
|
|||
|
|
@ -49,23 +49,20 @@ MSGS = {
|
|||
"\"How's it going?\" the bird asks, \"Are you enjoying this game so far?\"",
|
||||
"The bird says, \"This place has been a respite from the outside turbulence.\"",
|
||||
"\"A shortcut to |gsay|n is to type either a double or single quote, and then your message.\"",
|
||||
"\"You can also use |gpose|n to state something about yourself, \" it says. \" For instance, type |gpose smiles.|n\"",
|
||||
"\"You can also use |gemote|n to state something about yourself, \" it says. \" For instance, type |gemote smiles.|n\"",
|
||||
# POSE:
|
||||
"The bird stands on one leg.",
|
||||
"\"See,\" says the bird, \"I s'pose I can pose too.\"",
|
||||
"\"A shortcut to that command is typing |g:|n (a colon character).\"",
|
||||
"The bird says, \"The game has more commands. Typing |ghelp|n gives you a list of commands. That list changes depending on where you are and what you are carrying. For instance, you could type |gshoo|n for me to fly away, and then that command wouldn't be available again.\"",
|
||||
"Another useful command is |gpub|n which is a shortcut to sending messages to a public channel that everyone in the game receives |wout of character|n. That is, the message comes from your login account, not your character. The dad jokes go there without interrupting the role playing."
|
||||
"The bird says, \"Typing |ghelp|n gives you a list of commands. That list changes depending on where you are and what you are carrying. For instance, you could type |gshoo|n for me to fly away, and then that command wouldn't be available again.\"",
|
||||
# ],
|
||||
# "HELP": [
|
||||
"The bird chirps, \"You can learn more about the individual commands. For instance, type |ghelp say|n and you would see about the shortcuts I told you about. You can also see new commands, like |ghelp get|n.\"",
|
||||
"\"Type |ghelp start|n for a repeat of much of what we've talked about,\" says the bird.",
|
||||
"The bird chirps, \"You can learn more about the individual commands. For instance, type |ghelp emote|n and you can see more options to that command. You can also see new commands, like |ghelp get|n.\"",
|
||||
"\"Type |ghelp start|n for a repeat of much of what we've talked about,\" says the bird.",
|
||||
"The bird says, \"The |ginv|n shows you what you are carrying. I see you picked up a letter. You can also type |gread letter|n.\"",
|
||||
"\"Since this game hosts many users,\" says the bird, \"you can show them what they see when they look at you, using the |g@setdesc|n command. Check out the help, |ghelp @setdesc|n, or just type: |w@setdesc A pixie with a shock of blue hair and a gold chain.|n\"",
|
||||
"\"Since this game hosts many users,\" says the bird, \"you can show them what they see when they look at you, using the |gsetdesc|n command. Check out the help on it and, |gsdesc|n and |gpose|n.",
|
||||
"\"I'm guessing your last question,\" chirps the little bird on your shoulder, \"is the goal of this game.\"",
|
||||
"\"That is a good question,\" says the bird, \"and I'm not sure what to say. This game has no goal, nor point. I guess you could look at it as a philosophical metaphor for existential existence in a post-modern world in the death throes of capitalism, but…\"",
|
||||
"The bird says, \"This is a cozy little game about role playing a storybook character and maybe exploring and looking for eggs… no, that's not right. Something about eggs. Easter eggs? Something like that.\"",
|
||||
"\"Wander and look around. Find a cozy place to chat with others who have logged in,\" says the bird.",
|
||||
"\"If you're interested in |wbuilding|n and expanding this world,\" chirps the bird, \"talk to Dabbler... Oh, see if you can find Dabbler, the guy that made most of this. That is a great goal.\"",
|
||||
"\"That is a good question,\" quips the bird, \"and I'm not sure what to say. You could look at this as a philosophical antidote for our existential apprehension of a post-modern world in the death throes of capitalism, but…\"",
|
||||
"The bird says, \"This is a cozy little game about role playing a storybook character in an alternate plane populated by the Faerie, so wander and look around. Find a cozy place to role play with others,\" says the bird.",
|
||||
"\"What do you say?\" says the bird. \"Think you got the hang of playing this?\"",
|
||||
]
|
||||
}
|
||||
|
|
@ -107,8 +104,8 @@ class TutorBird(CarriableNPC):
|
|||
"""
|
||||
Bird that routinely tells a player a new hint.
|
||||
"""
|
||||
# The number of ticks to check for the time for a message:
|
||||
bird_tick = 18
|
||||
# The number of ticks (seconds) to check for the time for a message:
|
||||
bird_tick = 15
|
||||
|
||||
# The number of waiting messages to issue before stopping the tutorial:
|
||||
wait_messages = 3
|
||||
|
|
@ -125,6 +122,7 @@ class TutorBird(CarriableNPC):
|
|||
"SAY": 0,
|
||||
}
|
||||
self.db.waiting = 0
|
||||
self.db.tutor_level = TutorialState.START
|
||||
|
||||
TICKER_HANDLER.add(interval=self.bird_tick,
|
||||
callback=self.do_speak)
|
||||
|
|
@ -138,14 +136,26 @@ class TutorBird(CarriableNPC):
|
|||
})[0]
|
||||
bird.location = character
|
||||
|
||||
def do_end_tutorial(self, character):
|
||||
def do_speak(self):
|
||||
"""
|
||||
A visual way to stop the tutorial
|
||||
Called at a repeatable sequence by the ticker, and
|
||||
it calls at_say() in order to do a type of monologue.
|
||||
"""
|
||||
character.msg("\"Have fun, and I'll leave you here,\" chirps the bird, \"and remember to check out the letter you picked up!\" It flies away.")
|
||||
self.delete()
|
||||
character = self.location
|
||||
msg = self.get_msg(character)
|
||||
if msg:
|
||||
character.msg(f"\n{msg}")
|
||||
|
||||
def get_msg_level(self, character, level):
|
||||
def get_msg(self, character):
|
||||
"""
|
||||
Return a message based on the character's progression.
|
||||
The progression is marked by the character's variable:
|
||||
'tutorstate' (stored in its database).
|
||||
"""
|
||||
# We could skip ahead if the player enters a higher state, so
|
||||
# we find the "max" state available:
|
||||
level = TutorialState(max([character.db.tutorstate & state.value
|
||||
for state in TutorialState]))
|
||||
choices = MSGS[level.name]
|
||||
msg_num = self.db.tutor_msg_num.get(level.name, 0)
|
||||
if msg_num >= len(choices):
|
||||
|
|
@ -161,23 +171,9 @@ class TutorBird(CarriableNPC):
|
|||
self.db.waiting = 0 # Reset the waiting period
|
||||
return msg
|
||||
|
||||
def get_msg(self, character, remaining_states):
|
||||
try:
|
||||
# Take the highest value from states...
|
||||
level = remaining_states.pop()
|
||||
if character.db.tutorstate >= level.value:
|
||||
return self.get_msg_level(character, level)
|
||||
else:
|
||||
return self.get_msg(character, remaining_states)
|
||||
except IndexError:
|
||||
return self.get_msg_level(character, TutorialState.START)
|
||||
|
||||
def do_speak(self):
|
||||
def do_end_tutorial(self, character):
|
||||
"""
|
||||
Called at a repeatable sequence by the ticker, and
|
||||
it calls at_say() in order to do a type of monologue.
|
||||
A visual way to stop the tutorial
|
||||
"""
|
||||
character = self.location
|
||||
msg = self.get_msg(character, [level for level in TutorialState])
|
||||
if msg:
|
||||
character.msg(f"\n{msg}")
|
||||
character.msg("\"Have fun, and I'll leave you here,\" chirps the bird. \"Remember to check out the letter you picked up!\" It flies away.")
|
||||
self.delete()
|
||||
|
|
|
|||
|
|
@ -4,7 +4,19 @@ from itertools import batched
|
|||
from random import choice
|
||||
from re import compile, sub, split
|
||||
|
||||
from evennia.utils import logger
|
||||
from evennia.utils import logger, dedent
|
||||
|
||||
def paragraph(text):
|
||||
"""
|
||||
Removes initial spaces as well as final newlines.
|
||||
|
||||
Blank lines, however, are preserved.
|
||||
"""
|
||||
# Shame we can chain this or _thread_, but:
|
||||
return sub(r";;", "\n\n",
|
||||
sub(r"\n", " ",
|
||||
sub(r"\n\n+", ";;",
|
||||
dedent(text)))).strip()
|
||||
|
||||
def squish(text):
|
||||
"Remove series of spaces from the text."
|
||||
|
|
|
|||
495
world/chargen_menu.py
Normal file
495
world/chargen_menu.py
Normal file
|
|
@ -0,0 +1,495 @@
|
|||
"""
|
||||
The WyldWood Character Creation
|
||||
"""
|
||||
|
||||
import inflect
|
||||
from typeclasses.characters import Character
|
||||
|
||||
# from evennia.prototypes.spawner import spawn
|
||||
# from evennia.utils.evtable import EvTable
|
||||
|
||||
from utils.word_list import paragraph
|
||||
|
||||
_INFLECT = inflect.engine()
|
||||
|
||||
|
||||
#########################################################
|
||||
# Utilities
|
||||
#########################################################
|
||||
|
||||
# def create_objects(character):
|
||||
# """do the actual object spawning"""
|
||||
# # since our example chargen saves the starting prototype to an attribute, we retrieve that here
|
||||
# proto = dict(character.db.starter_weapon)
|
||||
# # set the location to our character, so they actually have it
|
||||
# proto["location"] = character
|
||||
# # create the object
|
||||
# spawn(proto)
|
||||
|
||||
|
||||
#########################################################
|
||||
# Welcome Page
|
||||
#########################################################
|
||||
|
||||
|
||||
def menunode_welcome(caller):
|
||||
"""Starting page."""
|
||||
|
||||
char = caller.new_char
|
||||
|
||||
# another decision, so save the resume point
|
||||
char.db.chargen_step = "menunode_welcome"
|
||||
|
||||
text = paragraph("""\
|
||||
|wHoward's Character Creator, v1|n
|
||||
|
||||
This game takes place in the |cDomain of the WyldWood|n, and your
|
||||
character may be a faery denizen of this strange wonderland, or
|
||||
perhaps someone more typical, but who stepped into a faery circle
|
||||
or other portal and landed in this strange, magical place.
|
||||
|
||||
To create this fantasy character, I will ask a series of
|
||||
questions, and you will type your answer.
|
||||
Type |ghelp|n at any of these prompts, for additional information.
|
||||
Type |gquit|n to stop the process (recover your session using
|
||||
|gcharcreate|n).
|
||||
""") + """
|
||||
|
||||
1. Short Description
|
||||
2. Set Gender
|
||||
3. Character's "Pose"
|
||||
4. Long Description
|
||||
5. Character's Name
|
||||
""" + "\nType |cReturn|n to begin creating your character."
|
||||
|
||||
help = paragraph("""
|
||||
Type |gnext|n or |gskip|n (or even a blank answer), to keep any
|
||||
previously entered data for the step and go to the next prompt.
|
||||
|
||||
Type |gback|n to return to a previous step, well, except for
|
||||
this first message. Type |gquit|n instead. :-D
|
||||
""")
|
||||
|
||||
options = {"key": "_default", "goto": _check_welcome}
|
||||
return (text, help), options
|
||||
|
||||
|
||||
def _check_welcome(caller, raw_string, **kwargs):
|
||||
"""Check and fix the welcome message."""
|
||||
return "menunode_sdesc"
|
||||
|
||||
|
||||
#########################################################
|
||||
# Short Description
|
||||
#########################################################
|
||||
|
||||
def menunode_sdesc(caller, raw_string, **kwargs):
|
||||
"""Starting page."""
|
||||
char = caller.new_char
|
||||
|
||||
# another decision, so save the resume point
|
||||
char.db.chargen_step = "menunode_sdesc"
|
||||
|
||||
# check if an error message was passed to the node. if so, you'll
|
||||
# want to include it into your "name prompt" at the end of the
|
||||
# node text.
|
||||
|
||||
if error := kwargs.get("error"):
|
||||
prompt_text = f"{error}. Enter a different short description:"
|
||||
elif char.db._sdesc:
|
||||
prompt_text = f"You previously wrote: |w{char.db._sdesc}|n"
|
||||
else:
|
||||
prompt_text = paragraph("""\
|
||||
Let's begin with a two or three word description, e.g.
|
||||
"big-eared hobbit", "beardless dwarf" or "frumpy, old woman":
|
||||
""")
|
||||
|
||||
text = paragraph(f"""\
|
||||
|wShort Description|n
|
||||
|
||||
Since this game emphasizes |wrole playing|n, we will pick our
|
||||
character's name at the end (since no one will know, or should
|
||||
know, your |wreal|n name).
|
||||
|
||||
When you and another player characters uses the |glook|n command,
|
||||
you will see a description of the area, including a list of all
|
||||
the other characters, but using a short description instead of the
|
||||
character's name.
|
||||
|
||||
What should your |wshort description|n identify your character?
|
||||
|
||||
{prompt_text}""")
|
||||
|
||||
help = paragraph("""\
|
||||
In fact, you will even use this description to refer to them, for
|
||||
instance, to get a better description of the character, you might
|
||||
type |glook hobbit|n, or if there are two hobbits in the area,
|
||||
they would need to type more of
|
||||
the description: |glook big-eared hobbit|n
|
||||
|
||||
|wNote:|n Type, |gnext|n to keep your value and skip to the next step.""")
|
||||
|
||||
# Free-formed text:
|
||||
options = {"key": "_default", "goto": _check_sdesc}
|
||||
return (text, help), options
|
||||
|
||||
|
||||
def _check_sdesc(caller, raw_string, **kwargs):
|
||||
"""Check and confirm short description"""
|
||||
|
||||
# strip any extraneous whitespace from the raw text
|
||||
sdesc = raw_string.strip()
|
||||
|
||||
if sdesc == 'back':
|
||||
return "menunode_welcome"
|
||||
if sdesc in ('next', 'skip') or not sdesc:
|
||||
return "menunode_gender"
|
||||
|
||||
# aside from validation, the built-in normalization function from
|
||||
# the caller's Account does some useful cleanup on the input, just
|
||||
# in case they try something sneaky:
|
||||
sdesc = caller.account.normalize_username(sdesc)
|
||||
|
||||
# Make sure it isn't too long.
|
||||
if len(sdesc) > 30:
|
||||
return (
|
||||
"menunode_sdesc",
|
||||
{"error": f"|w{sdesc}|n is too long. Try less than two dozen letters."},
|
||||
)
|
||||
|
||||
caller.new_char.db._sdesc = sdesc
|
||||
return "menunode_gender"
|
||||
|
||||
|
||||
#########################################################
|
||||
# Character Gender
|
||||
#########################################################
|
||||
|
||||
def menunode_gender(caller, raw_string, **kwargs):
|
||||
"""Choosing a gender for the character."""
|
||||
char = caller.new_char
|
||||
|
||||
# another decision, so save the resume point
|
||||
char.db.chargen_step = "menunode_gender"
|
||||
|
||||
text = paragraph(f"""\
|
||||
|wCharacter Gender|n
|
||||
|
||||
Specifying the gender allows the system to use the correct
|
||||
pronouns for your character. You can change this in the game
|
||||
using the |ggender|n command.
|
||||
|
||||
Choose one of the following:""")
|
||||
|
||||
help = paragraph("""\
|
||||
This feature only works from the system, not from other players.
|
||||
A character would need to tell others of their preferred pronouns.
|
||||
""")
|
||||
|
||||
options = (
|
||||
{"key": "m",
|
||||
"desc": "male with pronouns, he/him/his",
|
||||
"goto": (_check_gender, {"gender": "male"}),
|
||||
},
|
||||
{"key": "f",
|
||||
"desc": "female with pronouns, she/her/hers",
|
||||
"goto": (_check_gender, {"gender": "female"}),
|
||||
},
|
||||
{"key": "n",
|
||||
"desc": "neutral with pronouns, it/its",
|
||||
"goto": (_check_gender, {"gender": "neutral"}),
|
||||
},
|
||||
{"key": "a",
|
||||
"desc": "ambiguous with pronouns, they/them/their",
|
||||
"goto": (_check_gender, {"gender": "ambiguous"}),
|
||||
},
|
||||
{"key": "b",
|
||||
"desc": "go back and change the short description",
|
||||
"goto": (menunode_sdesc),
|
||||
},
|
||||
)
|
||||
return (text, help), options
|
||||
|
||||
|
||||
def _check_gender(caller, rawstring, **kwargs):
|
||||
"""Check and confirm pose"""
|
||||
|
||||
caller.new_char.db.gender = kwargs['gender']
|
||||
return "menunode_pose"
|
||||
|
||||
|
||||
#########################################################
|
||||
# Posing the Character
|
||||
#########################################################
|
||||
|
||||
def menunode_pose(caller, raw_string, **kwargs):
|
||||
"""Posing a character."""
|
||||
char = caller.new_char
|
||||
|
||||
# another decision, so save the resume point
|
||||
char.db.chargen_step = "menunode_pose"
|
||||
|
||||
if char.db.pose:
|
||||
prompt_text = f"You previously wrote: |w{char.db.pose}|n"
|
||||
else:
|
||||
prompt_text = ''
|
||||
|
||||
text = paragraph(f"""\
|
||||
|wPosing a Character|n
|
||||
|
||||
Along with the short description given in the previous step, we
|
||||
can |wpose|n our character. For instance, entering:
|
||||
|
||||
|wsmoking a pipe|n
|
||||
|
||||
Might show our character as:
|
||||
|
||||
a |mbig-eared hobbit|w smoking a pipe|n
|
||||
|
||||
As the game plays, you can change this at any time using the
|
||||
|gpose|n command. This is just sets up a default pose, which can
|
||||
return to with |gpose reset|n. Even this isn't permanent, as you
|
||||
can change the default with something like:
|
||||
|
||||
|gpose default sitting on the ground|n
|
||||
|
||||
{prompt_text}
|
||||
|
||||
Enter an initial pose to describe, |m{char.db._sdesc}|n:""")
|
||||
|
||||
help = paragraph("""\
|
||||
Don't worry much about this pose, since you can (and will) change
|
||||
it as you play the game.
|
||||
|
||||
Having an initial pose makes the game (and your character) look
|
||||
better.
|
||||
|
||||
Enter an initial pose to describe your character, or type |gback|n
|
||||
to change your short description.""")
|
||||
|
||||
# Free-formed text:
|
||||
options = {"key": "_default", "goto": _check_pose}
|
||||
return (text, help), options
|
||||
|
||||
|
||||
def _check_pose(caller, raw_string, **kwargs):
|
||||
"""Check and confirm pose"""
|
||||
|
||||
# strip any extraneous whitespace from the raw text
|
||||
pose = raw_string.strip()
|
||||
|
||||
if pose == 'back':
|
||||
return "menunode_gender"
|
||||
if pose in ('next', 'skip') or not pose:
|
||||
return "menunode_desc"
|
||||
|
||||
# First, allow the character to be posed:
|
||||
caller.new_char.pose = True
|
||||
caller.new_char.db.pose = pose
|
||||
caller.new_char.db.pose_default = pose
|
||||
|
||||
return "menunode_desc"
|
||||
|
||||
|
||||
#########################################################
|
||||
# Full Description
|
||||
#########################################################
|
||||
|
||||
def menunode_desc(caller, raw_string, **kwargs):
|
||||
"""Posing a character."""
|
||||
char = caller.new_char
|
||||
|
||||
# another decision, so save the resume point
|
||||
char.db.chargen_step = "menunode_desc"
|
||||
|
||||
if char.db.desc and char.db.desc != "This is a character.":
|
||||
prompt_text = f"You previously wrote:\n\n |w{char.db.desc}|n"
|
||||
else:
|
||||
prompt_text = ''
|
||||
|
||||
text = paragraph(f"""
|
||||
|wCharacter's Full Description|n
|
||||
|
||||
Unlike the short description given in the previous step (see only
|
||||
when glancing around the room), a player will see this description
|
||||
when they type:
|
||||
|
||||
|glook {char.db._sdesc}|n
|
||||
|
||||
This can be as long as you would like. Keep in mind this
|
||||
description is what a player |wsees|n. so, no backstory here.
|
||||
Well, unless you can cleverly weave in backstory hints from that
|
||||
scar across your cheek.
|
||||
|
||||
You can change this at any time using the |gdesc|n command.
|
||||
|
||||
{prompt_text}
|
||||
|
||||
Enter your character's full description:""")
|
||||
|
||||
help = paragraph("""\
|
||||
What makes a good character description? You want someone to be
|
||||
able to picture your character in their mind, so hair, skin and
|
||||
eye color, as well as what they are wearing.
|
||||
|
||||
To make your character truly memorable, mention the unique
|
||||
features, so think of your character's backstory, and describe the
|
||||
small hammer on your necklace, so you can tell others who inquire
|
||||
about your father's last wish...
|
||||
|
||||
Enter your character's full description or type |gback|n to change
|
||||
your pose.""")
|
||||
|
||||
# Free-formed text:
|
||||
options = {"key": "_default", "goto": _check_desc}
|
||||
return (text, help), options
|
||||
|
||||
|
||||
def _check_desc(caller, raw_string, **kwargs):
|
||||
"""Check and confirm desc"""
|
||||
|
||||
# strip any extraneous whitespace from the raw text
|
||||
desc = raw_string.strip()
|
||||
|
||||
if desc == 'back':
|
||||
return "menunode_pose"
|
||||
if desc in ('next', 'skip') or not desc:
|
||||
return "menunode_choose_name"
|
||||
|
||||
# First, allow the character to be descd:
|
||||
caller.new_char.db.desc = desc
|
||||
|
||||
return "menunode_choose_name"
|
||||
|
||||
|
||||
#########################################################
|
||||
# Choosing a Name
|
||||
#########################################################
|
||||
|
||||
def menunode_choose_name(caller, raw_string, **kwargs):
|
||||
"""Name selection"""
|
||||
char = caller.new_char
|
||||
|
||||
# another decision, so save the resume point
|
||||
char.db.chargen_step = "menunode_choose_name"
|
||||
|
||||
# check if an error message was passed to the node. if so, you'll
|
||||
# want to include it into your "name prompt" at the end of the
|
||||
# node text.
|
||||
if error := kwargs.get("error"):
|
||||
prompt_text = f"{error}. Enter a different name."
|
||||
else:
|
||||
# there was no error, so just ask them to enter a name.
|
||||
prompt_text = "Enter a name here to check if it's available."
|
||||
|
||||
# this will print every time the player is prompted to choose a name,
|
||||
# including the prompt text defined above
|
||||
text = paragraph(f"""\
|
||||
|wChoosing a Name|n
|
||||
|
||||
Now, let's give your character a name. Unless you tell others, no
|
||||
one will know (or need to know) your this name, however, you will
|
||||
use this name, when you type |gic Name|n to choose this character
|
||||
to play.
|
||||
|
||||
{prompt_text}""")
|
||||
|
||||
help = paragraph("""
|
||||
Since you may have to type the entire name, you may want this to
|
||||
be a single word for the first name. Your background could include
|
||||
your full family name.
|
||||
""")
|
||||
|
||||
# since this is a free-text field, we just have the one
|
||||
options = {"key": "_default", "goto": _check_charname}
|
||||
return (text, help), options
|
||||
|
||||
|
||||
def _check_charname(caller, raw_string, **kwargs):
|
||||
"""Check and confirm name choice"""
|
||||
|
||||
# strip any extraneous whitespace from the raw text
|
||||
# if you want to do any other validation on the name, e.g. no punctuation allowed, this
|
||||
# is the place!
|
||||
charname = raw_string.strip()
|
||||
|
||||
if charname == 'back':
|
||||
return "menunode_desc"
|
||||
if charname in ('next', 'skip') or not charname:
|
||||
return (
|
||||
"menunode_choose_name",
|
||||
{"error": f"Can't skip this step. Please enter a name."},
|
||||
)
|
||||
|
||||
# aside from validation, the built-in normalization function from the caller's Account does
|
||||
# some useful cleanup on the input, just in case they try something sneaky
|
||||
charname = caller.account.normalize_username(charname)
|
||||
|
||||
# check to make sure that the name doesn't already exist
|
||||
candidates = Character.objects.filter_family(db_key__iexact=charname)
|
||||
if len(candidates):
|
||||
# the name is already taken - report back with the error
|
||||
return (
|
||||
"menunode_choose_name",
|
||||
{"error": f"|w{charname}|n is unavailable. Enter a different name."},
|
||||
)
|
||||
else:
|
||||
# it's free! set the character's key to the name to reserve it
|
||||
caller.new_char.key = charname
|
||||
# continue on to the confirmation node
|
||||
return "menunode_confirm_name"
|
||||
|
||||
|
||||
def menunode_confirm_name(caller, raw_string, **kwargs):
|
||||
"""Confirm the name choice"""
|
||||
char = caller.new_char
|
||||
|
||||
text = paragraph(f"""\
|
||||
|wCreate Character?|n
|
||||
|
||||
Shall we create your character, |c{char.key}|n:
|
||||
|
||||
|w{char.db._sdesc} {char.db.pose}|n
|
||||
|
||||
Description:
|
||||
|
||||
{char.db.desc}""")
|
||||
|
||||
options = [
|
||||
{"key": ("Yes", "y"), "goto": "menunode_end"},
|
||||
{"key": ("No", "n"), "goto": "menunode_choose_name"},
|
||||
]
|
||||
return text, options
|
||||
|
||||
|
||||
#########################################################
|
||||
# The End
|
||||
#########################################################
|
||||
|
||||
|
||||
def menunode_end(caller, raw_string):
|
||||
"""End-of-chargen cleanup."""
|
||||
char = caller.new_char
|
||||
|
||||
# clear in-progress status
|
||||
char.attributes.remove("chargen_step")
|
||||
char.db.tutorstate = 0
|
||||
char.db.visited = False
|
||||
|
||||
text = paragraph(f"""
|
||||
You have created a character. I hope you enjoy playing this game.
|
||||
|
||||
To stop playing with this character, type: |gooc|n
|
||||
|
||||
You can create a new character with: |gcharcreate|n
|
||||
|
||||
To play with this one, type: |gic {caller.new_char.key}|n
|
||||
|
||||
As always, type |ghelp|n (especially |ghelp me start|n),
|
||||
to learn more about how to play this game.
|
||||
|
||||
Alrighty then, let's log in and start playing the game.
|
||||
Imagine that you are now in a strange land on the edge of Forever.
|
||||
Welcome to the cozy forest section in the |wDomain of the WyldWood|n.
|
||||
""")
|
||||
return text, None
|
||||
Loading…
Reference in a new issue