Neues Kommando zum Aendern des Fehlertyps

Spieler waehlen haeufig die falsche Kategorie fuer Fehlermeldungen.
Entdeckt man einen solchen Fehler, kann man ihn jetzt in die passende
Kategorie verschieben, um ihn spaeter leichter wiederzufinden.

Change-Id: I74761542321e1b558370fa049902a0593cea746f
diff --git a/doc/wiz/fehlerteufel b/doc/wiz/fehlerteufel
index 71ea058..711d161 100644
--- a/doc/wiz/fehlerteufel
+++ b/doc/wiz/fehlerteufel
@@ -145,6 +145,13 @@
   der Fehler in einem Objekt in /d/, solltet ihr auf jeden Fall
   d.region.magier benutzen, damit ggf. der RM der Region auch zustaendig wird.
 
+- faendere <id> <newtype> <bemerkung>
+  Wenn ein Fehler in der falschen Kategorie gemeldet wurde, kann man sie so
+  berichtigen, damit man ihn leichter wiederfindet. Es koennen nur von
+  Spielern gemeldete Fehler geaendert werden und auch nur zu Kategorien, die
+  von Spielern gemeldet werden koennen. Ihr koennt also keinen Typo zum
+  Laufzeitfehler erklaeren oder umgekehrt.
+
 UID-Filter:
   a) fehlerfilter an
      Schaltet den Filter ein
@@ -190,3 +197,4 @@
 SIEHE AUCH:
  QueryUIDsForWizard(), QueryWizardForUID()
 
+Letzte Aenderung: 22.09.2022, Bugfix
diff --git a/obj/tools/fehlerteufel.c b/obj/tools/fehlerteufel.c
index 706c28e..d5badd6 100644
--- a/obj/tools/fehlerteufel.c
+++ b/obj/tools/fehlerteufel.c
@@ -270,25 +270,48 @@
     return 1;
 }
 
-private int select_modus(string arg) {
+#define NO_FALLBACK 1
+#define NO_COMBINED_TYPES 2
+private int select_modus(string arg, int flags = 0) {
     int lmodus;
     switch(arg) {
     case "alles":
     case "alle":
-      lmodus = T_RTERROR | T_RTWARN | T_CTERROR | T_CTWARN | T_REPORTED_ERR
-               | T_REPORTED_IDEA | T_REPORTED_TYPO | T_REPORTED_MD | 
-               T_REPORTED_SYNTAX;
+      if(flags & NO_COMBINED_TYPES)
+      {
+        lmodus = 0;
+      }
+      else
+      {
+        lmodus = T_RTERROR | T_RTWARN | T_CTERROR | T_CTWARN | T_REPORTED_ERR
+                 | T_REPORTED_IDEA | T_REPORTED_TYPO | T_REPORTED_MD | 
+                 T_REPORTED_SYNTAX;
+      }
       break;
     case "fehler":
     case "error":
     case "errors":
-      lmodus=T_RTERROR | T_CTERROR | T_REPORTED_ERR;
+      if(flags & NO_COMBINED_TYPES)
+      {
+        lmodus = 0;
+      }
+      else
+      {
+        lmodus=T_RTERROR | T_CTERROR | T_REPORTED_ERR;
+      }
       break;
     case "warnungen":
     case "warnung":
     case "warning":
     case "warnings":
-      lmodus=T_RTWARN | T_CTWARN;
+      if(flags & NO_COMBINED_TYPES)
+      {
+        lmodus = 0;
+      }
+      else
+      {
+        lmodus=T_RTWARN | T_CTWARN;
+      }
       break;
     case "laufzeitfehler":
       lmodus=T_RTERROR;
@@ -326,7 +349,14 @@
       lmodus=T_CTWARN;
       break;
     default:
-      lmodus=modus;
+      if(flags & NO_FALLBACK)
+      {
+        lmodus = 0;
+      }
+      else
+      {
+        lmodus=modus;
+      }
     }
     return lmodus;
 }
@@ -821,6 +851,65 @@
   return 1;
   }
 
