Repository: evennia/evennia

Issue:
copy_object copies attributes, cmdsets and scripts - but not tags
#### Brief summary of issue / Description of requested feature:
Using copy_object from [https://github.com/evennia/evennia/wiki/evennia.objects.manager](https://github.com/evennia/evennia/wiki/evennia.objects.manager)

> copy_object(self, original_object, new_key=None, new_location=None, \
>     new_home=None, new_permissions=None, new_locks=None, \
>     new_aliases=None, new_destination=None)
> 
>     Create and return a new object as a copy of the original object. All will be identical to the original except for the arguments given specifically to this method.
> 

Original object has _tags_. Copy created does _not_ .

#### Steps to reproduce the issue / Reasons for adding feature:

1. Run this batchcode:

```


#
    
#HEADER
    
# This will be included in all other #CODE blocks
    
from evennia.utils import create, search
from typeclasses.objects import Object
from evennia.objects.models import ObjectDB
    
    
#CODE 

item = create.create_object('typeclasses.objects.Object', key='item')

item.tags.add("some_tag")
item.db.someattr = "someattr"

newitem = ObjectDB.objects.copy_object(item, new_key="newitem")

caller.msg("\n" + "item attribute: " + str(item.db.someattr) + "; item tags: " + str(item.tags.get()) + "\n")
caller.msg("\n" + "new item attribute: " + str(newitem.db.someattr) + " ; new item tags: " + str(newitem.tags.get()) + "\n")
```


#### Error output / Expected result of feature

@batchcode test
Running Batch-code processor - Automatic mode for test ...01/02:
02/02: item = create.create_object('typeclasses.objects.Object[...]

item attribute: someattr ; new item tags: some_tag

new item attribute: someattr ; new item tags: None
End of batch file.  Batchfile 'test' applied.


#### Extra information, such as Evennia revision/repo/branch, operating system and ideas for how to solve / implement:

Latest stable, running on a Raspberry Pi 3, OS is raspbian stretch, python 2.7.13

I believe copying tags was/should be the intended behaviour.

If copy_objects is reviewed, I am less certain of what should happen to object.contents.

- Should each object in contents be duplicated, recursively?
- If no, should there be an optional parameter to switch this behaviour on?
- If neither, should there be a comment in the source to that effect?


Discussion:
**Proposed Solution**

Original:
```

        # copy over all attributes from old to new.
        for attr in original_object.attributes.all():
            new_object.attributes.add(attr.key, attr.value)

        # copy over all cmdsets, if any
        for icmdset, cmdset in enumerate(original_object.cmdset.all()):
            if icmdset == 0:
                new_object.cmdset.add_default(cmdset)
            else:
                new_object.cmdset.add(cmdset)

        # copy over all scripts, if any
        for script in original_object.scripts.all():
            ScriptDB.objects.copy_script(script, new_obj=new_object)

return new_object
```

becomes:

```

        # copy over all attributes from old to new.
        for attr in original_object.attributes.all():
            new_object.attributes.add(attr.key, attr.value)

        # copy over all cmdsets, if any
        for icmdset, cmdset in enumerate(original_object.cmdset.all()):
            if icmdset == 0:
                new_object.cmdset.add_default(cmdset)
            else:
                new_object.cmdset.add(cmdset)

        # copy over all scripts, if any
        for script in original_object.scripts.all():
            ScriptDB.objects.copy_script(script, new_obj=new_object)

        # copy over all tags, if any
        for tag in original_object.scripts.tags.get():
             new_object.tags.add(tag)


return new_object
```

And
```


        """
        Create and return a new object as a copy of the original object. All
        will be identical to the original except for the arguments given
        specifically to this method.
        Args:
            original_object (Object): The object to make a copy from.
            new_key (str, optional): Name of the copy, if different
                from the original.
            new_location (Object, optional): Alternate location.
            new_home (Object, optional): Change the home location
            new_aliases (list, optional): Give alternate object
                aliases as a list of strings.
            new_destination (Object, optional): Used only by exits.
        Returns:
            copy (Object or None): The copy of `original_object`,
                optionally modified as per the ingoing keyword
                arguments.  `None` if an error was encountered.
        """
```

becomes

```        """
        Create and return a new object as a copy of the original object. All
        will be identical to the original except for the arguments given
        specifically to this method. Any contents will NOT be copied.
        Args:
            original_object (Object): The object to make a copy from.
            new_key (str, optional): Name of the copy, if different
                from the original.
            new_location (Object, optional): Alternate location.
            new_home (Object, optional): Change the home location
            new_aliases (list, optional): Give alternate object
                aliases as a list of strings.
            new_destination (Object, optional): Used only by exits.
        Returns:
            copy (Object or None): The copy of `original_object`,
                optionally modified as per the ingoing keyword
                arguments.  `None` if an error was encountered.
        """
```
@FatherGrishnak Thanks, that looks like an omission that would be fixed by your suggestions! Easiest way to contribute your code would be to create a [Pull Request](https://github.com/evennia/evennia/wiki/Contributing#contributing-through-a-forked-repository) with your code. That way it can be commented on and any changes applied before being merged into core. Your contribution will also be automatically credited in the code history. 

That said, if you are not familiar/comfortable enough with GIT to make a Pull Request, let me know and I'll apply your suggestions manually in the old fashioned way, no problem. :) 

Resolving patch:
diff --git a/evennia/objects/manager.py b/evennia/objects/manager.py
--- a/evennia/objects/manager.py
+++ b/evennia/objects/manager.py
@@ -437,7 +437,7 @@ def copy_object(self, original_object, new_key=None,
         """
         Create and return a new object as a copy of the original object. All
         will be identical to the original except for the arguments given
-        specifically to this method.
+        specifically to this method. Object contents will not be copied.
 
         Args:
             original_object (Object): The object to make a copy from.
@@ -502,6 +502,10 @@ def copy_object(self, original_object, new_key=None,
         for script in original_object.scripts.all():
             ScriptDB.objects.copy_script(script, new_obj=new_object)
 
+        # copy over all tags, if any
+        for tag in original_object.tags.get(return_tagobj=True, return_list=True):
+            new_object.tags.add(tag=tag.key, category=tag.category, data=tag.data)
+    
         return new_object
 
     def clear_all_sessids(self):


Test patch: