PageRenderTime 51ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/CaWE/DialogMapCheck.cpp

https://bitbucket.org/lennonchan/cafu
C++ | 524 lines | 333 code | 142 blank | 49 comment | 66 complexity | 4b738002ee4bd53722ab6de1d8dbdd4c MD5 | raw file
  1. /*
  2. =================================================================================
  3. This file is part of Cafu, the open-source game engine and graphics engine
  4. for multiplayer, cross-platform, real-time 3D action.
  5. Copyright (C) 2002-2013 Carsten Fuchs Software.
  6. Cafu is free software: you can redistribute it and/or modify it under the terms
  7. of the GNU General Public License as published by the Free Software Foundation,
  8. either version 3 of the License, or (at your option) any later version.
  9. Cafu is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
  10. without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
  11. PURPOSE. See the GNU General Public License for more details.
  12. You should have received a copy of the GNU General Public License
  13. along with Cafu. If not, see <http://www.gnu.org/licenses/>.
  14. For support and more information about Cafu, visit us at <http://www.cafu.de>.
  15. =================================================================================
  16. */
  17. #include "AppCaWE.hpp"
  18. #include "CommandHistory.hpp"
  19. #include "ParentFrame.hpp"
  20. #include "DialogMapCheck.hpp"
  21. #include "EntityClass.hpp"
  22. #include "GameConfig.hpp"
  23. #include "MapDocument.hpp"
  24. #include "MapEntity.hpp"
  25. #include "MapBrush.hpp"
  26. #include "ChildFrame.hpp"
  27. #include "ChildFrameViewWin2D.hpp"
  28. #include "MapWorld.hpp"
  29. #include "Options.hpp"
  30. #include "Tool.hpp"
  31. #include "ToolManager.hpp"
  32. #include "EditorMaterial.hpp"
  33. #include "EditorMaterialManager.hpp"
  34. #include "MapCommands/DeleteProp.hpp"
  35. #include "MapCommands/Select.hpp"
  36. #include "wx/wx.h"
  37. #include <typeinfo>
  38. /*
  39. * TODO:
  40. * 1. This dialog should be modeless and an observer of the world, just like our other dialogs/views/observers!
  41. * 2. VERY IMPORTANT: The "Fix All" button is currently dangerous - batches of commands are submitted, but what if several
  42. * commands process the same element, and e.g. the first command deletes the elem?
  43. * Or the first command manipulates the properties that the subsequent commands directly or indirectly referred to?
  44. * Have the ObserverT::Notify*() handlers deal with the issue? Special case code? ...?
  45. * One very important requirement seems to be to only ever find and keep at most ONE problem per map element!
  46. * 3. Add an MP_CheckUniqueValuesT checker, to find all entity properties that are supposed to be unique, but aren't.
  47. * 4. This needs WAY MORE testing - make a problem-cmap test file...
  48. */
  49. /// The common base class for concrete map checkers.
  50. class MapCheckerT
  51. {
  52. public:
  53. /// The constructor.
  54. MapCheckerT(MapDocumentT& MapDoc, MapElementT* Elem) : m_MapDoc(MapDoc), m_Elem(Elem) { }
  55. /// The virtual destructor.
  56. virtual ~MapCheckerT() { }
  57. /// Returns the element that is checker is assigned to.
  58. MapElementT* GetElem() const { return m_Elem; }
  59. /// Returns whether this map checker has actually identified a problem with the element it is assigned to.
  60. /// When false, there either never was a problem in the first place, or it has been fixed already.
  61. virtual bool HasProblem() const { return false; }
  62. /// Returns whether this checker is able to provide a fix the problem (some problems can only be detected, but not fixed).
  63. virtual bool CanFix() const { return false; }
  64. /// Returns a command that fixes the problem, if possible (that is, when HasProblem() and CanFix() returns true). Returns NULL otherwise.
  65. virtual CommandT* GetFix() const { return NULL; }
  66. /// Returns a brief information about the nature of the problem.
  67. virtual wxString GetInfo() const { return "No problem found."; }
  68. /// Returns a longer help text about the details of the problem and how it can be fixed.
  69. virtual wxString GetHelpText() const { return GetInfo(); }
  70. protected:
  71. MapDocumentT& m_MapDoc;
  72. MapElementT* m_Elem;
  73. };
  74. class MC_UnknownTargetT : public MapCheckerT
  75. {
  76. public:
  77. MC_UnknownTargetT(MapDocumentT& MapDoc, MapElementT* Elem) : MapCheckerT(MapDoc, Elem) { }
  78. bool HasProblem() const
  79. {
  80. // If m_Elem is not an entity, we don't have a problem here.
  81. if (m_Elem->GetType()!=&MapEntityT::TypeInfo) return false;
  82. const MapEntityT* Ent1 =static_cast<MapEntityT*>(m_Elem);
  83. const EntPropertyT* TargetProp=Ent1->FindProperty("target");
  84. // If this entity doesn't have the "target" property or an empty value, we don't have a problem.
  85. if (TargetProp==NULL) return false;
  86. if (TargetProp->Value=="") return false;
  87. // Finally search all entities in the world for a matching "name" property.
  88. for (unsigned long Ent2Nr=1/*skip world*/; Ent2Nr<m_MapDoc.GetEntities().Size(); Ent2Nr++)
  89. {
  90. const EntPropertyT* NameProp=m_MapDoc.GetEntities()[Ent2Nr]->FindProperty("name");
  91. if (NameProp==NULL) continue;
  92. if (NameProp->Value==TargetProp->Value) return false;
  93. }
  94. return true;
  95. }
  96. wxString GetTargetValue() const
  97. {
  98. if (m_Elem->GetType()!=&MapEntityT::TypeInfo) return "";
  99. const MapEntityT* Ent =static_cast<MapEntityT*>(m_Elem);
  100. const EntPropertyT* TargetProp=Ent->FindProperty("target");
  101. return TargetProp ? TargetProp->Value : "";
  102. }
  103. wxString GetInfo() const { return "Unmatched entity target."; }
  104. wxString GetHelpText() const { return "The \"target\" property of this entity has value \""+GetTargetValue()+"\", but there is no entity with a corresponding name."; }
  105. };
  106. static bool LargestFirst(int const& elem1, int const& elem2)
  107. {
  108. return elem2<elem1;
  109. }
  110. class MC_UndefinedClassOrKeysT : public MapCheckerT
  111. {
  112. public:
  113. MC_UndefinedClassOrKeysT(MapDocumentT& MapDoc, MapElementT* Elem)
  114. : MapCheckerT(MapDoc, Elem),
  115. m_Ent(NULL)
  116. {
  117. m_Ent=dynamic_cast<MapEntityT*>(m_Elem);
  118. }
  119. bool HasProblem() const
  120. {
  121. if (!m_Ent) return false;
  122. if (!m_Ent->GetClass()->IsInGameConfig()) return true;
  123. return GetUndefKeys().Size()>0; // This is inefficient...
  124. }
  125. bool CanFix() const
  126. {
  127. return m_Ent && m_Ent->GetClass()->IsInGameConfig() && GetUndefKeys().Size()>0;
  128. }
  129. CommandT* GetFix() const
  130. {
  131. if (!m_Ent) return NULL;
  132. if (!m_Ent->GetClass()->IsInGameConfig()) return NULL;
  133. const ArrayT<EntPropertyT>& Props=m_Ent->GetProperties();
  134. ArrayT<int> DelIndices;
  135. for (unsigned long PropNr=0; PropNr<Props.Size(); PropNr++)
  136. if (m_Ent->GetClass()->FindVar(Props[PropNr].Key)==NULL)
  137. DelIndices.PushBack(PropNr);
  138. if (DelIndices.Size()==0) return NULL;
  139. if (DelIndices.Size()==1) return new CommandDeletePropertyT(m_MapDoc, m_Ent, DelIndices[0]);
  140. ArrayT<CommandT*> Commands;
  141. DelIndices.QuickSort(LargestFirst); // Must delete by index strictly in largest-first order!
  142. for (unsigned long iNr=0; iNr<DelIndices.Size(); iNr++)
  143. Commands.PushBack(new CommandDeletePropertyT(m_MapDoc, m_Ent, DelIndices[iNr]));
  144. return new CommandMacroT(Commands, "Delete unused entity keys.");
  145. }
  146. ArrayT<wxString> GetUndefKeys() const
  147. {
  148. ArrayT<wxString> UndefKeys;
  149. if (m_Ent)
  150. {
  151. const ArrayT<EntPropertyT>& Props=m_Ent->GetProperties();
  152. for (unsigned long PropNr=0; PropNr<Props.Size(); PropNr++)
  153. if (m_Ent->GetClass()->FindVar(Props[PropNr].Key)==NULL)
  154. UndefKeys.PushBack(Props[PropNr].Key);
  155. }
  156. return UndefKeys;
  157. }
  158. wxString GetInfo() const
  159. {
  160. if (!m_Ent) return "";
  161. return m_Ent->GetClass()->IsInGameConfig() ? "Undefined entity keys." : "Undefined entity class.";
  162. }
  163. wxString GetHelpText() const
  164. {
  165. if (!m_Ent) return "";
  166. if (!m_Ent->GetClass()->IsInGameConfig())
  167. return "The class \""+m_Ent->GetClass()->GetName()+"\" of this entity is undefined in the game configuration of this map.";
  168. wxString Text="This entity has properties with keys that are undefined in its class \""+m_Ent->GetClass()->GetName()+"\", and are thus unused:\n\n";
  169. const ArrayT<wxString> UndefKeys=GetUndefKeys();
  170. for (unsigned long KeyNr=0; KeyNr<UndefKeys.Size(); KeyNr++)
  171. {
  172. Text+=UndefKeys[KeyNr];
  173. if (KeyNr+1<UndefKeys.Size()) Text+=", ";
  174. }
  175. Text+="\n\nThis problem is often a harmless by-product of importing maps from other games.";
  176. return Text;
  177. }
  178. private:
  179. MapEntityT* m_Ent;
  180. };
  181. class MC_DuplicateKeysT : public MapCheckerT
  182. {
  183. public:
  184. MC_DuplicateKeysT(MapDocumentT& MapDoc, MapElementT* Elem) : MapCheckerT(MapDoc, Elem) { }
  185. bool HasProblem() const
  186. {
  187. if (m_Elem->GetType()!=&MapEntityT::TypeInfo) return false;
  188. const MapEntityT* Ent =static_cast<MapEntityT*>(m_Elem);
  189. const ArrayT<EntPropertyT>& Props=Ent->GetProperties();
  190. for (unsigned long i=0; i<Props.Size(); i++)
  191. for (unsigned long j=i+1; j<Props.Size(); j++)
  192. if (Props[i].Key==Props[j].Key)
  193. return true;
  194. return false;
  195. }
  196. bool CanFix() const { return true; }
  197. CommandT* GetFix() const
  198. {
  199. if (m_Elem->GetType()!=&MapEntityT::TypeInfo) return NULL;
  200. MapEntityT* Ent =static_cast<MapEntityT*>(m_Elem);
  201. const ArrayT<EntPropertyT>& Props=Ent->GetProperties();
  202. ArrayT<int> DelIndices;
  203. for (unsigned long i=0; i<Props.Size(); i++)
  204. for (unsigned long j=i+1; j<Props.Size(); j++)
  205. if (Props[i].Key==Props[j].Key)
  206. if (DelIndices.Find(j)==-1)
  207. DelIndices.PushBack(j);
  208. if (DelIndices.Size()==0) return NULL;
  209. if (DelIndices.Size()==1) return new CommandDeletePropertyT(m_MapDoc, Ent, DelIndices[0]);
  210. ArrayT<CommandT*> Commands;
  211. DelIndices.QuickSort(LargestFirst); // Must delete by index strictly in largest-first order!
  212. for (unsigned long iNr=0; iNr<DelIndices.Size(); iNr++)
  213. Commands.PushBack(new CommandDeletePropertyT(m_MapDoc, Ent, DelIndices[iNr]));
  214. return new CommandMacroT(Commands, "Delete duplicate entity keys.");
  215. }
  216. wxString GetInfo() const { return "Duplicate entity keys."; }
  217. wxString GetHelpText() const { return "This entity has properties with keys that occur multiply."; }
  218. };
  219. class MC_EmptySolidEntityT : public MapCheckerT
  220. {
  221. public:
  222. MC_EmptySolidEntityT(MapDocumentT& MapDoc, MapElementT* Elem) : MapCheckerT(MapDoc, Elem) { }
  223. bool HasProblem() const
  224. {
  225. if (m_Elem->GetType()!=&MapEntityT::TypeInfo) return false;
  226. const MapEntityT* Ent=static_cast<MapEntityT*>(m_Elem);
  227. return Ent->GetClass()->IsSolidClass() && Ent->GetPrimitives().Size()==0;
  228. }
  229. wxString GetInfo() const { return "Empty solid entity."; }
  230. wxString GetHelpText() const { return "This entity is supposed to be composed of map primitives (brushes, patches, models, ...), but has none. Fixing the error deletes the empty entity."; }
  231. };
  232. class MC_WorldHasPlayerStartT : public MapCheckerT
  233. {
  234. public:
  235. MC_WorldHasPlayerStartT(MapDocumentT& MapDoc, MapElementT* Elem) : MapCheckerT(MapDoc, Elem) { }
  236. bool HasProblem() const
  237. {
  238. if (m_Elem->GetType()!=&MapWorldT::TypeInfo) return false;
  239. for (unsigned long EntNr=1/*skip world*/; EntNr<m_MapDoc.GetEntities().Size(); EntNr++)
  240. if (m_MapDoc.GetEntities()[EntNr]->GetClass()->GetName()=="info_player_start") return false;
  241. return true;
  242. }
  243. wxString GetInfo() const { return "No player start."; }
  244. wxString GetHelpText() const { return "There is no player start in the map. Use the \"New Entity\" tool in order to add one."; }
  245. };
  246. BEGIN_EVENT_TABLE(MapCheckDialogT, wxDialog)
  247. EVT_LISTBOX(MapCheckDialogT::ID_LISTBOX_PROBLEMS, MapCheckDialogT::OnListBoxProblemsSelChange)
  248. EVT_BUTTON(MapCheckDialogT::ID_BUTTON_GOTO_ERROR, MapCheckDialogT::OnButtonGoToError)
  249. EVT_BUTTON(MapCheckDialogT::ID_BUTTON_FIX, MapCheckDialogT::OnButtonFix)
  250. EVT_BUTTON(MapCheckDialogT::ID_BUTTON_FIXALL, MapCheckDialogT::OnButtonFixAll)
  251. END_EVENT_TABLE()
  252. MapCheckDialogT::MapCheckDialogT(wxWindow* Parent, MapDocumentT& MapDoc)
  253. : wxDialog(Parent, -1, "Map Problems Checker", wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER),
  254. m_MapDoc(MapDoc)
  255. {
  256. wxBoxSizer *item0 = new wxBoxSizer( wxVERTICAL );
  257. wxStaticText *item1 = new wxStaticText(this, -1, wxT("Problems found:"), wxDefaultPosition, wxDefaultSize, 0 );
  258. item0->Add( item1, 0, wxALIGN_CENTER_VERTICAL|wxLEFT|wxRIGHT|wxTOP, 5 );
  259. ListBoxProblems=new wxListBox(this, ID_LISTBOX_PROBLEMS, wxDefaultPosition, wxSize(200,100), 0, NULL, wxLB_SINGLE );
  260. item0->Add(ListBoxProblems, 1, wxGROW|wxALIGN_CENTER_VERTICAL|wxLEFT|wxRIGHT|wxBOTTOM, 5 );
  261. wxStaticBox *item4 = new wxStaticBox(this, -1, wxT("Description") );
  262. wxStaticBoxSizer *item3 = new wxStaticBoxSizer( item4, wxHORIZONTAL );
  263. StaticTextProblemDescription=new wxStaticText(this, -1, wxT("The description of the selected problem."), wxDefaultPosition, wxDefaultSize, wxST_NO_AUTORESIZE | wxSUNKEN_BORDER);
  264. item3->Add(StaticTextProblemDescription, 1, wxGROW|wxALIGN_CENTER_HORIZONTAL|wxALL, 5);
  265. wxBoxSizer *item6 = new wxBoxSizer( wxVERTICAL );
  266. ButtonGoToError=new wxButton(this, ID_BUTTON_GOTO_ERROR, wxT("Go to"), wxDefaultPosition, wxDefaultSize, 0 );
  267. ButtonGoToError->SetToolTip("Centers the 2D views on the related map element.");
  268. item6->Add(ButtonGoToError, 0, wxALIGN_CENTER|wxALL, 5 );
  269. ButtonFix= new wxButton(this, ID_BUTTON_FIX, wxT("Fix"), wxDefaultPosition, wxDefaultSize, 0 );
  270. ButtonFix->SetToolTip("Automatically fixes the currently selected problem, if possible.");
  271. item6->Add(ButtonFix, 0, wxALIGN_CENTER|wxALL, 5 );
  272. ButtonFixAll= new wxButton(this, ID_BUTTON_FIXALL, wxT("Fix all"), wxDefaultPosition, wxDefaultSize, 0 );
  273. ButtonFixAll->SetToolTip("Automatically fixes all problems of the same type, if possible.");
  274. item6->Add(ButtonFixAll, 0, wxALIGN_CENTER|wxALL, 5 );
  275. item3->Add( item6, 0, wxALIGN_CENTER_HORIZONTAL, 5 );
  276. item0->Add( item3, 1, wxGROW|wxALIGN_CENTER_VERTICAL|wxALL, 5 );
  277. wxButton *item10 = new wxButton(this, wxID_CANCEL, wxT("Close"), wxDefaultPosition, wxDefaultSize, 0 );
  278. item10->SetDefault();
  279. item0->Add( item10, 0, wxALIGN_RIGHT|wxALIGN_CENTER_VERTICAL|wxALL, 5 );
  280. this->SetSizer(item0);
  281. item0->SetSizeHints(this);
  282. UpdateProblems();
  283. }
  284. MapCheckDialogT::~MapCheckDialogT()
  285. {
  286. for (unsigned long ProblemNr=0; ProblemNr<m_Problems.Size(); ProblemNr++)
  287. delete m_Problems[ProblemNr];
  288. m_Problems.Clear();
  289. }
  290. void MapCheckDialogT::UpdateProblems()
  291. {
  292. for (unsigned long ProblemNr=0; ProblemNr<m_Problems.Size(); ProblemNr++)
  293. delete m_Problems[ProblemNr];
  294. m_Problems.Overwrite();
  295. ArrayT<MapElementT*> Elems;
  296. m_MapDoc.GetAllElems(Elems);
  297. for (unsigned long ElemNr=0; ElemNr<Elems.Size(); ElemNr++)
  298. {
  299. MapElementT* Elem=Elems[ElemNr];
  300. // IMPORTANT NOTE: Register at most ONE problem for each Elem.
  301. // This is supposed to avoid problems with CommandTs...
  302. MC_UnknownTargetT MC1(m_MapDoc, Elem); if (MC1.HasProblem()) { m_Problems.PushBack(new MC_UnknownTargetT (MC1)); continue; }
  303. MC_UndefinedClassOrKeysT MC2(m_MapDoc, Elem); if (MC2.HasProblem()) { m_Problems.PushBack(new MC_UndefinedClassOrKeysT(MC2)); continue; }
  304. MC_DuplicateKeysT MC3(m_MapDoc, Elem); if (MC3.HasProblem()) { m_Problems.PushBack(new MC_DuplicateKeysT (MC3)); continue; }
  305. MC_EmptySolidEntityT MC4(m_MapDoc, Elem); if (MC4.HasProblem()) { m_Problems.PushBack(new MC_EmptySolidEntityT (MC4)); continue; }
  306. MC_WorldHasPlayerStartT MC5(m_MapDoc, Elem); if (MC5.HasProblem()) { m_Problems.PushBack(new MC_WorldHasPlayerStartT (MC5)); continue; }
  307. }
  308. // If no problems were found, add the "no problem" problem.
  309. if (m_Problems.Size()==0) m_Problems.PushBack(new MapCheckerT(m_MapDoc, m_MapDoc.GetEntities()[0]));
  310. // Fill the wxListBox.
  311. ListBoxProblems->Clear();
  312. for (unsigned long ProblemNr=0; ProblemNr<m_Problems.Size(); ProblemNr++)
  313. ListBoxProblems->Append(wxString::Format("%lu ", ProblemNr+1)+m_Problems[ProblemNr]->GetInfo());
  314. ListBoxProblems->SetSelection(0);
  315. wxCommandEvent CE; OnListBoxProblemsSelChange(CE);
  316. }
  317. void MapCheckDialogT::OnListBoxProblemsSelChange(wxCommandEvent& Event)
  318. {
  319. const int SelectionNr=ListBoxProblems->GetSelection();
  320. if (SelectionNr<0)
  321. {
  322. ButtonFix->SetLabel("Fix");
  323. StaticTextProblemDescription->SetLabel("");
  324. ButtonGoToError->Disable();
  325. ButtonFix ->Disable();
  326. ButtonFixAll ->Disable();
  327. return;
  328. }
  329. MapCheckerT* Problem =m_Problems[SelectionNr];
  330. MapElementT* ProbElem=Problem->GetElem();
  331. ButtonFix->SetLabel(!Problem->HasProblem() ? "(is fixed)" : (Problem->CanFix() ? "Fix" : "(Can't fix)"));
  332. StaticTextProblemDescription->SetLabel(Problem->GetHelpText());
  333. ButtonGoToError->Enable(ProbElem!=NULL);
  334. ButtonFix ->Enable(Problem->HasProblem() && Problem->CanFix());
  335. ButtonFixAll ->Enable(Problem->CanFix());
  336. if (ProbElem && ProbElem->GetType()!=&MapWorldT::TypeInfo) m_MapDoc.GetHistory().SubmitCommand(CommandSelectT::Set(&m_MapDoc, ProbElem));
  337. else m_MapDoc.GetHistory().SubmitCommand(CommandSelectT::Clear(&m_MapDoc));
  338. }
  339. void MapCheckDialogT::OnButtonGoToError(wxCommandEvent& Event)
  340. {
  341. const int SelectionNr=ListBoxProblems->GetSelection();
  342. if (SelectionNr<0) return;
  343. MapCheckerT* Problem =m_Problems[SelectionNr];
  344. MapElementT* ProbElem=Problem->GetElem();
  345. // m_MapDoc.GetChildFrame()->GetToolManager().SetActiveTool(GetToolTIM().FindTypeInfoByName("ToolSelectionT"));
  346. m_MapDoc.GetChildFrame()->All2DViews_Center(ProbElem->GetBB().GetCenter());
  347. }
  348. void MapCheckDialogT::OnButtonFix(wxCommandEvent& Event)
  349. {
  350. const int SelectionNr=ListBoxProblems->GetSelection();
  351. if (SelectionNr<0) return;
  352. MapCheckerT* Problem=m_Problems[SelectionNr];
  353. CommandT* ProbCmd=Problem->GetFix();
  354. if (ProbCmd)
  355. {
  356. m_MapDoc.GetHistory().SubmitCommand(ProbCmd);
  357. }
  358. wxCommandEvent CE;
  359. OnListBoxProblemsSelChange(CE);
  360. }
  361. void MapCheckDialogT::OnButtonFixAll(wxCommandEvent& Event)
  362. {
  363. const int SelectionNr=ListBoxProblems->GetSelection();
  364. if (SelectionNr<0) return;
  365. wxBusyCursor BusyCursor;
  366. MapCheckerT* Problem=m_Problems[SelectionNr];
  367. ArrayT<CommandT*> Fixes;
  368. for (unsigned long ProblemNr=0; ProblemNr<m_Problems.Size(); ProblemNr++)
  369. if (typeid(Problem)==typeid(m_Problems[ProblemNr]))
  370. {
  371. CommandT* ProbCmd=m_Problems[ProblemNr]->GetFix();
  372. if (ProbCmd) Fixes.PushBack(ProbCmd);
  373. }
  374. if (Fixes.Size())
  375. m_MapDoc.GetHistory().SubmitCommand(new CommandMacroT(Fixes, "Fix all: "+Problem->GetInfo()));
  376. wxCommandEvent CE;
  377. OnListBoxProblemsSelChange(CE);
  378. }