+public int CmdFehlerAendere(string arg)
+{
+  string note;
+  arg = ({string})PL->_unparsed_args(0);
+  
+  notify_fail("Bitte ID, Typ und optional einen Kommentar angeben.\n");
+
+  if(!sizeof(arg)) return 0;
+  // Fuehrende Leerzeichen entfernen, um ID, Typ und Notiz zuverlaessig
+  // trennen zu koennen.
+  arg = trim(arg, TRIM_LEFT);
+  string* words = explode(arg, " ");
+  if(sizeof(words) < 2) return 0;
+  arg = words[0];
+  // Alles ab dem dritten Wort sollte eine Notiz sein.
+  if(sizeof(words) > 2)
+  {
+    note = implode(words[2..], " ");
+  }
+
+  struct fullissue_s issue = get_issue(arg);
+
+  if(!structp(issue))
+  {
+    notify_fail("Kein Fehler mit dieser ID gefunden.\n");
+    return 0;
+  }
+
+  int type = select_modus(words[1], NO_FALLBACK | NO_COMBINED_TYPES);
+  if(!type)
+  {
+    notify_fail("Fehlertyp nicht bekannt.\n");
+    return 0;
+  }
+
+  if(!(type in CHANGEABLE_TYPES) || !(issue->type in CHANGEABLE_TYPES))
+  {
+    ({int})PL->ReceiveMsg(
+      "Fehlertyp nicht aenderbar.\n",
+      MT_NOTIFICATION);
+    return 1;
+  }
+
+  if(({int})ERRORD->ChangeType(issue->id, type, issue->type, note) == 1)
+  {
+    string* type_name = ({string*})ERRORD->print_type(type);
+    ({int})PL->ReceiveMsg(
+      "Fehlertyp von " + issue->id + " auf " + type_name[0] + " geaendert.",
+      MT_NOTIFICATION);
+  }
+  else
+  {
+    ({int})PL->ReceiveMsg(
+      "Aendern nicht moeglich. Keine Schreibrechte?",
+      MT_NOTIFICATION);
+  }
+  return 1;
+}
+
 // ************** public 'internal' functions **************
 public string QueryOwner() {return owner;}
 public mixed QueryIssueList() {return issuelist;}
@@ -852,6 +941,8 @@
     "wechseln\n"
     "fuebertrage <id> <newuid> <note>\n"
     "                    - Fehler an die UID uebertragen\n"
+    "faendere <id> <newtype> <note>\n"
+    "                    - Fehlertyp aendern\n"
     );
     SetProp(P_NAME,"Fehlerteufel");
     SetProp(P_GENDER,MALE);
@@ -879,6 +970,7 @@
     AddCmd(({"fehleruebertrage","fuebertrage"}),"CmdReassign");
     AddCmd(({"fehlereingabe", "feingabe"}), "CmdFehlerEingabe");
     AddCmd(({"fehlerdir","fdir"}),"CmdFehlerDirectory");
+    AddCmd(({"fehleraendere", "faendere"}), "CmdFehlerAendere");
 }
 
 public varargs void init(object origin)
diff --git a/secure/errord.c b/secure/errord.c
index 84577ec..3faf424 100644
--- a/secure/errord.c
+++ b/secure/errord.c
@@ -37,6 +37,7 @@
 private int       access_check(string uid,int mode);
 private varargs int set_lock(int issueid, int lock, string note);
 private varargs int set_resolution(int issueid, int resolution, string note);
+public string* print_type(int type);
 
 private int versende_mail(struct fullissue_s fehler);
 
@@ -1024,6 +1025,28 @@
     return db_reassign_issue(issueid, newuid, note);
 }
 
