@Override
  public boolean invoke(
      MOB mob, List<String> commands, Physical givenTarget, boolean auto, int asLevel) {
    MOB target = CMLib.players().getLoadPlayer(CMParms.combine(commands, 0));
    if (target == null) target = getTargetAnywhere(mob, commands, givenTarget, false, true, false);
    if (target == null) return false;

    final Archon_Record A = (Archon_Record) target.fetchEffect(ID());
    if (A != null) {
      target.delEffect(A);
      if (target.playerStats() != null) target.playerStats().setLastUpdated(0);
      mob.tell(L("@x1 will no longer be recorded.", target.Name()));
      return true;
    }

    if (!super.invoke(mob, commands, givenTarget, auto, asLevel)) return false;

    final boolean success = proficiencyCheck(mob, 0, auto);

    if (success) {
      final CMMsg msg =
          CMClass.getMsg(
              mob,
              target,
              this,
              CMMsg.MASK_MOVE | CMMsg.TYP_JUSTICE | (auto ? CMMsg.MASK_ALWAYS : 0),
              L("^F<S-NAME> begin(s) recording <T-NAMESELF>.^?"));
      CMLib.color().fixSourceFightColor(msg);
      if (mob.location().okMessage(mob, msg)) {
        mob.location().send(mob, msg);
        final String filename = "/" + target.Name() + System.currentTimeMillis() + ".log";
        final CMFile file = new CMFile(filename, null, CMFile.FLAG_LOGERRORS);
        if (!file.canWrite()) {
          if (!CMSecurity.isASysOp(mob) || (CMSecurity.isASysOp(target)))
            Log.sysOut("Record", mob.Name() + " failed to start recording " + target.name() + ".");
        } else {
          if (!CMSecurity.isASysOp(mob) || (CMSecurity.isASysOp(target)))
            Log.sysOut(
                "Record",
                mob.Name() + " started recording " + target.name() + " to /" + filename + ".");
          final Archon_Record A2 = (Archon_Record) copyOf();
          final Session F = (Session) CMClass.getCommon("FakeSession");
          F.initializeSession(null, Thread.currentThread().getThreadGroup().getName(), filename);
          if (target.session() == null) target.setSession(F);
          A2.sess = F;
          target.addNonUninvokableEffect(A2);
          mob.tell(L("Enter RECORD @x1 again to stop recording.", target.Name()));
        }
      }
    } else
      return beneficialVisualFizzle(
          mob, target, L("<S-NAME> attempt(s) to hush <T-NAMESELF>, but fail(s)."));
    return success;
  }
  @Override
  public boolean invoke(MOB mob, Vector commands, Physical givenTarget, boolean auto, int asLevel) {
    if (CMParms.combine(commands, 0).equalsIgnoreCase("auto")) {
      DATA.clear();
      IPS.clear();
      final Hashtable<String, List<MOB>> ipes = new Hashtable<String, List<MOB>>();
      for (final Session S : CMLib.sessions().localOnlineIterable()) {
        if ((S.getAddress().length() > 0) && (S.mob() != null)) {
          List V = ipes.get(S.getAddress());
          if (V == null) {
            V = new Vector();
            ipes.put(S.getAddress(), V);
          }
          if (!V.contains(S.mob())) V.add(S.mob());
        }
      }
      final StringBuffer rpt = new StringBuffer("");
      for (final Enumeration e = ipes.keys(); e.hasMoreElements(); ) {
        final String addr = (String) e.nextElement();
        final List<MOB> names = ipes.get(addr);
        if (names.size() > 1) {
          IPS.put(addr, names);
          rpt.append("Watch #" + (IPS.size()) + " added: ");
          for (int n = 0; n < names.size(); n++) {
            final MOB MN = names.get(n);
            if (MN.fetchEffect(ID()) == null) {
              final Ability A = (Ability) copyOf();
              MN.addNonUninvokableEffect(A);
              A.setSavable(false);
            }
            rpt.append(MN.Name() + " ");
          }
          rpt.append("\n\r");
        }
      }
      if (rpt.length() == 0)
        rpt.append("No users with duplicate IDs found.  Try MULTIWATCH ADD name1 name2 ... ");
      mob.tell(rpt.toString());
      return true;
    } else if (CMParms.combine(commands, 0).equalsIgnoreCase("stop")) {
      boolean foundLegacy = false;
      for (final Session S : CMLib.sessions().localOnlineIterable()) {
        if ((S != null) && (S.mob() != null) && (S.mob().fetchEffect(ID()) != null)) {
          foundLegacy = true;
          break;
        }
      }
      if ((DATA.size() == 0) && (IPS.size() == 0) && (!foundLegacy)) {
        mob.tell(L("Multiwatch is already off."));
        return false;
      }
      for (final Enumeration<List<MOB>> e = IPS.elements(); e.hasMoreElements(); ) {
        final List<MOB> V = e.nextElement();
        for (int v = 0; v < V.size(); v++) {
          final MOB M = V.get(v);
          final Ability A = M.fetchEffect(ID());
          if (A != null) M.delEffect(A);
        }
      }
      for (final Session S : CMLib.sessions().localOnlineIterable()) {
        if ((S != null) && (S.mob() != null)) {
          final MOB M = S.mob();
          final Ability A = M.fetchEffect(ID());
          if (A != null) M.delEffect(A);
        }
      }
      mob.tell(L("Multiplay watcher is now turned off."));
      DATA.clear();
      IPS.clear();
      return true;
    } else if ((commands.size() > 1)
        && ((String) commands.firstElement()).equalsIgnoreCase("add")) {
      final Vector V = new Vector();
      for (int i = 1; i < commands.size(); i++) {
        final String name = (String) commands.elementAt(i);
        final MOB M = CMLib.players().getPlayer(name);
        if ((M.session() != null) && (CMLib.flags().isInTheGame(M, true))) V.addElement(M);
        else mob.tell(L("'@x1' is not online.", name));
      }
      if (V.size() > 1) {
        for (int n = 0; n < V.size(); n++) {
          final MOB MN = (MOB) V.elementAt(n);
          if (MN.fetchEffect(ID()) == null) {
            final Ability A = (Ability) copyOf();
            MN.addNonUninvokableEffect(A);
            A.setSavable(false);
          }
        }
        IPS.put("MANUAL" + (IPS.size() + 1), V);
        mob.tell(L("Manual Watch #@x1 added.", "" + IPS.size()));
      }
      return true;
    } else if ((commands.size() == 0) && (DATA.size() > 0) && (IPS.size() > 0)) {
      final StringBuffer report = new StringBuffer("");
      for (final Enumeration<String> e = IPS.keys(); e.hasMoreElements(); ) {
        final String key = e.nextElement();
        int sync = 0;
        final List<MOB> V = IPS.get(key);
        for (int v = 0; v < V.size(); v++) {
          final MOB M = V.get(v);
          final int data[] = DATA.get(M);
          if (data != null) sync += data[DATA_SYNCHROFOUND];
        }
        report.append("^x" + key + "^?^., Syncs: " + sync + "\n\r");
        report.append(
            CMStrings.padRight(L("Name"), 25)
                + CMStrings.padRight(L("Speech"), 15)
                + CMStrings.padRight(L("Socials"), 15)
                + CMStrings.padRight(L("CMD"), 10)
                + CMStrings.padRight(L("ORDERS"), 10)
                + "\n\r");
        for (int v = 0; v < V.size(); v++) {
          final MOB M = V.get(v);
          int data[] = DATA.get(M);
          if (data == null) data = new int[DATA_TOTAL];
          report.append(CMStrings.padRight(M.Name(), 25));
          report.append(
              CMStrings.padRight(
                  data[DATA_GOODSPEECH] + "/" + data[DATA_DIRSPEECH] + "/" + data[DATA_ANYSPEECH],
                  15));
          report.append(
              CMStrings.padRight(
                  data[DATA_GOODSOCIAL] + "/" + data[DATA_DIRSOCIAL] + "/" + data[DATA_ANYSOCIAL],
                  15));
          report.append(CMStrings.padRight(data[DATA_TYPEDCOMMAND] + "", 10));
          report.append(CMStrings.padRight(data[DATA_ORDER] + "", 10));
          report.append("\n\r");
        }
        report.append("\n\r");
      }

      mob.tell(report.toString());
      return true;
    } else {
      mob.tell(L("Try MULTIWATCH AUTO, MULTIWATCH STOP, or MULTIWATCH ADD name1 name2.."));
      return false;
    }
  }
  @Override
  public boolean invoke(
      MOB mob, List<String> commands, Physical givenTarget, boolean auto, int asLevel) {
    final Physical target = getAnyTarget(mob, commands, givenTarget, Wearable.FILTER_UNWORNONLY);
    if (target == null) return false;

    if (target == mob) {
      mob.tell(L("@x1 doesn't look dead yet.", target.name(mob)));
      return false;
    }
    if (!(target instanceof DeadBody)) {
      mob.tell(L("You can't animate that."));
      return false;
    }

    final DeadBody body = (DeadBody) target;
    if (body.isPlayerCorpse()
        || (body.getMobName().length() == 0)
        || ((body.charStats() != null)
            && (body.charStats().getMyRace() != null)
            && (body.charStats().getMyRace().racialCategory().equalsIgnoreCase("Undead")))) {
      mob.tell(L("You can't animate that."));
      return false;
    }
    String race = "a";
    if ((body.charStats() != null) && (body.charStats().getMyRace() != null))
      race = CMLib.english().startWithAorAn(body.charStats().getMyRace().name()).toLowerCase();

    String description = body.getMobDescription();
    if (description.trim().length() == 0) description = "It looks dead.";
    else description += "\n\rIt also looks dead.";

    if (body.basePhyStats().level() < 7) {
      mob.tell(L("This creature is too weak to create a ghast from."));
      return false;
    }

    if (!super.invoke(mob, commands, givenTarget, auto, asLevel)) return false;

    final boolean success = proficiencyCheck(mob, 0, auto);

    if (success) {
      final CMMsg msg =
          CMClass.getMsg(
              mob,
              target,
              this,
              verbalCastCode(mob, target, auto),
              auto
                  ? ""
                  : L("^S<S-NAME> @x1 to animate <T-NAMESELF> as a ghast.^?", prayForWord(mob)));
      if (mob.location().okMessage(mob, msg)) {
        mob.location().send(mob, msg);
        int undeadLevel = this.getUndeadLevel(mob, 6, body.phyStats().level());
        final MOB newMOB = CMClass.getMOB("GenUndead");
        newMOB.setName(L("@x1 ghast", race));
        newMOB.setDescription(description);
        newMOB.setDisplayText(L("@x1 ghast is here", race));
        newMOB.basePhyStats().setLevel(undeadLevel);
        newMOB
            .baseCharStats()
            .setStat(CharStats.STAT_GENDER, body.charStats().getStat(CharStats.STAT_GENDER));
        newMOB.baseCharStats().setMyRace(CMClass.getRace("Undead"));
        newMOB
            .baseCharStats()
            .setBodyPartsFromStringAfterRace(body.charStats().getBodyPartsAsString());
        final Ability P = CMClass.getAbility("Prop_StatTrainer");
        if (P != null) {
          P.setMiscText("NOTEACH STR=20 INT=10 WIS=10 CON=10 DEX=15 CHA=2");
          newMOB.addNonUninvokableEffect(P);
        }
        newMOB.recoverCharStats();
        newMOB.basePhyStats().setAttackAdjustment(CMLib.leveler().getLevelAttack(newMOB));
        newMOB.basePhyStats().setDamage(CMLib.leveler().getLevelMOBDamage(newMOB));
        newMOB.basePhyStats().setSensesMask(PhyStats.CAN_SEE_DARK);
        CMLib.factions().setAlignment(newMOB, Faction.Align.EVIL);
        newMOB.baseState().setHitPoints(25 * newMOB.basePhyStats().level());
        newMOB.baseState().setMovement(CMLib.leveler().getLevelMove(newMOB));
        newMOB.basePhyStats().setArmor(CMLib.leveler().getLevelMOBArmor(newMOB));
        newMOB.baseState().setMana(100);
        newMOB.recoverCharStats();
        newMOB.recoverPhyStats();
        newMOB.recoverMaxState();
        newMOB.resetToMaxState();
        newMOB.addAbility(CMClass.getAbility("Paralysis"));
        Behavior B = CMClass.getBehavior("CombatAbilities");
        if (B != null) newMOB.addBehavior(B);
        B = CMClass.getBehavior("Aggressive");
        if (B != null) {
          B.setParms("+NAMES \"-" + mob.Name() + "\" -LEVEL +>" + newMOB.basePhyStats().level());
          newMOB.addBehavior(B);
        }
        newMOB.addNonUninvokableEffect(CMClass.getAbility("Spell_CauseStink"));
        newMOB.addNonUninvokableEffect(CMClass.getAbility("Prop_ModExperience"));
        newMOB.text();
        newMOB.bringToLife(mob.location(), true);
        CMLib.beanCounter().clearZeroMoney(newMOB, null);
        // newMOB.location().showOthers(newMOB,null,CMMsg.MSG_OK_ACTION,L("<S-NAME> appears!"));
        int it = 0;
        while (it < newMOB.location().numItems()) {
          final Item item = newMOB.location().getItem(it);
          if ((item != null) && (item.container() == body)) {
            final CMMsg msg2 = CMClass.getMsg(newMOB, body, item, CMMsg.MSG_GET, null);
            newMOB.location().send(newMOB, msg2);
            final CMMsg msg4 = CMClass.getMsg(newMOB, item, null, CMMsg.MSG_GET, null);
            newMOB.location().send(newMOB, msg4);
            final CMMsg msg3 = CMClass.getMsg(newMOB, item, null, CMMsg.MSG_WEAR, null);
            newMOB.location().send(newMOB, msg3);
            if (!newMOB.isMine(item)) it++;
            else it = 0;
          } else it++;
        }
        body.destroy();
        mob.location().show(newMOB, null, CMMsg.MSG_OK_ACTION, L("<S-NAME> begin(s) to rise!"));
        newMOB.setStartRoom(null);
        beneficialAffect(mob, newMOB, 0, 0);
        mob.location().recoverRoomStats();
      }
    } else
      return beneficialWordsFizzle(
          mob,
          target,
          L("<S-NAME> @x1 to animate <T-NAMESELF>, but fail(s) miserably.", prayForWord(mob)));

    // return whether it worked
    return success;
  }