diff --git a/evennia/commands/default/general.py b/evennia/commands/default/general.py index 66c5afc4fd..8c031ac605 100644 --- a/evennia/commands/default/general.py +++ b/evennia/commands/default/general.py @@ -395,39 +395,64 @@ class CmdGet(COMMAND_DEFAULT_CLASS): locks = "cmd:all()" arg_regex = r"\s|$" + def parse(self): + super().parse() + self.number = 0 + if self.args: + # check for numbering + count, *args = self.args.split(maxsplit=1) + # we only use the first word as a count if it's a number and + # there is more text afterwards + if args and count.isdecimal(): + self.number = int(count) + self.args = args[0] + def func(self): """implements the command.""" caller = self.caller if not self.args: - caller.msg("Get what?") + self.msg("Get what?") return - obj = caller.search(self.args, location=caller.location) - if not obj: + objs = caller.search(self.args, location=caller.location, stacked=self.number) + if not objs: return - if caller == obj: - caller.msg("You can't get yourself.") - return - if not obj.access(caller, "get"): - if obj.db.get_err_msg: - caller.msg(obj.db.get_err_msg) - else: - caller.msg("You can't get that.") + # the 'stacked' search sometimes returns a list, sometimes not, so we make it always a list + # NOTE: this behavior may be a bug, see issue #3432 + objs = utils.make_iter(objs) + + if len(objs) == 1 and caller == objs[0]: + self.msg("You can't get yourself.") return - # calling at_pre_get hook method - if not obj.at_pre_get(caller): - return + # if we aren't allowed to get any of the objects, cancel the get + for obj in objs: + # check the locks + if not obj.access(caller, "get"): + if obj.db.get_err_msg: + self.msg(obj.db.get_err_msg) + else: + self.msg("You can't get that.") + return + # calling at_pre_get hook method + if not obj.at_pre_get(caller): + return - success = obj.move_to(caller, quiet=True, move_type="get") - if not success: - caller.msg("This can't be picked up.") + moved = [] + # attempt to move all of the objects + for obj in objs: + if obj.move_to(caller, quiet=True, move_type="get"): + moved.append(obj) + # calling at_get hook method + obj.at_get(caller) + + if not moved: + # none of the objects were successfully moved + self.msg("That can't be picked up.") else: - singular, _ = obj.get_numbered_name(1, caller) - caller.location.msg_contents(f"$You() $conj(pick) up {singular}.", from_obj=caller) - # calling at_get hook method - obj.at_get(caller) + singular, plural = moved[0].get_numbered_name(len(moved), caller) + caller.location.msg_contents(f"$You() $conj(pick) up {plural if len(moved) > 1 else singular}.", from_obj=caller) class CmdDrop(COMMAND_DEFAULT_CLASS): @@ -445,6 +470,18 @@ class CmdDrop(COMMAND_DEFAULT_CLASS): locks = "cmd:all()" arg_regex = r"\s|$" + def parse(self): + super().parse() + self.number = 0 + if self.args: + # check for numbering + count, *args = self.args.split(maxsplit=1) + # we only use the first word as a count if it's a number and + # there is more text afterwards + if args and count.isdecimal(): + self.number = int(count) + self.args = args[0] + def func(self): """Implement command""" @@ -455,27 +492,39 @@ class CmdDrop(COMMAND_DEFAULT_CLASS): # Because the DROP command by definition looks for items # in inventory, call the search function using location = caller - obj = caller.search( + objs = caller.search( self.args, location=caller, nofound_string=f"You aren't carrying {self.args}.", multimatch_string=f"You carry more than one {self.args}:", + stacked=self.number, ) - if not obj: + if not objs: return + # the 'stacked' search sometimes returns a list, sometimes not, so we make it always a list + # NOTE: this behavior may be a bug, see issue #3432 + objs = utils.make_iter(objs) - # Call the object script's at_pre_drop() method. - if not obj.at_pre_drop(caller): - return + # if any objects fail the drop permission check, cancel the drop + for obj in objs: + # Call the object's at_pre_drop() method. + if not obj.at_pre_drop(caller): + return - success = obj.move_to(caller.location, quiet=True, move_type="drop") - if not success: - caller.msg("This couldn't be dropped.") + # do the actual dropping + moved = [] + for obj in objs: + if obj.move_to(caller.location, quiet=True, move_type="drop"): + moved.append(obj) + # Call the object's at_drop() method. + obj.at_drop(caller) + + if not moved: + # none of the objects were successfully moved + self.msg("That can't be dropped.") else: - singular, _ = obj.get_numbered_name(1, caller) - caller.location.msg_contents(f"$You() $conj(drop) {singular}.", from_obj=caller) - # Call the object script's at_drop() method. - obj.at_drop(caller) + singular, plural = obj.get_numbered_name(len(moved), caller) + caller.location.msg_contents(f"$You() $conj(drop) {plural if len(moved) > 1 else singular}.", from_obj=caller) class CmdGive(COMMAND_DEFAULT_CLASS): @@ -494,6 +543,18 @@ class CmdGive(COMMAND_DEFAULT_CLASS): locks = "cmd:all()" arg_regex = r"\s|$" + def parse(self): + super().parse() + self.number = 0 + if self.lhs: + # check for numbering + count, *args = self.lhs.split(maxsplit=1) + # we only use the first word as a count if it's a number and + # there is more text afterwards + if args and count.isdecimal(): + self.number = int(count) + self.lhs = args[0] + def func(self): """Implement give""" @@ -501,37 +562,52 @@ class CmdGive(COMMAND_DEFAULT_CLASS): if not self.args or not self.rhs: caller.msg("Usage: give = ") return + # find the thing(s) to give away to_give = caller.search( self.lhs, location=caller, nofound_string=f"You aren't carrying {self.lhs}.", multimatch_string=f"You carry more than one {self.lhs}:", + stacked=self.number, ) + if not to_give: + return + # find the target to give to target = caller.search(self.rhs) - if not (to_give and target): + if not target: return - singular, _ = to_give.get_numbered_name(1, caller) + # the 'stacked' search sometimes returns a list, sometimes not, so we make it always a list + # NOTE: this behavior may be a bug, see issue #3432 + to_give = utils.make_iter(to_give) + + + singular, plural = to_give[0].get_numbered_name(len(to_give), caller) if target == caller: - caller.msg(f"You keep {singular} to yourself.") - return - if not to_give.location == caller: - caller.msg(f"You are not holding {singular}.") + caller.msg(f"You keep {plural if len(to_give) > 1 else singular} to yourself.") return - # calling at_pre_give hook method - if not to_give.at_pre_give(caller, target): - return + # if any of the objects aren't allowed to be given, cancel the give + for obj in to_give: + # calling at_pre_give hook method + if not obj.at_pre_give(caller, target): + return - # give object - success = to_give.move_to(target, quiet=True, move_type="give") - if not success: - caller.msg(f"You could not give {singular} to {target.key}.") + # do the actual moving + moved = [] + for obj in to_give: + if obj.move_to(target, quiet=True, move_type="give"): + moved.append(obj) + # Call the object's at_give() method. + obj.at_give(caller, target) + + if not moved: + caller.msg(f"You could not give that to {target.get_display_name(caller)}.") else: - caller.msg(f"You give {singular} to {target.key}.") - target.msg(f"{caller.key} gives you {singular}.") - # Call the object script's at_give() method. - to_give.at_give(caller, target) + singular, plural = to_give[0].get_numbered_name(len(moved), caller) + names = plural if len(moved) > 1 else singular + caller.msg(f"You give {names} to {target.get_display_name(caller)}.") + target.msg(f"{caller.get_display_name(target)} gives you {names}.") class CmdSetDesc(COMMAND_DEFAULT_CLASS):