├── .gitignore ├── LICENSE ├── MainWindow.cpp ├── MainWindow.h ├── MainWindow.ui ├── QDKEdit.cpp ├── QDKEdit.h ├── QTileEdit.cpp ├── QTileEdit.h ├── QTileSelector.cpp ├── QTileSelector.h ├── README.md ├── eDKit.pro ├── main.cpp └── win32 └── eDKit.rar /.gitignore: -------------------------------------------------------------------------------- 1 | # C++ objects and libs 2 | 3 | *.*~ 4 | *.slo 5 | *.lo 6 | *.o 7 | *.a 8 | *.la 9 | *.lai 10 | *.so 11 | *.dll 12 | *.dylib 13 | 14 | # Qt-es 15 | 16 | *.pro.user 17 | *.pro.user.* 18 | moc_*.cpp 19 | qrc_*.cpp 20 | Makefile 21 | *-build-* 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | {description} 294 | Copyright (C) {year} {fullname} 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /MainWindow.cpp: -------------------------------------------------------------------------------- 1 | #include "MainWindow.h" 2 | #include "ui_MainWindow.h" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | MainWindow::MainWindow(QWidget *parent) : 14 | QMainWindow(parent), 15 | ui(new Ui::MainWindow) 16 | { 17 | ui->setupUi(this); 18 | ui->lvlEdit->setupTileSelector(ui->tileSelect, 2.0f, 256); 19 | 20 | ui->actionOpen_ROM->setShortcut(QKeySequence::Open); 21 | ui->actionSave_ROM->setShortcut(QKeySequence::Save); 22 | ui->actionUndo->setShortcut(Qt::CTRL + Qt::Key_U); 23 | 24 | connect(ui->lvlEdit, SIGNAL(dataChanged()), this, SLOT(updateText())); 25 | connect(ui->tabWidget, SIGNAL(currentChanged(int)), ui->lvlEdit, SLOT(toggleSpriteMode(int))); 26 | connect(ui->tabWidget, SIGNAL(currentChanged(int)), this, SLOT(tabsSwitched(int))); 27 | connect(ui->btnWrite, SIGNAL(clicked()), ui->lvlEdit, SLOT(saveLevel())); 28 | connect(ui->actionOpen_ROM, SIGNAL(triggered()), this, SLOT(loadROM())); 29 | connect(ui->actionSave_ROM, SIGNAL(triggered()), this, SLOT(SaveROM())); 30 | connect(ui->actionUndo, SIGNAL(triggered()), ui->lvlEdit, SLOT(undo())); 31 | connect(ui->actionEmpty_Level, SIGNAL(triggered()), ui->lvlEdit, SLOT(clearLevel())); 32 | connect(ui->actionExportLvl, SIGNAL(triggered()), this, SLOT(ExportLvl())); 33 | connect(ui->actionImportLvl, SIGNAL(triggered()), this, SLOT(ImportLvl())); 34 | 35 | connect(ui->lvlEdit, SIGNAL(musicChanged(int)), ui->cmbMusic, SLOT(setCurrentIndex(int))); 36 | connect(ui->lvlEdit, SIGNAL(paletteChanged(int)), ui->spbPalette, SLOT(setValue(int))); 37 | connect(ui->lvlEdit, SIGNAL(sizeChanged(int)), ui->cmbSize, SLOT(setCurrentIndex(int))); 38 | connect(ui->lvlEdit, SIGNAL(tilesetChanged(int)), ui->cmbTileset, SLOT(setCurrentIndex(int))); 39 | connect(ui->lvlEdit, SIGNAL(timeChanged(int)), ui->spbTime, SLOT(setValue(int))); 40 | connect(ui->lvlEdit, SIGNAL(dataChanged()), this, SLOT(enableSaveBtn())); 41 | 42 | connect(ui->spbLevel, SIGNAL(valueChanged(int)), this, SLOT(changeLevel(int))); 43 | connect(ui->cmbSize, SIGNAL(currentIndexChanged(int)), ui->lvlEdit, SLOT(changeSize(int))); 44 | connect(ui->cmbMusic, SIGNAL(currentIndexChanged(int)), ui->lvlEdit, SLOT(changeMusic(int))); 45 | connect(ui->cmbTileset, SIGNAL(currentIndexChanged(int)), ui->lvlEdit, SLOT(changeTileset(int))); 46 | connect(ui->spbPalette, SIGNAL(valueChanged(int)), ui->lvlEdit, SLOT(changePalette(int))); 47 | connect(ui->spbTime, SIGNAL(valueChanged(int)), ui->lvlEdit, SLOT(changeTime(int))); 48 | 49 | connect(ui->ckbTransparency, SIGNAL(clicked(bool)), ui->lvlEdit, SLOT(changeSpriteTransparency(bool))); 50 | connect(ui->lvlEdit, SIGNAL(spriteSelected(int)), this, SLOT(selectSprite(int))); 51 | connect(ui->lvlEdit, SIGNAL(spriteAdded(QString, int)), this, SLOT(addSprite(QString, int))); 52 | connect(ui->lvlEdit, SIGNAL(spriteRemoved(int)), this, SLOT(removeSprite(int))); 53 | 54 | connect(ui->lvlEdit, SIGNAL(switchAdded(QDKSwitch*)), this, SLOT(addSwitch(QDKSwitch*))); 55 | connect(ui->lvlEdit, SIGNAL(switchUpdated(int,QDKSwitch*)), this, SLOT(updateSwitch(int,QDKSwitch*))); 56 | connect(ui->lvlEdit, SIGNAL(switchRemoved(int)), this, SLOT(removeSwitch(int))); 57 | 58 | connect(ui->lvlEdit, SIGNAL(tilesVRAMchanged(int)), this, SLOT(updateVRAMtiles(int))); 59 | connect(ui->lvlEdit, SIGNAL(spriteVRAMchanged(int)), this, SLOT(updateVRAMsprites(int))); 60 | 61 | if ((qApp->arguments().size() > 1) && (QFile::exists(qApp->arguments().at(1)))) 62 | { 63 | ui->lvlEdit->loadAllLevels(qApp->arguments().at(1)); 64 | ui->lvlEdit->changeLevel(0); 65 | ui->lvlInfo->setPlainText(ui->lvlEdit->getLevelInfo()); 66 | } 67 | else 68 | { 69 | ui->lvlEdit->loadAllLevels(BASE_ROM); 70 | ui->lvlEdit->changeLevel(0); 71 | ui->lvlInfo->setPlainText(ui->lvlEdit->getLevelInfo()); 72 | } 73 | ui->tabWidget->setCurrentIndex(0); 74 | ui->spbLevel->setFocus(); 75 | 76 | QMenu *newSpriteMenu = new QMenu(this); 77 | QDir dir("sprites/"); 78 | QStringList list = dir.entryList(QStringList("*.png"), QDir::Files, QDir::Name); 79 | 80 | QAction *action; 81 | QPixmap *pix; 82 | QPixmap pixLB(32, 32); 83 | qreal ar; 84 | 85 | for (int i = 0; i < list.size(); i++) 86 | { 87 | pix = new QPixmap("sprites/" + list.at(i)); 88 | pixLB.fill(); 89 | 90 | ar = (qreal)pix->width() / (qreal)pix->height(); 91 | 92 | if (ar >= 1.0f) 93 | { 94 | QPainter painter; 95 | painter.begin(&pixLB); 96 | painter.drawPixmap(0, 16 - (16.0f/ar), pix->scaled(QSize(32, 32), Qt::KeepAspectRatio)); 97 | painter.end(); 98 | } 99 | else 100 | { 101 | QPainter painter; 102 | painter.begin(&pixLB); 103 | painter.drawPixmap(16 - (16.0f*ar), 0, pix->scaled(QSize(32, 32), Qt::KeepAspectRatio)); 104 | painter.end(); 105 | } 106 | delete pix; 107 | 108 | action = new QAction(QIcon(pixLB), ui->lvlEdit->spriteNumToString(list.at(i).mid(7,2).toInt(0, 16)), newSpriteMenu); 109 | action->setStatusTip(list.at(i).mid(7,2)); 110 | newSpriteMenu->addAction(action); 111 | if (list.at(i).contains("set", Qt::CaseInsensitive)) 112 | i+=0x21; 113 | } 114 | 115 | ui->toolButton->setMenu(newSpriteMenu); 116 | connect(newSpriteMenu, SIGNAL(triggered(QAction*)), this, SLOT(addNewSprite(QAction*))); 117 | 118 | QShortcut* scSprites = new QShortcut(QKeySequence(Qt::Key_Delete), ui->lstSprites); 119 | connect(scSprites, SIGNAL(activated()), this, SLOT(removeSelectedSprite())); 120 | 121 | QShortcut* scSwitches = new QShortcut(QKeySequence(Qt::Key_Delete), ui->treSwitches); 122 | connect(scSwitches, SIGNAL(activated()), this, SLOT(delSwitchItem())); 123 | 124 | changeLevel(0); 125 | } 126 | 127 | void MainWindow::tabsSwitched(int index) 128 | { 129 | ui->lvlEdit->toggleSwitchMode((bool)(index > 1)); 130 | 131 | if (index > 1) 132 | on_treSwitches_currentItemChanged(ui->treSwitches->currentItem(), NULL); 133 | } 134 | 135 | void MainWindow::removeSelectedSprite() 136 | { 137 | int i = ui->lstSprites->currentRow(); 138 | if (i != -1) 139 | ui->lvlEdit->deleteSprite(i); 140 | } 141 | 142 | void MainWindow::selectSprite(int num) 143 | { 144 | ui->lstSprites->setCurrentRow(num); 145 | ui->grpSpriteProp->setEnabled(false); 146 | if (num == -1) 147 | return; 148 | 149 | int spriteID; 150 | int selSprite = ui->lvlEdit->getSelectedSprite(&spriteID); 151 | if (selSprite == -1) 152 | return; 153 | 154 | if ((spriteID != 0x7F) && (spriteID != 0x98) && (spriteID != 0x80) && (spriteID != 0x80) 155 | && (spriteID != 0x84) && (spriteID != 0x70) && (spriteID != 0x72) && (spriteID != 0x54) 156 | && (spriteID != 0xB8) && (spriteID != 0x6E) && (spriteID != 0x9A) && (spriteID != 0xCC)) 157 | return; 158 | 159 | quint8 flag; 160 | ui->lvlEdit->getSpriteFlag(selSprite, &flag); 161 | 162 | ui->grpSpriteProp->setEnabled(true); 163 | ui->btnFlip->setEnabled(false); 164 | ui->spbSpeed->setEnabled(false); 165 | ui->cmbElevator->setEnabled(false); 166 | ui->spbRAW->setEnabled(false); 167 | ui->spbRAW->setValue(flag); 168 | 169 | //sprites that may get flipped 170 | if ((spriteID == 0x7F) || (spriteID == 0x98) || (spriteID == 0x80)) 171 | { 172 | ui->btnFlip->setEnabled(true); 173 | ui->btnFlip->setText("Flip"); 174 | ui->lblDirection->setText("Direction"); 175 | } 176 | 177 | //sprites that may get flipped but should display another text ;) 178 | if (spriteID == 0x84) 179 | { 180 | ui->btnFlip->setEnabled(true); 181 | ui->lblDirection->setText("Direction"); 182 | if (flag & 1) 183 | ui->btnFlip->setText("Left"); 184 | else 185 | ui->btnFlip->setText("Right"); 186 | } 187 | 188 | //sprites with walking speed property 189 | if ((spriteID == 0x80) || (spriteID == 0x98) || (spriteID == 0x84)) 190 | { 191 | ui->spbSpeed->setEnabled(true); 192 | ui->spbSpeed->setValue(flag >> 1); 193 | } 194 | 195 | //elevator time between boards and speed 196 | //table @ 0x30F77 197 | //flag byte selects pair from table 198 | // 0x40 0x20 ; 0x60 0x20 ; 0x80 0x20 ; 0xA0 0x20 199 | // 0x40 0x40 ; 0x60 0x40 ; 0x80 0x40 ; 0xA0 0x40 200 | // 0x40 0x60 ; 0x60 0x60 ; 0x80 0x60 ; 0xA0 0x60 201 | // 0x40 0x80 ; 0x60 0x80 ; 0x80 0x80 ; 0xA0 0x80 202 | // 16 settings 203 | // default is 0x60 0x40 => 0x05 204 | //speed = (flag % 4) * 0x20 + 0x40; 205 | //time = (flag / 4) * 0x20 + 0x20; 206 | 207 | // first byte is speed; second byte time between new boards 208 | if ((spriteID == 0x70) || (spriteID == 0x72)) 209 | { 210 | ui->cmbElevator->setEnabled(true); 211 | if ((flag >= 0) && (flag < 16)) 212 | ui->cmbElevator->setCurrentIndex(flag); 213 | else 214 | ui->cmbElevator->setCurrentIndex(5); 215 | } 216 | 217 | //board speed 218 | if (spriteID == 0x54) 219 | { 220 | ui->btnFlip->setEnabled(true); 221 | ui->lblDirection->setText("Speed"); 222 | if (!flag) 223 | ui->btnFlip->setText("Normal"); 224 | else 225 | ui->btnFlip->setText("Slow"); 226 | } 227 | 228 | if ((spriteID == 0xB8) || (spriteID == 0x6E) || (spriteID == 0x9A) || (spriteID == 0xCC)) 229 | { 230 | ui->spbRAW->setEnabled(true); 231 | ui->spbRAW->setValue(flag); 232 | } 233 | } 234 | 235 | void MainWindow::addSprite(QString text, int id) 236 | { 237 | QString icon = QString("sprites/sprite_%1.png").arg(id, 2, 16, QChar('0')); 238 | if (!QFile::exists(icon)) 239 | icon = QString("sprites/sprite_%1_set_00.png").arg(id, 2, 16, QChar('0')); 240 | QListWidgetItem *item = new QListWidgetItem(QIcon(icon), text); 241 | item->setToolTip(text); 242 | item->setStatusTip(QString("%1").arg(id, 2, 16, QChar('0'))); 243 | ui->lstSprites->addItem(item); 244 | } 245 | 246 | void MainWindow::removeSprite(int index) 247 | { 248 | QListWidgetItem *tmp = ui->lstSprites->takeItem(index); 249 | delete tmp; 250 | } 251 | 252 | 253 | void MainWindow::changeLevel(int id) 254 | { 255 | if (ui->lvlEdit->isChanged()) 256 | { 257 | QMessageBox::StandardButton result = QMessageBox::question(NULL, "Level data changed", "The level data has been changed. Save data?", QMessageBox::Yes | QMessageBox::No); 258 | if (result == QMessageBox::Yes) // save changes 259 | ui->lvlEdit->saveLevel(); 260 | else if (result != QMessageBox::No) // discard changes 261 | qWarning() << "Unexpected return value form messagebox!"; 262 | } 263 | ui->lvlEdit->changeLevel(id); 264 | ui->btnWrite->setEnabled(false); 265 | } 266 | 267 | void MainWindow::updateText() 268 | { 269 | ui->lvlInfo->setPlainText(ui->lvlEdit->getLevelInfo()); 270 | } 271 | 272 | void MainWindow::loadROM() 273 | { 274 | QString file = QFileDialog::getOpenFileName(0, "Select Donkey Kong (GB) ROM", qApp->applicationDirPath(), "Donkey Kong (GB) ROM (*.gb)"); 275 | 276 | if (!QFile::exists(file)) 277 | return; 278 | 279 | ui->lvlEdit->loadAllLevels(file); 280 | ui->lvlEdit->changeLevel(0); 281 | ui->lvlInfo->setPlainText(ui->lvlEdit->getLevelInfo()); 282 | } 283 | 284 | void MainWindow::SaveROM() 285 | { 286 | if (ui->lvlEdit->isChanged()) 287 | { 288 | QMessageBox::StandardButton result = QMessageBox::question(NULL, "Level data changed", "The level data has been changed. Save data?", QMessageBox::Yes | QMessageBox::No); 289 | if (result == QMessageBox::Yes) // save changes 290 | ui->lvlEdit->saveLevel(); 291 | else if (result != QMessageBox::No) // discard changes 292 | qWarning() << "Unexpected return value form messagebox!"; 293 | } 294 | 295 | QString file = QFileDialog::getSaveFileName(0, "Select Donkey Kong (GB) ROM", qApp->applicationDirPath(), "Donkey Kong (GB) ROM (*.gb)"); 296 | 297 | if (!QFile::exists(file)) 298 | QFile::copy(qApp->applicationDirPath() + BASE_ROM, file); 299 | 300 | ui->lvlEdit->saveAllLevels(file); 301 | } 302 | 303 | void MainWindow::ExportLvl() 304 | { 305 | if (ui->lvlEdit->isChanged()) 306 | { 307 | QMessageBox::StandardButton result = QMessageBox::question(NULL, "Level data changed", "The level data has been changed. Save data?", QMessageBox::Yes | QMessageBox::No); 308 | if (result == QMessageBox::Yes) // save changes 309 | ui->lvlEdit->saveLevel(); 310 | else if (result != QMessageBox::No) // discard changes 311 | qWarning() << "Unexpected return value form messagebox!"; 312 | } 313 | 314 | QString file = QFileDialog::getSaveFileName(0, "Export level data", qApp->applicationDirPath(), "eDKit level data (*.lvl)"); 315 | 316 | if (!ui->lvlEdit->exportCurrentLevel(file)) 317 | qWarning() << "Level export failed"; 318 | } 319 | 320 | void MainWindow::ImportLvl() 321 | { 322 | QString file = QFileDialog::getOpenFileName(0, "Import level data", qApp->applicationDirPath(), "eDKit level data (*.lvl)"); 323 | 324 | if (!QFile::exists(file)) 325 | return; 326 | ui->lvlEdit->importLevel(file); 327 | } 328 | 329 | void MainWindow::addNewSprite(QAction *action) 330 | { 331 | ui->lvlEdit->addSprite(action->statusTip().toInt(0, 16)); 332 | } 333 | 334 | MainWindow::~MainWindow() 335 | { 336 | delete ui; 337 | } 338 | 339 | void MainWindow::on_btnFlip_clicked() 340 | { 341 | int spriteID; 342 | int selSprite = ui->lvlEdit->getSelectedSprite(&spriteID); 343 | if (selSprite == -1) 344 | return; 345 | 346 | quint8 flag; 347 | ui->lvlEdit->getSpriteFlag(selSprite, &flag); 348 | 349 | // Mario 350 | if (spriteID == 0x7F) 351 | { 352 | if (flag == 0x00) 353 | flag = 0x01; 354 | else 355 | flag = 0x00; 356 | } 357 | 358 | //walking guys 359 | if ((spriteID == 0x80) || (spriteID == 0x98)) 360 | { 361 | if ((flag & 1) == 0x01) 362 | flag ^= 1; 363 | else 364 | flag |= 1; 365 | 366 | } 367 | 368 | //sprites that may get flipped but should display another text ;) 369 | if (spriteID == 0x84) 370 | { 371 | if (flag & 1) 372 | { 373 | ui->btnFlip->setText("Right"); 374 | flag ^= 1; 375 | } 376 | else 377 | { 378 | ui->btnFlip->setText("Left"); 379 | flag |= 1; 380 | } 381 | } 382 | 383 | //board speed 384 | if (spriteID == 0x54) 385 | { 386 | if (flag) 387 | { 388 | flag = 0; 389 | ui->btnFlip->setText("Normal"); 390 | } 391 | else 392 | { 393 | flag = 1; 394 | ui->btnFlip->setText("Slow"); 395 | } 396 | } 397 | 398 | ui->lvlEdit->setSpriteFlag(selSprite, flag); 399 | ui->spbRAW->setValue(flag); 400 | } 401 | 402 | void MainWindow::on_spbSpeed_valueChanged(int arg1) 403 | { 404 | if ((arg1 < 0) || (arg1 > 127)) 405 | return; 406 | 407 | int spriteID; 408 | int selSprite = ui->lvlEdit->getSelectedSprite(&spriteID); 409 | if (selSprite == -1) 410 | return; 411 | 412 | quint8 flag; 413 | ui->lvlEdit->getSpriteFlag(selSprite, &flag); 414 | 415 | //sprites with walking speed property 416 | if ((spriteID == 0x80) || (spriteID == 0x98) || (spriteID == 0x84)) 417 | { 418 | flag = (arg1 * 2) | (flag & 1); 419 | ui->lvlEdit->setSpriteFlag(selSprite, flag); 420 | ui->spbRAW->setValue(flag); 421 | } 422 | } 423 | 424 | void MainWindow::on_cmbElevator_currentIndexChanged(int index) 425 | { 426 | if ((index < 0) || (index > 15)) 427 | return; 428 | 429 | int spriteID; 430 | int selSprite = ui->lvlEdit->getSelectedSprite(&spriteID); 431 | if (selSprite == -1) 432 | return; 433 | 434 | // elevator 435 | if ((spriteID == 0x70) || (spriteID == 0x72)) 436 | { 437 | ui->lvlEdit->setSpriteFlag(selSprite, index); 438 | ui->spbRAW->setValue(index); 439 | } 440 | } 441 | 442 | void MainWindow::on_spbRAW_valueChanged(int arg1) 443 | { 444 | if ((arg1 < 0) || (arg1 > 255)) 445 | return; 446 | 447 | int spriteID; 448 | int selSprite = ui->lvlEdit->getSelectedSprite(&spriteID); 449 | if (selSprite == -1) 450 | return; 451 | 452 | // DKs 453 | if ((spriteID == 0xB8) || (spriteID == 0x6E) || (spriteID == 0x9A) || (spriteID == 0xCC)) 454 | ui->lvlEdit->setSpriteFlag(selSprite, arg1); 455 | } 456 | 457 | void MainWindow::addSwitch(QDKSwitch *sw) 458 | { 459 | addSwitchAtPos(ui->treSwitches->topLevelItemCount(), sw); 460 | } 461 | 462 | void MainWindow::addSwitchAtPos(int i, QDKSwitch *sw) 463 | { 464 | QTreeWidgetItem *item = new QTreeWidgetItem(); 465 | QTreeWidgetItem *child; 466 | QDKSwitchObject *obj; 467 | 468 | QString state; 469 | switch (sw->state) 470 | { 471 | case 1: state = "middle"; break; 472 | case 2: state = "right"; break; 473 | default: state = "left"; 474 | } 475 | 476 | item->setText(0, QString("Switch %1 (%2) at %3x%4").arg(i).arg(state).arg(sw->x).arg(sw->y)); 477 | 478 | for (int i = 0; i < sw->connectedTo.size(); i++) 479 | { 480 | child = new QTreeWidgetItem(); 481 | obj = &sw->connectedTo[i]; 482 | if (!obj->isSprite) 483 | child->setText(0, QString("Tile at %1x%2").arg(obj->x).arg(obj->y)); 484 | else 485 | child->setText(0, QString("Sprite at %1x%2").arg(obj->x).arg(obj->y)); 486 | 487 | item->addChild(child); 488 | } 489 | 490 | ui->treSwitches->insertTopLevelItem(i, item); 491 | ui->treSwitches->expandAll(); 492 | } 493 | 494 | void MainWindow::updateSwitch(int i, QDKSwitch *sw) 495 | { 496 | QTreeWidgetItem *item = ui->treSwitches->takeTopLevelItem(i); 497 | if (!item) 498 | return; 499 | 500 | addSwitchAtPos(i, sw); 501 | } 502 | 503 | void MainWindow::removeSwitch(int i) 504 | { 505 | if ((i < 0) || (i > ui->treSwitches->topLevelItemCount())) 506 | return; 507 | 508 | QTreeWidgetItem *item = ui->treSwitches->takeTopLevelItem(i); 509 | delete item; 510 | } 511 | 512 | void MainWindow::on_treSwitches_currentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *) 513 | { 514 | if (!current) 515 | return; 516 | 517 | QTreeWidgetItem *parent; 518 | parent = current->parent(); 519 | if (!parent) 520 | parent = current; 521 | 522 | bool ok; 523 | int i = parent->text(0).mid(7, 1).toInt(&ok); 524 | if (!ok) 525 | i = -1; 526 | 527 | ui->lvlEdit->selectSwitch(i); 528 | } 529 | 530 | 531 | void MainWindow::delSwitchItem() 532 | { 533 | QTreeWidgetItem *item = ui->treSwitches->currentItem(); 534 | if (!item) 535 | return; 536 | if (!item->parent()) 537 | ui->lvlEdit->deleteCurrentSwitch(); 538 | else 539 | ui->lvlEdit->deleteSwitchObj(item->parent()->indexOfChild(item)); 540 | } 541 | 542 | void MainWindow::enableSaveBtn() 543 | { 544 | ui->btnWrite->setEnabled(true); 545 | } 546 | 547 | void MainWindow::updateVRAMtiles(int tiles) 548 | { 549 | if (tiles <= 80) 550 | { 551 | ui->barVRAMtiles->setRange(0, 80); 552 | ui->barVRAMtiles->setValue(tiles); 553 | QPalette p = ui->barVRAMtiles->palette(); 554 | p.setColor(QPalette::Highlight, Qt::green); 555 | ui->barVRAMtiles->setPalette(p); 556 | } 557 | else if (tiles < (256 - ui->barVRAMsprites->value()) + 80) 558 | { 559 | ui->barVRAMtiles->setRange(0, (256 - ui->barVRAMsprites->value()) + 80); 560 | ui->barVRAMtiles->setValue(tiles); 561 | QPalette p = ui->barVRAMtiles->palette(); 562 | p.setColor(QPalette::Highlight, QColor(0xFF, 0xA0, 0x00)); 563 | ui->barVRAMtiles->setPalette(p); 564 | } 565 | else 566 | { 567 | ui->barVRAMtiles->setRange(0, tiles); 568 | ui->barVRAMtiles->setValue(tiles); 569 | QPalette p = ui->barVRAMtiles->palette(); 570 | p.setColor(QPalette::Highlight, Qt::red); 571 | ui->barVRAMtiles->setPalette(p); 572 | } 573 | } 574 | 575 | void MainWindow::updateVRAMsprites(int sprites) 576 | { 577 | if (sprites <= 256) 578 | { 579 | ui->barVRAMsprites->setRange(0, 256); 580 | ui->barVRAMsprites->setValue(sprites); 581 | QPalette p = ui->barVRAMsprites->palette(); 582 | p.setColor(QPalette::Highlight, Qt::green); 583 | ui->barVRAMsprites->setPalette(p); 584 | } 585 | else 586 | { 587 | ui->barVRAMsprites->setRange(0, sprites); 588 | ui->barVRAMsprites->setValue(sprites); 589 | QPalette p = ui->barVRAMsprites->palette(); 590 | p.setColor(QPalette::Highlight, Qt::red); 591 | ui->barVRAMsprites->setPalette(p); 592 | } 593 | 594 | if (ui->barVRAMtiles->value() > 80) 595 | updateVRAMtiles(ui->barVRAMtiles->value()); 596 | } 597 | -------------------------------------------------------------------------------- /MainWindow.h: -------------------------------------------------------------------------------- 1 | #ifndef MAINWINDOW_H 2 | #define MAINWINDOW_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #define BASE_ROM "base.gb" 10 | 11 | struct QDKSwitch; 12 | 13 | namespace Ui { 14 | class MainWindow; 15 | } 16 | 17 | class MainWindow : public QMainWindow 18 | { 19 | Q_OBJECT 20 | 21 | public: 22 | explicit MainWindow(QWidget *parent = 0); 23 | ~MainWindow(); 24 | 25 | private: 26 | // void spriteContextMenu(QListWidgetItem *item, QPoint globalPos); 27 | void addSwitchAtPos(int i, QDKSwitch *sw); 28 | 29 | private slots: 30 | void updateText(); 31 | 32 | void changeLevel(int id); 33 | void loadROM(); 34 | void SaveROM(); 35 | void ExportLvl(); 36 | void ImportLvl(); 37 | void enableSaveBtn(); 38 | void selectSprite(int num); 39 | void addSprite(QString text, int id); 40 | void removeSelectedSprite(); 41 | void removeSprite(int index); 42 | void addNewSprite(QAction *action); 43 | void addSwitch(QDKSwitch *sw); 44 | void updateSwitch(int i, QDKSwitch *sw); 45 | void removeSwitch(int i); 46 | void tabsSwitched(int index); 47 | 48 | void on_btnFlip_clicked(); 49 | void on_spbSpeed_valueChanged(int arg1); 50 | void on_cmbElevator_currentIndexChanged(int index); 51 | void on_spbRAW_valueChanged(int arg1); 52 | void on_treSwitches_currentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *); 53 | void delSwitchItem(); 54 | void updateVRAMtiles(int tiles); 55 | void updateVRAMsprites(int sprites); 56 | 57 | private: 58 | Ui::MainWindow *ui; 59 | }; 60 | 61 | #endif // MAINWINDOW_H 62 | -------------------------------------------------------------------------------- /MainWindow.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MainWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 860 10 | 648 11 | 12 | 13 | 14 | eDKit alpha (c) 2014-15 by bailli 15 | 16 | 17 | 18 | 19 | 20 | 10 21 | 10 22 | 512 23 | 448 24 | 25 | 26 | 27 | 28 | 29 | 30 | 9 31 | 470 32 | 411 33 | 151 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 530 44 | 10 45 | 321 46 | 451 47 | 48 | 49 | 50 | QTabWidget::Rounded 51 | 52 | 53 | 0 54 | 55 | 56 | Qt::ElideNone 57 | 58 | 59 | 60 | &Tiles 61 | 62 | 63 | 64 | 65 | 7 66 | 18 67 | 301 68 | 386 69 | 70 | 71 | 72 | 73 | 74 | 75 | &Sprites 76 | 77 | 78 | 79 | 80 | 157 81 | 218 82 | 150 83 | 21 84 | 85 | 86 | 87 | Transparent sprites 88 | 89 | 90 | true 91 | 92 | 93 | 94 | 95 | 96 | 8 97 | 10 98 | 301 99 | 201 100 | 101 | 102 | 103 | Qt::MoveAction 104 | 105 | 106 | 107 | 108 | 109 | 8 110 | 217 111 | 126 112 | 24 113 | 114 | 115 | 116 | Add new sprite 117 | 118 | 119 | QToolButton::InstantPopup 120 | 121 | 122 | Qt::ToolButtonTextBesideIcon 123 | 124 | 125 | Qt::NoArrow 126 | 127 | 128 | 129 | 130 | false 131 | 132 | 133 | 134 | 8 135 | 250 136 | 289 137 | 161 138 | 139 | 140 | 141 | Sprite properties 142 | 143 | 144 | 145 | 146 | 147 | Direction 148 | 149 | 150 | 151 | 152 | 153 | 154 | false 155 | 156 | 157 | Flip/Speed 158 | 159 | 160 | 161 | 162 | 163 | 164 | Qt::Horizontal 165 | 166 | 167 | 168 | 42 169 | 20 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | Walking Speed 178 | 179 | 180 | 181 | 182 | 183 | 184 | false 185 | 186 | 187 | 127 188 | 189 | 190 | 191 | 192 | 193 | 194 | Qt::Horizontal 195 | 196 | 197 | 198 | 42 199 | 20 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | Elevator Speed/Time 208 | 209 | 210 | 211 | 212 | 213 | 214 | false 215 | 216 | 217 | 218 | Speed 1; Time 1 219 | 220 | 221 | 222 | 223 | Speed 2; Time 1 224 | 225 | 226 | 227 | 228 | Speed 3; Time 1 229 | 230 | 231 | 232 | 233 | Speed 4; Time 1 234 | 235 | 236 | 237 | 238 | Speed 1; Time 2 239 | 240 | 241 | 242 | 243 | Speed 2; Time 2 244 | 245 | 246 | 247 | 248 | Speed 3; Time 2 249 | 250 | 251 | 252 | 253 | Speed 4; Time 2 254 | 255 | 256 | 257 | 258 | Speed 1; Time 3 259 | 260 | 261 | 262 | 263 | Speed 2; Time 3 264 | 265 | 266 | 267 | 268 | Speed 3; Time 3 269 | 270 | 271 | 272 | 273 | Speed 4; Time 3 274 | 275 | 276 | 277 | 278 | Speed 1; Time 4 279 | 280 | 281 | 282 | 283 | Speed 2; Time 4 284 | 285 | 286 | 287 | 288 | Speed 3; Time 4 289 | 290 | 291 | 292 | 293 | Speed 4; Time 4 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | RAW data 302 | 303 | 304 | 305 | 306 | 307 | 308 | 255 309 | 310 | 311 | 312 | 313 | 314 | 315 | Qt::Horizontal 316 | 317 | 318 | 319 | 42 320 | 20 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | S&witches 331 | 332 | 333 | 334 | 335 | 8 336 | 10 337 | 301 338 | 401 339 | 340 | 341 | 342 | true 343 | 344 | 345 | false 346 | 347 | 348 | 349 | 1 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 430 359 | 467 360 | 421 361 | 155 362 | 363 | 364 | 365 | Level properties 366 | 367 | 368 | 369 | 370 | 371 | Level 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 255 382 | 383 | 384 | 385 | 386 | 387 | 388 | Qt::Horizontal 389 | 390 | 391 | 392 | 115 393 | 20 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | false 402 | 403 | 404 | Save current level 405 | 406 | 407 | 408 | 409 | 410 | 411 | Size 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | (00) 20x18 Tiles (32x18) 420 | 421 | 422 | 423 | 424 | (01) 30x28 Tiles (32x28) 425 | 426 | 427 | 428 | 429 | (02) 20x28 Tiles (32x28) 430 | 431 | 432 | 433 | 434 | (03) 30x18 Tiles (32x28) 435 | 436 | 437 | 438 | 439 | (04) 20x18 Tiles (1-2) 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | Qt::Horizontal 448 | 449 | 450 | 451 | 29 452 | 20 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | Time (sec) 461 | 462 | 463 | 464 | 465 | 466 | 467 | 999 468 | 469 | 470 | 471 | 472 | 473 | 474 | Music 475 | 476 | 477 | 478 | 479 | 480 | 481 | Qt::LeftToRight 482 | 483 | 484 | 485 | (00) No music 486 | 487 | 488 | 489 | 490 | (01) Time's almost up! 491 | 492 | 493 | 494 | 495 | (02) Item active 496 | 497 | 498 | 499 | 500 | (03) 30 seconds left 501 | 502 | 503 | 504 | 505 | (04) Pauline abducted 506 | 507 | 508 | 509 | 510 | (05) Arcade Level 1 511 | 512 | 513 | 514 | 515 | (06) Arcade Level 2 516 | 517 | 518 | 519 | 520 | (07) Big City 521 | 522 | 523 | 524 | 525 | (08) Forest 526 | 527 | 528 | 529 | 530 | (09) Ship 531 | 532 | 533 | 534 | 535 | (0A) Jungle 536 | 537 | 538 | 539 | 540 | (0B) Desert 541 | 542 | 543 | 544 | 545 | (0C) Desert 2 546 | 547 | 548 | 549 | 550 | (0D) Airplane 551 | 552 | 553 | 554 | 555 | (0E) Iceberg 556 | 557 | 558 | 559 | 560 | (0F) Rockey Valley 1 561 | 562 | 563 | 564 | 565 | (10) Tower 1 566 | 567 | 568 | 569 | 570 | (11) Tower 2 571 | 572 | 573 | 574 | 575 | (12) Level 1-3 576 | 577 | 578 | 579 | 580 | (13) Spooky 581 | 582 | 583 | 584 | 585 | (14) Level 1-7 586 | 587 | 588 | 589 | 590 | (15) Monkey Stage 591 | 592 | 593 | 594 | 595 | (16) Dangerous 596 | 597 | 598 | 599 | 600 | (17) Rock Valley 2 601 | 602 | 603 | 604 | 605 | (18) Level 1-2 606 | 607 | 608 | 609 | 610 | (19) Battle 3 611 | 612 | 613 | 614 | 615 | (1A) Battle 1 (1-4) 616 | 617 | 618 | 619 | 620 | (1B) Battle 2 (1-8) 621 | 622 | 623 | 624 | 625 | (1C) Level 9-8 626 | 627 | 628 | 629 | 630 | (1D) Level 9-9 631 | 632 | 633 | 634 | 635 | (1E) ??? 636 | 637 | 638 | 639 | 640 | (1F) ??? 641 | 642 | 643 | 644 | 645 | (20) ??? 646 | 647 | 648 | 649 | 650 | (21) Level 6-2 651 | 652 | 653 | 654 | 655 | (22) Level 6-4 656 | 657 | 658 | 659 | 660 | (23) Level 6-8 661 | 662 | 663 | 664 | 665 | 666 | 667 | 668 | Qt::Horizontal 669 | 670 | 671 | 672 | 29 673 | 20 674 | 675 | 676 | 677 | 678 | 679 | 680 | 681 | Palette 682 | 683 | 684 | 685 | 686 | 687 | 688 | 384 689 | 690 | 691 | 511 692 | 693 | 694 | 695 | 696 | 697 | 698 | Tileset 699 | 700 | 701 | 702 | 703 | 704 | 705 | 706 | (00) Classic Girder 1 707 | 708 | 709 | 710 | 711 | (01) Airplane 1 712 | 713 | 714 | 715 | 716 | (02) Iceberg 1 717 | 718 | 719 | 720 | 721 | (03) Forest 1 722 | 723 | 724 | 725 | 726 | (04) Rocky Valley 1 727 | 728 | 729 | 730 | 731 | (05) Ship 1 732 | 733 | 734 | 735 | 736 | (06) Desert 1 737 | 738 | 739 | 740 | 741 | (07) Airplane 2 742 | 743 | 744 | 745 | 746 | (08) Tower 1 747 | 748 | 749 | 750 | 751 | (09) Clouds 1 752 | 753 | 754 | 755 | 756 | (0A) Rocky Valley 2 757 | 758 | 759 | 760 | 761 | (0B) Desert 2 762 | 763 | 764 | 765 | 766 | (0C) Big City 1 767 | 768 | 769 | 770 | 771 | (0D) Ship 2 772 | 773 | 774 | 775 | 776 | (0E) Jungle 1 777 | 778 | 779 | 780 | 781 | (0F) Tower 2 782 | 783 | 784 | 785 | 786 | (10) Classic Girder 2 787 | 788 | 789 | 790 | 791 | (11) Airplane 3 792 | 793 | 794 | 795 | 796 | (12) Iceberg 2 797 | 798 | 799 | 800 | 801 | (13) Forest 2 802 | 803 | 804 | 805 | 806 | (14) Rocky Valley 3 807 | 808 | 809 | 810 | 811 | (15) Ship 3 812 | 813 | 814 | 815 | 816 | (16) Desert 3 817 | 818 | 819 | 820 | 821 | (17) Airplane 4 822 | 823 | 824 | 825 | 826 | (18) Tower 3 827 | 828 | 829 | 830 | 831 | (19) Clouds 2 832 | 833 | 834 | 835 | 836 | (1A) Rocky Valley 4 837 | 838 | 839 | 840 | 841 | (1B) Desert 4 842 | 843 | 844 | 845 | 846 | (1C) Big City 2 847 | 848 | 849 | 850 | 851 | (1D) Ship 4 852 | 853 | 854 | 855 | 856 | (1E) Jungle 2 857 | 858 | 859 | 860 | 861 | (1F) Tower 4 862 | 863 | 864 | 865 | 866 | (20) Classic Girder 3 867 | 868 | 869 | 870 | 871 | (21) Giant DK Battle 872 | 873 | 874 | 875 | 876 | 877 | 878 | 879 | 236 880 | 881 | 882 | 24 883 | 884 | 885 | Tiles %v 886 | 887 | 888 | 889 | 890 | 891 | 892 | 256 893 | 894 | 895 | 24 896 | 897 | 898 | Sprites %v 899 | 900 | 901 | 902 | 903 | 904 | 905 | 906 | 907 | 908 | 0 909 | 0 910 | 860 911 | 20 912 | 913 | 914 | 915 | false 916 | 917 | 918 | 919 | &File 920 | 921 | 922 | 923 | 924 | 925 | 926 | 927 | 928 | 929 | &Edit 930 | 931 | 932 | 933 | 934 | 935 | 936 | 937 | 938 | 939 | &Open ROM 940 | 941 | 942 | 943 | 944 | 945 | 946 | 947 | &Save ROM 948 | 949 | 950 | 951 | 952 | &Undo 953 | 954 | 955 | 956 | 957 | &Clear Level 958 | 959 | 960 | 961 | 962 | &Export level 963 | 964 | 965 | 966 | 967 | &Import level 968 | 969 | 970 | 971 | 972 | 973 | 974 | QDKEdit 975 | QWidget 976 |
QDKEdit.h
977 | 1 978 |
979 | 980 | QTileSelector 981 | QWidget 982 |
QTileSelector.h
983 | 1 984 |
985 |
986 | 987 | 988 |
989 | -------------------------------------------------------------------------------- /QDKEdit.h: -------------------------------------------------------------------------------- 1 | #ifndef QDKEDIT_H 2 | #define QDKEDIT_H 3 | 4 | #include "QTileEdit.h" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | //find rombanks containing the level data 12 | #define ROMBANK_1 0x05 13 | #define COMPARE_POS_1 0x25FC // < 0x2D 14 | 15 | #define ROMBANK_POS_2 0x25FF // 0x06 16 | #define COMPARE_POS_2 0x2603 // < 0x50 17 | 18 | #define ROMBANK_POS_3 0x2606 // 0x12 19 | 20 | // some constants 21 | #define MAX_LEVEL_ID 256 22 | #define LAST_LEVEL 105 23 | #define MAX_TILESETS 0x22 24 | #define MAX_SPRITES 0x1B 25 | #define POINTER_TABLE 0x14000 26 | #define SUBTILESET_TABLE 0x30EEB 27 | #define SGB_SYSTEM_PAL 0x786F0 // decompressed size 0x1000 28 | // the SGB packet is always 51.(quint16)var+0x80.E4 00.E5.00.E6 00.C1.00.00 00.00.00.00 29 | // asm @ 0x0E70 30 | #define PAL_ARCADE 0x30F9A 31 | // the palette for the 4 arcade levels are @ 0x4F9A (bank 0x0C) + (6*id) 32 | // this value is palette index + 0xC8 33 | // not freely editable! 34 | #define PAL_TABLE 0x6093B 35 | // PAL_TABLE + (id-4)*8 36 | // palettes for all other levels 37 | 38 | // BGP depends on tileset asm @ 0E9D 39 | #define TILE_INDEX_TABLE 0x60E5 40 | #define ADDITIONAL_TILES_TABLE 0x30E55 41 | 42 | #define VRAM_TILES 0x50 43 | #define VRAM_SPRITES 0x100 44 | 45 | #define ELEVATOR_TABLE 0x30F77 46 | class QMouseEvent; 47 | 48 | struct QDKSprite : QSprite 49 | { 50 | quint16 ramPos; 51 | quint32 levelPos; 52 | // quint8 addFlag; 53 | }; 54 | 55 | struct QDKSwitchObject 56 | { 57 | quint8 x, y; 58 | quint16 ramPos; 59 | quint32 levelPos; 60 | bool isSprite; 61 | }; 62 | 63 | struct QDKSwitch 64 | { 65 | quint8 state; 66 | quint8 x, y; 67 | quint32 levelPos; 68 | quint16 ramPos; 69 | QList connectedTo; 70 | }; 71 | 72 | typedef QColor QGBPalette[4]; 73 | 74 | struct QDKLevel 75 | { 76 | quint8 id; // max 256 ! 77 | quint8 rombank; 78 | quint32 offset; 79 | 80 | quint8 size; // 0x00 -> 0x240 bytes for tilemap else 0x380 bytes 81 | quint8 music; 82 | quint8 tileset; 83 | quint16 time; 84 | 85 | bool switchData; 86 | bool addSpriteData; 87 | bool fullDataUpToDate; 88 | 89 | QByteArray rawTilemap; 90 | QByteArray displayTilemap; 91 | quint16 paletteIndex; 92 | 93 | QList sprites; 94 | QList switches; 95 | 96 | QByteArray rawSwitchData; // 0x11 + 0x90 bytes 97 | QByteArray rawAddSpriteData; // 0x40 bytes 98 | 99 | QByteArray fullData; 100 | }; 101 | 102 | struct QTileInfo 103 | { 104 | quint8 w, h; 105 | quint8 type; // that's a bit vague - this is the pointer+N value 106 | quint8 count; 107 | quint8 fullCount; 108 | quint8 setSpecific; 109 | quint16 additionalTilesAt; 110 | quint32 romOffset; 111 | QList needsTiles; 112 | quint8 projectileTileCount; 113 | bool compressed; 114 | }; 115 | 116 | class QDKEdit : public QTileEdit 117 | { 118 | Q_OBJECT 119 | public: 120 | explicit QDKEdit(QWidget *parent = 0); 121 | ~QDKEdit(); 122 | bool loadAllLevels(QString romFile); 123 | bool saveAllLevels(QString romFile); 124 | bool exportCurrentLevel(QString filename); 125 | bool importLevel(QString filename); 126 | QString getLevelInfo(); 127 | void fillSpriteNames(); 128 | void fillTileNames(); 129 | void setupTileSelector(QTileSelector *tileSelector, float scale, int limitTileCount); 130 | 131 | private: 132 | void paintLevel(QPainter *painter); 133 | void mouseMoveEvent(QMouseEvent *e); 134 | void mousePressEvent(QMouseEvent *e); 135 | QByteArray LZSSDecompress(QDataStream *in, quint16 decompressedSize); 136 | QByteArray LZSSCompress(QByteArray *src); 137 | bool readLevel(QFile *src, quint8 id, bool fromLvlFile = false); 138 | bool readSGBPalettes(QFile *src); 139 | bool recompressLevel(quint8 id); 140 | bool expandRawTilemap(quint8 id); 141 | bool updateRawTilemap(quint8 id); 142 | void copyTileToSet(QFile *src, quint32 offset, QImage *img, quint16 tileID, quint8 tileSetID, bool compressed, quint8 tileCount, quint16 superOffset); 143 | bool getTileInfo(QFile *src); 144 | bool createTileSets(QFile *src, QGBPalette palette); 145 | bool createSprites(QFile *src, QGBPalette palette); 146 | void sortSprite(QImage *sprite, int id); 147 | void copyTile(QImage *img, int x1, int y1, int x2, int y2, bool mirror); 148 | void fillTile(QImage *img, int x, int y, int index); 149 | void swapTiles(QImage *img, int x1, int y1, int x2, int y2); 150 | QMap spritePix; 151 | QMap spriteImg; 152 | void updateTileset(); 153 | quint8 getSpriteDefaultFlag(int id); 154 | void rebuildAddSpriteData(int id); 155 | void rebuildSwitchData(int id); 156 | quint16 vramTiles; 157 | quint16 vramSprites; 158 | 159 | QDKLevel levels[MAX_LEVEL_ID]; 160 | QImage tilesets[MAX_TILESETS]; 161 | quint8 tilesetBGP[MAX_TILESETS]; 162 | QTileInfo tiles[256]; 163 | QGBPalette sgbPal[512]; 164 | int currentLevel; 165 | 166 | quint8 currentSize; // 0x00 -> 0x240 bytes for tilemap else 0x380 bytes 167 | quint8 currentMusic; 168 | quint8 currentTileset; 169 | quint16 currentTime; 170 | quint16 currentPalIndex; 171 | 172 | bool switchMode; 173 | int switchToEdit; 174 | int swObjToMove; 175 | 176 | QList currentSwitches; 177 | QStack > undoSwitches; 178 | 179 | bool romLoaded; 180 | bool transparentSprites; 181 | static bool isSprite[256]; 182 | 183 | signals: 184 | void paletteChanged(int palette); 185 | void tilesetChanged(int set); 186 | void timeChanged(int time); 187 | void musicChanged(int music); 188 | void sizeChanged(int size); 189 | void switchAdded(QDKSwitch *sw); 190 | void switchRemoved(int i); 191 | void switchUpdated(int i, QDKSwitch *sw); 192 | void tilesVRAMchanged(int used); 193 | void spriteVRAMchanged(int used); 194 | 195 | private slots: 196 | void checkForLargeTile(int x, int y, int drawnTile); 197 | void updateSprite(int num); 198 | void undo(); 199 | void createUndoData(); 200 | void clearUndoData(); 201 | void deleteLastUndo(); 202 | bool calcVRAMusageOld(); 203 | bool calcVRAMusage(); 204 | 205 | public slots: 206 | void changeLevel(int id); 207 | void saveLevel(); 208 | void changeTime(int time); 209 | void changePalette(int palette); 210 | void changeTileset(int tileset); 211 | void changeMusic(int music); 212 | void changeSize(int size); 213 | void changeSpriteTransparency(bool transparent); 214 | void addSprite(int id); 215 | 216 | void clearLevel(); 217 | 218 | void toggleSwitchMode(bool enabled); 219 | void toggleSwitchMode(int enabled); 220 | 221 | void selectSwitch(int num); 222 | void deleteSwitchObj(int num); 223 | void deleteCurrentSwitch(); 224 | QPixmap *getTilePixmap(int num); 225 | }; 226 | 227 | #endif // QDKEDIT_H 228 | 229 | 230 | /* sprite tiles: 231 | 232 | 3a 233 | 44 234 | 47 235 | 48 236 | 4d 237 | 4e 238 | 4f 239 | 50 240 | 54 241 | 57 242 | 58 243 | 5a 244 | 5c 245 | 5e 246 | 64 247 | 6e 248 | 7a 249 | 7c 250 | 7f 251 | 80 252 | 84 253 | 86 254 | 88 255 | 8a 256 | 8e 257 | 90 258 | 92 259 | 94 260 | 96 261 | 98 262 | 9a 263 | 9d 264 | a2 265 | a4 266 | a6 267 | a8 268 | aa 269 | ac 270 | ae 271 | b0 272 | b6 273 | b8 274 | ba 275 | be 276 | c0 277 | c2 278 | c6 279 | c8 280 | ca 281 | cc 282 | 283 | from add sprite data: 284 | 54 285 | 5C 286 | 5E 287 | 6E 288 | 70 <-- new; but elevator "tiles" 289 | 72 <-- new; but elevator "tiles" 290 | 7F 291 | 80 292 | 84 293 | 98 294 | 9A 295 | B8 296 | CC 297 | */ 298 | -------------------------------------------------------------------------------- /QTileEdit.cpp: -------------------------------------------------------------------------------- 1 | #include "QTileEdit.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | #include "QTileSelector.h" 11 | 12 | QTileEdit::QTileEdit(QWidget *parent) : 13 | QWidget(parent), dataIsChanged(false), mousePressed(false), emptyTile(0), tileToDraw(emptyTile), keepAspect(true), selector(NULL), tileDataIs16bit(false), spriteMode(false), spriteContext(false), spriteToMove(-1) 14 | { 15 | //setMouseTracking(true); 16 | background = QImage(levelDimension.width()*tileSize.width(), levelDimension.height()*tileSize.height(), QImage::Format_ARGB32); 17 | background.fill(Qt::white); 18 | originalSize = QImage(0, 0, QImage::Format_RGB32); 19 | connect(this, SIGNAL(dataChanged()), this, SLOT(keepUndoData())); 20 | } 21 | 22 | void QTileEdit::getMouse(bool enable) 23 | { 24 | setMouseTracking(enable); 25 | if (selector != NULL) 26 | selector->getMouse(enable); 27 | } 28 | 29 | bool QTileEdit::isChanged() 30 | { 31 | return dataIsChanged; 32 | } 33 | 34 | void QTileEdit::updateLevel() 35 | { 36 | update(); 37 | } 38 | 39 | void QTileEdit::resized(QSize newSize) 40 | { 41 | scaledSize = orgSize; 42 | scaledSize.scale(newSize, Qt::KeepAspectRatio); 43 | scaleFactorX = (float)scaledSize.width() / (float)orgSize.width(); 44 | scaleFactorY = (float)scaledSize.height() / (float)orgSize.height(); 45 | } 46 | 47 | void QTileEdit::setLevelDimension(int width, int height) 48 | { 49 | levelDimension = QSize(width, height); 50 | orgSize = QSize(levelDimension.width()*tileSize.width(), levelDimension.height()*tileSize.height()); 51 | resized(this->size()); 52 | originalSize = QImage(orgSize, QImage::Format_RGB32); 53 | } 54 | 55 | void QTileEdit::setTileSize(int width, int height) 56 | { 57 | tileSize = QSize(width, height); 58 | orgSize = QSize(levelDimension.width()*tileSize.width(), levelDimension.height()*tileSize.height()); 59 | resized(this->size()); 60 | originalSize = QImage(orgSize, QImage::Format_RGB32); 61 | } 62 | 63 | void QTileEdit::setTileToDraw(int tileNumber) 64 | { 65 | tileToDraw = tileNumber; 66 | } 67 | 68 | bool QTileEdit::loadTileSet(QString filename, int emptyTileNumber, int count) 69 | { 70 | if (!tileSet.load(filename)) 71 | return false; 72 | 73 | emptyTile = emptyTileNumber; 74 | tileCount = count; 75 | tileSetDimension = QSize(tileSet.width() / tileSize.width(), tileSet.height() / tileSize.height()); 76 | return true; 77 | } 78 | 79 | void QTileEdit::setLevelData(QByteArray data, int start = 0, int length = 0) 80 | { 81 | lvlData = data; 82 | lvlDataStart = start; 83 | if (length == 0) 84 | lvlDataLength = data.size() - start; 85 | else 86 | lvlDataLength = length; 87 | 88 | update(); 89 | } 90 | 91 | int QTileEdit::getTile(int x, int y) 92 | { 93 | if (!lvlData.size()) 94 | return -1; 95 | 96 | return getTile(y*levelDimension.width()+x); 97 | } 98 | 99 | int QTileEdit::getTile(int offset) 100 | { 101 | if (!lvlData.size() || (offset < 0)) 102 | return -1; 103 | 104 | int tileNumber; 105 | if (tileDataIs16bit) 106 | { 107 | if (lvlDataStart+offset*2 >= lvlData.size()) 108 | return -1; 109 | tileNumber = (unsigned char)lvlData[lvlDataStart+offset*2] + (unsigned char)lvlData[lvlDataStart+2*offset+1]*0x100; 110 | } 111 | else 112 | { 113 | if (lvlDataStart+offset >= lvlData.size()) 114 | return -1; 115 | tileNumber = (unsigned char)lvlData[lvlDataStart+offset]; 116 | } 117 | return tileNumber; 118 | 119 | } 120 | 121 | void QTileEdit::setTile(int x, int y, int tileNumber) 122 | { 123 | if ((x < 0) || (y < 0)) 124 | return; 125 | 126 | setTile(y*levelDimension.width()+x, tileNumber); 127 | } 128 | 129 | void QTileEdit::setTile(int offset, int tileNumber) 130 | { 131 | if (tileDataIs16bit) 132 | { 133 | if (lvlDataStart+offset*2+1 >= lvlData.size()) 134 | return; 135 | lvlData[lvlDataStart + offset * 2] = tileNumber % 0x100; 136 | lvlData[lvlDataStart + offset * 2 + 1] = tileNumber / 0x100; 137 | } 138 | else 139 | { 140 | if (lvlDataStart+offset >= lvlData.size()) 141 | return; 142 | lvlData[lvlDataStart + offset] = (unsigned char)tileNumber; 143 | } 144 | } 145 | 146 | 147 | QString QTileEdit::spriteNumToString(int sprite) 148 | { 149 | return spriteNames.value(sprite, QString("Sprite 0x%1").arg(sprite, 2, 16, QChar('0'))); 150 | } 151 | 152 | QString QTileEdit::tileNumToString(int tile) 153 | { 154 | if (tileDataIs16bit) 155 | return tileNames.value(tile, QString("0x%1").arg(tile, 4, 16, QChar('0')))+QString(" (0x%1)").arg(tile, 4, 16, QChar('0')); 156 | else 157 | return tileNames.value(tile, QString("0x%1").arg(tile, 2, 16, QChar('0')))+QString(" (0x%1)").arg(tile, 2, 16, QChar('0'));; 158 | } 159 | 160 | QRect QTileEdit::tileNumberToQRect(int tileNumber) 161 | { 162 | int x, y, x2, y2; 163 | 164 | y = tileNumber / tileSetDimension.width(); 165 | x = tileNumber % tileSetDimension.width(); 166 | if (y > tileSetDimension.height()) 167 | return QRect(0, 0, tileSize.width(), tileSize.height()); 168 | 169 | x2 = x*tileSize.width(); 170 | y2 = y*tileSize.height(); 171 | 172 | return QRect(x2, y2, tileSize.width(), tileSize.height()); 173 | } 174 | 175 | void QTileEdit::drawTile(int tileNumber, int x, int y) 176 | { 177 | QPainter painter(this); 178 | 179 | painter.setBackgroundMode(Qt::TransparentMode); 180 | painter.drawPixmap(QRect(x*tileSize.width(), y*tileSize.height(), tileSize.width(), tileSize.height()), tileSet, tileNumberToQRect(tileNumber)); 181 | 182 | } 183 | 184 | void QTileEdit::setBackground(QImage backgroundImage) 185 | { 186 | background = backgroundImage; 187 | } 188 | 189 | void QTileEdit::paintEvent(QPaintEvent *e) 190 | { 191 | QPainter *painter = getPainter(); 192 | 193 | if (!painter) 194 | return; 195 | 196 | paintLevel(painter); 197 | 198 | finishPainter(painter); 199 | } 200 | 201 | void QTileEdit::paintLevel(QPainter *painter) 202 | { 203 | //draw background 204 | //QPainter painter(this); 205 | painter->drawImage(QRect(0, 0, orgSize.width(), orgSize.height()), background); 206 | 207 | //draw tiles 208 | painter->setBackgroundMode(Qt::TransparentMode); 209 | int tileNumber; 210 | for (int i = 0; i < levelDimension.height(); i++) 211 | for (int j = 0; j < levelDimension.width(); j++) 212 | { 213 | tileNumber = getTile(j, i); 214 | painter->drawPixmap(QRect(j*tileSize.width(), i*tileSize.height(), tileSize.width(), tileSize.height()), tileSet, tileNumberToQRect(tileNumber)); 215 | } 216 | 217 | if (spriteMode) 218 | { 219 | int x, y; 220 | 221 | // draw sprites 222 | for (int i = 0; i < sprites.size(); i++) 223 | { 224 | if (!sprites.at(i).pixelPerfect) 225 | { 226 | x = sprites.at(i).x*tileSize.width() + (sprites.at(i).drawOffset.x()*(qreal)tileSize.width()); 227 | y = sprites.at(i).y*tileSize.height() + (sprites.at(i).drawOffset.y()*(qreal)tileSize.height()); 228 | } 229 | else 230 | { 231 | x = sprites.at(i).x; 232 | y = sprites.at(i).y; 233 | } 234 | 235 | switch (sprites.at(i).rotate) 236 | { 237 | case LEFT: 238 | painter->save(); 239 | painter->translate(sprites.at(i).sprite->height(), 0); 240 | painter->rotate(90); 241 | painter->drawPixmap(y, -1*x, *sprites.at(i).sprite); 242 | painter->restore(); 243 | break; 244 | case RIGHT: 245 | painter->save(); 246 | painter->translate(0, sprites.at(i).sprite->width()); 247 | painter->rotate(-90); 248 | painter->drawPixmap(-1*y, x, *sprites.at(i).sprite); 249 | painter->restore(); 250 | break; 251 | case TOP: 252 | painter->save(); 253 | painter->translate(sprites.at(i).sprite->width(), sprites.at(i).sprite->height()); 254 | painter->rotate(180); 255 | painter->drawPixmap(-1*x, -1*y, *sprites.at(i).sprite); 256 | painter->restore(); 257 | break; 258 | case FLIPPED: 259 | painter->save(); 260 | painter->translate(sprites.at(i).sprite->width(), 0); 261 | painter->scale(-1, 1); 262 | painter->drawPixmap(-1*x, y, *sprites.at(i).sprite); 263 | painter->restore(); 264 | break; 265 | case BOTTOM: 266 | default: painter->drawPixmap(x, y, *sprites.at(i).sprite); break; 267 | } 268 | } 269 | } 270 | 271 | //draw selection 272 | painter->setPen(Qt::gray); 273 | painter->drawRect(mouseOverTile.x(), mouseOverTile.y(), mouseOverTile.width(), mouseOverTile.height()); 274 | 275 | //draw sprite selection 276 | if (spriteMode) 277 | { 278 | painter->setPen(Qt::red); 279 | painter->drawRect(spriteSelection.x(), spriteSelection.y(), spriteSelection.width(), spriteSelection.height()); 280 | } 281 | } 282 | 283 | QPainter *QTileEdit::getPainter() 284 | { 285 | QPainter *painter = new QPainter(); 286 | 287 | bool ok; 288 | if (this->size() == orgSize) 289 | ok = painter->begin(this); 290 | else 291 | ok = painter->begin(&originalSize); 292 | 293 | if (!ok) 294 | return NULL; 295 | else 296 | return painter; 297 | } 298 | 299 | void QTileEdit::finishPainter(QPainter *painter) 300 | { 301 | if (!painter) 302 | return; 303 | 304 | painter->end(); 305 | 306 | if (this->size() != orgSize) 307 | { 308 | QPainter widgetPainter(this); 309 | QRect target; 310 | if (!keepAspect) 311 | target = this->rect(); 312 | else 313 | target = QRect(0, 0, scaledSize.width(), scaledSize.height()); 314 | 315 | widgetPainter.drawImage(target, originalSize); 316 | } 317 | 318 | delete painter; 319 | } 320 | 321 | void QTileEdit::resizeEvent(QResizeEvent *e) 322 | { 323 | resized(e->size()); 324 | } 325 | 326 | void QTileEdit::mouseMoveEvent(QMouseEvent *e) 327 | { 328 | if ((e->x()+1 > scaledSize.width()) || (e->x() < 0) || (e->y()+1 > scaledSize.height()) || (e->y() < 0)) 329 | return; 330 | 331 | if (!spriteMode) 332 | { 333 | //check if selection rect has moved 334 | int xTile= (float)e->x() / (float)tileSize.width() / scaleFactorX; 335 | int yTile = (float)e->y() / (float)tileSize.height() / scaleFactorY; 336 | 337 | setToolTip(QString("%1 (%2x%3)").arg(tileNumToString(getTile(xTile, yTile))).arg(xTile).arg(yTile)); 338 | 339 | QRect newSelection(xTile * tileSize.width(), yTile * tileSize.height(), tileSize.width()-1, tileSize.height()-1); 340 | 341 | if (mouseOverTile != newSelection) 342 | { 343 | mouseOverTile = newSelection; 344 | update(); 345 | } 346 | 347 | //check whether left or right mouse button has been pressed 348 | if ((e->buttons() != Qt::LeftButton) && (e->buttons() != Qt::RightButton)) 349 | return; 350 | 351 | int tmpTileToDraw = tileToDraw; 352 | 353 | //always draw emptyTile for right click 354 | if (e->buttons() == Qt::RightButton) 355 | tmpTileToDraw = emptyTile; 356 | 357 | 358 | if (getTile(xTile, yTile) != tmpTileToDraw) 359 | { 360 | setTile(xTile, yTile, tmpTileToDraw); 361 | dataIsChanged = true; 362 | emit dataChanged(); 363 | emit singleTileChanged(xTile, yTile, tmpTileToDraw); 364 | update(); 365 | } 366 | } 367 | else 368 | { 369 | //check if mouse is over a sprite 370 | QRect spriteRect; 371 | 372 | if (!mousePressed) 373 | { 374 | spriteToMove = getSpriteAtXY((float)e->x() / scaleFactorX, (float)e->y() / scaleFactorY, &spriteRect); 375 | 376 | if (spriteToMove == -1) 377 | { 378 | mouseOverTile = QRect(); 379 | if (e->buttons() != Qt::LeftButton) 380 | { 381 | update(); 382 | return; 383 | } 384 | } 385 | } 386 | else 387 | spriteRect = getSpriteRect(spriteToMove); 388 | 389 | if (spriteToMove == -1) 390 | return; 391 | 392 | setToolTip(QString("0x%1").arg(sprites.at(spriteToMove).id, 2, 16, QChar('0'))); 393 | 394 | // sprite changed - update all selections 395 | if (mouseOverTile != spriteRect) 396 | { 397 | mouseOverTile = spriteRect; 398 | update(); 399 | } 400 | 401 | if (e->buttons() != Qt::LeftButton) 402 | return; 403 | 404 | // mouse button is pressed 405 | // update sprite selection 406 | spriteSelection = spriteRect; 407 | 408 | // sprite gets moved 409 | // calculate new x,y 410 | int newX, newY; 411 | 412 | if (sprites.at(spriteToMove).pixelPerfect) 413 | { 414 | newX = e->x(); 415 | newY = e->y(); 416 | } 417 | else 418 | { 419 | newX = (float)e->x() / (float)tileSize.width() / scaleFactorX; 420 | newY = (float)e->y() / (float)tileSize.height() / scaleFactorY; 421 | } 422 | 423 | if ((newX != sprites.at(spriteToMove).x) || (newY != sprites.at(spriteToMove).y)) 424 | { 425 | sprites[spriteToMove].x = newX; 426 | sprites[spriteToMove].y = newY; 427 | 428 | if (sprites.at(spriteToMove).pixelPerfect) 429 | spriteRect = QRect(newX, newY, sprites.at(spriteToMove).size.width(), sprites.at(spriteToMove).size.height()); 430 | else 431 | spriteRect = QRect(newX*tileSize.width(), newY*tileSize.height(), sprites.at(spriteToMove).size.width()*tileSize.width(), sprites.at(spriteToMove).size.height()*tileSize.height()); 432 | 433 | mouseOverTile = spriteRect; 434 | spriteSelection = spriteRect; 435 | dataIsChanged = true; 436 | 437 | emit dataChanged(); 438 | update(); 439 | } 440 | } 441 | } 442 | 443 | void QTileEdit::mousePressEvent(QMouseEvent *e) 444 | { 445 | if (e->type() == QEvent::MouseButtonPress) 446 | { 447 | createUndoData(); 448 | mousePressed = true; 449 | } 450 | 451 | if (!spriteMode) 452 | mouseMoveEvent(e); 453 | else 454 | { 455 | if ((e->button() == Qt::LeftButton) || (e->button() == Qt::RightButton)) 456 | { 457 | if (spriteSelection != mouseOverTile) 458 | { 459 | spriteSelection = mouseOverTile; 460 | selectedSprite = spriteToMove; 461 | emit spriteSelected(selectedSprite); 462 | update(); 463 | } 464 | } 465 | if (e->button() == Qt::RightButton) 466 | { 467 | if (!spriteContext) 468 | { 469 | if (spriteToMove == -1) 470 | return; 471 | sprites.remove(spriteToMove); 472 | mouseOverTile = QRect(); 473 | spriteSelection = QRect(); 474 | dataIsChanged = true; 475 | emit spriteSelected(-1); 476 | emit spriteRemoved(spriteToMove); 477 | emit dataChanged(); 478 | spriteToMove = -1; 479 | selectedSprite = -1; 480 | update(); 481 | return; 482 | } 483 | else 484 | { 485 | emit customContextMenuRequested(e->pos()); 486 | mouseOverTile = QRect(); 487 | mousePressed = false; 488 | spriteToMove = -1; 489 | update(); 490 | return; 491 | } 492 | } 493 | } 494 | } 495 | 496 | int QTileEdit::getSpriteAtXY(int x, int y, QRect *spriteRect = NULL) 497 | { 498 | int i; 499 | int sx1, sx2, sy1, sy2; 500 | 501 | for (i = 0; i < sprites.size(); i++) 502 | { 503 | if (sprites.at(i).pixelPerfect) 504 | { 505 | sx1 = sprites.at(i).x; 506 | sy1 = sprites.at(i).y; 507 | if ((sprites.at(i).rotate == LEFT) || (sprites.at(i).rotate == RIGHT)) 508 | { 509 | sx2 = sprites.at(i).x + sprites.at(i).size.width(); 510 | sy2 = sprites.at(i).x + sprites.at(i).size.width(); 511 | } 512 | else 513 | { 514 | sx2 = sprites.at(i).x + sprites.at(i).size.height(); 515 | sy2 = sprites.at(i).x + sprites.at(i).size.height(); 516 | } 517 | } 518 | else 519 | { 520 | sx1 = sprites.at(i).x * tileSize.width() + (sprites.at(i).drawOffset.x()*(qreal)tileSize.width()); 521 | sy1 = sprites.at(i).y * tileSize.height() + (sprites.at(i).drawOffset.y()*(qreal)tileSize.height()); 522 | 523 | if ((sprites.at(i).rotate == LEFT) || (sprites.at(i).rotate == RIGHT)) 524 | { 525 | sx2 = (sprites.at(i).x + sprites.at(i).size.height()) * tileSize.width() + (sprites.at(i).drawOffset.x()*(qreal)tileSize.width()); 526 | sy2 = (sprites.at(i).y + sprites.at(i).size.width()) * tileSize.height() + (sprites.at(i).drawOffset.y()*(qreal)tileSize.height()); 527 | } 528 | else 529 | { 530 | sx2 = (sprites.at(i).x + sprites.at(i).size.width()) * tileSize.width() + (sprites.at(i).drawOffset.x()*(qreal)tileSize.width()); 531 | sy2 = (sprites.at(i).y + sprites.at(i).size.height()) * tileSize.height() + (sprites.at(i).drawOffset.y()*(qreal)tileSize.height()); 532 | } 533 | } 534 | 535 | if ((x >= sx1) && (x < sx2) && (y >= sy1) && (y < sy2)) 536 | break; 537 | } 538 | 539 | if (i == sprites.size()) 540 | return -1; 541 | else 542 | { 543 | if (spriteRect) 544 | *spriteRect = QRect(sx1, sy1, sx2-sx1-1, sy2-sy1-1); 545 | return i; 546 | } 547 | } 548 | 549 | void QTileEdit::mouseReleaseEvent(QMouseEvent *) 550 | { 551 | mousePressed = false; 552 | 553 | if (!keepUndo) 554 | deleteLastUndo(); 555 | } 556 | 557 | int QTileEdit::getSelectedSprite(int *id) 558 | { 559 | if (selectedSprite != -1) 560 | *id = sprites.at(selectedSprite).id; 561 | else 562 | *id = 0x00; 563 | 564 | return selectedSprite; 565 | } 566 | 567 | 568 | QRect QTileEdit::getSpriteRect(int num) 569 | { 570 | int sx1, sx2, sy1, sy2; 571 | 572 | if (num >= sprites.size()) 573 | return QRect(); 574 | 575 | if (sprites.at(num).pixelPerfect) 576 | { 577 | sx1 = sprites.at(num).x; 578 | sy1 = sprites.at(num).y; 579 | if ((sprites.at(num).rotate == LEFT) || (sprites.at(num).rotate == RIGHT)) 580 | { 581 | sx2 = sprites.at(num).x + sprites.at(num).size.height(); 582 | sy2 = sprites.at(num).x + sprites.at(num).size.width(); 583 | } 584 | else 585 | { 586 | sx2 = sprites.at(num).x + sprites.at(num).size.width(); 587 | sy2 = sprites.at(num).x + sprites.at(num).size.height(); 588 | } 589 | } 590 | else 591 | { 592 | sx1 = sprites.at(num).x * tileSize.width() + (sprites.at(num).drawOffset.x()*(qreal)tileSize.width()); 593 | sy1 = sprites.at(num).y * tileSize.height() + (sprites.at(num).drawOffset.y()*(qreal)tileSize.height()); 594 | if ((sprites.at(num).rotate == LEFT) || (sprites.at(num).rotate == RIGHT)) 595 | { 596 | sx2 = (sprites.at(num).x + sprites.at(num).size.height()) * tileSize.width() + (sprites.at(num).drawOffset.x()*(qreal)tileSize.width()); 597 | sy2 = (sprites.at(num).y + sprites.at(num).size.width()) * tileSize.height() + (sprites.at(num).drawOffset.y()*(qreal)tileSize.height()); 598 | } 599 | else 600 | { 601 | sx2 = (sprites.at(num).x + sprites.at(num).size.width()) * tileSize.width() + (sprites.at(num).drawOffset.x()*(qreal)tileSize.width()); 602 | sy2 = (sprites.at(num).y + sprites.at(num).size.height()) * tileSize.height() + (sprites.at(num).drawOffset.y()*(qreal)tileSize.height()); 603 | } 604 | 605 | } 606 | 607 | return QRect(sx1, sy1, sx2-sx1-1, sy2-sy1-1); 608 | } 609 | 610 | void QTileEdit::setupTileSelector(QTileSelector *tileSelector, float scale, int limitTileCount) 611 | { 612 | selector = tileSelector; 613 | QFile names("tiles.txt"); 614 | QStringList tileNames; 615 | if (names.exists()) 616 | { 617 | if (names.open(QIODevice::ReadOnly)) 618 | { 619 | QTextStream namesStream(&names); 620 | while (!namesStream.atEnd()) 621 | { 622 | tileNames.append(namesStream.readLine()); 623 | } 624 | } 625 | } 626 | else 627 | for (int i = 0; i < limitTileCount; i++) 628 | tileNames.append(tileNumToString(i)); 629 | 630 | int count; 631 | 632 | if (limitTileCount != 0) 633 | count = limitTileCount; 634 | else 635 | count = tileCount; 636 | 637 | selector->setTilePixmap(tileSet, tileSize, scale, count, tileNames); 638 | if (hasMouseTracking()) 639 | selector->getMouse(true); 640 | connect(selector, SIGNAL(tileSelected(int)), this, SLOT(setTileToDraw(int))); 641 | } 642 | 643 | void QTileEdit::toggleSpriteMode(bool enabled) 644 | { 645 | if (enabled != spriteMode) 646 | { 647 | spriteMode = enabled; 648 | update(); 649 | } 650 | } 651 | 652 | void QTileEdit::deleteSprite(int num) 653 | { 654 | if (num >= sprites.size()) 655 | return; 656 | 657 | sprites.remove(num); 658 | 659 | mouseOverTile = QRect(); 660 | spriteSelection = QRect(); 661 | dataIsChanged = true; 662 | emit spriteSelected(-1); 663 | emit spriteRemoved(num); 664 | emit dataChanged(); 665 | spriteToMove = -1; 666 | selectedSprite = -1; 667 | update(); 668 | } 669 | 670 | void QTileEdit::toggleSpriteMode(int enabled) 671 | { 672 | toggleSpriteMode((bool)enabled); 673 | } 674 | 675 | void QTileEdit::getSpriteFlag(int num, quint8 *flag) 676 | { 677 | if (num < sprites.size()) 678 | *flag = sprites.at(num).flagByte; 679 | } 680 | 681 | void QTileEdit::setSpriteFlag(int num, quint8 flag) 682 | { 683 | if (num < sprites.size()) 684 | sprites[num].flagByte = flag; 685 | 686 | emit flagByteChanged(num); 687 | } 688 | 689 | void QTileEdit::keepUndoData() 690 | { 691 | keepUndo = true; 692 | } 693 | 694 | void QTileEdit::createUndoData() 695 | { 696 | QByteArray undoBytes; 697 | QVector undoSprites; 698 | 699 | keepUndo = false; 700 | 701 | undoBytes.clear(); 702 | undoBytes.append(lvlData); 703 | 704 | for (int i = 0; i < sprites.size(); i++) 705 | undoSprites.append(sprites.at(i)); 706 | 707 | undoStack.push(qMakePair(undoBytes, undoSprites)); 708 | } 709 | 710 | void QTileEdit::undo() 711 | { 712 | if (undoStack.isEmpty()) 713 | return; 714 | 715 | QVector undoSprites; 716 | QPair > undoData; 717 | 718 | spriteToMove = -1; 719 | 720 | undoData = undoStack.pop(); 721 | 722 | lvlData.clear(); 723 | lvlData.append(undoData.first); 724 | 725 | for (int i = sprites.size()-1; i >= 0; i--) 726 | emit spriteRemoved(i); 727 | 728 | sprites.clear(); 729 | 730 | undoSprites = undoData.second; 731 | for (int i = 0; i < undoSprites.size(); i++) 732 | { 733 | sprites.append(undoSprites.at(i)); 734 | emit spriteAdded(spriteNumToString(undoSprites.at(i).id), undoSprites.at(i).id); 735 | } 736 | undoSprites.clear(); 737 | 738 | spriteSelection = QRect(); 739 | 740 | update(); 741 | } 742 | 743 | void QTileEdit::clearUndoData() 744 | { 745 | QPair > undoData; 746 | 747 | while (!undoStack.isEmpty()) 748 | { 749 | undoData = undoStack.pop(); 750 | undoData.first.clear(); 751 | undoData.second.clear(); 752 | } 753 | } 754 | 755 | void QTileEdit::deleteLastUndo() 756 | { 757 | if (!undoStack.isEmpty()) 758 | { 759 | QPair > undoData = undoStack.pop(); 760 | undoData.first.clear(); 761 | undoData.second.clear(); 762 | } 763 | } 764 | void QTileEdit::clearLevel() 765 | { 766 | createUndoData(); 767 | 768 | if (tileDataIs16bit) 769 | { 770 | for (int i = 0; i < lvlData.size(); i+=2) 771 | { 772 | lvlData[i] = (quint8)(emptyTile % 0x100); 773 | lvlData[i+1] = (quint8)(emptyTile / 0x100); 774 | } 775 | } 776 | else 777 | lvlData.fill(emptyTile); 778 | 779 | for (int i = sprites.size()-1; i >= 0; i--) 780 | emit spriteRemoved(i); 781 | 782 | sprites.clear(); 783 | spriteSelection = QRect(); 784 | spriteToMove = -1; 785 | 786 | dataIsChanged = true; 787 | emit dataChanged(); 788 | 789 | update(); 790 | } 791 | -------------------------------------------------------------------------------- /QTileEdit.h: -------------------------------------------------------------------------------- 1 | #ifndef QTILEEDIT_H 2 | #define QTILEEDIT_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | class QTileSelector; 9 | 10 | enum { BOTTOM = 0, FLIPPED = 1, TOP = 2, LEFT = 3, RIGHT = 4 }; 11 | 12 | struct QSprite 13 | { 14 | bool pixelPerfect; 15 | int x, y; 16 | QSize size; 17 | QPointF drawOffset; 18 | QPixmap *sprite; 19 | int rotate; 20 | int id; 21 | quint8 flagByte; 22 | }; 23 | 24 | class QTileEdit : public QWidget 25 | { 26 | Q_OBJECT 27 | public: 28 | explicit QTileEdit(QWidget *parent = 0); 29 | 30 | bool isChanged(); 31 | bool loadTileSet(QString filename, int emptyTileNumber, int count); 32 | 33 | void setLevelDimension(int width, int height); 34 | void setTileSize(int width, int height); 35 | void setLevelData(QByteArray data, int start, int length); 36 | void setBackground(QImage backgroundImage); 37 | int getSelectedSprite(int *id); 38 | 39 | QString spriteNumToString(int sprite); 40 | QString tileNumToString(int tile); 41 | 42 | void getMouse(bool enable); 43 | 44 | void setupTileSelector(QTileSelector *tileSelector, float scale = 1.25f, int limitTileCount = 0); 45 | 46 | protected: 47 | void paintEvent(QPaintEvent *e); 48 | virtual void paintLevel(QPainter *painter); 49 | void mouseMoveEvent(QMouseEvent *e); 50 | void mousePressEvent(QMouseEvent *e); 51 | void mouseReleaseEvent(QMouseEvent *); 52 | void resizeEvent(QResizeEvent *e); 53 | QPainter *getPainter(); 54 | void finishPainter(QPainter *painter); 55 | 56 | void resized(QSize newSize); 57 | void drawTile(int tileNumber, int x, int y); 58 | QRect tileNumberToQRect(int tileNumber); 59 | int getSpriteAtXY(int x, int y, QRect *spriteRect); 60 | QRect getSpriteRect(int num); 61 | int getTile(int x, int y); 62 | int getTile(int offset); 63 | void setTile(int x, int y, int tileNumber); 64 | void setTile(int offset, int tileNumber); 65 | 66 | QMap spriteNames; 67 | QMap tileNames; 68 | 69 | QStack > > undoStack; 70 | bool keepUndo; 71 | virtual void createUndoData(); 72 | virtual void clearUndoData(); 73 | virtual void deleteLastUndo(); 74 | 75 | QVector sprites; 76 | QByteArray lvlData; 77 | int lvlDataStart; 78 | int lvlDataLength; 79 | int emptyTile; 80 | int tileToDraw; 81 | int tileCount; 82 | int spriteToMove; 83 | int selectedSprite; 84 | int mousePressed; 85 | float scaleFactorX; 86 | float scaleFactorY; 87 | bool dataIsChanged; 88 | bool tileDataIs16bit; 89 | bool spriteContext; 90 | bool keepAspect; 91 | bool spriteMode; 92 | QRect mouseOverTile; 93 | QRect spriteSelection; 94 | QSize orgSize; 95 | QSize scaledSize; 96 | QSize levelDimension; 97 | QSize tileSetDimension; 98 | QSize tileSize; 99 | QPixmap tileSet; 100 | QImage background; 101 | QImage tiledLevel; 102 | QImage originalSize; 103 | QTileSelector *selector; 104 | 105 | signals: 106 | void dataChanged(); 107 | void singleTileChanged(int x, int y, int drawnTile); 108 | void spriteSelected(int spriteNo); 109 | void spriteAdded(QString text, int id); 110 | void spriteRemoved(int index); 111 | void flagByteChanged(int num); 112 | 113 | public slots: 114 | void updateLevel(); 115 | void keepUndoData(); 116 | virtual void clearLevel(); 117 | void setTileToDraw(int tileNumber); 118 | void toggleSpriteMode(bool enabled); 119 | void toggleSpriteMode(int enabled); 120 | void getSpriteFlag(int num, quint8 *flag); 121 | void setSpriteFlag(int num, quint8 flag); 122 | void deleteSprite(int num); 123 | virtual void undo(); 124 | }; 125 | 126 | #endif // QTILEEDIT_H 127 | -------------------------------------------------------------------------------- /QTileSelector.cpp: -------------------------------------------------------------------------------- 1 | #include "QTileSelector.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | QTileSelector::QTileSelector(QWidget *parent) : 10 | QWidget(parent), pixSrc(NULL) 11 | { 12 | arrangedTiles = QImage(this->rect().width(), this->rect().height(), QImage::Format_ARGB32); 13 | QPainter painter; 14 | painter.begin(&arrangedTiles); 15 | painter.fillRect(this->rect(), Qt::white); 16 | painter.end(); 17 | mouseOverTile = QRect(0, 0, 0, 0); 18 | //setMouseTracking(true); 19 | } 20 | 21 | void QTileSelector::changeTilePixmap(QPixmap tilePixmap) 22 | { 23 | tiles = tilePixmap; 24 | updateImage(); 25 | } 26 | 27 | void QTileSelector::setTilePixmap(QPixmap tilePixmap, QSize size, float scale, int count, QStringList names) 28 | { 29 | tiles = tilePixmap; 30 | orgTileSize = size; 31 | tileCount = count; 32 | tileNames = names; 33 | scaleFactor = scale; 34 | tileSize = orgTileSize * scale; 35 | 36 | updateImage(); 37 | } 38 | 39 | bool QTileSelector::event(QEvent *e) 40 | { 41 | if (e->type() == QEvent::ToolTip) 42 | { 43 | if (groups.isEmpty()) 44 | { 45 | QHelpEvent *he = static_cast(e); 46 | int xTile= he->x() / tileSize.width(); 47 | int yTile = he->y() / tileSize.height(); 48 | 49 | if ((xTile >= arrangedTilesDimension.width()) || (yTile >= arrangedTilesDimension.height())) 50 | { 51 | setToolTip(""); 52 | return QWidget::event(e); 53 | } 54 | 55 | int num = yTile*imageDimension.width()+xTile; 56 | if (num < tileCount) 57 | setToolTip(tileNames.value(num, QString("TileNumber: 0x%1").arg(num, 0, 16))); 58 | else 59 | setToolTip(""); 60 | } 61 | else 62 | { 63 | QHelpEvent *he = static_cast(e); 64 | if ((he->x() >= allTilesRect.width()) || (he->y() >= allTilesRect.height())) 65 | { 66 | setToolTip(""); 67 | return QWidget::event(e); 68 | } 69 | 70 | int tile = -1; 71 | QRect tileRect; 72 | QMap::const_iterator it = tileRects.constBegin(); 73 | while (it != tileRects.constEnd()) 74 | { 75 | tileRect = it.value(); 76 | 77 | if ((he->x() >= tileRect.x()) && (he->y() >= tileRect.y()) && 78 | (he->x() < tileRect.x() + tileRect.width()) && (he->y() < tileRect.y() + tileRect.height())) 79 | { 80 | tile = it.key(); 81 | break; 82 | } 83 | 84 | ++it; 85 | } 86 | 87 | if (tile != -1) 88 | setToolTip(tileNames.value(tile, QString("TileNumber: 0x%1").arg(tile, 0, 16))); 89 | else 90 | setToolTip(""); 91 | } 92 | } 93 | 94 | return QWidget::event(e); 95 | } 96 | 97 | void QTileSelector::groupTiles(QTileGroups grouping, QSpacings spacings) 98 | { 99 | groups = grouping; 100 | space = spacings; 101 | updateImage(); 102 | } 103 | 104 | void QTileSelector::setTilePixSrc(QDKEdit *src) 105 | { 106 | pixSrc = src; 107 | updateImage(); 108 | } 109 | 110 | void QTileSelector::updateImage() 111 | { 112 | int pixmapX, pixmapY; 113 | pixmapX = tiles.width() / orgTileSize.width(); 114 | pixmapY = tiles.height() / orgTileSize.height(); 115 | 116 | imageDimension.setWidth(this->rect().width() / tileSize.width()); 117 | imageDimension.setHeight(this->rect().height() / tileSize.height()); 118 | 119 | if (groups.isEmpty()) 120 | allTilesRect = QRect(0, 0, imageDimension.width()*tileSize.width(), ((tileCount / imageDimension.width()) + 1)*tileSize.height()); 121 | else 122 | allTilesRect = this->rect(); 123 | 124 | arrangedTilesDimension.setWidth(allTilesRect.width() / tileSize.width()); 125 | arrangedTilesDimension.setHeight(allTilesRect.height() / tileSize.height()); 126 | 127 | int x, x2, y, y2; 128 | QPainter painter; 129 | 130 | arrangedTiles = QImage(this->rect().width(), this->rect().height(), QImage::Format_ARGB32); 131 | arrangedTiles.fill(Qt::white); 132 | painter.begin(&arrangedTiles); 133 | painter.fillRect(allTilesRect, Qt::white); 134 | 135 | if (groups.empty()) 136 | { 137 | for (int i = 0; i < tileCount; i++) 138 | { 139 | x = (i % imageDimension.width()) * tileSize.width(); 140 | y = (i / imageDimension.width()) * tileSize.height(); 141 | x2 = (i % pixmapX) * orgTileSize.width(); 142 | y2 = (i / pixmapX) * orgTileSize.height(); 143 | QRect target(x, y, tileSize.width(), tileSize.height()); 144 | QRect source(x2, y2, orgTileSize.width(), orgTileSize.height()); 145 | painter.drawPixmap(target, tiles, source); 146 | } 147 | } 148 | 149 | else 150 | { 151 | QVector tilelist; 152 | int vPos, hPos, x, y; 153 | QString title; 154 | QPixmap *pix; 155 | 156 | vPos = space.top; 157 | hPos = space.left; 158 | 159 | tileRects.clear(); 160 | 161 | QTileGroups::const_iterator it = groups.constBegin(); 162 | while (it != groups.constEnd()) 163 | { 164 | title = it.key(); 165 | tilelist = it.value(); 166 | ++it; 167 | 168 | hPos = space.left; 169 | painter.drawText(QRect(hPos, vPos, allTilesRect.width(), tileSize.height()), Qt::AlignVCenter, title); 170 | vPos += tileSize.height() + space.afterText; 171 | 172 | for (int i = 0; i < tilelist.size(); i++) 173 | { 174 | x = (tilelist[i] % pixmapX) * orgTileSize.width(); 175 | y = (tilelist[i] / pixmapX) * orgTileSize.height(); 176 | if (pixSrc) 177 | pix = pixSrc->getTilePixmap(tilelist[i]); 178 | 179 | else 180 | { 181 | pix = new QPixmap(tileSize); 182 | QPainter p; 183 | p.begin(pix); 184 | x2 = (tilelist[i] % pixmapX) * orgTileSize.width(); 185 | y2 = (tilelist[i] / pixmapX) * orgTileSize.height(); 186 | QRect source(x2, y2, orgTileSize.width(), orgTileSize.height()); 187 | p.drawPixmap(pix->rect(), tiles, source); 188 | p.end(); 189 | } 190 | QSize pixSize = pix->size(); 191 | pixSize.scale(tileSize, Qt::KeepAspectRatio); 192 | QRect target(hPos+(tileSize.width() - pixSize.width())/2, vPos+(tileSize.height() - pixSize.height())/2, pixSize.width(), pixSize.height()); 193 | tileRects.insert(tilelist[i], QRect(hPos, vPos, tileSize.width(), tileSize.height())); 194 | painter.drawPixmap(target, *pix); 195 | delete pix; 196 | 197 | hPos += tileSize.width() + space.hSpace; 198 | if (hPos + tileSize.width() + space.right > arrangedTiles.width()) 199 | { 200 | hPos = space.left; 201 | vPos += tileSize.height() + space.vSpace; 202 | } 203 | } 204 | if (hPos == space.left) 205 | vPos -= tileSize.height() + space.vSpace; 206 | vPos += tileSize.height() + space.beforeText; 207 | } 208 | 209 | } 210 | 211 | painter.end(); 212 | update(); 213 | } 214 | 215 | void QTileSelector::paintEvent(QPaintEvent *) 216 | { 217 | QPainter painter(this); 218 | painter.drawImage(QPoint(0, 0), arrangedTiles); 219 | 220 | //draw selection 221 | painter.setPen(Qt::black); 222 | painter.drawRect(mouseOverTile.x(), mouseOverTile.y(), mouseOverTile.width(), mouseOverTile.height()); 223 | 224 | //draw selected tile 225 | painter.setPen(Qt::red); 226 | painter.drawRect(selectedTile.x(), selectedTile.y(), selectedTile.width(), selectedTile.height()); 227 | } 228 | 229 | void QTileSelector::mouseMoveEvent(QMouseEvent *e) 230 | { 231 | if (groups.isEmpty()) 232 | { 233 | //check if selection rect has moved 234 | int xTile= e->x() / tileSize.width(); 235 | int yTile = e->y() / tileSize.height(); 236 | 237 | QRect newSelection; 238 | 239 | if ((xTile >= arrangedTilesDimension.width()) || (yTile >= arrangedTilesDimension.height())) 240 | newSelection = QRect(0, 0, 0, 0); 241 | else 242 | newSelection = QRect(xTile * tileSize.width(), yTile * tileSize.height(), tileSize.width()-1, tileSize.height()-1); 243 | 244 | if (mouseOverTile != newSelection) 245 | { 246 | mouseOverTile = newSelection; 247 | update(); 248 | } 249 | 250 | 251 | //check whether left or right mouse button has been pressed 252 | if (e->buttons() != Qt::LeftButton) 253 | return; 254 | 255 | if (newSelection != selectedTile) 256 | { 257 | int newTileNumber = xTile + (yTile * imageDimension.width()); 258 | if (newTileNumber >= tileCount) 259 | return; 260 | 261 | selectedTile = newSelection; 262 | selectedTileNumber = newTileNumber; 263 | update(); 264 | emit tileSelected(selectedTileNumber); 265 | } 266 | } 267 | else 268 | { 269 | QRect newSelection; 270 | int tile = -1; 271 | QRect tileRect; 272 | //check if mouse is inside rect 273 | if ((e->x() >= allTilesRect.width()) || (e->y() >= allTilesRect.height())) 274 | newSelection = QRect(0, 0, 0, 0); 275 | else 276 | { 277 | QMap::const_iterator it = tileRects.constBegin(); 278 | while (it != tileRects.constEnd()) 279 | { 280 | tileRect = it.value(); 281 | 282 | if ((e->x() >= tileRect.x()) && (e->y() >= tileRect.y()) && 283 | (e->x() < tileRect.x() + tileRect.width()) && (e->y() < tileRect.y() + tileRect.height())) 284 | { 285 | tile = it.key(); 286 | break; 287 | } 288 | 289 | ++it; 290 | } 291 | 292 | if (tile != -1) 293 | newSelection = tileRect; 294 | } 295 | 296 | if (mouseOverTile != newSelection) 297 | { 298 | mouseOverTile = newSelection; 299 | update(); 300 | } 301 | 302 | //check whether left or right mouse button has been pressed 303 | if (e->buttons() != Qt::LeftButton) 304 | return; 305 | 306 | if (newSelection != selectedTile) 307 | { 308 | if ((tile >= tileCount) || (tile == -1)) 309 | return; 310 | 311 | selectedTile = newSelection; 312 | selectedTileNumber = tile; 313 | update(); 314 | 315 | emit tileSelected(selectedTileNumber); 316 | } 317 | } 318 | } 319 | 320 | void QTileSelector::getMouse(bool enable) 321 | { 322 | setMouseTracking(enable); 323 | } 324 | 325 | void QTileSelector::mousePressEvent(QMouseEvent *e) 326 | { 327 | mouseMoveEvent(e); 328 | } 329 | 330 | void QTileSelector::resizeEvent(QResizeEvent *e) 331 | { 332 | if (e->oldSize() != QSize(-1, -1)) 333 | updateImage(); 334 | } 335 | -------------------------------------------------------------------------------- /QTileSelector.h: -------------------------------------------------------------------------------- 1 | #ifndef QTILESELECTOR_H 2 | #define QTILESELECTOR_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | class QDKEdit; 9 | 10 | typedef QColor QGBPalette[4]; 11 | 12 | typedef QMap > QTileGroups; 13 | 14 | struct QSpacings 15 | { 16 | int top; 17 | int bottom; 18 | int left; 19 | int right; 20 | int vSpace; 21 | int hSpace; 22 | int beforeText; 23 | int afterText; 24 | }; 25 | 26 | class QTileSelector : public QWidget 27 | { 28 | Q_OBJECT 29 | public: 30 | explicit QTileSelector(QWidget *parent = 0); 31 | void getMouse(bool enable); 32 | 33 | 34 | private: 35 | QSize tileSize; 36 | QSize orgTileSize; 37 | QSize imageDimension; 38 | QSize arrangedTilesDimension; 39 | QPixmap tiles; 40 | QImage arrangedTiles; 41 | QRect selectedTile; 42 | QRect mouseOverTile; 43 | QRect allTilesRect; 44 | QStringList tileNames; 45 | int tileCount; 46 | int selectedTileNumber; 47 | void updateImage(); 48 | float scaleFactor; 49 | QTileGroups groups; 50 | QSpacings space; 51 | QMap tileRects; 52 | QDKEdit *pixSrc; 53 | 54 | protected: 55 | void paintEvent(QPaintEvent *); 56 | void mouseMoveEvent(QMouseEvent *e); 57 | void mousePressEvent(QMouseEvent *e); 58 | void resizeEvent(QResizeEvent *e); 59 | bool event(QEvent *e); 60 | 61 | 62 | signals: 63 | void tileSelected(int tileNumber); 64 | 65 | public slots: 66 | void setTilePixmap(QPixmap tilePixmap, QSize size, float scale, int count, QStringList names); 67 | void changeTilePixmap(QPixmap tilePixmap); 68 | void groupTiles(QTileGroups grouping, QSpacings spacings); 69 | void setTilePixSrc(QDKEdit *src); 70 | }; 71 | 72 | #endif // QTILESELECTOR_H 73 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | eDKit 2 | ===== 3 | 4 | Level editor for Donkey Kong '94 for Classic Gameboy 5 | 6 | ===== 7 | 8 | Just run qmake && make. This has only been tested on Linux (Kubuntu 12.04 x64) 9 | 10 | The application expects "base.gb" in its directory. 11 | -------------------------------------------------------------------------------- /eDKit.pro: -------------------------------------------------------------------------------- 1 | #------------------------------------------------- 2 | # 3 | # Project created by QtCreator 2014-01-31T20:08:43 4 | # 5 | #------------------------------------------------- 6 | 7 | QT += core gui 8 | 9 | greaterThan(QT_MAJOR_VERSION, 4): QT += widgets 10 | 11 | TARGET = eDKit 12 | TEMPLATE = app 13 | 14 | 15 | SOURCES += main.cpp\ 16 | MainWindow.cpp\ 17 | QTileEdit.cpp\ 18 | QTileSelector.cpp\ 19 | QDKEdit.cpp 20 | 21 | HEADERS += MainWindow.h\ 22 | QTileEdit.h\ 23 | QTileSelector.h\ 24 | QDKEdit.h 25 | 26 | FORMS += MainWindow.ui 27 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "MainWindow.h" 5 | 6 | int main(int argc, char *argv[]) 7 | { 8 | QApplication a(argc, argv); 9 | 10 | if (!QFile::exists(qApp->applicationDirPath() + "/" BASE_ROM)) 11 | { 12 | QMessageBox::warning(NULL, "Error", "You need a base.gb in the editor's directory!"); 13 | return 1; 14 | } 15 | 16 | MainWindow w; 17 | w.show(); 18 | 19 | return a.exec(); 20 | } 21 | -------------------------------------------------------------------------------- /win32/eDKit.rar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bailli/eDKit/63d3d3d48d4de75bc0b4c5e6da9b9f9d74c08251/win32/eDKit.rar --------------------------------------------------------------------------------