def add_mod(self): """Adds a modifier to target""" from server.utils.arx_utils import dict_from_choices_field choices = dict_from_choices_field(RollModifier, "CHECK_CHOICES") try: value = int(self.rhslist[0]) tag_name = self.rhslist[1].lower() check = choices[self.rhslist[2].lower()] except (IndexError, AttributeError): self.msg( "You must provide value, tag name, and the type of check.") except KeyError: self.msg("Not a valid check type: %s" % ", ".join(choices.keys())) else: targ = self.caller.search(self.lhs) if not targ: return if "targetmod" in self.switches: mod = targ.add_modifier(value, check_type=check, target_tag=tag_name) else: mod = targ.add_modifier(value, check_type=check, user_tag=tag_name) self.msg("You have added a modifier to %s: %s." % (targ, mod))
def set_property_for_dompc(self, plot, field, choices_attr): """Sets a property for someone involved in a plot""" choices = dict_from_choices_field(PCPlotInvolvement, choices_attr) try: name, choice = self.rhslist except (TypeError, ValueError): raise CommandError("You must give both a name and a value.") choice = choice.lower() try: choice_value = choices[choice] except KeyError: keys = [ob[1].lower() for ob in getattr(PCPlotInvolvement, choices_attr)] raise CommandError("Choice must be one of: %s." % ", ".join(keys)) dompc = self.dompc_search(name) involvement, _ = plot.dompc_involvement.get_or_create(dompc=dompc) setattr(involvement, field, choice_value) involvement.save() self.msg("You have set %s as a %s in %s." % (dompc, getattr(involvement, "get_%s_display" % field)(), plot))
class CmdAction(ActionCommandMixin, ArxPlayerCommand): """ A character's story actions that a GM responds to. Usage: @action/newaction [<crisis #>=]<story of action> @action/tldr <action #>=<title> @action/category <action #>=<category> @action/roll <action #>=<stat>,<skill> @action/ooc_intent <action #>=<ooc intent, then post-submission questions> @action/question <action #>=<ask a question> @action/cancel <action #> @action/readycheck <action #> @action/submit <action #> Options: @action [<action #>] @action/invite <action #>=<character>[,<character2>,...] @action/setaction <action #>=<action text> @action/setsecret[/traitor] <action #>=<secret action> @action/setcrisis <action #>=<crisis #> @action/add <action#>=<resource or 'ap' or 'army'>,<amount or army ID#> @action/makepublic <action #> @action/toggletraitor <action #> @action/toggleattend <action #> @action/noscene <action #> Creating /newaction costs Action Points (ap). Requires 'tldr' which is a short summary in a few words of the action, a category, stat/skill for the dice check (just pick whatever might be plausible, GMs can adjust it if they disagree), and /ooc_intent which is the what sort of outcome you are hoping for, whether that's reputation adjustments, some advancement in a story, the favor of important npcs, magical power, whatever. It's just to make sure that GMs know what you have in mind for what you're trying to accomplish. Use /submit after all options, when ready for GM review. GMs may require more info or ask you to edit with /setaction and /submit again. Categories: combat, scouting, support, diplomacy, sabotage, research. With /invite you ask others to assist your action. They use /setaction or /cancel, and require all the same fields except category. A covert action can be added with /setsecret. Optional /traitor (and /toggletraitor) switch makes your dice roll detract from goal. With /setcrisis this becomes your response to a Crisis. Allocate resources with /add by specifying a type (ap, army, social, silver, etc.) and amount, or the ID# of your army. The /makepublic switch allows everyone to see your action after a GM publishes an outcome. If you prefer offscreen resolution, use /noscene toggle. To ask questions for GMs, use /question. Using /toggleattend switches whether your character is physically present, or arranging for the action's occurance in other ways. One action may be attended per crisis update; all others must be passive to represent simultaneous response by everyone involved. Up to 5 attendees are allowed per crisis response action, unless it is /noscene. Actions are private by default, but there's a small xp reward for marking a completed action as public with the /makepublic switch. """ key = "@action" locks = "cmd:all()" help_category = "Story" aliases = ["@actions", "+storyrequest"] action_categories = dict_from_choices_field(PlotAction, "CATEGORY_CHOICES") requires_draft_switches = ("invite", "setcrisis", "readycheck") requires_editable_switches = ("roll", "tldr", "title", "category", "submit", "invite", "setaction", "setcrisis", "add", "toggletraitor", "toggleattend", "ooc_intent", "setsecret") requires_unpublished_switches = ("question", "cancel", "noscene") requires_owner_switches = ("invite", "makepublic", "category", "setcrisis", "noscene", "readycheck") @property def dompc(self): """Shortcut for getting their dominion playerornpc object""" return self.caller.Dominion @property def actions_and_invites(self): """Lists non-cancelled actions for creator and those invited to it""" return PlotAction.objects.filter( Q(dompc=self.dompc) | Q(assistants=self.dompc)).exclude( status=PlotAction.CANCELLED).distinct() # noinspection PyUnusedLocal def get_help(self, caller, cmdset): """Overrides basic help, which defaults to the __doc__ string""" msg = self.__doc__ recent_actions = caller.recent_actions max_actions = PlotAction.max_requests max_assists = PlotActionAssistant.MAX_ASSISTS recent_assists = caller.recent_assists msg += """ You are permitted {w%s{n actions and {w%s{n assists every 60 days, and have currently taken {w%s{n actions and {w%s{n assists. Assists can be made instead of actions, and assists over %s count toward the action cap.""" % ( max_actions, max_assists, recent_actions.count(), recent_assists.count(), max_assists) return msg def func(self): """Executes @action command""" if not self.args and not self.switches: return self.list_actions() if "newaction" in self.switches: return self.new_action() action = self.get_action(self.lhs) if not action: return if not self.switches: return self.view_action(action) if not self.check_valid_switch_for_action_type(action): return if "makepublic" in self.switches: return self.make_public(action) if "question" in self.switches: return self.ask_question(action) if self.check_switches(self.requires_draft_switches): return self.do_requires_draft_switches(action) if self.check_switches(self.requires_editable_switches): # PS - NV is f*****g amazing return self.do_requires_editable_switches(action) if self.check_switches(self.requires_unpublished_switches): return self.do_requires_unpublished_switches(action) else: self.msg("Invalid switch. See 'help @action'.") def check_valid_switch_for_action_type(self, action): """ Checks if the specified switches require the main action, and if so, whether our action is the main action. Args: action (PlotAction or PlotActionAssistant): action or assisting action Returns: True or False for whether we're okay to proceed. """ if not (set(self.switches) & set(self.requires_owner_switches)): return True if action.is_main_action: return True self.msg("Only the action leader can use that switch.") return False def make_public(self, action): """Makes an action public knowledge""" try: action.make_public() except ActionSubmissionError as err: self.msg(err) def do_requires_draft_switches(self, action): """Executes switches that require the action to be in Draft status""" if not action.status == PlotAction.DRAFT: return self.send_too_late_msg() elif "invite" in self.switches: return self.invite_assistant(action) elif "setcrisis" in self.switches: return self.set_crisis(action) elif "readycheck" in self.switches: return self.ready_check(action) def do_requires_editable_switches(self, action): """Executes switches that requires the action to be in an editable state""" if not action.editable: return self.send_no_edits_msg() if "roll" in self.switches: return self.set_roll(action) if "tldr" in self.switches or "title" in self.switches: return self.set_topic(action) elif "category" in self.switches: return self.set_category(action) elif "submit" in self.switches: return self.submit_action(action) elif "setaction" in self.switches: return self.set_action(action) elif "add" in self.switches: return self.add_resource(action) elif "toggletraitor" in self.switches: return self.toggle_traitor(action) elif "toggleattend" in self.switches: return self.toggle_attend(action) elif "ooc_intent" in self.switches: return self.set_ooc_intent(action) elif "setsecret" in self.switches: return self.set_secret_action(action) def do_requires_unpublished_switches(self, action): """Executes switches that require the action to not be Published""" if action.status in (PlotAction.PUBLISHED, PlotAction.PENDING_PUBLISH): return self.send_no_edits_msg() elif "cancel" in self.switches: return self.cancel_action(action) elif "noscene" in self.switches: return self.toggle_noscene(action) def send_no_edits_msg(self): """Tells the caller they can't edit the action.""" self.msg("You cannot edit that action at this time.") def send_too_late_msg(self): """Tells the caller that they could only do that in Draft mode""" self.msg("Can only be done while the action is in Draft status.") def list_actions(self): """Prints a table of the actions we've taken""" table = EvTable("ID", "Crisis", "Date", "Owner", "Status") actions = self.actions_and_invites attending = list( actions.filter( Q(dompc=self.dompc, attending=True) | Q(assisting_actions__dompc=self.dompc, assisting_actions__attending=True))) for action in actions: date = "--" if action.date_submitted: date = action.date_submitted.strftime("%x") def color_unsubmitted(string): """Colors the status display for assisting actions of the player that aren't ready to submit""" if action.status == PlotAction.DRAFT and action.check_unready_assistant( self.dompc): return "|r%s|n" % string return string is_attending = action in attending id_str = "{w*%s{n" % action.id if is_attending else str(action.id) table.add_row(id_str, str(action.plot)[:20], date, str(action.dompc), color_unsubmitted(action.get_status_display())) msg = "\nActions you're attending will be highlighted with {w*{n." self.msg(str(table) + msg) def send_no_args_msg(self, noun): """Sends failure message about what they're missing.""" if not noun: noun = "args" self.msg("You need to include %s." % noun) def new_action(self): """Create a new action.""" if not self.args: return self.send_no_args_msg("a story") crisis = None crisis_msg = "" actions = self.lhs if self.rhs: crisis = self.get_valid_crisis(self.lhs) actions = self.rhs crisis_msg = " to respond to %s" % str(crisis) if not crisis: return if not self.can_create(crisis): return diff = PlotAction.NORMAL_DIFFICULTY action = self.dompc.actions.create(actions=actions, plot=crisis, stat_used="", skill_used="", difficulty=diff) self.msg("You have drafted a new action {w(#%s){n%s: %s" % (action.id, crisis_msg, actions)) self.msg( "Please note that you cannot invite players to an action once it is submitted." ) if crisis: self.warn_crisis_omnipresence(action) def get_action(self, arg): """Returns an action we are involved in from ID# args. Args: arg (str): String to use to find the ID of the crisis action. Returns: A CrisisAction or a CrisisActionAssistant depending if the caller is the owner of the main action or an assistant. """ try: dompc = self.dompc action = self.actions_and_invites.get(id=arg) try: action = action.assisting_actions.get(dompc=dompc) except PlotActionAssistant.DoesNotExist: pass return action except (PlotAction.DoesNotExist, ValueError): self.msg("No action found by that ID.") self.list_actions() def get_my_actions(self, crisis=False, assists=False): """Returns caller's actions.""" dompc = self.dompc if not assists: actions = dompc.actions.all() else: actions = PlotAction.objects.filter( Q(dompc=dompc) | Q(assistants=dompc)).distinct() if not crisis: actions = actions.filter(plot__isnull=True) return actions def get_valid_crisis(self, name_or_id): """Gets crisis they can participate in that matches name or ID""" try: qs = Plot.objects.viewable_by_player(self.caller) if name_or_id.isdigit(): return qs.get(id=name_or_id) return qs.get(name__iexact=name_or_id) except Plot.DoesNotExist: self.msg("No crisis found by that name or ID.") def can_create(self, crisis=None): """Checks criteria for creating a new action.""" if crisis and not self.can_set_crisis(crisis): return False my_draft = self.get_my_actions().filter(status=PlotAction.DRAFT).last() if my_draft: self.msg( "You have drafted an action which needs to be submitted or canceled: %s" % my_draft.id) return False if not self.caller.pay_action_points(50): self.msg("You do not have enough action points.") return False return True def can_set_crisis(self, crisis): """Checks criteria for linking to a Crisis.""" try: crisis.raise_creation_errors(self.dompc) except ActionSubmissionError as err: self.msg(err) return False return True def set_category(self, action): """Sets the category for an action""" if not action.is_main_action: self.msg("Only the main action has a category.") return if not self.rhs: return self.send_no_args_msg("a category") category_names = self.action_categories.keys() if self.rhs not in category_names: category_names = set(ob.lower() for ob in category_names) self.send_no_args_msg("one of these categories: %s" % ", ".join(category_names)) return self.set_action_field(action, "category", self.action_categories[self.rhs]) def set_roll(self, action): """Sets a stat and skill for action or assistant""" from world.stats_and_skills import VALID_SKILLS, VALID_STATS try: stat, skill = self.rhslist stat = stat.lower() skill = skill.lower() except (ValueError, TypeError, AttributeError): self.msg("Usage: @action/roll <action #>=<stat>,<skill>") return if stat not in VALID_STATS or skill not in VALID_SKILLS: self.msg("You must provide a valid stat and skill.") return field_name = "stat_used" self.set_action_field(action, field_name, stat, verbose_name="stat") field_name = "skill_used" return self.set_action_field(action, field_name, skill, verbose_name="skill") def set_topic(self, action): """Sets the topic for an action""" if not self.rhs: return self.send_no_args_msg("a title") if len(self.rhs) > 80: self.send_no_args_msg( "a shorter title; aim for under 80 characters") return return self.set_action_field(action, "topic", self.rhs) def set_ooc_intent(self, action): """ Sets our ooc intent, or if we're already submitted and have an intent set, it asks a question. """ if not self.rhs: self.msg("You must enter a message.") return action.set_ooc_intent(self.rhs) self.msg("You have set your ooc intent to be: %s" % self.rhs) def ask_question(self, action): """Submits an OOC question for an action""" if not self.rhs: self.msg("You must enter text for your question.") return action.ask_question(self.rhs) self.msg("You have submitted a question: %s" % self.rhs) def cancel_action(self, action): """Cancels the action""" action.cancel() self.msg("Action cancelled.") def submit_action(self, action): """I love a bishi. He too will submit.""" try: action.submit() except ActionSubmissionError as err: self.msg(err) else: self.msg("You have submitted your action.") def toggle_noscene(self, action): """Toggles whether they prefer the action get offscreen resolution""" action.prefer_offscreen = not action.prefer_offscreen action.save() color = "{r" if action.prefer_offscreen else "{w" self.msg("Preference for offscreen resolution set to: %s%s" % (color, action.prefer_offscreen)) def toggle_traitor(self, action): """Toggles whether they're working against the main action/crisis""" action.traitor = not action.traitor color = "{r" if action.traitor else "{w" action.save() self.msg("Traitor is now set to: %s%s{n" % (color, action.traitor)) def set_action(self, action): """Sets the main story of what they're doing in the action""" if not self.rhs: return self.send_no_args_msg("a story") if not action.is_main_action: try: action.set_action(self.rhs) except ActionSubmissionError as err: self.msg(err) return else: self.msg("%s now has your assistance: %s" % (action.plot_action, self.rhs)) else: self.set_action_field(action, "actions", self.rhs) if action.plot: self.do_passive_warnings(action) def set_secret_action(self, action): """Sets a secret story of what they're doing in the action""" if not self.rhs: return self.send_no_args_msg("a story of your secret actions") self.set_action_field(action, "secret_actions", self.rhs, verbose_name="Secret actions") def warn_crisis_overcrowd(self, action): """Warns that too many people are attending""" try: action.check_plot_overcrowd() except ActionSubmissionError as err: self.msg("{yWarning:{n %s" % err) def warn_crisis_omnipresence(self, action): """Warns that they're already doing stuff for the crisis""" try: action.check_plot_omnipresence() except ActionSubmissionError as err: self.msg("{yWarning:{n %s" % err) def do_passive_warnings(self, action): """Delivers warnings of what would make submission fail""" self.warn_crisis_omnipresence(action) if not action.prefer_offscreen: self.warn_crisis_overcrowd(action) def set_crisis(self, action): """Sets the crisis for the action""" if not self.rhs: action.plot = None action.save() self.msg("Your action no longer targets any crisis.") return crisis = self.get_valid_crisis(self.rhs) if not crisis: return if not self.can_set_crisis(crisis): return action.plot = crisis action.save() self.msg("You have set the action to be for crisis: %s" % crisis) self.do_passive_warnings(action) def ready_check(self, action): """Checks whether assistants are ready for the player""" unready = action.get_unready_assisting_actions() if not unready: self.msg("All invited assistants are currently ready.") else: self.msg("The following assistants aren't ready: %s" % ", ".join(str(ob.author) for ob in unready)) def toggle_attend(self, action): """Toggles whether they're attending the action in-person, onscreen""" if action.attending: action.attending = False action.save() self.msg("You are marked as no longer attending the action.") return try: action.mark_attending() except ActionSubmissionError as err: self.msg(err) return self.msg( "You have marked yourself as physically being present for that action." )