+varargs int ChangeType(int issueid, int newtype, int oldtype, string note)
+{
+  struct fullissue_s issue = db_get_issue(issueid, 0);
+  if(!issue)
+    return -1;
+
+  if (!access_check(issue->uid, M_CHANGE_TYPE))
+  {
+    // Zugriff zum Schreiben nicht gestattet
+    return(-10);
+  }
+
+  sl_exec(
+    "UPDATE issues SET type=?1,mtime=?2 WHERE id=?3;",
+    newtype, time(), issueid);
+  db_add_note(
+    (<note_s> id: issueid, time: time(), user: getuid(TI),
+    txt: sprintf("Fehlertyp von %s auf %s geaendert. (%s)",
+    print_type(oldtype)[0], print_type(newtype)[0], (note || "<kein Kommentar>"))));
+  return 1;
+}
+
 /* *********** Eher fuer Debug-Zwecke *********************** */
 /*
 mixed QueryAll(int type) {
@@ -1167,6 +1190,7 @@
       case M_DELETE:
       case M_REASSIGN:
       case M_FIX:
+      case M_CHANGE_TYPE:
         // Master nach UIDs fragen, fuer die der jew. Magier
         // zustaendig ist.
         if (member(({string*})master()->QueryUIDsForWizard(secure_euid()),uid) >= 0)
@@ -1266,6 +1290,44 @@
   return txt;
 }
 
+public string* print_type(int type)
+{
+  string* res;
+  switch(type)
+  {
+    case T_RTERROR:
+      res = ({"Laufzeitfehler","Dieser Laufzeitfehler"});
+      break;
+    case T_REPORTED_ERR:
+      res = ({"Fehlerhinweis","Dieser Fehlerhinweis"});
+      break;
+    case T_REPORTED_TYPO:
+      res = ({"Typo","Dieser Typo"});
+      break;
+    case T_REPORTED_IDEA:
+      res = ({"Idee","Diese Idee"});
+      break;
+    case T_REPORTED_MD:
+      res = ({"Fehlendes Detail","Dieses fehlende Detail"});
+      break;
+    case T_REPORTED_SYNTAX:
+      res = ({"Syntaxproblem","Dieses Syntaxproblem"});
+      break;
+    case T_RTWARN:
+      res = ({"Laufzeitwarnung","Diese Laufzeitwarnung"});
+      break;
+    case T_CTWARN:
+      res = ({"Ladezeitwarnung","Diese Ladezeitwarnung"});
+      break;
+    case T_CTERROR:
+      res = ({"Ladezeitfehler","Dieser Ladezeitfehler"});
+      break;
+    default:
+      res = 0;
+  }
+  return res;
+}
+
 public string format_error(struct fullissue_s issue, int only_essential)
 {
   string txt;
@@ -1274,37 +1336,8 @@
   if (!issue)
     return 0;
 
-  switch(issue->type)
-  {
-    case T_RTERROR:
-      label=({"Laufzeitfehler","Dieser Laufzeitfehler"});
-      break;
-    case T_REPORTED_ERR:
-      label=({"Fehlerhinweis","Dieser Fehlerhinweis"});
-      break;
-    case T_REPORTED_TYPO:
-      label=({"Typo","Dieser Typo"});
-      break;
-    case T_REPORTED_IDEA:
-      label=({"Idee","Diese Idee"});
-      break;
-    case T_REPORTED_MD:
-      label=({"Fehlendes Detail","Dieses fehlende Detail"});
-      break;
-    case T_REPORTED_SYNTAX:
-      label=({"Syntaxproblem","Dieses Syntaxproblem"});
-      break;
-    case T_RTWARN:
-      label=({"Laufzeitwarnung","Diese Laufzeitwarnung"});
-      break;
-    case T_CTWARN:
-      label=({"Ladezeitwarnung","Diese Ladezeitwarnung"});
-      break;
-    case T_CTERROR:
-      label=({"Ladezeitfehler","Dieser Ladezeitfehler"});
-      break;
-    default: return 0;
-  }
+  label = print_type(issue->type);
+  if(!label) return 0;
 
   txt=sprintf( "\nDaten fuer %s mit ID %d:\n"
                "Hashkey: %s\n"
diff --git a/secure/errord.h b/secure/errord.h
index da41f89..8da611b 100644
--- a/secure/errord.h
+++ b/secure/errord.h
@@ -22,6 +22,8 @@
 #define ALL_ERR_TYPES ({T_RTERROR, T_RTWARN, T_CTERROR, T_CTWARN, \
                         T_REPORTED_ERR, T_REPORTED_TYPO, T_REPORTED_IDEA, \
                         T_REPORTED_MD, T_REPORTED_SYNTAX })
+#define CHANGEABLE_TYPES ({T_REPORTED_ERR, T_REPORTED_TYPO, T_REPORTED_IDEA, \
+                           T_REPORTED_MD, T_REPORTED_SYNTAX})
 
 // Status
 #define STAT_DELETED  0x1
@@ -83,6 +85,7 @@
 #define M_FIX    4
 #define M_REASSIGN 8
 #define M_DELETE 16
+#define M_CHANGE_TYPE 32
 
 // Changelog
 #define CHANGELOG "/log/CHANGELOG"