diff --git a/evennia/contrib/puzzles.py b/evennia/contrib/puzzles.py index 47166c88f9..1738954066 100644 --- a/evennia/contrib/puzzles.py +++ b/evennia/contrib/puzzles.py @@ -301,12 +301,22 @@ class CmdEditPuzzle(MuxCommand): @puzzleedit[/delete] <#dbref> @puzzleedit <#dbref>/use_success_message = @puzzleedit <#dbref>/use_success_location_message = + @puzzleedit[/addpart] <#dbref> = + @puzzleedit[/delpart] <#dbref> = + @puzzleedit[/addresult] <#dbref> = + @puzzleedit[/delresult] <#dbref> = Switches: + addpart - adds parts to the puzzle + delpart - removes parts from the puzzle + addresult - adds results to the puzzle + delresult - removes results from the puzzle delete - deletes the recipe. Existing parts and results aren't modified use_success_location_message containing {result_names} and {caller} will automatically be replaced with correct values. Both are optional. + When removing parts/results, it's possible to remove all. + """ key = '@puzzleedit' @@ -314,11 +324,11 @@ class CmdEditPuzzle(MuxCommand): help_category = 'Puzzles' def func(self): - _USAGE = "Usage: @puzzleedit[/switches] [/attribute = ]" + self._USAGE = "Usage: @puzzleedit[/switches] [/attribute = ]" caller = self.caller if not self.lhslist: - caller.msg(_USAGE) + caller.msg(self._USAGE) return if '/' in self.lhslist[0]: @@ -327,7 +337,7 @@ class CmdEditPuzzle(MuxCommand): recipe_dbref = self.lhslist[0] if not utils.dbref(recipe_dbref): - caller.msg("A puzzle recipe's #dbref must be specified.\n" + _USAGE) + caller.msg("A puzzle recipe's #dbref must be specified.\n" + self._USAGE) return puzzle = search.search_script(recipe_dbref) @@ -347,6 +357,34 @@ class CmdEditPuzzle(MuxCommand): caller.msg('%s was deleted' % puzzle_name_id) return + elif 'addpart' in self.switches: + objs = self._get_objs() + if objs: + added = self._add_parts(objs, puzzle) + caller.msg('%s were added to parts' % (', '.join(added))) + return + + elif 'delpart' in self.switches: + objs = self._get_objs() + if objs: + removed = self._remove_parts(objs, puzzle) + caller.msg('%s were removed from parts' % (', '.join(removed))) + return + + elif 'addresult' in self.switches: + objs = self._get_objs() + if objs: + added = self._add_results(objs, puzzle) + caller.msg('%s were added to results' % (', '.join(added))) + return + + elif 'delresult' in self.switches: + objs = self._get_objs() + if objs: + removed = self._remove_results(objs, puzzle) + caller.msg('%s were removed from results' % (', '.join(removed))) + return + else: # edit attributes @@ -367,6 +405,58 @@ class CmdEditPuzzle(MuxCommand): ) return + def _get_objs(self): + if not self.rhslist: + self.caller.msg(self._USAGE) + return + objs = [] + for o in self.rhslist: + obj = self.caller.search(o) + if obj: + objs.append(obj) + return objs + + def _add_objs_to(self, objs, to): + """Adds propto objs to the given set (parts or results)""" + added = [] + toobjs = list(to[:]) + for obj in objs: + protoobj = proto_def(obj) + toobjs.append(protoobj) + added.append(obj.key) + return added, toobjs + + def _remove_objs_from(self, objs, frm): + """Removes propto objs from the given set (parts or results)""" + removed = [] + fromobjs = list(frm[:]) + for obj in objs: + protoobj = proto_def(obj) + if protoobj in fromobjs: + fromobjs.remove(protoobj) + removed.append(obj.key) + return removed, fromobjs + + def _add_parts(self, objs, puzzle): + added, toobjs = self._add_objs_to(objs, puzzle.db.parts) + puzzle.db.parts = tuple(toobjs) + return added + + def _remove_parts(self, objs, puzzle): + removed, fromobjs = self._remove_objs_from(objs, puzzle.db.parts) + puzzle.db.parts = tuple(fromobjs) + return removed + + def _add_results(self, objs, puzzle): + added, toobjs = self._add_objs_to(objs, puzzle.db.results) + puzzle.db.results = tuple(toobjs) + return added + + def _remove_results(self, objs, puzzle): + removed, fromobjs = self._remove_objs_from(objs, puzzle.db.results) + puzzle.db.results = tuple(fromobjs) + return removed + class CmdArmPuzzle(MuxCommand): """ diff --git a/evennia/contrib/tests.py b/evennia/contrib/tests.py index 744251774c..6eca07289b 100644 --- a/evennia/contrib/tests.py +++ b/evennia/contrib/tests.py @@ -1437,11 +1437,6 @@ class TestPuzzles(CommandTest): def test_puzzleedit(self): recipe_dbref = self._good_recipe('makefire', ['stone', 'flint'], ['fire'] , and_destroy_it=False) - # delete proto parts and proto results - self.stone.delete() - self.flint.delete() - self.fire.delete() - def _puzzleedit(swt, dbref, args, expmsg): if (swt is None) and (dbref is None) and (args is None): cmdstr = '' @@ -1454,6 +1449,11 @@ class TestPuzzles(CommandTest): caller=self.char1 ) + # delete proto parts and proto results + self.stone.delete() + self.flint.delete() + self.fire.delete() + # bad syntax _puzzleedit(None, None, None, "A puzzle recipe's #dbref must be specified.\nUsage: @puzzleedit") _puzzleedit('', '1', '', "A puzzle recipe's #dbref must be specified.\nUsage: @puzzleedit") @@ -1472,6 +1472,7 @@ class TestPuzzles(CommandTest): # edit use_success_message and use_success_location_message _puzzleedit('', recipe_dbref, '/use_success_message = Yes!', 'makefire(%s) use_success_message = Yes!' % recipe_dbref) _puzzleedit('', recipe_dbref, '/use_success_location_message = {result_names} Yeah baby! {caller}', 'makefire(%s) use_success_location_message = {result_names} Yeah baby! {caller}' % recipe_dbref) + self._arm(recipe_dbref, 'makefire', ['stone', 'flint']) self.room1.msg_contents = Mock() self._use('stone, flint', 'Yes!') @@ -1481,6 +1482,67 @@ class TestPuzzles(CommandTest): _puzzleedit('/delete', recipe_dbref, '', 'makefire(%s) was deleted' % recipe_dbref) self._assert_no_recipes() + def test_puzzleedit_add_remove_parts_results(self): + recipe_dbref = self._good_recipe('makefire', ['stone', 'flint'], ['fire'] , and_destroy_it=False) + + def _puzzleedit(swt, dbref, rhslist, expmsg): + cmdstr = '%s %s = %s' % (swt, dbref, ', '.join(rhslist)) + self.call( + puzzles.CmdEditPuzzle(), + cmdstr, + expmsg, + caller=self.char1 + ) + + red_stone = create_object(key='red stone', location=self.char1.location) + smoke = create_object(key='smoke', location=self.char1.location) + + _puzzleedit('/addresult', recipe_dbref, ['smoke'], 'smoke were added to results') + _puzzleedit('/addpart', recipe_dbref, ['red stone', 'stone'], 'red stone, stone were added to parts') + + # create a box so we can put all objects in + # so that they can't be found during puzzle resolution + self.box = create_object(key='box', location=self.char1.location) + def _box_all(): + for o in self.room1.contents: + if o not in [self.char1, self.char2, self.exit, + self.obj1, self.obj2, self.box]: + o.location = self.box + _box_all() + + self._arm(recipe_dbref, 'makefire', ['stone', 'flint', 'red stone', 'stone']) + self._check_room_contents({ + 'stone': 2, + 'red stone': 1, + 'flint': 1, + }) + self._use('1-stone, flint', 'You try to utilize these but nothing happens ... something amiss?') + self._use('1-stone, flint, red stone, 3-stone', 'You are a Genius') + self._check_room_contents({ + 'smoke': 1, + 'fire': 1 + }) + _box_all() + + self.fire.location = self.room1 + self.stone.location = self.room1 + + _puzzleedit('/delresult', recipe_dbref, ['fire'], 'fire were removed from results') + _puzzleedit('/delpart', recipe_dbref, ['stone', 'stone'], 'stone, stone were removed from parts') + + _box_all() + + self._arm(recipe_dbref, 'makefire', ['flint', 'red stone']) + self._check_room_contents({ + 'red stone': 1, + 'flint': 1, + }) + self._use('red stone, flint', 'You are a Genius') + self._check_room_contents({ + 'smoke': 1, + 'fire': 0 + }) + def test_lspuzzlerecipes_lsarmedpuzzles(self): msg = self.call( puzzles.CmdListPuzzleRecipes(),