Nothing exists for doing that, but here ya go!
Command is:
!dup-char-by-token
Select some tokens and run that to create copies of them. They'll be prefaced with (COPY) for the name so you know which is the new one. You'll have to drag them out from the panel and if the avatar image of the character is not in a user library (i.e: it's a marketplace image or from the Monster Manual, or the like), it won't set an avatar image.
on('ready',()=>{
const simpleObj = (o)=>JSON.parse(JSON.stringify(o));
const getCleanImgsrc = (imgsrc) => {
let parts = imgsrc.match(/(.*\/images\/.*)(thumb|med|original|max)([^?]*)(\?[^?]+)?$/);
if(parts) {
return parts[1]+'thumb'+parts[3]+(parts[4]?parts[4]:`?${Math.round(Math.random()*9999999)}`);
}
return;
};
const duplicateCharacter = (o) => {
let c = simpleObj(o.character);
let oldCid = o.character.id;
delete c.id;
c.name=`(COPY) ${c.name}`;
c.avatar=getCleanImgsrc(c.avatar)||'';
let newC = createObj('character',c);
o.token.set('represents',newC.id);
setDefaultTokenForCharacter(newC,o.token);
o.token.set('represents',oldCid);
_.each(findObjs({type:'attribute',characterid:oldCid}),(a)=>{
let sa = simpleObj(a);
delete sa.id;
delete sa._type;
delete sa._characterid;
sa.characterid = newC.id;
createObj('attribute',sa);
});
_.each(findObjs({type:'ability',characterid:oldCid}),(a)=>{
let sa = simpleObj(a);
delete sa.id;
delete sa._type;
delete sa._characterid;
sa.characterid = newC.id;
createObj('ability',sa);
});
};
on('chat:message',(msg)=>{
if('api'===msg.type && playerIsGM(msg.playerid) && /^!dup-char-by-token\b/.test(msg.content)){
if(msg.selected){
_.chain(msg.selected)
.map((o)=>getObj('graphic',o._id))
.reject(_.isUndefined)
.map(o=>({token: o, character: getObj('character',o.get('represents'))}))
.reject(o=>_.isUndefined(o.character))
.tap(o=>{
if(!o.length){
sendChat('',`/w gm <div style="color: #993333;font-weight:bold;">Please select one or more tokens which represent characters.</div>`);
} else {
sendChat('',`/w gm <div style="color: #993333;font-weight:bold;">Duplicating: ${o.map((obj)=>obj.character.get('name')).join(', ')}</div>`);
}
})
.each(duplicateCharacter);
} else {
sendChat('',`/w gm <div style="color: #993333;font-weight:bold;">Please select one or more tokens.</div>`);
}
}
});
});