├── .gitattributes ├── .gitignore ├── CHANGES.txt ├── COPYING.txt ├── KDCEditor.pro ├── README.md ├── docs ├── images │ ├── 7-1-1layer.png │ ├── 7-1-2layers.png │ ├── background.png │ ├── diag-1layer.png │ ├── diag-2layers.png │ ├── edit.png │ ├── mainwindow.png │ ├── preview.png │ └── properties.png ├── index.htm └── style.css ├── kirbybowl-notes.txt ├── samples ├── KirbysRevCourseSampleSelects.kdc ├── carbowl.kdc └── demo-6holes.kdc └── src ├── TODO.txt ├── compress.c ├── compress.h ├── coursefiles.txt ├── coursewindow.cpp ├── coursewindow.h ├── coursewindow.ui ├── graphics.h ├── icons.qrc ├── images.qrc ├── images ├── 3dtiles-water.png ├── 3dtiles.png ├── bounce.png ├── bumpers.pdn ├── bumpers.png ├── conveyor.pdn ├── conveyor.png ├── dedede.pdn ├── dedede.png ├── enemies.pdn ├── enemies.png ├── gordo.pdn ├── gordo.png ├── gordo3d.png ├── grid.png ├── icons │ ├── arrow_in.png │ ├── arrow_redo.png │ ├── arrow_undo.png │ ├── brick_edit.png │ ├── cut.png │ ├── delete.png │ ├── disk.png │ ├── door_in.png │ ├── folder.png │ ├── help.png │ ├── image.png │ ├── magnifier.png │ ├── page_copy.png │ ├── paste_plain.png │ ├── resultset_first.png │ ├── resultset_last.png │ ├── resultset_next.png │ ├── resultset_previous.png │ └── wand.png ├── kirby.png ├── main.icns ├── main.ico ├── main16.png ├── main32.png ├── main48.png ├── main64.png ├── mainicon.pdn ├── movers.pdn ├── movers.png ├── rotate.pdn ├── rotate.png ├── switches.png ├── terrain.pdn ├── terrain.png ├── tiletemplate.png ├── traps.pdn ├── traps.png ├── unknown.png ├── warps.pdn ├── warps.png ├── water.pdn └── water.png ├── kirby.cpp ├── kirby.h ├── level.cpp ├── level.h ├── main.cpp ├── mainwindow.cpp ├── mainwindow.h ├── mainwindow.ui ├── mapchange.cpp ├── mapchange.h ├── mapscene.cpp ├── mapscene.h ├── metatile.cpp ├── metatile.h ├── metatile_borders.cpp ├── metatile_obstacles.cpp ├── metatile_terrain.cpp ├── previewscene.cpp ├── previewscene.h ├── previewwindow.cpp ├── previewwindow.h ├── previewwindow.ui ├── propertieswindow.cpp ├── propertieswindow.h ├── propertieswindow.ui ├── romfile.cpp ├── romfile.h ├── tileeditwindow.cpp ├── tileeditwindow.h ├── tileeditwindow.ui ├── uthash.h ├── version.h └── windows.rc /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | debug/ 2 | release/ 3 | Makefile 4 | Makefile.Release 5 | Makefile.Debug 6 | object_script.* 7 | *.pro.user* 8 | *.ini 9 | ui_*.h 10 | *.sfc 11 | currentlevel.txt 12 | *.autosave 13 | 14 | ################# 15 | ## Eclipse 16 | ################# 17 | 18 | *.pydevproject 19 | .project 20 | .metadata 21 | bin/ 22 | tmp/ 23 | *.tmp 24 | *.bak 25 | *.swp 26 | *~.nib 27 | local.properties 28 | .classpath 29 | .settings/ 30 | .loadpath 31 | 32 | # External tool builders 33 | .externalToolBuilders/ 34 | 35 | # Locally stored "Eclipse launch configurations" 36 | *.launch 37 | 38 | # CDT-specific 39 | .cproject 40 | 41 | # PDT-specific 42 | .buildpath 43 | 44 | 45 | ################# 46 | ## Visual Studio 47 | ################# 48 | 49 | ## Ignore Visual Studio temporary files, build results, and 50 | ## files generated by popular Visual Studio add-ons. 51 | 52 | # User-specific files 53 | *.suo 54 | *.user 55 | *.sln.docstates 56 | 57 | # Build results 58 | 59 | [Dd]ebug/ 60 | [Rr]elease/ 61 | x64/ 62 | build/ 63 | [Bb]in/ 64 | [Oo]bj/ 65 | 66 | # MSTest test Results 67 | [Tt]est[Rr]esult*/ 68 | [Bb]uild[Ll]og.* 69 | 70 | *_i.c 71 | *_p.c 72 | *.ilk 73 | *.meta 74 | *.obj 75 | *.pch 76 | *.pdb 77 | *.pgc 78 | *.pgd 79 | *.rsp 80 | *.sbr 81 | *.tlb 82 | *.tli 83 | *.tlh 84 | *.tmp 85 | *.tmp_proj 86 | *.log 87 | *.vspscc 88 | *.vssscc 89 | .builds 90 | *.pidb 91 | *.log 92 | *.scc 93 | 94 | # Visual C++ cache files 95 | ipch/ 96 | *.aps 97 | *.ncb 98 | *.opensdf 99 | *.sdf 100 | *.cachefile 101 | 102 | # Visual Studio profiler 103 | *.psess 104 | *.vsp 105 | *.vspx 106 | 107 | # Guidance Automation Toolkit 108 | *.gpState 109 | 110 | # ReSharper is a .NET coding add-in 111 | _ReSharper*/ 112 | *.[Rr]e[Ss]harper 113 | 114 | # TeamCity is a build add-in 115 | _TeamCity* 116 | 117 | # DotCover is a Code Coverage Tool 118 | *.dotCover 119 | 120 | # NCrunch 121 | *.ncrunch* 122 | .*crunch*.local.xml 123 | 124 | # Installshield output folder 125 | [Ee]xpress/ 126 | 127 | # DocProject is a documentation generator add-in 128 | DocProject/buildhelp/ 129 | DocProject/Help/*.HxT 130 | DocProject/Help/*.HxC 131 | DocProject/Help/*.hhc 132 | DocProject/Help/*.hhk 133 | DocProject/Help/*.hhp 134 | DocProject/Help/Html2 135 | DocProject/Help/html 136 | 137 | # Click-Once directory 138 | publish/ 139 | 140 | # Publish Web Output 141 | *.Publish.xml 142 | *.pubxml 143 | 144 | # NuGet Packages Directory 145 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line 146 | #packages/ 147 | 148 | # Windows Azure Build Output 149 | csx 150 | *.build.csdef 151 | 152 | # Windows Store app package directory 153 | AppPackages/ 154 | 155 | # Others 156 | sql/ 157 | *.Cache 158 | ClientBin/ 159 | [Ss]tyle[Cc]op.* 160 | ~$* 161 | *~ 162 | *.dbmdl 163 | *.[Pp]ublish.xml 164 | *.pfx 165 | *.publishsettings 166 | 167 | # RIA/Silverlight projects 168 | Generated_Code/ 169 | 170 | # Backup & report files from converting an old project file to a newer 171 | # Visual Studio version. Backup files are not needed, because we have git ;-) 172 | _UpgradeReport_Files/ 173 | Backup*/ 174 | UpgradeLog*.XML 175 | UpgradeLog*.htm 176 | 177 | # SQL Server files 178 | App_Data/*.mdf 179 | App_Data/*.ldf 180 | 181 | ############# 182 | ## Windows detritus 183 | ############# 184 | 185 | # Windows image file caches 186 | Thumbs.db 187 | ehthumbs.db 188 | 189 | # Folder config file 190 | Desktop.ini 191 | 192 | # Recycle Bin used on file shares 193 | $RECYCLE.BIN/ 194 | 195 | # Mac crap 196 | .DS_Store 197 | 198 | 199 | ############# 200 | ## Python 201 | ############# 202 | 203 | *.py[co] 204 | 205 | # Packages 206 | *.egg 207 | *.egg-info 208 | dist/ 209 | build/ 210 | eggs/ 211 | parts/ 212 | var/ 213 | sdist/ 214 | develop-eggs/ 215 | .installed.cfg 216 | 217 | # Installer logs 218 | pip-log.txt 219 | 220 | # Unit test / coverage reports 221 | .coverage 222 | .tox 223 | 224 | #Translations 225 | *.mo 226 | 227 | #Mr Developer 228 | .mr.developer.cfg 229 | -------------------------------------------------------------------------------- /CHANGES.txt: -------------------------------------------------------------------------------- 1 | v1.13c [2015-06-10] 2 | Fix some occasional minor issues when loading/saving files 3 | (including memory access issues when saving on OS X) 4 | 5 | v1.13b [2015-03-29] 6 | Fixed occasional broken tiles for terrain 09 and 0C when placed against higher slopes 7 | 8 | v1.13 [2015-03-22] 9 | Much faster multithreaded saving 10 | Added loading/saving of individual level files 11 | Increased maximum tile height from 31 to 49 and max level width/length to 100 12 | (so you can waste more tile memory. Note that 2D and 3D map area limits are unchanged) 13 | Some more build/deployment improvements for OS X (thanks ConnorRK) 14 | Settings now saved in system-standard paths (%AppData%, etc.) 15 | Fixed buggy repainting of 2D map when scrolling horizontally 16 | Fixed possible bad graphics pointers when saving European ROMs 17 | 18 | v1.12 [2014-06-12] 19 | Fixed the rewritten 2D display exhibiting some visual strangeness when resizing levels 20 | 21 | v1.11 [2014-01-13] 22 | Fixed possible data corruption when saving levels to headered ROMs 23 | Heavily rewrote 2D map display (including better handling of font metrics) 24 | Tweaked compression code again (just slightly faster, probably the last change) 25 | 26 | v1.10b [2013-12-19] 27 | Fixed corrupted demo-6holes.kdc 28 | 29 | v1.10 [2013-12-18] 30 | Updated compression code based on exhal v1.20 31 | (now several times faster again) 32 | Added undo & redo commands/keys 33 | Fixed a critical bug that caused the Windows build to save corrupted .kdc files 34 | (due to gcc changing the behavior of sizeof() w/r/t bit fields for some reason) 35 | [huge thanks to DarkMatt for finding this] 36 | 37 | v1.00 [2013-09-15] 38 | Released source under MIT license 39 | Updated source and build to Qt 5 40 | Added sample course "demo-6holes.kdc" [by Revenant] 41 | Added sample course "KirbysRevCourseSampleSelects.kdc" [by DarkMatt] 42 | 2D view displays an indicator for layer 2 tiles 43 | Added support for bumpers on diagonal slope top/bottom tiles, and some 2-way slopes 44 | Fixed several Gordo path types sometimes behaving like invisible Gordos (?! HAL...) 45 | Fixed tiles for terrain 0x1A (slope northwest middle) 46 | Fixed tiles for obstacle 0x37 (east conveyor sloping down) 47 | Fixed east/west bumpers for north slope 48 | Fixed north/west bumpers of north/west slopes next to a flat tile with no bumpers 49 | Fixed tiles for adjacent rotating spaces 50 | Reworked some other tile data to be less broken sometimes 51 | Slopes on layer 1 next to terrain on layer 2 don't sometimes have messed up edges anymore 52 | Window maximized state and level image path are saved on exit 53 | More menu/toolbar actions are enabled/disabled when appropriate 54 | Current file name is displayed in titlebar 55 | Removed several unused icons 56 | Prevent main window being closed while file save is in progress 57 | Fixed small memory leaks 58 | 59 | v0.96 [2013-07-06] 60 | Fixed bug where a level being saved near the end of a ROM bank sometimes caused a bad pointer 61 | (i.e. sometimes a chunk of data would need to be moved to the beginning of the next bank 62 | when the current bank ran out of space, but the pointer to the data didn't reflect this 63 | because the pointer was written BEFORE moving to the next bank. oops...) 64 | [thanks DarkMatt for initially finding this] 65 | 66 | v0.95 [2013-06-27] 67 | Added sample course "carbowl.kdc" [thanks Kles] 68 | Replaced README.txt with HTML documentation 69 | Added course select dialog 70 | Added cut/copy/paste 71 | Added various keyboard shortcuts 72 | Added support for several unused rotating tile types 73 | Added "keep" layer option to preserve existing layer assignment 74 | Sprites are now displayed in preview window 75 | Applying obstacle numbers 24/30/31/32/33 now adjusts for slopes 76 | Fixed bug with selection area not being updated when changing levels or closing a ROM 77 | Fixed bug with scrollbar size remaining when closing ROM 78 | Fixed bug relating to layer 2 terrain drawing too many wall tiles when behind layer 1 79 | Fixed background 3 not displaying correctly in US/EU ROMs [thanks DarkMatt] 80 | Fixed missing tile for terrain 0x15 81 | Fixed messed up south bumper for terrain 0x07 82 | Setting terrain type 0x00 now fully removes other tile properties 83 | Removed some menu items for a few still-unimplemented actions 84 | Window positions/sizes and settings are saved on exit 85 | 86 | v0.90 [2013-06-15]: 87 | US/Europe ROM support 88 | Updated compression routine to make saving levels about 3-4x faster 89 | Editor now generates sprite clipping tables 90 | (this means Kirby won't appear to be on top of something when falling behind it anymore) 91 | Added load/save course files option 92 | Fixed "save level" menu item not doing anything 93 | Added notice to readme about putting Kracko and Whispy Woods in same level 94 | Editor now displays error when: 95 | - trying to save to a file that has been moved/deleted [thanks Kles] 96 | - saving a level whose 3D tile map becomes too large 97 | Fixed bugs relating to opening/closing ROMs with headers 98 | Handle possible corrupted ROMs a bit more gracefully 99 | Fixed bug where west slope had the wrong tiles for the east border [thanks Kles] 100 | Dedede's stage sprites/tiles now supported 101 | Changed some music titles to match the snesmusic.org set 102 | Bit of interface tweaks, mostly under the hood 103 | Updated README.txt (real documentation still forthcoming) 104 | 105 | v0.80b [2013-06-01]: 106 | First public release 107 | Fixed some border tiles for slope down to east 108 | Fixed maximum values for level length/width boxes 109 | 110 | v0.80 [2013-06-01]: 111 | Initial limited release 112 | -------------------------------------------------------------------------------- /COPYING.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013-2015 Devin Acker 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /KDCEditor.pro: -------------------------------------------------------------------------------- 1 | QT += core widgets 2 | 3 | QMAKE_CFLAGS += -std=c99 4 | QMAKE_CXXFLAGS += -std=c++11 5 | 6 | TARGET = KDCEditor 7 | TEMPLATE = app 8 | CONFIG += c++11 9 | 10 | CONFIG(debug, debug|release) { 11 | DESTDIR = debug 12 | } 13 | CONFIG(release, debug|release) { 14 | DESTDIR = release 15 | } 16 | 17 | OBJECTS_DIR = obj/$$DESTDIR 18 | MOC_DIR = $$OBJECTS_DIR 19 | RCC_DIR = $$OBJECTS_DIR 20 | 21 | # copy docs and samples on build 22 | copydata.commands += \ 23 | $(COPY_DIR) \"$$PWD/docs\" $$DESTDIR &&\ 24 | $(COPY_FILE) \"$$PWD/CHANGES.txt\" $$DESTDIR &&\ 25 | $(COPY_FILE) \"$$PWD/COPYING.txt\" $$DESTDIR &&\ 26 | $(COPY_DIR) \"$$PWD/samples\" $$DESTDIR 27 | 28 | first.depends = $(first) copydata 29 | export(first.depends) 30 | export(copydata.commands) 31 | QMAKE_EXTRA_TARGETS += first copydata 32 | 33 | # OS-specific metadata and stuff 34 | win32:RC_FILE = src/windows.rc 35 | macx:ICON = src/images/main.icns 36 | 37 | # build on OS X with xcode/clang and libc++ 38 | macx:QMAKE_CXXFLAGS += -stdlib=libc++ 39 | 40 | SOURCES += src/main.cpp\ 41 | src/mainwindow.cpp \ 42 | src/tileeditwindow.cpp \ 43 | src/compress.c \ 44 | src/level.cpp \ 45 | src/kirby.cpp \ 46 | src/mapscene.cpp \ 47 | src/previewwindow.cpp \ 48 | src/propertieswindow.cpp \ 49 | src/romfile.cpp \ 50 | src/metatile.cpp \ 51 | src/metatile_terrain.cpp \ 52 | src/metatile_borders.cpp \ 53 | src/metatile_obstacles.cpp \ 54 | src/coursewindow.cpp \ 55 | src/previewscene.cpp \ 56 | src/mapchange.cpp 57 | 58 | HEADERS += src/mainwindow.h \ 59 | src/tileeditwindow.h \ 60 | src/compress.h \ 61 | src/level.h \ 62 | src/kirby.h \ 63 | src/mapscene.h \ 64 | src/graphics.h \ 65 | src/previewwindow.h \ 66 | src/propertieswindow.h \ 67 | src/romfile.h \ 68 | src/metatile.h \ 69 | src/coursewindow.h \ 70 | src/version.h \ 71 | src/previewscene.h \ 72 | src/mapchange.h 73 | 74 | FORMS += src/mainwindow.ui \ 75 | src/tileeditwindow.ui \ 76 | src/previewwindow.ui \ 77 | src/propertieswindow.ui \ 78 | src/coursewindow.ui 79 | 80 | RESOURCES += \ 81 | src/images.qrc \ 82 | src/icons.qrc 83 | 84 | OTHER_FILES += \ 85 | CHANGES.txt \ 86 | README.md \ 87 | src/TODO.txt \ 88 | src/coursefiles.txt \ 89 | src/windows.rc 90 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Kirby's Dream Course Editor 2 | =========================== 3 | version 1.13c 4 | 5 | ![Screenshot](http://dl.dropboxusercontent.com/u/43107309/kdceditor-111.png) 6 | 7 | Here it is, warts and all. After many more months of hard work and laziness than there ever should have been, it's done. For now, anyway. 8 | 9 | This is a fully-featured level editor for the Super NES game Kirby's Dream Course (also known as Kirby Bowl). See the docs directory for a real introduction. 10 | 11 | More or less every issue that's come up since the initial beta release has been resolved, but there's still some stuff I may want to do eventually, which is all listed in TODO.txt. 12 | 13 | This is a Qt 5 project. So far it has been built and tested on Windows (with MinGW, because MSVC sucks), Mac OS X (with Clang), and Linux (with GCC). You are encouraged to try building and running it for yourself and seeing how it works. Other issues and suggestions are always welcome, of course. 14 | 15 | See the releases section for the latest official builds for Windows and OS X (both as of version 1.13). 16 | 17 | Some example course files are in the samples directory, courtesy of myself and others. Feel free to submit your own via pull request or email if you've made some cool levels that you want to show off. 18 | -------------------------------------------------------------------------------- /docs/images/7-1-1layer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devinacker/kdceditor/52069434e7945bf5651bdd497b78aa08ab168778/docs/images/7-1-1layer.png -------------------------------------------------------------------------------- /docs/images/7-1-2layers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devinacker/kdceditor/52069434e7945bf5651bdd497b78aa08ab168778/docs/images/7-1-2layers.png -------------------------------------------------------------------------------- /docs/images/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devinacker/kdceditor/52069434e7945bf5651bdd497b78aa08ab168778/docs/images/background.png -------------------------------------------------------------------------------- /docs/images/diag-1layer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devinacker/kdceditor/52069434e7945bf5651bdd497b78aa08ab168778/docs/images/diag-1layer.png -------------------------------------------------------------------------------- /docs/images/diag-2layers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devinacker/kdceditor/52069434e7945bf5651bdd497b78aa08ab168778/docs/images/diag-2layers.png -------------------------------------------------------------------------------- /docs/images/edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devinacker/kdceditor/52069434e7945bf5651bdd497b78aa08ab168778/docs/images/edit.png -------------------------------------------------------------------------------- /docs/images/mainwindow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devinacker/kdceditor/52069434e7945bf5651bdd497b78aa08ab168778/docs/images/mainwindow.png -------------------------------------------------------------------------------- /docs/images/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devinacker/kdceditor/52069434e7945bf5651bdd497b78aa08ab168778/docs/images/preview.png -------------------------------------------------------------------------------- /docs/images/properties.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devinacker/kdceditor/52069434e7945bf5651bdd497b78aa08ab168778/docs/images/properties.png -------------------------------------------------------------------------------- /docs/index.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Kirby's Dream Course Editor 7 | 8 | 9 | 10 | 11 | 12 |

Kirby's Dream Course Editor

13 | version 1.13c 14 |

15 | 16 |
17 | Introduction - The main window - The preview window - The edit window - The properties window
18 | Credits - What's new - Remarks - Contact 19 |
20 |

21 | 22 | 23 |

Introduction

24 | Kirby's Dream Course Editor (yes, still untitled) is a level editor for Kirby's Dream Course and its Japanese counterpart, Kirby Bowl. 25 |

26 | The editor supports full editing of all of the game's courses, including demo courses and unused test courses which are only present in the Japanese version. In addition, background and music selections can be changed in order to fully customize courses. Individual courses can be saved to standalone files for easy distribution, and sample levels are included to demonstrate some of the editor's capabilities. 27 |

28 | The editor supports US, European, and Japanese ROMs, both with or without copier headers. Special Tee Shot support is currently planned for future versions, but is not yet implemented due to numerous differences between the two games in how level and graphics data are organized. 29 |

30 | Source code is available on GitHub, and is distributed under the MIT license. 31 |

32 | (top) 33 | 34 | 35 |

The main window

36 | 37 |

38 | The main window is where all level editing takes place. When you load a ROM, level data is shown from a top-down perspective. Levels are composed of tiles, each one having a terrain type and an optional obstacle or enemy, as well as optional "bumpers" to the north, east, south, and west. 39 |

40 | Each tile displays its current height in the corner. Hovering over a tile with the mouse will also display its current terrain and obstacle values (in hexadecimal), which are also displayed in more detail on the window's status bar. 41 |

42 | Clicking and dragging will select a tile or range of tiles, which you can then edit by double-clicking, selecting the "Edit Tiles" option on the Edit menu or toolbar, or pressing the "E" key. This will bring up the edit window, allowing you to modify the properties of the selected tiles. To deselect tiles, right-click anywhere within the level view. 43 |

44 | To change levels, click the "course select" option on the Level menu or the toolbar. Use the "previous/next course/level" options to move back/forward one course or hole at a time, or hold Ctrl and use the left/right arrow keys (holding Shift at the same time will move by entire courses). 45 |

46 | (top) 47 | 48 | 49 |

The preview window

50 | 51 |

52 | The preview window appears alongside the main menu and shows what the current level will look like in game. This window displays the isometric tile maps which the editor generates and the game displays, so what you see here is exactly what you get in game. 53 |

54 | By default, the view in the preview window will automatically center on any tile that you move the mouse over in the main window, allowing you to view what you're editing without having to manually scroll the window. You can enable or disable this feature by selecting "Center Preview" in the Level menu or on the toolbar. Selecting "Show Preview" will redisplay the window if it is ever closed. 55 |

56 | Note that the preview window does not account for palette changes, due to using pre-made graphics rather than loading them from the ROM (mostly due to laziness). This may change in future versions. 57 |

58 | (top) 59 | 60 | 61 |

The edit window

62 | 63 |

64 | This is the window which appears when you double-click a selection of tiles or select the "Edit Tiles" menu option. From here, you can edit the terrain and obstacle values of a tile or tiles, increase/decrease tiles' height, add side bumpers, and select whether to display the tile's graphics on layer 1 or layer 2. 65 | 66 |

Terrain

67 | The Terrain dropdown allows you to select one of several terrain types for the currently selected tile or tiles. The available selections consist mostly of: 68 | 83 |

84 | If you are editing a range of tiles with multiple terrain types, the dropdown will default to a value of "(multiple)". This option causes each tile's existing terrain value to remain the same. 85 | 86 |

Height

87 | The height slider sets the height of the selected tile or tiles. Possible values range from 0 to 49 - this maximum value is not a limitation of the game, but is imposed by the editor to prevent isometric tilemaps from becoming too large (see the properties section for more details). 88 |

89 | If you are editing tiles which use one of the sloped terrain types, the height value must be at least 1. This is because all of the slope types start at the given height value and slope down to 1 unit lower. 90 |

91 | If you are editing a range of tiles with multiple height values, the slider will be labeled "Raise/Lower", and will allow you to raise or lower the selection by a relative amount. 92 | 93 |

Obstacles

94 | The obstacle dropdown allows you to put down the things that go on top of terrain, including both enemies and other static hazards such as sand traps, water, spikes, and pretty much everything else. 95 |

96 | A few things you should keep in mind when placing obstacles: 97 |

107 |

108 | If you are editing a range of tiles with multiple obstacle types, the dropdown will default to a value of "(multiple)", just like the terrain dropdown. 109 | 110 |

Bumpers

111 | These checkboxes allow you to place solid green bumpers on the north, south, east, and west edges of tiles. This is usually only useful for tiles which border empty space. Note that due to limited amounts of graphics assets, bumpers will only be visible when placed on flat tiles or slopes facing the four cardinal directions (including most "two-way" slopes). 112 |

113 | These checkboxes only cover bumpers which span the very edges of tiles. For horizontal, vertical, and diagonal bumpers which pass through the centers of tiles, use the obstacle dropdown. 114 |

115 | Also note that, due to the way the editor generates tile maps, there will be some situations in which the appearance of bumpers will not be 100% consistent with the way they appear in the original game. This is a purely cosmetic issue and is typically not very noticeable. 116 |

117 | If you are editing a range of tiles with multiple bumper on/off values in any direction, the respective checkbox will have a value of "partially checked". This value will cause all selected tiles to retain their existing settings for that individual bumper. 118 | 119 |

Layers

120 | During gameplay, graphics are divided up among three layers. The background is on layer 3, while the course is on layers 1 and 2. Most of the time, the ground is on layer 1, and non-sprite obstacles are on layer 2. However, there are times when terrain needs to overlap other terrain on screen, or times when Kirby can be behind some terrain while in front of other terrain. For these situations, you can cause selected tiles' terrain to appear on layer 2 instead, where it will be drawn on top of normal layer 1 terrain. 121 |

122 | 123 | 125 | 127 |

124 | 1P course 7-1 using only layer 1

126 | 1P course 7-1 using layers 1 and 2
128 |

129 | Note that Kirby is always drawn in front of layer 2, except when above empty space (i.e. if Kirby is immediately capable of going out of bounds), where Kirby can be between layers 1 and 2, or behind both layers. 130 |

131 | Another word of warning: when putting tiles on layer 2, any non-sprite obstacles will be drawn on layer 1, with the priority bit set so that they still appear on top of the terrain. Putting obstacles on layer 1 like this can cause visual issues with any layer 1 terrain which is close by on-screen, so use caution when putting obstacles on layer 2 tiles if they are also close to layer 1 tiles. 132 |

133 | In rare cases, you might place combinations of terrain which don't occur in the original levels, and thus don't have appropriate graphics available. If this happens, you can place one or more of the offending tiles on layer 2, causing them to be drawn separately instead of "connected" to adjacent terrain. One use for this is the ability to place diagonal slopes next to walls: 134 |

135 | 136 | 138 | 140 |

137 | Diagonal slopes on layer 1

139 | Diagonal slopes on layer 2
141 |

142 | If you are editing a range of tiles which use both layers, the "Keep" radio button will be selected by default. As with the other editing controls, this option causes the selected tiles' layer settings to be left alone. 143 |

144 | (top) 145 | 146 | 147 |

The properties window

148 | 149 |

150 | The properties window allows you to customize some details of both the current course and the specific hole you are editing. 151 |

Course properties

152 | The course properties controls allow you to change the current course's background and palette settings. 153 |

154 | Note that, as the text in the screenshot says, changing a course's background always effects multiple courses. For example, if you change 1P Course 1's background, the same change will be applied to 1P Extra 1, 2P Course 1, and 2P Extra 1. 155 |

156 | Palettes, unlike backgrounds, only affect the current course. The editor allows you to choose palettes for both the ground and the water hazards. Normally, water hazards use the same-numbered palette as the ground, but different combinations are also possible. 157 |

Level properties

158 | The level properties controls allow you to change a level's dimensions and background music. 159 |

160 | Kirby's Dream Course normally uses the same music tracks for several holes in a row, but that kind of consistency isn't actually a requirement - you can just as easily use 8 different music tracks in one course if you want to. 161 |

162 | Note that, because the Japanese version's intro sequence was removed from the US/European versions, its music track is only usable in the Japanese version. 163 |

164 | The level length and width boxes allow you to resize the level (length = north to south, width = east to west). Due to memory restrictions, the two-dimensional area of a level cannot be greater than 2,048; all of the original levels are much smaller in area than this, so this limitation is not likely to be much of a problem. 165 |

166 | Note that the isometric tilemaps are also limited in size; their size is determined by the length, width, and height of the level, as well as how much (or how little) of the level is empty space. Once again, the limit here is much higher than what any of the original levels reach, so this should only present a problem if you are making unusually large levels that come close to the length/width limits. In the event that this actually happens, you will be presented with a warning when saving your ROM, and part of your level will not be visible in game. 167 |

168 | (top) 169 | 170 | 171 |

Credits

172 | The author would like to extend well-earned salami sandwiches to each of the following individuals and groups: 173 |

174 |

188 |

189 | (top) 190 | 191 | 192 |

What's new

193 | See the full changelog for a complete version history. 194 |

195 | (top) 196 | 197 | 198 |

Remarks

199 | As of writing the documentation for version 1.00, it's been a little over half nearly a year since I started researching Kirby's Dream Course, and it wasn't too much longer before I started working on this editor. I was itching for a cool ROM hacking project to undertake, and I had coincidentally been digging into this game to research the Japanese version's debug mode for an article on The Cutting Room Floor. 200 |

201 | At some point while messing around with Kirby Bowl, I stumbled upon the game's decompression routine, rewrote it in the form of a small tool called "exhal", and began exploring the game's data. Most of it quickly became clear, and I set out to write a document on the level format used by the game. 202 |

203 | Around that time, I semi-publically announced my intention to write an editor, and by chance I found video footage on Nico Nico Douga of a Japanese hacker's custom Kirby Bowl levels, and was later given a link to their editor, "KBE". While the original editor was a bit lacking in the interface department, this obscure tool and the hacked Kirby Bowl videos proved that there was an audience for this sort of thing, and I resolved to make a more accessible alternative. 204 |

205 | A couple of months later, delayed by my acquiring a full-time job, I had a rudimentary level editor put together. It still needed to generate the pre-made isometric tile maps that the game actually displayed, so I set out opening up dozens of save states and punching in what ended up as just a little bit under 3,500 lines of raw tile data, eight tiles per line. The lack of a clear-cut pattern in the way the game's graphics were organized, along with some annoying properties of the visual perspective, made this a monstrously tedious affair. 206 |

207 | Nevertheless, it was eventually done, and after patching up some quirks in the tile map generation code, the editor was finally almost ready for prime time. About two months of testing and user feedback later (and several more months of laziness), and version 1.00 is finally here. 208 |

209 | At this point, I'm probably going to take a break to work on something else (or nothing at all), but comments, etc., will always be welcome. 210 |

211 | Special Tee Shot support is still in the cards, but it'll be a lot more tedious tile-wrangling before that happens "for real". 212 |

213 | Oh, and as the old text readme mentioned, this is my first real GUI application ever. I'm sorry that it doesn't look very good. (I'm also sorry that the download for the Windows build is so much larger than before, as I updated to Qt 5 but didn't ever have time to build smaller Qt DLLs with fewer dependencies. At least you can delete the Qt 4 DLLs from previous versions.) 214 |

215 | (top) 216 | 217 | 218 |

Contact

219 | If you want to yell at me about something, here's how you can: 220 |

221 | Email:
222 | d@revenant1.net 223 |

224 | IRC:
225 | "devin" on irc.badnik.net
226 | "Revenant" on irc.oftc.net
227 | "Revenant`" on irc.synirc.net and irc.dal.net 228 |

229 | Forums:
230 | http://jul.rustedlogic.net/profile.php?id=504
231 | http://www.romhacking.net/forum/index.php?action=profile;u=10455 232 |

233 | (top) 234 | 235 |

236 | copyright 2013-2015 Devin "Revenant" Acker - "life is but a dream" 237 | 238 | -------------------------------------------------------------------------------- /docs/style.css: -------------------------------------------------------------------------------- 1 | h1 { 2 | font-family: verdana, sans-serif; 3 | color: black; 4 | clear: both; 5 | } 6 | 7 | h2 { 8 | font-family: verdana, sans-serif; 9 | color: black; 10 | } 11 | 12 | body { 13 | font-family: sans-serif; 14 | color: black; 15 | background: url("images/background.png"); 16 | } 17 | 18 | a:link { 19 | color: blue; 20 | text-decoration: none; 21 | } 22 | 23 | a:active { 24 | color: blue; 25 | } 26 | 27 | a:hover { 28 | color: blue; 29 | text-decoration: underline; 30 | } 31 | 32 | a:visited { 33 | color: blue; 34 | text-decoration: none; 35 | } 36 | 37 | span.smallLink { 38 | font-size: 75%; 39 | } 40 | 41 | img.right { 42 | float: right; 43 | padding: 4px; 44 | } 45 | 46 | img.center { 47 | display: block; 48 | margin-left: auto; 49 | margin-right: auto; 50 | } 51 | 52 | table { 53 | background-color: #FFE0F0; 54 | border-collapse: collapse; 55 | margin-left: auto; 56 | margin-right: auto; 57 | } 58 | 59 | td { 60 | border: solid 2px; 61 | border-color: #FFC0C8; 62 | padding: 3px; 63 | text-align: center; 64 | } -------------------------------------------------------------------------------- /samples/KirbysRevCourseSampleSelects.kdc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devinacker/kdceditor/52069434e7945bf5651bdd497b78aa08ab168778/samples/KirbysRevCourseSampleSelects.kdc -------------------------------------------------------------------------------- /samples/carbowl.kdc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devinacker/kdceditor/52069434e7945bf5651bdd497b78aa08ab168778/samples/carbowl.kdc -------------------------------------------------------------------------------- /samples/demo-6holes.kdc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devinacker/kdceditor/52069434e7945bf5651bdd497b78aa08ab168778/samples/demo-6holes.kdc -------------------------------------------------------------------------------- /src/TODO.txt: -------------------------------------------------------------------------------- 1 | Interface/misc: 2 | - use tr() more when appropriate (for future translation support) 3 | - kirby-related toolbar/menu/mainwindow icons 4 | - move terrain/obstacle enums into kirby.h or something 5 | - emulator preview button/asm hack 6 | - use QHash/QMap for metatile data 7 | - much smaller build of Qt5 binaries on windows 8 | 9 | 2D view: 10 | - less bad programmer art 11 | 12 | 3D view: 13 | - rest of possible bumpers for 2way slopes (sometimes with gaps in corners if necessary) 14 | - come up with a way to fix the QPixmap -> QImage -> QPixmap bottleneck when flipping 8x8 tiles 15 | -------------------------------------------------------------------------------- /src/compress.c: -------------------------------------------------------------------------------- 1 | /* 2 | exhal / inhal (de)compression routines 3 | 4 | This code is released under the terms of the MIT license. 5 | See COPYING.txt for details. 6 | 7 | Copyright (c) 2013-2015 Devin Acker 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy 10 | of this software and associated documentation files (the "Software"), to deal 11 | in the Software without restriction, including without limitation the rights 12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | copies of the Software, and to permit persons to whom the Software is 14 | furnished to do so, subject to the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included in 17 | all copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | THE SOFTWARE. 26 | 27 | */ 28 | 29 | #include 30 | #include 31 | #include "compress.h" 32 | #include "uthash.h" 33 | 34 | #ifdef DEBUG_OUT 35 | #define debug(...) printf(__VA_ARGS__) 36 | #else 37 | #define debug(...) 38 | #endif 39 | 40 | // compression method values for backref_t and rle_t 41 | typedef enum { 42 | rle_8 = 0, 43 | rle_16 = 1, 44 | rle_seq = 2, 45 | 46 | lz_norm = 0, 47 | lz_rot = 1, 48 | lz_rev = 2 49 | } method_e; 50 | 51 | // used to store and compare backref candidates 52 | typedef struct { 53 | uint16_t offset, size; 54 | method_e method; 55 | } backref_t; 56 | 57 | // used to store RLE candidates 58 | typedef struct { 59 | uint16_t size, data; 60 | method_e method; 61 | } rle_t; 62 | 63 | // used to hash and index byte tuples 64 | typedef struct { 65 | int bytes; 66 | uint16_t offset; 67 | UT_hash_handle hh; 68 | } tuple_t; 69 | // turn 4 bytes into a single integer for quicker hashing/searching 70 | #define COMBINE(w, x, y, z) ((w << 24) | (x << 16) | (y << 8) | z) 71 | 72 | uint8_t rotate (uint8_t); 73 | rle_t rle_check (uint8_t*, uint8_t*, uint32_t, int); 74 | backref_t ref_search (uint8_t*, uint8_t*, uint32_t, tuple_t*, int); 75 | uint16_t write_backref (uint8_t*, uint16_t, backref_t); 76 | uint16_t write_rle (uint8_t*, uint16_t, rle_t); 77 | uint16_t write_raw (uint8_t*, uint16_t, uint8_t*, uint16_t); 78 | void free_offsets(tuple_t*); 79 | 80 | // Compresses a file of up to 64 kb. 81 | // unpacked/packed are 65536 byte buffers to read/from write to, 82 | // inputsize is the length of the uncompressed data. 83 | // Returns the size of the compressed data in bytes, or 0 if compression failed. 84 | size_t pack(uint8_t *unpacked, size_t inputsize, uint8_t *packed, int fast) { 85 | if (inputsize > DATA_SIZE) return 0; 86 | 87 | // current input/output positions 88 | uint32_t inpos = 0; 89 | uint32_t outpos = 0; 90 | 91 | // backref and RLE compression candidates 92 | backref_t backref; 93 | rle_t rle; 94 | 95 | // used to collect data which should be written uncompressed 96 | uint8_t dontpack[LONG_RUN_SIZE]; 97 | uint16_t dontpacksize = 0; 98 | 99 | // index of first locations of byte-tuples used to speed up LZ string search 100 | tuple_t *offsets = NULL; 101 | 102 | debug("inputsize = %d\n", inputsize); 103 | 104 | for (uint16_t i = 0; inputsize >= 4 && i < inputsize - 4; i++) { 105 | tuple_t *tuple; 106 | int currbytes = COMBINE(unpacked[i], unpacked[i+1], unpacked[i+2], unpacked[i+3]); 107 | 108 | // has this one been indexed already 109 | HASH_FIND_INT(offsets, &currbytes, tuple); 110 | if (!tuple) { 111 | tuple = (tuple_t*)malloc(sizeof(tuple_t)); 112 | tuple->bytes = currbytes; 113 | tuple->offset = i; 114 | HASH_ADD_INT(offsets, bytes, tuple); 115 | } 116 | } 117 | 118 | while (inpos < inputsize) { 119 | // check for a potential RLE 120 | rle = rle_check(unpacked, unpacked + inpos, inputsize, fast); 121 | // check for a potential back reference 122 | if (rle.size < LONG_RUN_SIZE && inputsize >= 3 && inpos < inputsize - 3) 123 | backref = ref_search(unpacked, unpacked + inpos, inputsize, offsets, fast); 124 | else backref.size = 0; 125 | 126 | // if the backref is a better candidate, use it 127 | if (backref.size > 3 && backref.size > rle.size) { 128 | if (outpos + dontpacksize + backref.size >= DATA_SIZE) { 129 | free_offsets(offsets); 130 | return 0; 131 | } 132 | 133 | // flush the raw data buffer first 134 | outpos += write_raw(packed, outpos, dontpack, dontpacksize); 135 | dontpacksize = 0; 136 | 137 | outpos += write_backref(packed, outpos, backref); 138 | inpos += backref.size; 139 | } 140 | // or if the RLE is a better candidate, use it instead 141 | else if (rle.size >= 2) { 142 | if (outpos + dontpacksize + rle.size >= DATA_SIZE) { 143 | free_offsets(offsets); 144 | return 0; 145 | } 146 | 147 | // flush the raw data buffer first 148 | outpos += write_raw(packed, outpos, dontpack, dontpacksize); 149 | dontpacksize = 0; 150 | 151 | outpos += write_rle(packed, outpos, rle); 152 | inpos += rle.size; 153 | 154 | } 155 | // otherwise, write this byte uncompressed 156 | else { 157 | dontpack[dontpacksize++] = unpacked[inpos++]; 158 | 159 | if (outpos + dontpacksize >= DATA_SIZE) { 160 | free_offsets(offsets); 161 | return 0; 162 | } 163 | 164 | // if the raw data buffer is full, flush it 165 | if (dontpacksize == LONG_RUN_SIZE) { 166 | outpos += write_raw(packed, outpos, dontpack, dontpacksize); 167 | dontpacksize = 0; 168 | } 169 | } 170 | } 171 | 172 | // flush any remaining uncompressed data 173 | if (outpos + dontpacksize + 1 > DATA_SIZE) { 174 | free_offsets(offsets); 175 | return 0; 176 | } 177 | 178 | outpos += write_raw(packed, outpos, dontpack, dontpacksize); 179 | 180 | //add the terminating byte 181 | packed[outpos++] = 0xFF; 182 | 183 | free_offsets(offsets); 184 | return (size_t)outpos; 185 | } 186 | 187 | void free_offsets(tuple_t *offsets) { 188 | tuple_t *curr, *temp; 189 | HASH_ITER(hh, offsets, curr, temp) { 190 | HASH_DEL(offsets, curr); 191 | free(curr); 192 | } 193 | } 194 | 195 | // Decompresses a file of up to 64 kb. 196 | // unpacked/packed are 65536 byte buffers to read/from write to, 197 | // Returns the size of the uncompressed data in bytes or 0 if decompression failed. 198 | size_t unpack(uint8_t *packed, uint8_t *unpacked) { 199 | // current input/output positions 200 | uint32_t inpos = 0; 201 | uint32_t outpos = 0; 202 | 203 | uint8_t input; 204 | uint16_t command, length, offset; 205 | int methoduse[7] = {0}; 206 | 207 | while (1) { 208 | // read command byte from input 209 | input = packed[inpos++]; 210 | 211 | // command 0xff = end of data 212 | if (input == 0xFF) 213 | break; 214 | 215 | // check if it is a long or regular command, get the command no. and size 216 | if ((input & 0xE0) == 0xE0) { 217 | command = (input >> 2) & 0x07; 218 | // get LSB of length from next byte 219 | length = (((input & 0x03) << 8) | packed[inpos++]) + 1; 220 | } else { 221 | command = input >> 5; 222 | length = (input & 0x1F) + 1; 223 | } 224 | 225 | // don't try to decompress > 64kb 226 | if (((command == 2) && (outpos + 2*length > DATA_SIZE)) 227 | || (outpos + length > DATA_SIZE)) { 228 | return 0; 229 | } 230 | 231 | switch (command) { 232 | // write uncompressed bytes 233 | // (note: i had to go back to using loops for all of these because memcpy/memmove 234 | // were both fucking up and handling these inconsistently between versions of gcc) 235 | case 0: 236 | memcpy(&unpacked[outpos], &packed[inpos], length); 237 | 238 | outpos += length; 239 | inpos += length; 240 | break; 241 | 242 | // 8-bit RLE 243 | case 1: 244 | for (int i = 0; i < length; i++) 245 | unpacked[outpos++] = packed[inpos]; 246 | 247 | inpos++; 248 | break; 249 | 250 | // 16-bit RLE 251 | case 2: 252 | for (int i = 0; i < length; i++) { 253 | unpacked[outpos++] = packed[inpos]; 254 | unpacked[outpos++] = packed[inpos+1]; 255 | } 256 | 257 | inpos += 2; 258 | break; 259 | 260 | // 8-bit increasing sequence 261 | case 3: 262 | for (int i = 0; i < length; i++) 263 | unpacked[outpos++] = packed[inpos] + i; 264 | 265 | inpos++; 266 | break; 267 | 268 | // regular backref 269 | // (offset is big-endian) 270 | case 4: 271 | case 7: 272 | // 7 isn't a real method number, but it behaves the same as 4 due to a quirk in how 273 | // the original decompression routine is programmed. (one of Parasyte's docs confirms 274 | // this for GB games as well). let's handle it anyway 275 | command = 4; 276 | 277 | offset = (packed[inpos] << 8) | packed[inpos+1]; 278 | for (int i = 0; i < length; i++) 279 | unpacked[outpos++] = unpacked[offset + i]; 280 | 281 | inpos += 2; 282 | break; 283 | 284 | // backref with bit rotation 285 | // (offset is big-endian) 286 | case 5: 287 | offset = (packed[inpos] << 8) | packed[inpos+1]; 288 | for (int i = 0; i < length; i++) 289 | unpacked[outpos++] = rotate(unpacked[offset + i]); 290 | 291 | inpos += 2; 292 | break; 293 | 294 | // backwards backref 295 | // (offset is big-endian) 296 | case 6: 297 | offset = (packed[inpos] << 8) | packed[inpos+1]; 298 | for (int i = 0; i < length; i++) 299 | unpacked[outpos++] = unpacked[offset - i]; 300 | 301 | inpos += 2; 302 | } 303 | 304 | // keep track of how many times each compression method is used 305 | methoduse[command]++; 306 | } 307 | 308 | #ifdef EXTRA_OUT 309 | printf("Method Uses\n"); 310 | printf("No compression : %i\n", methoduse[0]); 311 | printf("RLE (8-bit) : %i\n", methoduse[1]); 312 | printf("RLE (16-bit) : %i\n", methoduse[2]); 313 | printf("RLE (sequence) : %i\n", methoduse[3]); 314 | printf("Backref (normal) : %i\n", methoduse[4]); 315 | printf("Backref (rotate) : %i\n", methoduse[5]); 316 | printf("Backref (reverse): %i\n", methoduse[6]); 317 | 318 | printf("\nCompressed size: %u bytes\n", inpos); 319 | #endif 320 | 321 | return (size_t)outpos; 322 | } 323 | 324 | // Decompress data from an offset into a file 325 | size_t unpack_from_file (FILE *file, size_t offset, uint8_t *unpacked) { 326 | uint8_t packed[DATA_SIZE]; 327 | 328 | fseek(file, offset, SEEK_SET); 329 | fread((void*)packed, DATA_SIZE, 1, file); 330 | if (!ferror(file)) 331 | return unpack(packed, unpacked); 332 | 333 | return 0; 334 | } 335 | 336 | // Reverses the order of bits in a byte. 337 | // One of the back reference methods does this. As far as game data goes, it seems to be 338 | // pretty useful for compressing graphics. 339 | uint8_t rotate (uint8_t i) { 340 | uint8_t j = 0; 341 | if (i & 0x01) j |= 0x80; 342 | if (i & 0x02) j |= 0x40; 343 | if (i & 0x04) j |= 0x20; 344 | if (i & 0x08) j |= 0x10; 345 | if (i & 0x10) j |= 0x08; 346 | if (i & 0x20) j |= 0x04; 347 | if (i & 0x40) j |= 0x02; 348 | if (i & 0x80) j |= 0x01; 349 | 350 | return j; 351 | } 352 | 353 | // Searches for possible RLE compressed data. 354 | // start and current are positions within the uncompressed input stream. 355 | // fast enables faster compression by ignoring sequence RLE. 356 | rle_t rle_check (uint8_t *start, uint8_t *current, uint32_t insize, int fast) { 357 | rle_t candidate = { 0, 0, 0 }; 358 | size_t size; 359 | 360 | // check for possible 8-bit RLE 361 | for (size = 0; size <= LONG_RUN_SIZE && current + size < start + insize; size++) 362 | if (current[size] != current[0]) break; 363 | 364 | // if this is better than the current candidate, use it 365 | if (size > LONG_RUN_SIZE) size = LONG_RUN_SIZE; 366 | if (size > 2 && size > candidate.size) { 367 | candidate.size = size; 368 | candidate.data = current[0]; 369 | candidate.method = rle_8; 370 | 371 | debug("\trle_check: found new candidate (size = %d, method = %d)\n", candidate.size, candidate.method); 372 | } 373 | 374 | // check for possible 16-bit RLE 375 | uint16_t first = current[0] | (current[1] << 8); 376 | for (size = 0; size <= LONG_RUN_SIZE && current + size < start + insize - 1; size += 2) { 377 | uint16_t next = current[size] | (current[size + 1] << 8); 378 | if (next != first) break; 379 | } 380 | 381 | // if this is better than the current candidate, use it 382 | if (size > LONG_RUN_SIZE) size = LONG_RUN_SIZE; 383 | if (size > 2 && size > candidate.size) { 384 | candidate.size = size; 385 | candidate.data = first; 386 | candidate.method = rle_16; 387 | 388 | debug("\trle_check: found new candidate (size = %d, method = %d)\n", candidate.size, candidate.method); 389 | } 390 | 391 | // fast mode: don't use sequence RLE 392 | if (fast) return candidate; 393 | 394 | // check for possible sequence RLE 395 | for (size = 0; size <= LONG_RUN_SIZE && current + size < start + insize; size++) 396 | if (current[size] != (current[0] + size)) break; 397 | 398 | // if this is better than the current candidate, use it 399 | if (size > LONG_RUN_SIZE) size = LONG_RUN_SIZE; 400 | if (size > 2 && size > candidate.size) { 401 | candidate.size = size; 402 | candidate.data = current[0]; 403 | candidate.method = rle_seq; 404 | 405 | debug("\trle_check: found new candidate (size = %d, method = %d)\n", candidate.size, candidate.method); 406 | } 407 | 408 | return candidate; 409 | } 410 | 411 | // Searches for the best possible back reference. 412 | // start and current are positions within the uncompressed input stream. 413 | // fast enables fast mode which only uses regular forward references 414 | backref_t ref_search (uint8_t *start, uint8_t *current, uint32_t insize, tuple_t *offsets, int fast) { 415 | backref_t candidate = { 0, 0, 0 }; 416 | uint16_t size; 417 | int currbytes; 418 | tuple_t *tuple; 419 | 420 | // references to previous data which goes in the same direction 421 | // see if this byte pair exists elsewhere, then start searching. 422 | currbytes = COMBINE(current[0], current[1], current[2], current[3]); 423 | HASH_FIND_INT(offsets, &currbytes, tuple); 424 | if (tuple) for (uint8_t *pos = start + tuple->offset; pos < current; pos++) { 425 | // see how many bytes in a row are the same between the current uncompressed data 426 | // and the data at the position being searched 427 | for (size = 0; size <= LONG_RUN_SIZE && current + size < start + insize; size++) 428 | if (pos[size] != current[size]) break; 429 | 430 | // if this is better than the current candidate, use it 431 | if (size > LONG_RUN_SIZE) size = LONG_RUN_SIZE; 432 | if (size > 3 && size > candidate.size) { 433 | candidate.size = size; 434 | candidate.offset = pos - start; 435 | candidate.method = lz_norm; 436 | 437 | debug("\tref_search: found new candidate (offset: %4x, size: %d, method = %d)\n", candidate.offset, candidate.size, candidate.method); 438 | } 439 | } 440 | 441 | // fast mode: forward references only 442 | if (fast) return candidate; 443 | 444 | // references to data where the bits are rotated 445 | // see if this byte pair exists elsewhere, then start searching. 446 | currbytes = COMBINE(rotate(current[0]), rotate(current[1]), rotate(current[2]), rotate(current[3])); 447 | HASH_FIND_INT(offsets, &currbytes, tuple); 448 | if (tuple) for (uint8_t *pos = start + tuple->offset; pos < current; pos++) { 449 | // now repeat the check with the bit rotation method 450 | for (size = 0; size <= LONG_RUN_SIZE && current + size < start + insize; size++) 451 | if (pos[size] != rotate(current[size])) break; 452 | 453 | // if this is better than the current candidate, use it 454 | if (size > LONG_RUN_SIZE) size = LONG_RUN_SIZE; 455 | if (size > 3 && size > candidate.size) { 456 | candidate.size = size; 457 | candidate.offset = pos - start; 458 | candidate.method = lz_rot; 459 | 460 | debug("\tref_search: found new candidate (offset: %4x, size: %d, method = %d)\n", candidate.offset, candidate.size, candidate.method); 461 | } 462 | } 463 | 464 | // references to data which goes backwards 465 | // see if this byte pair exists elsewhere, then start searching. 466 | currbytes = COMBINE(current[3], current[2], current[1], current[0]); 467 | HASH_FIND_INT(offsets, &currbytes, tuple); 468 | // add 3 to offset since we're starting at the end of the 4 byte sequence here 469 | if (tuple) for (uint8_t *pos = start + tuple->offset + 3; pos < current; pos++) { 470 | // now repeat the check but go backwards 471 | for (size = 0; size <= LONG_RUN_SIZE && current + size < start + insize; size++) 472 | if (start[pos - start - size] != current[size]) break; 473 | 474 | // if this is better than the current candidate, use it 475 | if (size > LONG_RUN_SIZE) size = LONG_RUN_SIZE; 476 | if (size > 3 && size > candidate.size) { 477 | candidate.size = size; 478 | candidate.offset = pos - start; 479 | candidate.method = lz_rev; 480 | 481 | debug("\tref_search: found new candidate (offset: %4x, size: %d, method = %d)\n", candidate.offset, candidate.size, candidate.method); 482 | } 483 | } 484 | 485 | return candidate; 486 | } 487 | 488 | // Writes a back reference to the compressed output stream. 489 | // Returns number of bytes written 490 | uint16_t write_backref (uint8_t *out, uint16_t outpos, backref_t backref) { 491 | uint16_t size = backref.size - 1; 492 | int outsize; 493 | 494 | debug("write_backref: writing backref to %4x, size %d (method %d)\n", backref.offset, backref.size, backref.method); 495 | 496 | // long run 497 | if (size >= RUN_SIZE) { 498 | // write command byte / MSB of size 499 | out[outpos++] = (0xF0 + (backref.method << 2)) | (size >> 8); 500 | // write LSB of size 501 | out[outpos++] = size & 0xFF; 502 | 503 | outsize = 4; 504 | } 505 | // normal size run 506 | else { 507 | // write command byte / size 508 | out[outpos++] = (0x80 + (backref.method << 5)) | size; 509 | outsize = 3; 510 | } 511 | 512 | // write MSB of offset 513 | out[outpos++] = backref.offset >> 8; 514 | // write LSB of offset 515 | out[outpos++] = backref.offset & 0xFF; 516 | 517 | return outsize; 518 | } 519 | 520 | // Writes RLE data to the compressed output stream. 521 | // Returns number of bytes written 522 | uint16_t write_rle (uint8_t *out, uint16_t outpos, rle_t rle) { 523 | uint16_t size; 524 | int outsize; 525 | 526 | if (rle.method == rle_16) 527 | size = (rle.size / 2) - 1; 528 | else 529 | size = rle.size - 1; 530 | 531 | debug("write_rle: writing %d bytes of data 0x%02x (method %d)\n", rle.size, rle.data, rle.method); 532 | 533 | // long run 534 | if (size >= RUN_SIZE) { 535 | // write command byte / MSB of size 536 | out[outpos++] = (0xE4 + (rle.method << 2)) | (size >> 8); 537 | // write LSB of size 538 | out[outpos++] = size & 0xFF; 539 | 540 | outsize = 3; 541 | } 542 | // normal size run 543 | else { 544 | // write command byte / size 545 | out[outpos++] = (0x20 + (rle.method << 5)) | size; 546 | 547 | outsize = 2; 548 | } 549 | 550 | out[outpos++] = rle.data; 551 | // write upper byte of 16-bit RLE (and adjust written data size) 552 | if (rle.method == rle_16) { 553 | out[outpos++] = rle.data >> 8; 554 | outsize++; 555 | } 556 | 557 | return outsize; 558 | } 559 | 560 | // Write uncompressed data to the output stream. 561 | // Returns number of bytes written. 562 | uint16_t write_raw (uint8_t *out, uint16_t outpos, uint8_t *in, uint16_t insize) { 563 | if (!insize) return 0; 564 | 565 | #ifdef DEBUG_OUT 566 | printf("write_raw: writing %d bytes unpacked data: ", insize); 567 | for (int i = 0; i < insize; i++) 568 | printf("%02x ", in[i]); 569 | 570 | printf("\n"); 571 | #endif 572 | 573 | uint16_t size = insize - 1; 574 | int outsize; 575 | 576 | if (size >= RUN_SIZE) { 577 | // write command byte + MSB of size 578 | out[outpos++] = 0xE0 + (size >> 8); 579 | // write LSB of size 580 | out[outpos++] = size & 0xFF; 581 | 582 | outsize = insize + 2; 583 | } 584 | // normal size run 585 | else { 586 | // write command byte / size 587 | out[outpos++] = size; 588 | 589 | outsize = insize + 1; 590 | } 591 | 592 | // write data 593 | memcpy(&out[outpos], in, insize); 594 | 595 | return outsize; 596 | } 597 | -------------------------------------------------------------------------------- /src/compress.h: -------------------------------------------------------------------------------- 1 | /* 2 | exhal / inhal (de)compression routines 3 | 4 | Copyright (c) 2013 Devin Acker 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | 24 | */ 25 | 26 | #ifndef _COMPRESS_H 27 | #define _COMPRESS_H 28 | 29 | #ifdef __cplusplus 30 | extern "C" { 31 | #endif 32 | 33 | #include 34 | #include 35 | #include 36 | 37 | #define DATA_SIZE 65536 38 | #define RUN_SIZE 32 39 | #define LONG_RUN_SIZE 1024 40 | 41 | size_t pack (uint8_t *unpacked, size_t inputsize, uint8_t *packed, int fast); 42 | size_t unpack (uint8_t *packed, uint8_t *unpacked); 43 | 44 | size_t unpack_from_file (FILE *file, size_t offset, uint8_t *unpacked); 45 | 46 | #ifdef __cplusplus 47 | } 48 | #endif 49 | 50 | // end include guard 51 | #endif 52 | -------------------------------------------------------------------------------- /src/coursefiles.txt: -------------------------------------------------------------------------------- 1 | KDC course file format: 2 | 3 | Offset Size Description 4 | ------------------------------------------------- 5 | 0 4 String "KDC", null-terminated 6 | 4 1 Game (0 = KDC, 1 = STS) 7 | 5 1 Background number 8 | 6 1 Terrain palette 9 | 7 1 Water palette 10 | 8 8 Music list (one byte per level) 11 | 16 4*8 Pointers to individual levels in file 12 | (a null pointer means level is not present in file; don't try to load it) 13 | 14 | Each individual level consists of the header, followed by the tile data as maptile_t: 15 | (west to east, north to south, w * l * 4 bytes) 16 | 17 | 0 1 Terrain 18 | 1 1 Obstacle 19 | 2 1 Height 20 | 3 1 Flags 21 | -------------------------------------------------------------------------------- /src/coursewindow.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | This code is released under the terms of the MIT license. 3 | See COPYING.txt for details. 4 | */ 5 | 6 | #include "coursewindow.h" 7 | #include "ui_coursewindow.h" 8 | 9 | #include "kirby.h" 10 | #include "level.h" 11 | #include "romfile.h" 12 | 13 | CourseWindow::CourseWindow(QWidget *parent) : 14 | QDialog(parent, Qt::CustomizeWindowHint 15 | | Qt::WindowTitleHint 16 | | Qt::WindowCloseButtonHint 17 | | Qt::MSWindowsFixedSizeDialogHint 18 | ), 19 | ui(new Ui::CourseWindow) 20 | { 21 | ui->setupUi(this); 22 | 23 | // prevent window resizing 24 | this->layout()->setSizeConstraint(QLayout::SetFixedSize); 25 | } 26 | 27 | CourseWindow::~CourseWindow() 28 | { 29 | delete ui; 30 | } 31 | 32 | int CourseWindow::select(int level, ROMFile::game_e game) { 33 | // add course names to dropdown 34 | ui->comboBox->clear(); 35 | for (int i = 0; i < numLevels[game] / 8; i++) 36 | ui->comboBox->addItem(courseNames[game][i]); 37 | 38 | ui->comboBox->setCurrentIndex(level / 8); 39 | ui->spinBox->setValue((level % 8) + 1); 40 | 41 | if (this->exec()) 42 | return ui->comboBox->currentIndex() * 8 43 | + ui->spinBox->value() - 1; 44 | 45 | return level; 46 | } 47 | -------------------------------------------------------------------------------- /src/coursewindow.h: -------------------------------------------------------------------------------- 1 | /* 2 | This code is released under the terms of the MIT license. 3 | See COPYING.txt for details. 4 | */ 5 | 6 | #ifndef COURSEWINDOW_H 7 | #define COURSEWINDOW_H 8 | 9 | #include 10 | #include "level.h" 11 | #include "romfile.h" 12 | 13 | namespace Ui { 14 | class CourseWindow; 15 | } 16 | 17 | class CourseWindow : public QDialog 18 | { 19 | Q_OBJECT 20 | 21 | public: 22 | explicit CourseWindow(QWidget *parent = 0); 23 | ~CourseWindow(); 24 | 25 | int select(int level, ROMFile::game_e game = ROMFile::kirby); 26 | 27 | private: 28 | Ui::CourseWindow *ui; 29 | }; 30 | 31 | #endif // COURSEWINDOW_H 32 | -------------------------------------------------------------------------------- /src/coursewindow.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | CourseWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 303 10 | 69 11 | 12 | 13 | 14 | 15 | 0 16 | 0 17 | 18 | 19 | 20 | Select Course 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 0 30 | 0 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | Hole 39 | 40 | 41 | 42 | 43 | 44 | 45 | Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 46 | 47 | 48 | 1 49 | 50 | 51 | 8 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | Qt::Horizontal 61 | 62 | 63 | QDialogButtonBox::Cancel|QDialogButtonBox::Ok 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | buttonBox 73 | accepted() 74 | CourseWindow 75 | accept() 76 | 77 | 78 | 248 79 | 254 80 | 81 | 82 | 157 83 | 274 84 | 85 | 86 | 87 | 88 | buttonBox 89 | rejected() 90 | CourseWindow 91 | reject() 92 | 93 | 94 | 316 95 | 260 96 | 97 | 98 | 286 99 | 274 100 | 101 | 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /src/graphics.h: -------------------------------------------------------------------------------- 1 | /* 2 | This code is released under the terms of the MIT license. 3 | See COPYING.txt for details. 4 | */ 5 | 6 | #ifndef GRAPHICS_H 7 | #define GRAPHICS_H 8 | 9 | #define PAL(x) (x << 10) 10 | #define PALN(x) (x >> 10 & 0x7) 11 | #define TILE(x) (x & 0x3FF) 12 | #define PRI 0x2000 13 | #define FH 0x4000 14 | #define FV 0x8000 15 | #define FB FH|FV 16 | 17 | #define TILE_SIZE 64 18 | #define ISO_TILE_SIZE 8 19 | 20 | #endif // GRAPHICS_H 21 | -------------------------------------------------------------------------------- /src/icons.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | images/icons/arrow_in.png 4 | images/icons/brick_edit.png 5 | images/icons/cut.png 6 | images/icons/delete.png 7 | images/icons/disk.png 8 | images/icons/door_in.png 9 | images/icons/folder.png 10 | images/icons/help.png 11 | images/icons/image.png 12 | images/icons/paste_plain.png 13 | images/icons/resultset_first.png 14 | images/icons/resultset_last.png 15 | images/icons/resultset_next.png 16 | images/icons/resultset_previous.png 17 | images/icons/wand.png 18 | images/icons/page_copy.png 19 | images/icons/magnifier.png 20 | images/icons/arrow_redo.png 21 | images/icons/arrow_undo.png 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/images.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | images/water.png 4 | images/terrain.png 5 | images/bounce.png 6 | images/traps.png 7 | images/conveyor.png 8 | images/kirby.png 9 | images/warps.png 10 | images/unknown.png 11 | images/gordo.png 12 | images/enemies.png 13 | images/rotate.png 14 | images/3dtiles.png 15 | images/movers.png 16 | images/bumpers.png 17 | images/3dtiles-water.png 18 | images/switches.png 19 | images/dedede.png 20 | images/gordo3d.png 21 | images/main16.png 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/images/3dtiles-water.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devinacker/kdceditor/52069434e7945bf5651bdd497b78aa08ab168778/src/images/3dtiles-water.png -------------------------------------------------------------------------------- /src/images/3dtiles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devinacker/kdceditor/52069434e7945bf5651bdd497b78aa08ab168778/src/images/3dtiles.png -------------------------------------------------------------------------------- /src/images/bounce.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devinacker/kdceditor/52069434e7945bf5651bdd497b78aa08ab168778/src/images/bounce.png -------------------------------------------------------------------------------- /src/images/bumpers.pdn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devinacker/kdceditor/52069434e7945bf5651bdd497b78aa08ab168778/src/images/bumpers.pdn -------------------------------------------------------------------------------- /src/images/bumpers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devinacker/kdceditor/52069434e7945bf5651bdd497b78aa08ab168778/src/images/bumpers.png -------------------------------------------------------------------------------- /src/images/conveyor.pdn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devinacker/kdceditor/52069434e7945bf5651bdd497b78aa08ab168778/src/images/conveyor.pdn -------------------------------------------------------------------------------- /src/images/conveyor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devinacker/kdceditor/52069434e7945bf5651bdd497b78aa08ab168778/src/images/conveyor.png -------------------------------------------------------------------------------- /src/images/dedede.pdn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devinacker/kdceditor/52069434e7945bf5651bdd497b78aa08ab168778/src/images/dedede.pdn -------------------------------------------------------------------------------- /src/images/dedede.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devinacker/kdceditor/52069434e7945bf5651bdd497b78aa08ab168778/src/images/dedede.png -------------------------------------------------------------------------------- /src/images/enemies.pdn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devinacker/kdceditor/52069434e7945bf5651bdd497b78aa08ab168778/src/images/enemies.pdn -------------------------------------------------------------------------------- /src/images/enemies.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devinacker/kdceditor/52069434e7945bf5651bdd497b78aa08ab168778/src/images/enemies.png -------------------------------------------------------------------------------- /src/images/gordo.pdn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devinacker/kdceditor/52069434e7945bf5651bdd497b78aa08ab168778/src/images/gordo.pdn -------------------------------------------------------------------------------- /src/images/gordo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devinacker/kdceditor/52069434e7945bf5651bdd497b78aa08ab168778/src/images/gordo.png -------------------------------------------------------------------------------- /src/images/gordo3d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devinacker/kdceditor/52069434e7945bf5651bdd497b78aa08ab168778/src/images/gordo3d.png -------------------------------------------------------------------------------- /src/images/grid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devinacker/kdceditor/52069434e7945bf5651bdd497b78aa08ab168778/src/images/grid.png -------------------------------------------------------------------------------- /src/images/icons/arrow_in.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devinacker/kdceditor/52069434e7945bf5651bdd497b78aa08ab168778/src/images/icons/arrow_in.png -------------------------------------------------------------------------------- /src/images/icons/arrow_redo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devinacker/kdceditor/52069434e7945bf5651bdd497b78aa08ab168778/src/images/icons/arrow_redo.png -------------------------------------------------------------------------------- /src/images/icons/arrow_undo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devinacker/kdceditor/52069434e7945bf5651bdd497b78aa08ab168778/src/images/icons/arrow_undo.png -------------------------------------------------------------------------------- /src/images/icons/brick_edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devinacker/kdceditor/52069434e7945bf5651bdd497b78aa08ab168778/src/images/icons/brick_edit.png -------------------------------------------------------------------------------- /src/images/icons/cut.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devinacker/kdceditor/52069434e7945bf5651bdd497b78aa08ab168778/src/images/icons/cut.png -------------------------------------------------------------------------------- /src/images/icons/delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devinacker/kdceditor/52069434e7945bf5651bdd497b78aa08ab168778/src/images/icons/delete.png -------------------------------------------------------------------------------- /src/images/icons/disk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devinacker/kdceditor/52069434e7945bf5651bdd497b78aa08ab168778/src/images/icons/disk.png -------------------------------------------------------------------------------- /src/images/icons/door_in.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devinacker/kdceditor/52069434e7945bf5651bdd497b78aa08ab168778/src/images/icons/door_in.png -------------------------------------------------------------------------------- /src/images/icons/folder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devinacker/kdceditor/52069434e7945bf5651bdd497b78aa08ab168778/src/images/icons/folder.png -------------------------------------------------------------------------------- /src/images/icons/help.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devinacker/kdceditor/52069434e7945bf5651bdd497b78aa08ab168778/src/images/icons/help.png -------------------------------------------------------------------------------- /src/images/icons/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devinacker/kdceditor/52069434e7945bf5651bdd497b78aa08ab168778/src/images/icons/image.png -------------------------------------------------------------------------------- /src/images/icons/magnifier.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devinacker/kdceditor/52069434e7945bf5651bdd497b78aa08ab168778/src/images/icons/magnifier.png -------------------------------------------------------------------------------- /src/images/icons/page_copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devinacker/kdceditor/52069434e7945bf5651bdd497b78aa08ab168778/src/images/icons/page_copy.png -------------------------------------------------------------------------------- /src/images/icons/paste_plain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devinacker/kdceditor/52069434e7945bf5651bdd497b78aa08ab168778/src/images/icons/paste_plain.png -------------------------------------------------------------------------------- /src/images/icons/resultset_first.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devinacker/kdceditor/52069434e7945bf5651bdd497b78aa08ab168778/src/images/icons/resultset_first.png -------------------------------------------------------------------------------- /src/images/icons/resultset_last.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devinacker/kdceditor/52069434e7945bf5651bdd497b78aa08ab168778/src/images/icons/resultset_last.png -------------------------------------------------------------------------------- /src/images/icons/resultset_next.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devinacker/kdceditor/52069434e7945bf5651bdd497b78aa08ab168778/src/images/icons/resultset_next.png -------------------------------------------------------------------------------- /src/images/icons/resultset_previous.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devinacker/kdceditor/52069434e7945bf5651bdd497b78aa08ab168778/src/images/icons/resultset_previous.png -------------------------------------------------------------------------------- /src/images/icons/wand.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devinacker/kdceditor/52069434e7945bf5651bdd497b78aa08ab168778/src/images/icons/wand.png -------------------------------------------------------------------------------- /src/images/kirby.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devinacker/kdceditor/52069434e7945bf5651bdd497b78aa08ab168778/src/images/kirby.png -------------------------------------------------------------------------------- /src/images/main.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devinacker/kdceditor/52069434e7945bf5651bdd497b78aa08ab168778/src/images/main.icns -------------------------------------------------------------------------------- /src/images/main.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devinacker/kdceditor/52069434e7945bf5651bdd497b78aa08ab168778/src/images/main.ico -------------------------------------------------------------------------------- /src/images/main16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devinacker/kdceditor/52069434e7945bf5651bdd497b78aa08ab168778/src/images/main16.png -------------------------------------------------------------------------------- /src/images/main32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devinacker/kdceditor/52069434e7945bf5651bdd497b78aa08ab168778/src/images/main32.png -------------------------------------------------------------------------------- /src/images/main48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devinacker/kdceditor/52069434e7945bf5651bdd497b78aa08ab168778/src/images/main48.png -------------------------------------------------------------------------------- /src/images/main64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devinacker/kdceditor/52069434e7945bf5651bdd497b78aa08ab168778/src/images/main64.png -------------------------------------------------------------------------------- /src/images/mainicon.pdn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devinacker/kdceditor/52069434e7945bf5651bdd497b78aa08ab168778/src/images/mainicon.pdn -------------------------------------------------------------------------------- /src/images/movers.pdn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devinacker/kdceditor/52069434e7945bf5651bdd497b78aa08ab168778/src/images/movers.pdn -------------------------------------------------------------------------------- /src/images/movers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devinacker/kdceditor/52069434e7945bf5651bdd497b78aa08ab168778/src/images/movers.png -------------------------------------------------------------------------------- /src/images/rotate.pdn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devinacker/kdceditor/52069434e7945bf5651bdd497b78aa08ab168778/src/images/rotate.pdn -------------------------------------------------------------------------------- /src/images/rotate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devinacker/kdceditor/52069434e7945bf5651bdd497b78aa08ab168778/src/images/rotate.png -------------------------------------------------------------------------------- /src/images/switches.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devinacker/kdceditor/52069434e7945bf5651bdd497b78aa08ab168778/src/images/switches.png -------------------------------------------------------------------------------- /src/images/terrain.pdn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devinacker/kdceditor/52069434e7945bf5651bdd497b78aa08ab168778/src/images/terrain.pdn -------------------------------------------------------------------------------- /src/images/terrain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devinacker/kdceditor/52069434e7945bf5651bdd497b78aa08ab168778/src/images/terrain.png -------------------------------------------------------------------------------- /src/images/tiletemplate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devinacker/kdceditor/52069434e7945bf5651bdd497b78aa08ab168778/src/images/tiletemplate.png -------------------------------------------------------------------------------- /src/images/traps.pdn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devinacker/kdceditor/52069434e7945bf5651bdd497b78aa08ab168778/src/images/traps.pdn -------------------------------------------------------------------------------- /src/images/traps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devinacker/kdceditor/52069434e7945bf5651bdd497b78aa08ab168778/src/images/traps.png -------------------------------------------------------------------------------- /src/images/unknown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devinacker/kdceditor/52069434e7945bf5651bdd497b78aa08ab168778/src/images/unknown.png -------------------------------------------------------------------------------- /src/images/warps.pdn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devinacker/kdceditor/52069434e7945bf5651bdd497b78aa08ab168778/src/images/warps.pdn -------------------------------------------------------------------------------- /src/images/warps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devinacker/kdceditor/52069434e7945bf5651bdd497b78aa08ab168778/src/images/warps.png -------------------------------------------------------------------------------- /src/images/water.pdn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devinacker/kdceditor/52069434e7945bf5651bdd497b78aa08ab168778/src/images/water.pdn -------------------------------------------------------------------------------- /src/images/water.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devinacker/kdceditor/52069434e7945bf5651bdd497b78aa08ab168778/src/images/water.png -------------------------------------------------------------------------------- /src/kirby.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | This code is released under the terms of the MIT license. 3 | See COPYING.txt for details. 4 | */ 5 | 6 | #include "kirby.h" 7 | #include 8 | 9 | const int fgPaletteBase[] = {0xD4A9, 0xD8ED, 0xD8ED}; 10 | const int waterBase[][3] = {{0xA444, 0xE030, 0xE030}, 11 | {0xA46E, 0xE05A, 0xE05A}}; 12 | 13 | const int paletteTable[] = {0x80D425, 0x80D869, 0x80D869}; 14 | const int waterTable[][3] = {{0x8484AF, 0x8484AF, 0x8484AF}, 15 | {0x8484F1, 0x8484F1, 0x8484F1}}; 16 | const int backgroundTable[][3] = {{0x80D0AF, 0x80D517, 0x80D517}, 17 | {0x80D304, 0x80D748, 0x80D748}, 18 | {0x80D324, 0x80D768, 0x80D768}, 19 | {0x84CD23, 0x84CD42, 0x84CD42}}; 20 | 21 | const int musicTable[] = {0x80C533, 0x80C99D, 0x80C99D}; 22 | const int newMusicAddr[] = {0x80F440, 0x80F950, 0x80F950}; 23 | 24 | // first dimension is indexed by game number (0 = kirby, 1 = sts) 25 | const char* courseNames[][224 / 8] = { 26 | // KDC / Kirby Bowl courses 27 | { 28 | "1P Course 1", 29 | "1P Course 2", 30 | "1P Course 3", 31 | "1P Course 4", 32 | "1P Course 5", 33 | "1P Course 6", 34 | "1P Course 7", 35 | "1P Course 8", 36 | "1P Extra Course 1", 37 | "1P Extra Course 2", 38 | "1P Extra Course 3", 39 | "1P Extra Course 4", 40 | "1P Extra Course 5", 41 | "1P Extra Course 6", 42 | "1P Extra Course 7", 43 | "1P Extra Course 8", 44 | "2P Course 1", 45 | "2P Course 2", 46 | "2P Course 3", 47 | "2P Course 4", 48 | "Demo Course 1", 49 | "Demo Course 2", 50 | "Demo Course 3 / Test Course", 51 | "Test Course", 52 | "2P Extra Course 1", 53 | "2P Extra Course 2", 54 | "2P Extra Course 3", 55 | "2P Extra Course 4" 56 | }, 57 | // Special Tee Shot courses 58 | { 59 | "Beginner Course", 60 | "Amateur Course", 61 | "Professional Course", 62 | "Master Course", 63 | "Extra Course 1", 64 | "Extra Course 2", 65 | "Extra Course 3", 66 | "Extra Course 4", 67 | "Gold Course" 68 | } 69 | }; 70 | 71 | const bg_t bgNames[] = { 72 | {"Background 1 (clouds)", 73 | {0x8290, 0xC290, 0xC290}, 74 | {0x92BEB1, 0x90A836, 0x90A836}, 75 | {0x94AC83, 0x928000, 0x92855B}, 76 | {0xCD33, 0xCD52, 0xCD52}}, 77 | 78 | {"Background 2 (stars & moon)", 79 | {0x83D0, 0xC3D0, 0xC3D0}, 80 | {0x92D18C, 0x90B1B2, 0x90B1B2}, 81 | {0x94EDAB, 0x8EFB5F, 0x8EFB5F}, 82 | {0xCECA, 0xCEE9, 0xCEE9}}, 83 | 84 | {"Background 3 (waterfalls)", 85 | {0x8330, 0xC330, 0xC330}, 86 | {0x93A043, 0x90ED83, 0x90ED83}, 87 | {0x94967D, 0x91E7A2, 0x91EE62}, 88 | {0xCE79, 0xCE98, 0xCE98}}, 89 | 90 | {"Background 4 (jigsaw)", 91 | {0x82E0, 0xC2E0, 0xC2E0}, 92 | {0x93E286, 0x91AD5B, 0x91B41B}, 93 | {0x93D5F8, 0x91A0CD, 0x91A78D}, 94 | {0xCE64, 0xCE83, 0xCE83}}, 95 | 96 | {"Background 5 (candy)", 97 | {0x8380, 0xC380, 0xC380}, 98 | {0x92AB0F, 0x909494, 0x909494}, 99 | {0x93FA68, 0x91E20F, 0x91E8CF}, 100 | {0xCFAE, 0xCFCD, 0xCFCD}}, 101 | 102 | {"Background 6 (ocean)", 103 | {0x85E0, 0xC5E0, 0xC5E0}, 104 | {0x9398A1, 0x90E5E1, 0x90E5E1}, 105 | {0x94DA7C, 0x92B347, 0x92B8A2}, 106 | {0xCFF3, 0xD012, 0xD012}} 107 | /* 108 | something here (the GFX?) gets loaded to a different address than normal 109 | so it's not usable with the rest of the course BGs 110 | {"Background 7 (diamonds)", 111 | {0xD368, 0}, 112 | {0x93EEC0, 0}, 113 | {0x9798EF, 0}, 114 | {0xD12D, 0}}, 115 | */ 116 | }; 117 | 118 | const char* paletteNames[] = { 119 | "Course 1 (blue)", 120 | "Course 2 (green)", 121 | "Course 3 (purple)", 122 | "Course 4 (pink)", 123 | "Course 5 (tan)", 124 | "Course 6 (beige)", 125 | "Course 7 (grey)", 126 | "Course 8 (red)", 127 | "Extra course 7/8 (dark grey)", 128 | "Demo course (teal)" 129 | }; 130 | 131 | const StringMap musicNames ({ 132 | {0x7e, "7E: (none)"}, 133 | {0x80, "80: Epilogue"}, 134 | {0x82, "82: Title"}, 135 | {0x83, "83: Opening demo (JP only)"}, 136 | {0x84, "84: High scores"}, 137 | {0x85, "85: Space Valley (course 2/7b)"}, 138 | {0x86, "86: Over Water (course 1b)"}, 139 | {0x87, "87: The Tricky Stuff (course 5b)"}, 140 | {0x88, "88: Castles of Cake (course 6)"}, 141 | {0x89, "89: Green Fields (course 5a/7a)"}, 142 | {0x8a, "8A: The First Hole (course 1a/3)"}, 143 | {0x8b, "8B: Iceberg Ocean (course 8)"}, 144 | {0x8c, "8C: Last Hole"}, 145 | {0x8d, "8D: Jigsaw Plains (course 4)"}, 146 | {0x8f, "8F: Continue?"}, 147 | {0x92, "92: Final score"}, 148 | {0x93, "93: 2P course select"}, 149 | {0x94, "94: Eyecatch"}, 150 | {0x95, "95: Main menu"}, 151 | {0x96, "96: 1P course select"}, 152 | {0x97, "97: Scorecard"}, 153 | {0x9a, "9A: Demo play"}, 154 | {0x9b, "9B: Dedede 1"}, 155 | {0x9c, "9C: Dedede 2"}, 156 | {0x9f, "9F: Game over"} 157 | }); 158 | 159 | const StringMap kirbyGeometry ({ 160 | {0, "00: None"}, 161 | {1, "01: Flat"}, 162 | {2, "02: Four slopes up towards center"}, 163 | 164 | // this type is unusable 165 | // {3, "03: Four slopes down into ground"}, 166 | 167 | {4, "04: Slope down towards south"}, 168 | {5, "05: Slope down towards east"}, 169 | {6, "06: Slope down towards north"}, 170 | {7, "07: Slope down towards west"}, 171 | 172 | {8, "08: Slopes down towards south and east (inner)"}, 173 | {9, "09: Slopes down towards north and east (inner)"}, 174 | {10, "0A: Slopes down towards north and west (inner)"}, 175 | {11, "0B: Slopes down towards south and west (inner)"}, 176 | 177 | {12, "0C: Slopes down towards south and east (outer)"}, 178 | {13, "0D: Slopes down towards north and east (outer)"}, 179 | {14, "0E: Slopes down towards north and west (outer)"}, 180 | {15, "0F: Slopes down towards south and west (outer)"}, 181 | 182 | {16, "10: Slope down towards southeast (top)"}, 183 | {17, "11: Slope down towards northeast (top)"}, 184 | {18, "12: Slope down towards northwest (top)"}, 185 | {19, "13: Slope down towards southwest (top)"}, 186 | 187 | {20, "14: Slope down towards southeast (bottom)"}, 188 | {21, "15: Slope down towards northeast (bottom)"}, 189 | {22, "16: Slope down towards northwest (bottom)"}, 190 | {23, "17: Slope down towards southwest (bottom)"}, 191 | 192 | {24, "18: Slope down towards southeast (middle)"}, 193 | {25, "19: Slope down towards northeast (middle)"}, 194 | {26, "1A: Slope down towards northwest (middle)"}, 195 | {27, "1B: Slope down towards southwest (middle)"} 196 | }); 197 | 198 | const StringMap kirbyObstacles ({ 199 | {0x00, "00: None"}, 200 | 201 | {0x02, "02: Whispy Woods"}, 202 | 203 | {0x04, "04: Sand trap"}, 204 | {0x05, "05: Spike pit"}, 205 | 206 | /* 207 | hole can't be placed independently in KDC 208 | (unless it can be placed on obstacle layer w/ correct palette somehow) 209 | {0x08, "08: Hole"}, 210 | */ 211 | 212 | {0x0c, "0C: Kirby"}, 213 | {0x0d, "0D: King Dedede (course 24-1 only)"}, 214 | 215 | {0x10, "10: Current (south)"}, 216 | {0x11, "11: Current (east)"}, 217 | {0x12, "12: Current (north)"}, 218 | {0x13, "13: Current (west)"}, 219 | 220 | {0x14, "14: Arrow (south)"}, 221 | {0x15, "15: Arrow (east)"}, 222 | {0x16, "16: Arrow (north)"}, 223 | {0x17, "17: Arrow (west)"}, 224 | 225 | {0x18, "18: Booster (south)"}, 226 | {0x19, "19: Booster (east)"}, 227 | {0x1a, "1A: Booster (north)"}, 228 | {0x1b, "1B: Booster (west)"}, 229 | 230 | {0x1c, "1C: Air vent (north-south)"}, 231 | {0x1d, "1D: Air vent (east-west)"}, 232 | 233 | {0x20, "20: Bounce (use with tile 04)"}, 234 | {0x21, "21: Bounce (use with tile 05)"}, 235 | {0x22, "22: Bounce (use with tile 06)"}, 236 | {0x23, "23: Bounce (use with tile 07)"}, 237 | {0x24, "24: Bounce"}, 238 | 239 | {0x28, "28: Bumper (north to south)"}, 240 | {0x29, "29: Bumper (east to west)"}, 241 | {0x2a, "2A: Bumper (south to west)"}, 242 | {0x2b, "2B: Bumper (north to west)"}, 243 | {0x2c, "2C: Bumper (north to east)"}, 244 | {0x2d, "2D: Bumper (south to east)"}, 245 | 246 | {0x30, "30: Conveyor belt (south)"}, 247 | {0x31, "31: Conveyor belt (east)"}, 248 | {0x32, "32: Conveyor belt (north)"}, 249 | {0x33, "33: Conveyor belt (west)"}, 250 | 251 | {0x34, "34: Conveyor belt (north, use with tile 04)"}, 252 | {0x35, "35: Conveyor belt (south, use with tile 04)"}, 253 | {0x36, "36: Conveyor belt (west, use with tile 05)"}, 254 | {0x37, "37: Conveyor belt (east, use with tile 05)"}, 255 | {0x38, "38: Conveyor belt (south, use with tile 06)"}, 256 | {0x39, "39: Conveyor belt (north, use with tile 06)"}, 257 | {0x3a, "3A: Conveyor belt (east, use with tile 07)"}, 258 | {0x3b, "3B: Conveyor belt (west, use with tile 07)"}, 259 | 260 | {0x40, "40: Waddle Dee"}, 261 | {0x41, "41: Rocky"}, 262 | {0x42, "42: Waddle Doo"}, 263 | {0x43, "43: Flamer"}, 264 | {0x44, "44: Spiney"}, 265 | {0x45, "45: Twister"}, 266 | {0x46, "46: Wheelie"}, 267 | {0x47, "47: Sparky"}, 268 | {0x48, "48: Starman"}, 269 | {0x49, "49: Chilly"}, 270 | {0x4a, "4A: Broom Hatter"}, 271 | {0x4b, "4B: Squishy"}, 272 | {0x4c, "4C: Kabu"}, 273 | {0x4d, "4D: Gaspar"}, 274 | {0x4e, "4E: Pumpkin"}, 275 | {0x4f, "4F: UFO"}, 276 | {0x50, "50: Gaspar (higher)"}, 277 | {0x51, "51: Pumpkin (higher)"}, 278 | {0x52, "52: UFO (higher)"}, 279 | {0x57, "57: Transformer"}, 280 | 281 | {0x58, "58: Mr. Bright switch"}, 282 | {0x59, "59: Mr. Shine switch"}, 283 | {0x5a, "5A: Rotating space switch (off)"}, 284 | {0x5b, "5B: Rotating space switch (on)"}, 285 | {0x5c, "5C: Water switch (on)"}, 286 | {0x5d, "5D: Water switch (off)"}, 287 | 288 | {0x61, "61: Water hazard"}, 289 | {0x64, "64: Water hazard (use with tile 04)"}, 290 | {0x65, "65: Water hazard (use with tile 05)"}, 291 | {0x66, "66: Water hazard (use with tile 06)"}, 292 | {0x67, "67: Water hazard (use with tile 07)"}, 293 | {0x68, "68: Water hazard (use with tile 08)"}, 294 | {0x69, "69: Water hazard (use with tile 09)"}, 295 | {0x6a, "6A: Water hazard (use with tile 0A)"}, 296 | {0x6b, "6B: Water hazard (use with tile 0B)"}, 297 | {0x6c, "6C: Water hazard (use with tile 0C)"}, 298 | {0x6d, "6D: Water hazard (use with tile 0D)"}, 299 | {0x6e, "6E: Water hazard (use with tile 0E)"}, 300 | {0x6f, "6F: Water hazard (use with tile 0F)"}, 301 | 302 | /* 303 | Most of these are not used in KDC. 304 | */ 305 | {0x70, "70: Rotating space (clockwise, always on)"}, 306 | {0x71, "71: Rotating space (counterclockwise, always on)"}, 307 | {0x72, "72: Rotating space (clockwise, always on, slow)"}, 308 | {0x73, "73: Rotating space (counterclockwise, always on, slow)"}, 309 | {0x74, "74: Rotating space (clockwise, switch)"}, 310 | {0x75, "75: Rotating space (counterclockwise, switch)"}, 311 | {0x76, "76: Rotating space (clockwise, switch, slow)"}, 312 | {0x77, "77: Rotating space (counterclockwise, switch, slow)"}, 313 | {0x78, "78: Rotating space (clockwise, switch-opposite)"}, 314 | {0x79, "79: Rotating space (counterclockwise, switch-opposite)"}, 315 | {0x7a, "7A: Rotating space (clockwise, switch-opposite, slow)"}, 316 | {0x7b, "7B: Rotating space (counterclockwise, switch-opposite, slow)"}, 317 | 318 | {0x80, "80: Gordo (moves south, faces south)"}, 319 | {0x81, "81: Gordo (moves east, faces south)"}, 320 | {0x82, "82: Gordo (moves north, faces south)"}, 321 | {0x83, "83: Gordo (moves west, faces south)"}, 322 | {0x84, "84: Gordo (moves south, faces east)"}, 323 | {0x85, "85: Gordo (moves east, faces east)"}, 324 | {0x86, "86: Gordo (moves north, faces east)"}, 325 | {0x87, "87: Gordo (moves west, faces east)"}, 326 | {0x88, "88: Gordo (moves south, faces north)"}, 327 | {0x89, "89: Gordo (moves east, faces north)"}, 328 | {0x8a, "8A: Gordo (moves north, faces north)"}, 329 | {0x8b, "8B: Gordo (moves west, faces north)"}, 330 | {0x8c, "8C: Gordo (moves south, faces west)"}, 331 | {0x8d, "8D: Gordo (moves east, faces west)"}, 332 | {0x8e, "8E: Gordo (moves north, faces west)"}, 333 | {0x8f, "8F: Gordo (moves west, faces west)"}, 334 | 335 | {0x90, "90: Gordo (moves up/down, faces south)"}, 336 | {0x91, "91: Gordo (moves up/down, faces east)"}, 337 | {0x92, "92: Gordo (moves up/down, faces north)"}, 338 | {0x93, "93: Gordo (moves up/down, faces west)"}, 339 | {0x94, "94: Gordo (moves down/up, faces south)"}, 340 | {0x95, "95: Gordo (moves down/up, faces east)"}, 341 | {0x96, "96: Gordo (moves down/up, faces north)"}, 342 | {0x97, "97: Gordo (moves down/up, faces west)"}, 343 | 344 | {0x98, "98: Gordo path (north-south)"}, 345 | {0x99, "99: Gordo path (east-west)"}, 346 | {0x9a, "9A: Gordo path (northwest corner)"}, 347 | {0x9b, "9B: Gordo path (southwest corner)"}, 348 | {0x9c, "9C: Gordo path (southeast corner)"}, 349 | {0x9d, "9D: Gordo path (northeast corner)"}, 350 | {0x9e, "9E: Gordo endpoint (south)"}, 351 | {0x9f, "9F: Gordo endpoint (east)"}, 352 | {0xa0, "A0: Gordo endpoint (north)"}, 353 | {0xa1, "A1: Gordo endpoint (west)"}, 354 | 355 | {0xac, "AC: Kracko (no lightning)"}, 356 | {0xad, "AD: Kracko (lightning 1)"}, 357 | {0xae, "AE: Kracko (lightning 2)"}, 358 | 359 | {0xb0, "B0: Blue warp 1 (south)"}, 360 | {0xb1, "B1: Blue warp 1 (east)"}, 361 | {0xb2, "B2: Blue warp 1 (north)"}, 362 | {0xb3, "B3: Blue warp 1 (west)"}, 363 | {0xb4, "B4: Blue warp 2 (south)"}, 364 | {0xb5, "B5: Blue warp 2 (east)"}, 365 | {0xb6, "B6: Blue warp 2 (north)"}, 366 | {0xb7, "B7: Blue warp 2 (west)"}, 367 | {0xb8, "B8: Red warp 1"}, 368 | {0xb9, "B9: Red warp 2"}, 369 | 370 | {0xc0, "C0: Starting line (west end)"}, 371 | {0xc1, "C1: Starting line"}, 372 | {0xc2, "C2: Starting line (east end)"}, 373 | {0xc3, "C3: Kirby (course 24-1 only)"}, 374 | }); 375 | -------------------------------------------------------------------------------- /src/kirby.h: -------------------------------------------------------------------------------- 1 | /* 2 | This code is released under the terms of the MIT license. 3 | See COPYING.txt for details. 4 | */ 5 | 6 | #ifndef KIRBY_H 7 | #define KIRBY_H 8 | 9 | #define KIRBY_GEOM_TYPES 0x1C 10 | #define KIRBY_OBSTACLE_TYPES 0xC4 11 | #define NUM_FG_PALETTES 10 12 | #define FG_PALETTE_SIZE 0x20 13 | #define WATER_PALETTE_SIZE 0x120 14 | #define BG_PALETTE_SIZE 0x28 15 | #define NUM_BACKGROUNDS 6 16 | 17 | #include 18 | #include 19 | 20 | typedef std::map StringMap; 21 | 22 | typedef struct { 23 | char name[50]; 24 | int palette[3]; 25 | int pointer1[3]; 26 | int pointer2[3]; 27 | int anim[3]; 28 | } bg_t; 29 | 30 | extern const int fgPaletteBase[3]; 31 | extern const int waterBase[2][3]; 32 | extern const int bgPaletteBase[3]; 33 | 34 | extern const int paletteTable[3]; 35 | extern const int waterTable[2][3]; 36 | extern const int backgroundTable[4][3]; 37 | extern const int musicTable[3]; 38 | extern const int newMusicAddr[3]; 39 | 40 | extern const char* courseNames[][224 / 8]; 41 | extern const bg_t bgNames[]; 42 | extern const char* paletteNames[]; 43 | extern const StringMap musicNames; 44 | 45 | extern const StringMap kirbyGeometry; 46 | extern const StringMap kirbyObstacles; 47 | 48 | #endif // KIRBY_H 49 | -------------------------------------------------------------------------------- /src/level.h: -------------------------------------------------------------------------------- 1 | /* 2 | This code is released under the terms of the MIT license. 3 | See COPYING.txt for details. 4 | */ 5 | 6 | #ifndef LEVEL_H 7 | #define LEVEL_H 8 | 9 | #include "romfile.h" 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #define CHUNK_SIZE 2048 18 | #define BIG_CHUNK_SIZE 26624 19 | 20 | #define MAX_2D_AREA CHUNK_SIZE 21 | #define MAX_3D_AREA (BIG_CHUNK_SIZE / 2) 22 | 23 | // these are the limits for length/width and height of levels! 24 | // (for individual dimensions only; see the above two #defines too) 25 | #define MAX_2D_SIZE 100 26 | #define MAX_HEIGHT 49 27 | 28 | // maximum size of the 8x8 tile maps based on 2D map size 29 | // (with a bit of padding at the bottom just because) 30 | #define MAX_FIELD_HEIGHT (2 * (MAX_HEIGHT + MAX_2D_SIZE + MAX_2D_SIZE + 2)) 31 | #define MAX_FIELD_WIDTH (8 * MAX_2D_SIZE) 32 | 33 | extern const int numLevels[]; 34 | extern const int clippingTable[]; 35 | 36 | // location of where to write new level data 37 | extern const uint newDataAddress[]; 38 | 39 | /* 40 | The level header. Some fields currently unknown. 41 | Most of this should be generated automatically by the editor. 42 | */ 43 | #pragma pack(1) 44 | typedef struct { 45 | uint16_t dummy1; 46 | uint16_t width, length; 47 | uint16_t dummy2; 48 | uint16_t fieldWidth, fieldHeight; 49 | uint16_t alignHoriz, alignVert; 50 | char mapID[12]; 51 | } header_t; 52 | 53 | /* 54 | Level tile info (in chunks 1 to 4). 55 | */ 56 | typedef struct { 57 | uint8_t geometry, obstacle, height; 58 | struct { 59 | unsigned bumperSouth: 1; 60 | unsigned bumperEast: 1; 61 | unsigned bumperNorth: 1; 62 | unsigned bumperWest: 1; 63 | unsigned dummy: 3; 64 | unsigned layer: 1; 65 | } flags; 66 | } maptile_t; 67 | 68 | /* 69 | Z-clipping hash table entry (in chunk 10). 70 | */ 71 | typedef struct { 72 | uint8_t xLower, xUpper, prio; 73 | uint16_t zref; 74 | } clip_t; 75 | #pragma pack() 76 | 77 | extern const maptile_t noTile; 78 | 79 | /* 80 | Level tile info as it is passed to/from the tile edit window 81 | (and possibly a "copy/paste tile properties" feature in the future.) 82 | */ 83 | typedef struct { 84 | int geometry, obstacle, height, 85 | bumperSouth, bumperEast, bumperNorth, bumperWest, 86 | layer, minHeight, maxHeight; 87 | } tileinfo_t; 88 | 89 | /* 90 | Definition for level data. 91 | Currently consists of tile and obstacle data and flags, as well as modified state 92 | (both overall and for the current session) and music track. 93 | */ 94 | typedef struct { 95 | header_t header; 96 | 97 | // The maximum area of a map is 2048 maptiles. 64 is the maximum 98 | // size of either dimension needed to fit all of the original levels 99 | // (but the maximum size of each dimension depends on the size of the other, 100 | // so that width/length is always <= 2048. 101 | maptile_t tiles[MAX_2D_SIZE][MAX_2D_SIZE]; 102 | 103 | // have any of the tile data fields been changed from the original data? 104 | // (determined based on their position in the ROM file, also set as soon 105 | // as level is edited) 106 | bool modified; 107 | // have any of the tile data fields been changed in this session? 108 | // (set when modified, cleared when saved) 109 | bool modifiedRecently; 110 | 111 | // music track number. see kirby.cpp (or stuff.cpp if i ever rename it) 112 | uint8_t music; 113 | } leveldata_t; 114 | 115 | /* 116 | Functions for loading/saving level data 117 | */ 118 | leveldata_t* loadLevel(ROMFile& file, uint num); 119 | QList saveLevel(leveldata_t *level, int *fieldSize = 0); 120 | uint saveAllLevels(ROMFile& file, leveldata_t **levels); 121 | 122 | size_t makeClipTable(const leveldata_t *level, uint8_t *buffer); 123 | void makeIsometricMap(uint16_t playfield[2][MAX_FIELD_HEIGHT][MAX_FIELD_WIDTH], leveldata_t *level); 124 | 125 | uint levelHeight(const leveldata_t *level); 126 | bool waterLevel(const leveldata_t *level); 127 | 128 | /* 129 | * Worker objects for generating compressed level data 130 | */ 131 | class SaveWorker : public QRunnable { 132 | 133 | public: 134 | SaveWorker(leveldata_t *level) 135 | : level(level) { 136 | this->setAutoDelete(false); 137 | QThreadPool::globalInstance()->start(this); 138 | } 139 | 140 | ~SaveWorker() { 141 | for (QByteArray*& chunk: chunks) { 142 | delete chunk; 143 | } 144 | } 145 | 146 | void run() { 147 | chunks = saveLevel(level, &fieldSize); 148 | } 149 | 150 | const QList& getChunks() const { 151 | QThreadPool::globalInstance()->waitForDone(); 152 | return chunks; 153 | } 154 | 155 | int getFieldSize() const { 156 | QThreadPool::globalInstance()->waitForDone(); 157 | return fieldSize; 158 | } 159 | 160 | protected: 161 | QList chunks; 162 | leveldata_t *level; 163 | int fieldSize; 164 | 165 | }; 166 | 167 | #endif // LEVEL_H 168 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "mainwindow.h" 3 | #include "version.h" 4 | 5 | int main(int argc, char *argv[]) 6 | { 7 | QApplication a(argc, argv); 8 | 9 | a.setApplicationName(INFO_NAME); 10 | a.setApplicationVersion(INFO_VERS); 11 | 12 | MainWindow w; 13 | w.show(); 14 | 15 | return a.exec(); 16 | } 17 | -------------------------------------------------------------------------------- /src/mainwindow.h: -------------------------------------------------------------------------------- 1 | /* 2 | This code is released under the terms of the MIT license. 3 | See COPYING.txt for details. 4 | */ 5 | 6 | #ifndef MAINWINDOW_H 7 | #define MAINWINDOW_H 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "romfile.h" 15 | #include "mapscene.h" 16 | #include "level.h" 17 | #include "previewwindow.h" 18 | 19 | namespace Ui { 20 | class MainWindow; 21 | } 22 | 23 | class MainWindow : public QMainWindow 24 | { 25 | Q_OBJECT 26 | 27 | public: 28 | explicit MainWindow(QWidget *parent = 0); 29 | ~MainWindow(); 30 | 31 | protected slots: 32 | // file menu 33 | void openFile(); 34 | void saveFile(); 35 | void saveFileAs(); 36 | int closeFile(); 37 | 38 | void setUnsaved(); 39 | 40 | // level menu 41 | void loadLevelFromFile(); 42 | void saveLevelToFile(); 43 | void loadCourseFromFile(); 44 | void saveCourseToFile(); 45 | 46 | void saveCurrentLevel(); 47 | 48 | void levelProperties(); 49 | 50 | void selectCourse(); 51 | void prevLevel(); 52 | void nextLevel(); 53 | void prevCourse(); 54 | void nextCourse(); 55 | 56 | // help menu 57 | void showHelp(); 58 | void showAbout(); 59 | 60 | // display text on the statusbar 61 | void status(const QString &msg); 62 | 63 | // debug menu crap 64 | void dumpLevel(); 65 | 66 | // toolbar updates 67 | void setOpenFileActions(bool val); 68 | void setEditActions(bool val); 69 | void setUndoRedoActions(bool val = true); 70 | void setLevelChangeActions(bool val); 71 | 72 | protected: 73 | void closeEvent(QCloseEvent *); 74 | 75 | private: 76 | Ui::MainWindow *ui; 77 | 78 | QSettings *settings; 79 | 80 | // Information about the currently open file 81 | QString fileName; 82 | ROMFile rom; 83 | bool fileOpen, unsaved, saving; 84 | 85 | // The level data (28 courses, 8 holes each) 86 | int level; 87 | leveldata_t* levels[224]; 88 | leveldata_t currentLevel; 89 | QLabel *levelLabel; 90 | 91 | // course background/palette settings 92 | // (background selectiosn repeat every 8 courses) 93 | int background[8]; 94 | int palette[28]; 95 | int waterPalette[28]; 96 | 97 | // renderin stuff 98 | MapScene *scene; 99 | // the preview window 100 | PreviewWindow *previewWin; 101 | 102 | // various funcs 103 | void setupSignals(); 104 | void setupActions(); 105 | void getSettings(); 106 | void saveSettings(); 107 | void updateTitle(); 108 | void setLevel(int); 109 | QMessageBox::StandardButton checkSaveLevel(); 110 | QMessageBox::StandardButton checkSaveROM(); 111 | }; 112 | 113 | #endif // MAINWINDOW_H 114 | -------------------------------------------------------------------------------- /src/mainwindow.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MainWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 800 10 | 600 11 | 12 | 13 | 14 | 15 | 640 16 | 480 17 | 18 | 19 | 20 | Kirby's Dream Course Editor 21 | 22 | 23 | 24 | :/images/main16.png:/images/main16.png 25 | 26 | 27 | 1.000000000000000 28 | 29 | 30 | 31 | 32 | 0 33 | 0 34 | 35 | 36 | 37 | 38 | 39 | 40 | Qt::ScrollBarAlwaysOn 41 | 42 | 43 | Qt::ScrollBarAlwaysOn 44 | 45 | 46 | true 47 | 48 | 49 | Qt::AlignCenter 50 | 51 | 52 | 53 | 54 | 0 55 | 0 56 | 763 57 | 510 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 0 69 | 0 70 | 800 71 | 21 72 | 73 | 74 | 75 | 76 | &File 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | &Edit 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | &Level 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | &Help 127 | 128 | 129 | 130 | 131 | 132 | 133 | Debug 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | toolBar 147 | 148 | 149 | true 150 | 151 | 152 | Qt::BottomToolBarArea|Qt::TopToolBarArea 153 | 154 | 155 | false 156 | 157 | 158 | TopToolBarArea 159 | 160 | 161 | false 162 | 163 | 164 | 165 | 166 | 167 | :/images/icons/folder.png:/images/icons/folder.png 168 | 169 | 170 | &Open ROM... 171 | 172 | 173 | Ctrl+O 174 | 175 | 176 | 177 | 178 | 179 | :/images/icons/disk.png:/images/icons/disk.png 180 | 181 | 182 | &Save ROM 183 | 184 | 185 | Ctrl+S 186 | 187 | 188 | 189 | 190 | Save ROM As... 191 | 192 | 193 | Ctrl+Shift+S 194 | 195 | 196 | 197 | 198 | Close ROM 199 | 200 | 201 | 202 | 203 | true 204 | 205 | 206 | Load Course from &File... 207 | 208 | 209 | 210 | 211 | true 212 | 213 | 214 | Save Level... 215 | 216 | 217 | 218 | 219 | true 220 | 221 | 222 | Save Course to File... 223 | 224 | 225 | 226 | 227 | E&xit 228 | 229 | 230 | Ctrl+Q 231 | 232 | 233 | 234 | 235 | true 236 | 237 | 238 | 239 | :/images/icons/cut.png:/images/icons/cut.png 240 | 241 | 242 | Cu&t 243 | 244 | 245 | Ctrl+X 246 | 247 | 248 | 249 | 250 | true 251 | 252 | 253 | 254 | :/images/icons/page_copy.png:/images/icons/page_copy.png 255 | 256 | 257 | &Copy 258 | 259 | 260 | Ctrl+C 261 | 262 | 263 | 264 | 265 | true 266 | 267 | 268 | 269 | :/images/icons/paste_plain.png:/images/icons/paste_plain.png 270 | 271 | 272 | &Paste 273 | 274 | 275 | Ctrl+V 276 | 277 | 278 | 279 | 280 | true 281 | 282 | 283 | 284 | :/images/icons/delete.png:/images/icons/delete.png 285 | 286 | 287 | &Delete 288 | 289 | 290 | Del 291 | 292 | 293 | 294 | 295 | true 296 | 297 | 298 | 299 | :/images/icons/help.png:/images/icons/help.png 300 | 301 | 302 | &Contents... 303 | 304 | 305 | F1 306 | 307 | 308 | 309 | 310 | &About... 311 | 312 | 313 | 314 | 315 | true 316 | 317 | 318 | 319 | :/images/icons/brick_edit.png:/images/icons/brick_edit.png 320 | 321 | 322 | &Edit Tiles... 323 | 324 | 325 | E 326 | 327 | 328 | 329 | 330 | 331 | :/images/icons/resultset_previous.png:/images/icons/resultset_previous.png 332 | 333 | 334 | Previous Level 335 | 336 | 337 | Ctrl+Left 338 | 339 | 340 | 341 | 342 | 343 | :/images/icons/resultset_next.png:/images/icons/resultset_next.png 344 | 345 | 346 | Next Level 347 | 348 | 349 | Ctrl+Right 350 | 351 | 352 | 353 | 354 | 355 | :/images/icons/resultset_first.png:/images/icons/resultset_first.png 356 | 357 | 358 | Previous Course 359 | 360 | 361 | Ctrl+Shift+Left 362 | 363 | 364 | 365 | 366 | 367 | :/images/icons/resultset_last.png:/images/icons/resultset_last.png 368 | 369 | 370 | Next Course 371 | 372 | 373 | Ctrl+Shift+Right 374 | 375 | 376 | 377 | 378 | 379 | :/images/icons/wand.png:/images/icons/wand.png 380 | 381 | 382 | Level Properties 383 | 384 | 385 | Ctrl+P 386 | 387 | 388 | 389 | 390 | Dump level header to stdout 391 | 392 | 393 | true 394 | 395 | 396 | 397 | 398 | Dump level chunks to text file 399 | 400 | 401 | 402 | 403 | 404 | :/images/icons/magnifier.png:/images/icons/magnifier.png 405 | 406 | 407 | Show Preview 408 | 409 | 410 | 411 | 412 | true 413 | 414 | 415 | true 416 | 417 | 418 | 419 | :/images/icons/arrow_in.png:/images/icons/arrow_in.png 420 | 421 | 422 | Center Preview 423 | 424 | 425 | 426 | 427 | 428 | :/images/icons/image.png:/images/icons/image.png 429 | 430 | 431 | Save Level to Image... 432 | 433 | 434 | 435 | 436 | 437 | :/images/icons/door_in.png:/images/icons/door_in.png 438 | 439 | 440 | Select Course... 441 | 442 | 443 | 444 | 445 | 446 | :/images/icons/arrow_undo.png:/images/icons/arrow_undo.png 447 | 448 | 449 | Undo 450 | 451 | 452 | Ctrl+Z 453 | 454 | 455 | 456 | 457 | 458 | :/images/icons/arrow_redo.png:/images/icons/arrow_redo.png 459 | 460 | 461 | Redo 462 | 463 | 464 | Ctrl+Y 465 | 466 | 467 | 468 | 469 | Load Level from File... 470 | 471 | 472 | 473 | 474 | Save Level to File... 475 | 476 | 477 | 478 | 479 | Raise Tiles 480 | 481 | 482 | = 483 | 484 | 485 | 486 | 487 | Lower Tiles 488 | 489 | 490 | - 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | -------------------------------------------------------------------------------- /src/mapchange.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | This code is released under the terms of the MIT license. 3 | See COPYING.txt for details. 4 | */ 5 | 6 | #include "mapchange.h" 7 | #include "level.h" 8 | 9 | MapChange::MapChange(leveldata_t *currLevel, uint selX, uint selY, uint selW, uint selL, QUndoCommand *parent) : 10 | QUndoCommand(parent), 11 | level(currLevel), 12 | x(selX), y(selY), w(selW), l(selL), 13 | before(new maptile_t[l * w]), 14 | after (new maptile_t[l * w]), 15 | first(true) 16 | { 17 | // when instantiated, save the region's pre-edit state 18 | if (level) { 19 | for (uint row = 0; row < l; row++) 20 | for (uint col = 0; col < w; col++) 21 | before[(row * w) + col] = this->level->tiles[y + row][x + col]; 22 | 23 | this->setText("edit"); 24 | } 25 | } 26 | 27 | MapChange::~MapChange() { 28 | delete[] before; 29 | delete[] after; 30 | } 31 | 32 | void MapChange::undo() { 33 | // restore to the region's pre-edit state 34 | if (level) { 35 | for (uint row = 0; row < this->l; row++) 36 | for (uint col = 0; col < this->w; col++) 37 | this->level->tiles[y + row][x + col] = before[(row * w) + col]; 38 | } 39 | } 40 | 41 | void MapChange::redo() { 42 | // if being pushed, save the region's post-edit state 43 | // otherwise restore it 44 | if (level) { 45 | for (uint row = 0; row < this->l; row++) 46 | for (uint col = 0; col < this->w; col++) { 47 | if (first) after[(row * w) + col] = this->level->tiles[y + row][x + col]; 48 | else this->level->tiles[y + row][x + col] = after[(row * w) + col]; 49 | } 50 | 51 | if (first) { 52 | level->modified = true; 53 | level->modifiedRecently = true; 54 | } 55 | } 56 | 57 | first = false; 58 | } 59 | 60 | void MapChange::setText(const QString &text) { 61 | QUndoCommand::setText(QString(text) 62 | .append(" from (%1, %2) to (%3, %4)") 63 | .arg(x).arg(y).arg(x + w - 1).arg(y + l - 1)); 64 | } 65 | -------------------------------------------------------------------------------- /src/mapchange.h: -------------------------------------------------------------------------------- 1 | /* 2 | This code is released under the terms of the MIT license. 3 | See COPYING.txt for details. 4 | */ 5 | 6 | #ifndef MAPCHANGE_H 7 | #define MAPCHANGE_H 8 | 9 | #include 10 | 11 | #include "level.h" 12 | 13 | class MapChange : public QUndoCommand 14 | { 15 | public: 16 | explicit MapChange(leveldata_t *currLevel, 17 | uint x, uint y, uint w, uint l, 18 | QUndoCommand *parent = 0); 19 | ~MapChange(); 20 | 21 | void undo(); 22 | void redo(); 23 | void setText(const QString &text); 24 | 25 | private: 26 | leveldata_t *level; 27 | uint x, y, w, l; 28 | maptile_t *before, *after; 29 | bool first; 30 | }; 31 | 32 | #endif // MAPCHANGE_H 33 | -------------------------------------------------------------------------------- /src/mapscene.h: -------------------------------------------------------------------------------- 1 | /* 2 | This code is released under the terms of the MIT license. 3 | See COPYING.txt for details. 4 | */ 5 | 6 | #ifndef MAPSCENE_H 7 | #define MAPSCENE_H 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include "level.h" 14 | 15 | // subclass of QGraphicsScene used to draw the 2d map and handle mouse/kb events for it 16 | class MapScene : public QWidget { 17 | Q_OBJECT 18 | 19 | private: 20 | static const QColor infoColor, infoBackColor; 21 | static const QColor selectionColor, selectionBorder; 22 | static const QColor layerColor; 23 | static const QFont infoFont; 24 | static const QFontMetrics infoFontMetrics; 25 | 26 | int tileX, tileY; 27 | int selX, selY, selLength, selWidth; 28 | bool selecting; 29 | 30 | maptile_t copyBuffer[MAX_2D_SIZE][MAX_2D_SIZE]; 31 | uint copyWidth, copyLength; 32 | 33 | QUndoStack stack; 34 | 35 | leveldata_t *level; 36 | 37 | //QGraphicsPixmapItem *infoItem, *selectionItem; 38 | 39 | QPixmap tiles, kirby, enemies, traps, bounce, movers, rotate, 40 | conveyor, bumpers, water, warps, gordo, switches, dedede, 41 | unknown; 42 | 43 | void copyTiles(bool cut); 44 | void showTileInfo(QMouseEvent *event); 45 | void beginSelection(QMouseEvent *event); 46 | void updateSelection(QMouseEvent *event = NULL); 47 | 48 | public: 49 | explicit MapScene(QWidget *parent = 0, leveldata_t *currentLevel = 0); 50 | 51 | bool canUndo() const; 52 | bool canRedo() const; 53 | bool isClean() const; 54 | 55 | void cancelSelection(); 56 | 57 | public slots: 58 | void editTiles(); 59 | void undo(); 60 | void redo(); 61 | void clearStack(); 62 | void setClean(); 63 | void cut(); 64 | void copy(); 65 | void paste(); 66 | void deleteTiles(); 67 | void raiseTiles(); 68 | void lowerTiles(); 69 | void refresh(bool keepMouse = true); 70 | 71 | signals: 72 | void doubleClicked(); 73 | void statusMessage(QString); 74 | void mouseOverTile(int x, int y); 75 | void edited(); 76 | 77 | protected: 78 | void mouseMoveEvent(QMouseEvent *event); 79 | void mousePressEvent(QMouseEvent *event); 80 | void mouseReleaseEvent(QMouseEvent *event); 81 | void mouseDoubleClickEvent(QMouseEvent *event); 82 | void wheelEvent(QWheelEvent *event); 83 | void paintEvent(QPaintEvent *event); 84 | }; 85 | 86 | #endif // MAPSCENE_H 87 | -------------------------------------------------------------------------------- /src/metatile.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | metatile.cpp 3 | 4 | Contains code for generating metatiles - the individual parts of the isometric tilemap 5 | corresponding to individual tiles on the 2D map, based on a tile's terrain (or obstacle) and the 6 | ones to the north and west of it. 7 | 8 | Metatiles are stored as 8x8 arrays of tiles, where the first 4 columns represent a tile and its 9 | connection to the west (if any), and the other 4 represent its connection to the north. 10 | 11 | For (most of) the actual tile data, see the other metatile_*.cpp files. 12 | 13 | This code is released under the terms of the MIT license. 14 | See COPYING.txt for details. 15 | 16 | */ 17 | 18 | #include "metatile.h" 19 | #include "graphics.h" 20 | 21 | using namespace stuff; 22 | 23 | // These tables map halves/edges of some terrain types to other types. 24 | // This is done when a terrain type (mainly two-way slopes or diagonal slope top/bottom parts) 25 | // can be drawn using parts of other types. 26 | const stuff::type_e trueLeftTable[KIRBY_GEOM_TYPES] = { 27 | // nothing, flat, slopes up, slopes down 28 | nothing, flat, slopeEast, slopeSouthAndWestInner, 29 | // cardinal slopes 30 | slopeSouth, slopeEast, slopeNorth, slopeWest, 31 | // inner slopes (SE, NE, NW, SW) 32 | slopeSouth, slopeNorth, slopeWest, slopeSouthAndWestInner, 33 | // outer slopes 34 | slopeEast, slopeEast, slopeNorth, slopeSouthAndWestOuter, 35 | // diagonal slopes upper 36 | slopeSoutheastFull, slopeNortheastFull, flat, flat, 37 | // diagonal slopes lower 38 | flat, flat, flat, slopeSouthwestFull, 39 | // diagonal slopes full 40 | slopeSoutheastFull, slopeNortheastFull, slopeNorthwestFull, slopeSouthwestFull 41 | }; 42 | 43 | const stuff::type_e trueRightTable[KIRBY_GEOM_TYPES] = { 44 | // nothing, flat, slopes up, slopes down 45 | nothing, flat, slopeSouth, slopeNorthAndEastInner, 46 | // cardinal slopes 47 | slopeSouth, slopeEast, slopeNorth, slopeWest, 48 | // inner slopes (SE, NE, NW, SW) 49 | slopeEast, slopeNorthAndEastInner, slopeNorth, slopeWest, 50 | // outer slopes 51 | slopeSouth, slopeNorthAndEastOuter, slopeWest, slopeSouth, 52 | // diagonal slopes upper 53 | slopeSoutheastFull, flat, flat, slopeSouthwestFull, 54 | // diagonal slopes lower 55 | flat, slopeNortheastFull, flat, flat, 56 | // diagonal slopes full 57 | slopeSoutheastFull, slopeNortheastFull, slopeNorthwestFull, slopeSouthwestFull 58 | }; 59 | 60 | const stuff::type_e trueCenterLeftTable[KIRBY_GEOM_TYPES] = { 61 | // nothing, flat, slopes up, slopes down 62 | nothing, flat, slopeSouthAndWestOuter, slopeEast, 63 | // cardinal slopes 64 | slopeSouth, slopeEast, slopeNorth, slopeWest, 65 | // inner slopes (SE, NE, NW, SW) 66 | slopeEast, slopeEast, slopeNorth, slopeSouth, 67 | // outer slopes 68 | slopeSouth, slopeNorthAndEastOuter, slopeWest, slopeSouthAndWestOuter, 69 | // diagonal slopes upper 70 | flat, flat, nothing, slopeSouthwestFull, 71 | // diagonal slopes lower 72 | slopeSoutheastFull, slopeNortheastFull, flat, flat, 73 | // diagonal slopes full 74 | slopeSoutheastFull, slopeNortheastFull, slopeNorthwestFull, slopeSouthwestFull 75 | }; 76 | 77 | const stuff::type_e trueCenterRightTable[KIRBY_GEOM_TYPES] = { 78 | // nothing, flat, slopes up, slopes down 79 | nothing, flat, slopeNorthAndEastOuter, slopeSouth, 80 | // cardinal slopes 81 | slopeSouth, slopeEast, slopeNorth, slopeWest, 82 | // inner slopes (SE, NE, NW, SW) 83 | slopeSouth, slopeEast, slopeWest, slopeSouth, 84 | // outer slopes 85 | slopeEast, slopeNorthAndEastOuter, slopeNorth, slopeSouthAndWestOuter, 86 | // diagonal slopes upper 87 | flat, slopeNortheastFull, nothing, flat, 88 | // diagonal slopes lower 89 | slopeSoutheastFull, flat, flat, slopeSouthwestFull, 90 | // diagonal slopes full 91 | slopeSoutheastFull, slopeNortheastFull, slopeNorthwestFull, slopeSouthwestFull 92 | }; 93 | 94 | metatile_t buildMetatile(int center, int left, int right, 95 | bool north, bool east, bool south, bool west, 96 | bool northStart, bool westStart) { 97 | metatile_t result = {nothing, nothing, {{0}}}; 98 | int trueCenterLeft, trueCenterRight, trueLeft, trueRight; 99 | 100 | // do initial type mapping here 101 | if (left >= nothing) 102 | trueLeft = trueLeftTable[left]; 103 | else trueLeft = left; 104 | 105 | if (right >= nothing) 106 | trueRight = trueRightTable[right]; 107 | else trueRight = right; 108 | 109 | trueCenterLeft = trueCenterLeftTable[center]; 110 | trueCenterRight = trueCenterRightTable[center]; 111 | 112 | // get center tile 113 | metatile_t centerTile = findMetatile(metatilesTerrain, center, nothing); 114 | 115 | // get left edge 116 | metatile_t leftTile = findMetatile(metatilesTerrain, trueCenterLeft, trueLeft); 117 | // get right edge 118 | metatile_t rightTile = findMetatile(metatilesTerrain, trueCenterRight, trueRight); 119 | 120 | //get borders 121 | //also use center type mapping to maybe get bumpers for 2-way slopes etc 122 | metatile_t northBorder, southBorder, eastBorder, westBorder; 123 | 124 | if (center == slopeNorth && west && south) { 125 | westBorder = findMetatile(bordersAll, trueCenterLeft, trueRight); 126 | southBorder = westBorder; 127 | } else { 128 | if (westStart) 129 | westBorder = findMetatile(bordersWestStart, trueCenterLeft, trueRight); 130 | else 131 | westBorder = findMetatile(bordersWest, trueCenterLeft, trueRight); 132 | 133 | //southBorder = findMetatile(bordersSouth, trueCenterLeft, trueLeft); 134 | southBorder = findMetatile(bordersSouth, trueRightTable[center], trueLeft); 135 | } 136 | 137 | if (center == slopeWest && north && east) { 138 | northBorder = findMetatile(bordersAll, trueCenterRight, trueLeft); 139 | eastBorder = northBorder; 140 | } else { 141 | if (northStart) 142 | northBorder = findMetatile(bordersNorthStart, trueCenterRight, trueLeft); 143 | else 144 | northBorder = findMetatile(bordersNorth, trueCenterRight, trueLeft); 145 | 146 | //eastBorder = findMetatile(bordersEast, trueCenterRight, trueRight); 147 | eastBorder = findMetatile(bordersEast, trueLeftTable[center], trueRight); 148 | } 149 | 150 | // if the center is a lower part of a diagonal slope, put the edge tiles lower 151 | // (including bumpers) 152 | int yOffLeft = 0; 153 | int yOffRight = 0; 154 | int yOffBumper = 0; 155 | if (center >= slopesLower && center < endSlopesLower) { 156 | if (left != nothing) 157 | yOffLeft = 2; 158 | if (right != nothing) 159 | yOffRight = 2; 160 | 161 | yOffBumper = 2; 162 | } 163 | 164 | // combine tiles 165 | for (int y = 0; y < 8; y++) 166 | for (int x = 0; x < 8; x++) { 167 | if (south && y >= yOffBumper && TILE(southBorder.tiles[y - yOffBumper][x])) 168 | result.tiles[y][x] = southBorder.tiles[y - yOffBumper][x]; 169 | else if (east && y >= yOffBumper && TILE(eastBorder.tiles[y - yOffBumper][x])) 170 | result.tiles[y][x] = eastBorder.tiles[y - yOffBumper][x]; 171 | else if ((north || northStart) && y >= yOffBumper && TILE(northBorder.tiles[y - yOffBumper][x])) 172 | result.tiles[y][x] = northBorder.tiles[y - yOffBumper][x]; 173 | else if ((west || westStart) && y >= yOffBumper && TILE(westBorder.tiles[y - yOffBumper][x])) 174 | result.tiles[y][x] = westBorder.tiles[y - yOffBumper][x]; 175 | else if (y >= yOffLeft && x < 4 && left != nothing && TILE(leftTile.tiles[y - yOffLeft][x])) 176 | result.tiles[y][x] = leftTile.tiles[y - yOffLeft][x]; 177 | else if (y >= yOffRight && x >= 4 && right != nothing && TILE(rightTile.tiles[y - yOffRight][x])) 178 | result.tiles[y][x] = rightTile.tiles[y - yOffRight][x]; 179 | else result.tiles[y][x] = centerTile.tiles[y][x]; 180 | } 181 | 182 | return result; 183 | } 184 | 185 | metatile_t buildObstacle(int center, int left, int right) { 186 | metatile_t result = {nothing, nothing, {{0}}}; 187 | int trueCenter, trueLeft, trueRight; 188 | 189 | // do initial type mapping here 190 | if (center >= warpSouth2 191 | && center <= warpWest2) trueCenter = center - 4; 192 | else if (center == warpRed2) trueCenter = warpRed; 193 | else if (center == switchShine) trueCenter = switchShineBright; 194 | else if (center == switchRotateOn) trueCenter = switchRotate; 195 | else if (center == switchWaterOff) trueCenter = switchWater; 196 | else if (center >= startLine 197 | && center <= kirbyStartLine) trueCenter = startLine; 198 | else if ((center >= rotate) & (center < endRotate)) { 199 | if (center & 1) trueCenter = rotateCCW; 200 | else trueCenter = rotateCW; 201 | } 202 | else if ((center >= endRotate) & (center < endRotateOpposite)) { 203 | if (center & 1) trueCenter = rotateCCWOpposite; 204 | else trueCenter = rotateCWOpposite; 205 | } 206 | else trueCenter = center; 207 | 208 | // get center tile 209 | metatile_t centerTile = findMetatile(metatilesObstacles, trueCenter, nothing); 210 | 211 | // do pre-left type mapping here 212 | if (left == waterSouthAndEastOuter) trueLeft = waterEast; 213 | else if (left == waterNorthAndEastOuter) trueLeft = waterEast; 214 | else if (left >= startLine 215 | && left <= kirbyStartLine) trueLeft = startLine; 216 | else if ((left >= rotate) & (left < endRotate)) { 217 | if (left & 1) trueLeft = rotateCCW; 218 | else trueLeft = rotateCW; 219 | } 220 | else if ((left >= endRotate) & (left < endRotateOpposite)) { 221 | if (left & 1) trueLeft = rotateCCWOpposite; 222 | else trueLeft = rotateCWOpposite; 223 | } 224 | else trueLeft = left; 225 | 226 | // get left edge 227 | metatile_t leftTile = findMetatile(metatilesObstacles, trueCenter, trueLeft); 228 | 229 | // do pre-right type mapping here 230 | if (right == waterSouthAndEastOuter) trueRight = waterSouth; 231 | else if (right == waterSouthAndWestOuter) trueRight = waterSouth; 232 | else if (right >= startLine 233 | && right <= kirbyStartLine) trueRight = startLine; 234 | else if ((right >= rotate) & (right < endRotate)) { 235 | if (right & 1) trueRight = rotateCCW; 236 | else trueRight = rotateCW; 237 | } 238 | else if ((right >= endRotate) & (right < endRotateOpposite)) { 239 | if (right & 1) trueRight = rotateCCWOpposite; 240 | else trueRight = rotateCWOpposite; 241 | } 242 | else trueRight = right; 243 | 244 | // get right edge 245 | metatile_t rightTile = findMetatile(metatilesObstacles, trueCenter, trueRight); 246 | 247 | // combine tiles 248 | for (int y = 0; y < 8; y++) 249 | for (int x = 0; x < 4; x++) { 250 | if (trueLeft != nothing && TILE(leftTile.tiles[y][x]) > 0) 251 | result.tiles[y][x] = leftTile.tiles[y][x]; 252 | else result.tiles[y][x] = centerTile.tiles[y][x]; 253 | 254 | if (trueRight != nothing && TILE(rightTile.tiles[y][x + 4]) > 0) 255 | result.tiles[y][x + 4] = rightTile.tiles[y][x + 4]; 256 | else result.tiles[y][x + 4] = centerTile.tiles[y][x + 4]; 257 | } 258 | 259 | return result; 260 | } 261 | 262 | metatile_t findMetatile(const metatile_t *array, int type, int other) { 263 | metatile_t result = {nothing, nothing, {{0}}}; 264 | 265 | for (int i = 0; array[i].type; i++) { 266 | if (array[i].type == type) { 267 | // use (in order): 268 | // selected tile touching nothing 269 | // selected tile touching selected other type 270 | if (array[i].adjacent == 0 || array[i].adjacent == other) 271 | result = array[i]; 272 | if (array[i].adjacent == other) 273 | break; 274 | } 275 | } 276 | return result; 277 | } 278 | 279 | const uint16_t bottomTile[2][8] = 280 | {{ 16|PAL(7) , 17|PAL(7) , 1|PAL(7) , 205|PAL(7) , 202|PAL(7)|FB, 2|PAL(7) , 18|PAL(7) , 19|PAL(7) }, 281 | { 0|PAL(0) , 0|PAL(0) , 16|PAL(7) , 17|PAL(7) , 18|PAL(7) , 19|PAL(7) , 0|PAL(0) , 0|PAL(0) }}; 282 | 283 | const uint16_t stackTile[2][8] = 284 | {{214|PAL(7) , 203|PAL(7)|FB, 205|PAL(7)|FB, 1|PAL(7) , 2|PAL(7) , 202|PAL(7) , 90|PAL(7) , 215|PAL(7) }, 285 | {180|PAL(7) , 205|PAL(7) , 203|PAL(7) , 203|PAL(7)|FB, 90|PAL(7) , 90|PAL(7)|FB, 202|PAL(7)|FB, 181|PAL(7) }}; 286 | -------------------------------------------------------------------------------- /src/metatile.h: -------------------------------------------------------------------------------- 1 | /* 2 | This code is released under the terms of the MIT license. 3 | See COPYING.txt for details. 4 | */ 5 | 6 | #ifndef METATILE_H 7 | #define METATILE_H 8 | 9 | #include "kirby.h" 10 | 11 | namespace stuff { 12 | enum type_e { 13 | nothing = 0, 14 | wall = -1, 15 | 16 | // terrain tile types 17 | flat = 0x1, 18 | slopesUp = 0x2, 19 | slopesDown = 0x3, 20 | slopes = 0x4, 21 | slopeSouth = 0x4, 22 | slopeEast = 0x5, 23 | slopeNorth = 0x6, 24 | slopeWest = 0x7, 25 | slopesDouble = 0x8, 26 | slopeSouthAndEastInner = 0x8, 27 | slopeNorthAndEastInner = 0x9, 28 | slopeNorthAndWestInner = 0xA, 29 | slopeSouthAndWestInner = 0xB, 30 | slopeSouthAndEastOuter = 0xC, 31 | slopeNorthAndEastOuter = 0xD, 32 | slopeNorthAndWestOuter = 0xE, 33 | slopeSouthAndWestOuter = 0xF, 34 | endSlopes = 0x10, 35 | slopesUpper = 0x10, 36 | slopeSoutheastUpper = 0x10, 37 | slopeNortheastUpper = 0x11, 38 | slopeNorthwestUpper = 0x12, 39 | slopeSouthwestUpper = 0x13, 40 | endSlopesUpper = 0x14, 41 | slopesLower = 0x14, 42 | slopeSoutheastLower = 0x14, 43 | slopeNortheastLower = 0x15, 44 | slopeNorthwestLower = 0x16, 45 | slopeSouthwestLower = 0x17, 46 | endSlopesLower = 0x18, 47 | slopesFull = 0x18, 48 | slopeSoutheastFull = 0x18, 49 | slopeNortheastFull = 0x19, 50 | slopeNorthwestFull = 0x1A, 51 | slopeSouthwestFull = 0x1B, 52 | 53 | // obstacle tile types 54 | extraTiles = 0x100, 55 | 56 | sand = 0x4, 57 | spikes = 0x5, 58 | spikesEx = spikes | extraTiles, 59 | 60 | current = 0x10, 61 | currentSouth = 0x10, 62 | currentEast = 0x11, 63 | currentNorth = 0x12, 64 | currentWest = 0x13, 65 | endCurrent = 0x14, 66 | 67 | arrowSouth = 0x14, 68 | arrowEast = 0x15, 69 | arrowNorth = 0x16, 70 | arrowWest = 0x17, 71 | 72 | boosterSouth = 0x18, 73 | boosterEast = 0x19, 74 | boosterNorth = 0x1A, 75 | boosterWest = 0x1B, 76 | 77 | ventNorthSouth = 0x1C, 78 | ventEastWest = 0x1D, 79 | 80 | bounce = 0x20, 81 | bounceSouth = 0x20, 82 | bounceEast = 0x21, 83 | bounceNorth = 0x22, 84 | bounceNorthEx = bounceNorth | extraTiles, 85 | bounceWest = 0x23, 86 | bounceWestEx = bounceWest | extraTiles, 87 | bounceFlat = 0x24, 88 | 89 | bumperNorthSouth = 0x28, 90 | bumperEastWest = 0x29, 91 | bumperSouthWest = 0x2A, 92 | bumperNorthWest = 0x2B, 93 | bumperNorthEast = 0x2C, 94 | bumperSouthEast = 0x2D, 95 | 96 | belts = 0x30, 97 | beltSouth = 0x30, 98 | beltEast = 0x31, 99 | beltNorth = 0x32, 100 | beltWest = 0x33, 101 | beltSlopes = 0x34, 102 | beltNorthUp = 0x34, 103 | beltSouthDown = 0x35, 104 | beltWestUp = 0x36, 105 | beltEastDown = 0x37, 106 | beltSouthUp = 0x38, 107 | beltNorthDown = 0x39, 108 | beltEastUp = 0x3A, 109 | beltWestDown = 0x3B, 110 | 111 | switchShineBright = 0x58, 112 | switchBright = 0x58, 113 | switchShine = 0x59, 114 | switchRotate = 0x5A, 115 | switchRotateOff = 0x5A, 116 | switchRotateOn = 0x5B, 117 | switchWater = 0x5C, 118 | switchWaterOn = 0x5C, 119 | switchWaterOff = 0x5D, 120 | 121 | water = 0x61, 122 | waterSouth = 0x64, 123 | waterEast = 0x65, 124 | waterNorth = 0x66, 125 | waterWest = 0x67, 126 | waterSouthAndEastInner = 0x68, 127 | waterNorthAndEastInner = 0x69, 128 | waterNorthAndWestInner = 0x6A, 129 | waterSouthAndWestInner = 0x6B, 130 | waterSouthAndEastOuter = 0x6C, 131 | waterNorthAndEastOuter = 0x6D, 132 | waterNorthAndWestOuter = 0x6E, 133 | waterSouthAndWestOuter = 0x6F, 134 | endWater = 0x70, 135 | 136 | 137 | rotate = 0x70, 138 | rotateCW = 0x70, 139 | rotateCCW = 0x71, 140 | endRotate = 0x78, 141 | rotateCWOpposite = 0x78, 142 | rotateCCWOpposite = 0x79, 143 | endRotateOpposite = 0x7C, 144 | 145 | warpSouth = 0xB0, 146 | warpEast = 0xB1, 147 | warpNorth = 0xB2, 148 | warpWest = 0xB3, 149 | 150 | // remapped to B0-B3 at render time 151 | warpSouth2 = 0xB4, 152 | warpEast2 = 0xB5, 153 | warpNorth2 = 0xB6, 154 | warpWest2 = 0xB7, 155 | 156 | warpRed = 0xB8, 157 | // remapped to B8 158 | warpRed2 = 0xB9, 159 | 160 | // all remapped to startLine 161 | startLineWest = 0xC0, 162 | startLine = 0xC1, 163 | startLineEast = 0xC2, 164 | kirbyStartLine = 0xC3 165 | 166 | }; 167 | } 168 | 169 | typedef struct { 170 | stuff::type_e type, adjacent; 171 | uint16_t tiles[8][8]; 172 | } metatile_t; 173 | 174 | extern const uint16_t stackTile[2][8]; 175 | extern const uint16_t bottomTile[2][8]; 176 | extern const metatile_t metatilesTerrain[]; 177 | extern const metatile_t metatilesObstacles[]; 178 | 179 | extern const stuff::type_e trueCenterLeftTable[]; 180 | extern const stuff::type_e trueCenterRightTable[]; 181 | 182 | extern const metatile_t bordersSouth[]; 183 | extern const metatile_t bordersEast[]; 184 | extern const metatile_t bordersNorth[]; 185 | extern const metatile_t bordersWest[]; 186 | extern const metatile_t bordersNorthStart[]; 187 | extern const metatile_t bordersWestStart[]; 188 | extern const metatile_t bordersAll[]; 189 | 190 | metatile_t buildMetatile(int, int, int, 191 | bool, bool, bool, bool, 192 | bool northStart, bool westStart); 193 | metatile_t buildObstacle(int, int, int); 194 | metatile_t findMetatile(const metatile_t *array, int type, int other); 195 | 196 | #endif // METATILE_H 197 | -------------------------------------------------------------------------------- /src/previewscene.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | previewscene.cpp 3 | 4 | Contains a QGraphicsScene subclass for rendering levels' isometric views. This is used by the preview 5 | window to display the "real" view of the level being edited. In the future this may be used to add a 6 | mini preview to the tile edit window or something. 7 | 8 | For the code which actually generates the isometric tile maps, see level.cpp. 9 | 10 | This code is released under the terms of the MIT license. 11 | See COPYING.txt for details. 12 | */ 13 | 14 | #include 15 | #include 16 | 17 | #include "previewscene.h" 18 | #include "graphics.h" 19 | #include "level.h" 20 | #include "mapscene.h" 21 | #include "metatile.h" 22 | 23 | PreviewScene::PreviewScene(QObject *parent, leveldata_t *currentLevel) 24 | : QGraphicsScene(parent), 25 | level(currentLevel), 26 | sprites(true) 27 | { 28 | // set up sprite pixmaps 29 | dedede.load (":images/dedede.png"); 30 | enemies.load (":images/enemies.png"); 31 | gordo.load (":images/gordo3d.png"); 32 | player.load (":images/kirby.png"); 33 | } 34 | 35 | void PreviewScene::refresh(uint16_t (&playfield)[2][MAX_FIELD_HEIGHT][MAX_FIELD_WIDTH]) { 36 | 37 | // load the 3d tile resource 38 | // (NOTE: this is a temporary measure until actual graphic/palette 39 | // loading is implemented) 40 | // each palette zone is 216px tall and each row of tiles is 41 | // 32 tiles long. 42 | if (waterLevel(level)) 43 | tiles.load(":images/3dtiles-water.png"); 44 | else 45 | tiles.load(":images/3dtiles.png"); 46 | 47 | int mapHeight = levelHeight(level); 48 | int mapWidth = level->header.width; 49 | int mapLength = level->header.length; 50 | 51 | int width = qMin(MAX_FIELD_WIDTH, (int)level->header.fieldWidth); 52 | int height = qMin(MAX_FIELD_HEIGHT, (int)level->header.fieldHeight); 53 | 54 | // reset the scene (remove all members) 55 | this->clear(); 56 | 57 | // set the pixmap and scene size based on the playfield's size 58 | QPixmap pixmap(width * ISO_TILE_SIZE, height * ISO_TILE_SIZE); 59 | pixmap.fill(QColor(0, 0, 0, 0)); 60 | 61 | this->setSceneRect(0, 0, width * ISO_TILE_SIZE, height * ISO_TILE_SIZE); 62 | 63 | // no level area = don't render anything 64 | if (mapLength + mapWidth == 0) { 65 | this->addPixmap(pixmap); 66 | this->update(); 67 | return; 68 | } 69 | 70 | // used to render one tile w/ flip etc. for each layer 71 | QPixmap layer1tile(ISO_TILE_SIZE, ISO_TILE_SIZE); 72 | QPixmap layer2tile(ISO_TILE_SIZE, ISO_TILE_SIZE); 73 | 74 | QPainter layer1painter, layer2painter; 75 | QPainter painter; 76 | 77 | painter.begin(&pixmap); 78 | 79 | for (int h = 0; h < height; h++) { 80 | for (int w = 0; w < width; w++) { 81 | layer1tile.fill(QColor(0,0,0,0)); 82 | layer2tile.fill(QColor(0,0,0,0)); 83 | 84 | uint16_t tile1 = playfield[0][h][w]; 85 | if (TILE(tile1)) { 86 | int y1 = (TILE(tile1) / 32) * ISO_TILE_SIZE 87 | + (PALN(tile1) * 216); 88 | int x1 = (TILE(tile1) % 32) * ISO_TILE_SIZE; 89 | 90 | layer1painter.begin(&layer1tile); 91 | layer1painter.drawPixmap(0, 0, 92 | tiles, x1, y1, ISO_TILE_SIZE, ISO_TILE_SIZE); 93 | layer1painter.end(); 94 | 95 | // flip the pixmap horizontally and/or vertically 96 | layer1tile = QPixmap::fromImage(layer1tile.toImage().mirrored(tile1 & FH, tile1 & FV)); 97 | } 98 | 99 | uint16_t tile2 = playfield[1][h][w]; 100 | if (TILE(tile2)) { 101 | int y2 = (TILE(tile2) / 32) * ISO_TILE_SIZE 102 | + (PALN(tile2) * 216); 103 | int x2 = (TILE(tile2) % 32) * ISO_TILE_SIZE; 104 | 105 | layer2painter.begin(&layer2tile); 106 | layer2painter.drawPixmap(0, 0, 107 | tiles, x2, y2, ISO_TILE_SIZE, ISO_TILE_SIZE); 108 | layer2painter.end(); 109 | 110 | // flip the pixmap horizontally and/or vertically 111 | layer2tile = QPixmap::fromImage(layer2tile.toImage().mirrored(tile2 & FH, tile2 & FV)); 112 | } 113 | 114 | // draw the tile pixmaps onto the scene 115 | // if layer 1 has priority, draw layer 2 first 116 | if (tile1 & PRI) { 117 | painter.drawPixmap(w * ISO_TILE_SIZE, h * ISO_TILE_SIZE, layer2tile); 118 | painter.drawPixmap(w * ISO_TILE_SIZE, h * ISO_TILE_SIZE, layer1tile); 119 | } else { 120 | painter.drawPixmap(w * ISO_TILE_SIZE, h * ISO_TILE_SIZE, layer1tile); 121 | painter.drawPixmap(w * ISO_TILE_SIZE, h * ISO_TILE_SIZE, layer2tile); 122 | } 123 | } 124 | } 125 | // next, if sprites are enabled, draw them 126 | // most of this is copied from the 2D draw code 127 | if (sprites) { 128 | for (int y = 0; y < mapLength; y++) { 129 | for (int x = 0; x < mapWidth; x++) { 130 | QPixmap gfx; 131 | int frame; 132 | int obs = level->tiles[y][x].obstacle; 133 | int z = level->tiles[y][x].height; 134 | 135 | if (obs == 0) continue; 136 | 137 | // whispy woods (index 0x00 in enemies.png) 138 | if (obs == 0x02) { 139 | gfx = enemies; 140 | frame = 0; 141 | 142 | // kirby's start pos (kirby.png) 143 | // (this time also use the final boss version) 144 | } else if (obs == 0x0c || obs == 0xc3) { 145 | gfx = player; 146 | frame = 0; 147 | 148 | // dedede (frame 0 in dedede.png) 149 | } else if (obs == 0x0d) { 150 | gfx = dedede; 151 | frame = 0; 152 | 153 | // most enemies (ind. 01 to 13 in enemies.png) 154 | } else if (obs >= 0x40 && obs <= 0x52) { 155 | gfx = enemies; 156 | frame = obs - 0x40 + 1; 157 | 158 | // transformer (ind. 14 in enemies.png 159 | } else if (obs == 0x57) { 160 | gfx = enemies; 161 | frame = 0x14; 162 | 163 | // gordo (ind. 00 to 21 in gordo.png) 164 | } else if (obs >= 0x80 && obs <= 0x97) { 165 | gfx = gordo; 166 | frame = obs - 0x80; 167 | 168 | // kracko (index 15-17 in enemies.png) 169 | } else if (obs >= 0xac && obs <= 0xae) { 170 | gfx = enemies; 171 | frame = obs - 0xac + 0x15; 172 | 173 | // anything else - question mark (or don't draw) 174 | } else { 175 | obs = 0; 176 | } 177 | 178 | // draw the selected obstacle 179 | if (obs) { 180 | // horizontal: start at 0 pixels 181 | // move TILE_SIZE / 2 right for each positive move on the x-axis (west to east) 182 | // and TILE_SIZE / 2 left for each positive move on the y-axis (north to south) 183 | int startX = (TILE_SIZE / 2) * (x + (mapLength - y - 1)); 184 | // start at h * TILE_SIZE / 4 tiles 185 | // move TILE_SIZE / 4 down for each positive move on the x-axis (west to east) 186 | // and TILE_SIZE / 4 down for each positive move on the y-axis (north to south) 187 | // and TILE_SIZE / 4 up for each positive move on the z-axis (tile z) 188 | // and then adjust for height of sprites 189 | int startY = (TILE_SIZE / 4) * (mapHeight + x + y - z + 4) - gfx.height(); 190 | // move down half a tile's worth if the sprite is on a slope 191 | if (level->tiles[y][x].geometry >= stuff::slopes) 192 | startY += TILE_SIZE / 8; 193 | 194 | painter.drawPixmap(startX, startY, 195 | gfx, frame * TILE_SIZE, 0, 196 | TILE_SIZE, gfx.height()); 197 | } 198 | } 199 | } 200 | } 201 | 202 | // finally, add the 3d map pixmap onto the scene 203 | painter.end(); 204 | 205 | this->addPixmap(pixmap); 206 | this->update(); 207 | } 208 | 209 | -------------------------------------------------------------------------------- /src/previewscene.h: -------------------------------------------------------------------------------- 1 | /* 2 | This code is released under the terms of the MIT license. 3 | See COPYING.txt for details. 4 | */ 5 | 6 | #ifndef PREVIEWSCENE_H 7 | #define PREVIEWSCENE_H 8 | 9 | #include 10 | #include 11 | #include "level.h" 12 | 13 | class PreviewScene : public QGraphicsScene { 14 | Q_OBJECT 15 | 16 | private: 17 | leveldata_t *level; 18 | bool sprites; 19 | 20 | QPixmap tiles, dedede, enemies, gordo, player; 21 | 22 | public: 23 | PreviewScene(QObject *parent, leveldata_t *currentLevel); 24 | void refresh(uint16_t (&playfield)[2][MAX_FIELD_HEIGHT][MAX_FIELD_WIDTH]); 25 | }; 26 | 27 | #endif // PREVIEWSCENE_H 28 | -------------------------------------------------------------------------------- /src/previewwindow.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | This code is released under the terms of the MIT license. 3 | See COPYING.txt for details. 4 | */ 5 | 6 | #include "previewwindow.h" 7 | #include "ui_previewwindow.h" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include "previewscene.h" 15 | 16 | PreviewWindow::PreviewWindow(QWidget *parent, leveldata_t *currentLevel) : 17 | QDialog(parent, Qt::Tool 18 | | Qt::CustomizeWindowHint 19 | | Qt::WindowTitleHint 20 | | Qt::WindowMinimizeButtonHint 21 | | Qt::WindowCloseButtonHint 22 | // | Qt::WindowStaysOnTopHint 23 | ), 24 | ui(new Ui::PreviewWindow), 25 | level(currentLevel), 26 | scene(new PreviewScene(this, currentLevel)), 27 | center(true) 28 | { 29 | ui->setupUi(this); 30 | 31 | //remove margins around graphics view 32 | this->layout()->setContentsMargins(0,0,0,0); 33 | 34 | ui->graphicsView->setScene(scene); 35 | } 36 | 37 | 38 | PreviewWindow::~PreviewWindow() 39 | { 40 | delete ui; 41 | } 42 | 43 | void PreviewWindow::setLevel(leveldata_t *level) { 44 | this->level = level; 45 | refresh(); 46 | } 47 | 48 | void PreviewWindow::refresh() { 49 | // rebuild the 3D tile map 50 | makeIsometricMap(this->playfield, this->level); 51 | // and draw it 52 | scene->refresh(this->playfield); 53 | // refresh the display 54 | ui->graphicsView->update(); 55 | } 56 | 57 | /* 58 | Converts 2D map coordinates into 3D display coordinates 59 | in order to center the preview display on a specific tile. 60 | */ 61 | void PreviewWindow::centerOn(int x, int y) { 62 | if (!center) return; 63 | 64 | int z = level->tiles[y][x].height; 65 | int h = levelHeight(level); 66 | int l = level->header.length; 67 | 68 | // start at 32 pixels 69 | // move 32 right for each positive move on the x-axis (west to east) 70 | // and 32 left for each positive move on the y-axis (north to south) 71 | int centerX = 32 * (x + l - y); 72 | // start at 16 *(h + 1) pixels 73 | // move 16 down for each positive move on the x-axis (west to east) 74 | // and 16 down for each positive move on the y-axis (north to south) 75 | // and 16 up for each positive move on the z-axis (tile z) 76 | int centerY = 16 * (h + x + y - z + 1); 77 | 78 | ui->graphicsView->centerOn(centerX, centerY); 79 | } 80 | 81 | void PreviewWindow::enableCenter(bool center) { 82 | this->center = center; 83 | } 84 | 85 | void PreviewWindow::savePreview() { 86 | QList items = scene->items(); 87 | 88 | if (!items.size()) { 89 | QMessageBox::information(NULL, tr("Save Level to Image"), 90 | tr("No level is currently open."), 91 | QMessageBox::Ok); 92 | return; 93 | } 94 | 95 | QSettings settings("settings.ini"); 96 | 97 | QString imageName = QFileDialog::getSaveFileName(this, 98 | tr("Save Level to Image"), 99 | settings.value("PreviewWindow/fileName", "").toString(), 100 | tr("PNG image (*.png)")); 101 | if (!imageName.isNull()) { 102 | if (!((QGraphicsPixmapItem*)items[0])->pixmap().save(imageName)) { 103 | QMessageBox::warning(NULL, tr("Save Level to Image"), 104 | tr("Error saving %1.").arg(imageName), 105 | QMessageBox::Ok); 106 | } 107 | 108 | settings.setValue("PreviewWindow/fileName", imageName); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/previewwindow.h: -------------------------------------------------------------------------------- 1 | /* 2 | This code is released under the terms of the MIT license. 3 | See COPYING.txt for details. 4 | */ 5 | 6 | #ifndef PREVIEWWINDOW_H 7 | #define PREVIEWWINDOW_H 8 | 9 | #include 10 | #include "level.h" 11 | #include "previewscene.h" 12 | 13 | namespace Ui { 14 | class PreviewWindow; 15 | } 16 | 17 | class PreviewWindow : public QDialog 18 | { 19 | Q_OBJECT 20 | 21 | public: 22 | explicit PreviewWindow(QWidget *parent = 0, leveldata_t *currentLevel = 0); 23 | ~PreviewWindow(); 24 | 25 | void setLevel(leveldata_t *level); 26 | 27 | public slots: 28 | void refresh(); 29 | void centerOn(int x, int y); 30 | void enableCenter(bool); 31 | void savePreview(); 32 | 33 | private: 34 | Ui::PreviewWindow *ui; 35 | 36 | leveldata_t *level; 37 | 38 | PreviewScene *scene; 39 | bool center; 40 | 41 | // The maximum area of the playfield is 13312 tiles. 42 | // The max for each dimension here was chosen as 384, but the 43 | // length * width cannot ever exceed 13312 tiles (or 26624 bytes.) 44 | // There are two playfields (one per layer) with the same size and layout. 45 | uint16_t playfield[2][MAX_FIELD_HEIGHT][MAX_FIELD_WIDTH]; 46 | 47 | }; 48 | 49 | #endif // PREVIEWWINDOW_H 50 | -------------------------------------------------------------------------------- /src/previewwindow.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | PreviewWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 300 10 | 300 11 | 12 | 13 | 14 | 15 | 200 16 | 200 17 | 18 | 19 | 20 | Preview 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 0 29 | 64 30 | 192 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/propertieswindow.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | This code is released under the terms of the MIT license. 3 | See COPYING.txt for details. 4 | */ 5 | 6 | #include 7 | #include 8 | #include "propertieswindow.h" 9 | #include "ui_propertieswindow.h" 10 | #include "kirby.h" 11 | #include "level.h" 12 | 13 | PropertiesWindow::PropertiesWindow(QWidget *parent) : 14 | QDialog(parent, Qt::CustomizeWindowHint 15 | | Qt::WindowTitleHint 16 | | Qt::WindowCloseButtonHint 17 | | Qt::MSWindowsFixedSizeDialogHint 18 | ), 19 | ui(new Ui::PropertiesWindow) 20 | { 21 | ui->setupUi(this); 22 | 23 | // prevent window resizing 24 | this->layout()->setSizeConstraint(QLayout::SetFixedSize); 25 | 26 | // add BG, palette, and music names to boxes 27 | for (int i = 0; i < NUM_BACKGROUNDS; i++) 28 | ui->comboBox_Background->addItem(bgNames[i].name); 29 | 30 | for (int i = 0; i < NUM_FG_PALETTES; i++) { 31 | ui->comboBox_Palette->addItem(paletteNames[i]); 32 | ui->comboBox_Water->addItem(paletteNames[i]); 33 | } 34 | for (StringMap::const_iterator i = musicNames.begin(); i != musicNames.end(); i++) { 35 | ui->comboBox_Music->addItem(i->second, i->first); 36 | } 37 | 38 | // set up signals to handle width/length constraints 39 | QObject::connect(ui->spinBox_Length, SIGNAL(valueChanged(int)), 40 | this, SLOT(setMaxLevelWidth(int))); 41 | QObject::connect(ui->spinBox_Width, SIGNAL(valueChanged(int)), 42 | this, SLOT(setMaxLevelLength(int))); 43 | } 44 | 45 | PropertiesWindow::~PropertiesWindow() 46 | { 47 | delete ui; 48 | } 49 | 50 | void PropertiesWindow::setMaxLevelWidth(int length) { 51 | ui->spinBox_Width->setMaximum(std::min(MAX_2D_SIZE, MAX_2D_AREA / length)); 52 | } 53 | 54 | void PropertiesWindow::setMaxLevelLength(int width) { 55 | ui->spinBox_Length->setMaximum(std::min(MAX_2D_SIZE, MAX_2D_AREA / width)); 56 | } 57 | 58 | void PropertiesWindow::startEdit(leveldata_t *level, 59 | int *bg, int *pal, int *water) { 60 | 61 | // save pointers 62 | this->level = level; 63 | this->bg = bg; 64 | this->pal = pal; 65 | this->water = water; 66 | 67 | // set height and width values 68 | ui->spinBox_Length->setValue(level->header.length); 69 | 70 | ui->spinBox_Width ->setValue(level->header.width); 71 | 72 | // set BG and FG stuff 73 | ui->comboBox_Background->setCurrentIndex(*bg); 74 | ui->comboBox_Palette ->setCurrentIndex(*pal); 75 | ui->comboBox_Water ->setCurrentIndex(*water); 76 | 77 | // set music value 78 | ui->comboBox_Music->setCurrentIndex(std::distance(musicNames.begin(), 79 | musicNames.find(level->music))); 80 | 81 | this->exec(); 82 | } 83 | 84 | void PropertiesWindow::accept() { 85 | // apply level size 86 | level->header.length = ui->spinBox_Length->value(); 87 | level->header.width = ui->spinBox_Width ->value(); 88 | 89 | // apply FG/BG settings 90 | *bg = ui->comboBox_Background->currentIndex(); 91 | *pal = ui->comboBox_Palette->currentIndex(); 92 | *water = ui->comboBox_Water->currentIndex(); 93 | 94 | // apply music setting 95 | level->music = ui->comboBox_Music->itemData(ui->comboBox_Music->currentIndex()).toUInt(); 96 | 97 | level->modified = true; 98 | level->modifiedRecently = true; 99 | 100 | QDialog::accept(); 101 | } 102 | -------------------------------------------------------------------------------- /src/propertieswindow.h: -------------------------------------------------------------------------------- 1 | /* 2 | This code is released under the terms of the MIT license. 3 | See COPYING.txt for details. 4 | */ 5 | 6 | #ifndef PROPERTIESWINDOW_H 7 | #define PROPERTIESWINDOW_H 8 | 9 | #include 10 | 11 | #include "level.h" 12 | 13 | namespace Ui { 14 | class PropertiesWindow; 15 | } 16 | 17 | class PropertiesWindow : public QDialog 18 | { 19 | Q_OBJECT 20 | 21 | public: 22 | explicit PropertiesWindow(QWidget *parent = 0); 23 | ~PropertiesWindow(); 24 | 25 | void startEdit(leveldata_t *level, int *bg, int *pal, int *water); 26 | 27 | public slots: 28 | void setMaxLevelWidth(int); 29 | void setMaxLevelLength(int); 30 | 31 | private: 32 | Ui::PropertiesWindow *ui; 33 | 34 | leveldata_t *level; 35 | int *bg, *pal, *water; 36 | 37 | private slots: 38 | void accept(); 39 | }; 40 | 41 | #endif // PROPERTIESWINDOW_H 42 | -------------------------------------------------------------------------------- /src/propertieswindow.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | PropertiesWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 374 10 | 298 11 | 12 | 13 | 14 | 15 | 0 16 | 0 17 | 18 | 19 | 20 | Level and Course Properties 21 | 22 | 23 | true 24 | 25 | 26 | 27 | 28 | 29 | Course Properties 30 | 31 | 32 | 33 | QFormLayout::AllNonFixedFieldsGrow 34 | 35 | 36 | 37 | 38 | Course Background 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | (note: course BG setting effects normal, extra, and 2P courses with the same number) 49 | 50 | 51 | true 52 | 53 | 54 | 55 | 56 | 57 | 58 | Foreground Palette 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | Water Palettes 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | Level Properties 82 | 83 | 84 | 85 | 86 | 87 | Width 88 | 89 | 90 | 91 | 92 | 93 | 94 | 1 95 | 96 | 97 | 98 | 99 | 100 | 101 | Length 102 | 103 | 104 | 105 | 106 | 107 | 108 | 1 109 | 110 | 111 | 112 | 113 | 114 | 115 | Music 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | Qt::Horizontal 129 | 130 | 131 | QDialogButtonBox::Cancel|QDialogButtonBox::Ok 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | buttonBox 141 | accepted() 142 | PropertiesWindow 143 | accept() 144 | 145 | 146 | 248 147 | 254 148 | 149 | 150 | 157 151 | 274 152 | 153 | 154 | 155 | 156 | buttonBox 157 | rejected() 158 | PropertiesWindow 159 | reject() 160 | 161 | 162 | 316 163 | 260 164 | 165 | 166 | 286 167 | 274 168 | 169 | 170 | 171 | 172 | 173 | -------------------------------------------------------------------------------- /src/romfile.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | romfile.cpp 3 | Contains functions for loading and saving data to/from a ROM file. 4 | 5 | Automatically detects valid game ROMs and allows reading/writing by CPU addresses, automatically 6 | adjusting for a 512-byte copier header if necessary. 7 | 8 | This code is released under the terms of the MIT license. 9 | See COPYING.txt for details. 10 | */ 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include 18 | 19 | #include "romfile.h" 20 | #include "compress.h" 21 | 22 | ROMFile::ROMFile() : QFile(), 23 | header(false), 24 | game(kirby), 25 | version(kirby_jp) 26 | {} 27 | 28 | ROMFile::game_e ROMFile::getGame() { 29 | return game; 30 | } 31 | 32 | ROMFile::version_e ROMFile::getVersion() { 33 | return version; 34 | } 35 | 36 | /* 37 | Converts a file offset to a LoROM address. 38 | If header == true, 512 bytes will be subtracted from the offset. 39 | 40 | Returns the corresponding SNES LoROM address (mapped from bank 80+) 41 | if successful, otherwise returns -1. 42 | */ 43 | uint ROMFile::toAddress(uint offset) { 44 | // outside of fast lorom range = invalid 45 | if (offset >= 0x800000) return -1; 46 | // within header area = invalid 47 | if (header && offset < 0x200) return -1; 48 | 49 | // adjust for header 50 | offset -= (header ? 0x200 : 0); 51 | // map bank number and 32kb range within bank 52 | int address = (offset & 0x7FFF) | 0x8000 53 | | (offset & 0x3F8000) << 1 54 | | 0x800000; 55 | 56 | return address; 57 | } 58 | 59 | /* 60 | Converts a LoROM address to a file offset. 61 | If header == true, 512 bytes will be added to the offset. 62 | 63 | Returns the corresponding file offset if successful, otherwise 64 | returns -1. 65 | */ 66 | uint ROMFile::toOffset(uint address) { 67 | uint offset = ((address & 0x7FFF) | ((address & 0x7F0000) >> 1)) 68 | + (header ? 0x200 : 0); 69 | 70 | return offset; 71 | } 72 | 73 | /* 74 | Opens the file and also verifies that it is one of the ROMs supported 75 | by the editor; displays a dialog and returns false on failure. 76 | 77 | For KDC, this checks for the string "ninten" at various offsets. 78 | It also determines whether the ROM is headered or not. 79 | */ 80 | const struct {int address; char string[7]; uint8_t region; ROMFile::game_e game;} versions[] = { 81 | // Kirby Bowl (JP) 82 | {0x8ECE, "ninten", 0, ROMFile::kirby}, 83 | // Kirby's Dream Course (US/EU) 84 | {0x8ECC, "ninten", 1, ROMFile::kirby}, 85 | {0x8ECC, "ninten", 2, ROMFile::kirby}, 86 | 87 | // Special Tee Shot (currently debug mode only) 88 | // checks title of rom (which can and may be changed); find something better 89 | {0xFFC0, "\xBD\xCD\xDF\xBC\xAC\xD9", 0, ROMFile::sts}, 90 | 91 | {0, "", 0, ROMFile::kirby} 92 | }; 93 | 94 | bool ROMFile::openROM(OpenMode flags) { 95 | if (!this->open(flags)) 96 | return false; 97 | 98 | QSettings settings(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/settings.ini", 99 | QSettings::IniFormat, this); 100 | char buf[6]; 101 | uint8_t region = readByte(0xFFD9); 102 | bool debug = settings.value("MainWindow/debug", false).toBool(); 103 | 104 | header = this->size() % BANK_SIZE == 0x200; 105 | 106 | for (int i = 0; versions[i].address; i++) { 107 | #ifdef QT_NO_DEBUG 108 | if (!debug && versions[i].game == sts) 109 | continue; 110 | #endif 111 | 112 | readBytes(versions[i].address, 6, buf); 113 | if (!memcmp(buf, versions[i].string, 6) 114 | && (versions[i].game == sts || region == versions[i].region)) { 115 | game = versions[i].game; 116 | version = (ROMFile::version_e)i; 117 | return true; 118 | } 119 | } 120 | 121 | // no valid ROM detected - throw error 122 | QMessageBox::critical(NULL, "Open File", 123 | "Please select a valid Kirby Bowl or Kirby's Dream Course ROM.", 124 | QMessageBox::Ok); 125 | return false; 126 | } 127 | 128 | /* 129 | Reads data from a file into a pre-existing char buffer. 130 | If "size" == 0, the data is decompressed, with a maximum decompressed 131 | size of 65,536 bytes (64 kb). 132 | 133 | Returns the size of the data read from the file, or 0 if the read was 134 | unsuccessful. 135 | */ 136 | size_t ROMFile::readBytes(uint addr, uint size, void *buffer) { 137 | if (!size) { 138 | char packed[DATA_SIZE]; 139 | this->seek(toOffset(addr)); 140 | if (this->read(packed, DATA_SIZE) > 0) 141 | return unpack((uint8_t*)packed, (uint8_t*)buffer); 142 | else 143 | return 0; 144 | 145 | } else { 146 | this->seek(toOffset(addr)); 147 | return qMax((int)read((char*)buffer, size), 0); 148 | } 149 | } 150 | 151 | uint8_t ROMFile::readByte(uint addr) { 152 | uint8_t data; 153 | readBytes(addr, 1, &data); 154 | return data; 155 | } 156 | 157 | uint16_t ROMFile::readInt16(uint addr) { 158 | uint16_t data; 159 | readBytes(addr, 2, &data); 160 | return data; 161 | } 162 | 163 | uint32_t ROMFile::readInt32(uint addr) { 164 | uint32_t data; 165 | readBytes(addr, 2, &data); 166 | return data; 167 | } 168 | 169 | /* 170 | Reads a 24-bit ROM pointer from a file, then dereferences the pointer and 171 | reads from the address pointed to. If "size" == 0, the data is decompressed. 172 | 173 | Returns the size of data read, or 0 if unsuccessful. 174 | */ 175 | size_t ROMFile::readFromPointer(uint addr, uint size, void *buffer) { 176 | uint pointer; 177 | // first, read the pointer 178 | this->seek(toOffset(addr)); 179 | if (!(this->read((char*)&pointer, sizeof(uint)))) return 0; 180 | pointer &= 0x00FFFFFF; 181 | 182 | // then, read from where it points 183 | return readBytes(pointer, size, buffer); 184 | } 185 | 186 | /* 187 | Writes data to an ROM address in a file. 188 | Since this (currently) only deals with SNES ROMs, offsets will be moved up 189 | to 32kb boundaries when needed. 190 | 191 | Returns the next available address to write data to. 192 | */ 193 | uint ROMFile::writeBytes(uint addr, uint size, void *buffer) { 194 | uint offset = toOffset(addr); 195 | uint spaceLeft = BANK_SIZE - (addr % BANK_SIZE); 196 | 197 | // move offset forward if there's not enough space left in the bank 198 | if (size > spaceLeft) 199 | offset += spaceLeft; 200 | 201 | // now write data to file 202 | this->seek(offset); 203 | this->write((const char*)buffer, size); 204 | 205 | // return new ROM address 206 | return toAddress(pos()); 207 | } 208 | 209 | uint ROMFile::writeByte(uint addr, uint8_t data) { 210 | return writeBytes(addr, 1, &data); 211 | } 212 | 213 | uint ROMFile::writeInt16(uint addr, uint16_t data) { 214 | return writeBytes(addr, 2, &data); 215 | } 216 | 217 | uint ROMFile::writeInt32(uint addr, uint32_t data) { 218 | return writeBytes(addr, 4, &data); 219 | } 220 | 221 | /* 222 | Writes data to an offset in a file, and then writes the 24-bit SNES pointer 223 | to that data into a second offset. 224 | 225 | Returns the next available offset to write data to. 226 | */ 227 | uint ROMFile::writeToPointer(uint pointer, uint addr, 228 | uint size, void *buffer) { 229 | // write the data 230 | addr = writeBytes(addr, size, buffer); 231 | 232 | // write the data pointer 233 | // (do this AFTER data is written in case writeData needs to move to the next 234 | // ROM bank) 235 | int startAddr = addr - size; 236 | this->seek(toOffset(pointer)); 237 | this->write((const char*)&startAddr, 3); 238 | 239 | return addr; 240 | } 241 | -------------------------------------------------------------------------------- /src/romfile.h: -------------------------------------------------------------------------------- 1 | /* 2 | This code is released under the terms of the MIT license. 3 | See COPYING.txt for details. 4 | */ 5 | 6 | #ifndef FILE_H 7 | #define FILE_H 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #define BANK_SIZE 0x8000 14 | 15 | 16 | class ROMFile: public QFile { 17 | public: 18 | 19 | enum version_e { 20 | kirby_jp, 21 | kirby_us, 22 | kirby_eu, 23 | sts_jp 24 | }; 25 | 26 | enum game_e { 27 | kirby, 28 | sts 29 | }; 30 | 31 | ROMFile(); 32 | 33 | bool openROM(OpenMode flags); 34 | 35 | ROMFile::game_e getGame(); 36 | ROMFile::ROMFile::version_e getVersion(); 37 | 38 | uint toAddress(uint offset); 39 | uint toOffset(uint addr); 40 | 41 | size_t readBytes(uint addr, uint size, void *buffer); 42 | uint8_t readByte(uint addr); 43 | uint16_t readInt16(uint addr); 44 | uint32_t readInt32(uint addr); 45 | size_t readFromPointer(uint addr, uint size, void *buffer); 46 | uint writeBytes(uint addr, uint size, void *buffer); 47 | uint writeByte(uint addr, uint8_t data); 48 | uint writeInt16(uint addr, uint16_t data); 49 | uint writeInt32(uint addr, uint32_t data); 50 | uint writeToPointer(uint ptr, uint addr, uint size, void *buffer); 51 | 52 | private: 53 | bool header; 54 | ROMFile::game_e game; 55 | ROMFile::version_e version; 56 | }; 57 | 58 | #endif // FILE_H 59 | -------------------------------------------------------------------------------- /src/tileeditwindow.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | This code is released under the terms of the MIT license. 3 | See COPYING.txt for details. 4 | */ 5 | 6 | #include 7 | 8 | #include "tileeditwindow.h" 9 | #include "ui_tileeditwindow.h" 10 | 11 | #include "kirby.h" 12 | #include "level.h" 13 | #include "metatile.h" 14 | 15 | using namespace stuff; 16 | 17 | uint getCheckBox(const QCheckBox *box); 18 | void setCheckBox(QCheckBox *box, int state); 19 | 20 | TileEditWindow::TileEditWindow(QWidget *parent) : 21 | QDialog(parent, Qt::CustomizeWindowHint 22 | | Qt::WindowTitleHint 23 | | Qt::WindowCloseButtonHint 24 | | Qt::MSWindowsFixedSizeDialogHint 25 | ), 26 | ui(new Ui::TileEditWindow) 27 | { 28 | ui->setupUi(this); 29 | 30 | // prevent window resizing 31 | this->layout()->setSizeConstraint(QLayout::SetFixedSize); 32 | 33 | // add geometry types to dropdown 34 | for (StringMap::const_iterator i = kirbyGeometry.begin(); i != kirbyGeometry.end(); i++) { 35 | if (i->second.size()) { 36 | ui->comboBox_Terrain->addItem(i->second, i->first); 37 | } 38 | } 39 | 40 | // add obstacle types to dropdown (only valid ones) 41 | for (StringMap::const_iterator i = kirbyObstacles.begin(); i != kirbyObstacles.end(); i++) { 42 | if (i->second.size()) { 43 | ui->comboBox_Obstacle->addItem(i->second, i->first); 44 | } 45 | } 46 | } 47 | 48 | TileEditWindow::~TileEditWindow() 49 | { 50 | delete ui; 51 | } 52 | 53 | int TileEditWindow::startEdit(leveldata_t *level, QRect sel) { 54 | this->level = level; 55 | 56 | // iterate through the tiles in the selection and build up the 57 | // selection info. 58 | selX = sel.x(); 59 | selY = sel.y(); 60 | selWidth = sel.width(); 61 | selLength = sel.height(); 62 | maptile_t tile = level->tiles[selY][selX]; 63 | 64 | // set initial values to compare against 65 | // (to determine when the selection covers multiple values) 66 | tileInfo.geometry = tile.geometry; 67 | tileInfo.obstacle = tile.obstacle; 68 | tileInfo.height = tile.height; 69 | tileInfo.bumperEast = tile.flags.bumperEast; 70 | tileInfo.bumperNorth = tile.flags.bumperNorth; 71 | tileInfo.bumperSouth = tile.flags.bumperSouth; 72 | tileInfo.bumperWest = tile.flags.bumperWest; 73 | 74 | tileInfo.layer = tile.flags.layer; 75 | 76 | tileInfo.maxHeight = tile.height; 77 | tileInfo.minHeight = tile.height; 78 | 79 | for (int v = selY; v < selY + selLength; v++) { 80 | for (int h = selX; h < selX + selWidth; h++) { 81 | tile = level->tiles[v][h]; 82 | if (tile.geometry != tileInfo.geometry) 83 | tileInfo.geometry = -1; 84 | if (tile.obstacle != tileInfo.obstacle) 85 | tileInfo.obstacle = -1; 86 | if (tile.height != tileInfo.height) 87 | tileInfo.height = -1; 88 | 89 | tileInfo.minHeight = std::min((int)tileInfo.minHeight, (int)tile.height); 90 | tileInfo.maxHeight = std::max((int)tileInfo.maxHeight, (int)tile.height); 91 | 92 | if (tile.flags.bumperEast != tileInfo.bumperEast) 93 | tileInfo.bumperEast = -1; 94 | if (tile.flags.bumperNorth != tileInfo.bumperNorth) 95 | tileInfo.bumperNorth = -1; 96 | if (tile.flags.bumperSouth != tileInfo.bumperSouth) 97 | tileInfo.bumperSouth = -1; 98 | if (tile.flags.bumperWest != tileInfo.bumperWest) 99 | tileInfo.bumperWest = -1; 100 | 101 | if (tile.flags.layer != tileInfo.layer) 102 | tileInfo.layer = -1; 103 | } 104 | } 105 | 106 | // multiple height values = raise/lower instead of set height 107 | bool relativeHeight = tileInfo.height == -1; 108 | 109 | // set the control values to the stuff in the tileinfo 110 | if (tileInfo.geometry == -1) { 111 | ui->comboBox_Terrain->insertItem(0, tr("(multiple)"), 0); 112 | ui->comboBox_Terrain->setCurrentIndex(0); 113 | } else { 114 | ui->comboBox_Terrain->setCurrentIndex(std::distance(kirbyGeometry.begin(), 115 | kirbyGeometry.find(tileInfo.geometry))); 116 | } 117 | if (tileInfo.obstacle == -1) { 118 | ui->comboBox_Obstacle->insertItem(0, tr("(multiple)")); 119 | ui->comboBox_Obstacle->setCurrentIndex(0); 120 | } else { 121 | ui->comboBox_Obstacle->setCurrentIndex(std::distance(kirbyObstacles.begin(), 122 | kirbyObstacles.find(tileInfo.obstacle))); 123 | } 124 | 125 | // set bumper checkboxes to correct values 126 | setCheckBox(ui->checkBox_North, tileInfo.bumperNorth); 127 | setCheckBox(ui->checkBox_South, tileInfo.bumperSouth); 128 | setCheckBox(ui->checkBox_East, tileInfo.bumperEast); 129 | setCheckBox(ui->checkBox_West, tileInfo.bumperWest); 130 | 131 | // set up the height slider 132 | relativeHeight = tileInfo.height == -1; 133 | int min, max, value; 134 | if (relativeHeight) { 135 | ui->label_Height->setText(tr("Raise/Lower")); 136 | min = 0 - tileInfo.minHeight; 137 | max = MAX_HEIGHT - tileInfo.maxHeight; 138 | value = 0; 139 | } else { 140 | ui->label_Height->setText(tr("Height")); 141 | min = 0; 142 | max = MAX_HEIGHT; 143 | value = tileInfo.height; 144 | } 145 | ui->horizontalSlider_Height->setMinimum(min); 146 | ui->spinBox_Height->setMinimum(min); 147 | ui->horizontalSlider_Height->setMaximum(max); 148 | ui->spinBox_Height->setMaximum(max); 149 | ui->horizontalSlider_Height->setValue(value); 150 | 151 | // set up the layer radio buttons 152 | ui->radioButton_Keep->setChecked(tileInfo.layer == -1); 153 | ui->radioButton_Layer1->setChecked(tileInfo.layer == 0); 154 | ui->radioButton_Layer2->setChecked(tileInfo.layer == 1); 155 | 156 | // run the modal dialog 157 | return this->exec(); 158 | } 159 | 160 | /* 161 | This array maps conveyor belt types to their counterparts for the different slope types. 162 | Dimension 1 is the belt direction, dimension 2 is the slope direction 163 | */ 164 | const stuff::type_e conveyorMap[4][4] = { 165 | //slope south east north west 166 | //beltSouth 167 | {beltSouthDown, nothing, beltSouthUp, nothing}, 168 | //beltEast 169 | {nothing, beltEastDown, nothing, beltEastUp}, 170 | //beltNorth 171 | {beltNorthUp, nothing, beltNorthDown, nothing}, 172 | //beltWest 173 | {nothing, beltWestUp, nothing, beltWestDown} 174 | }; 175 | 176 | void TileEditWindow::accept() { 177 | // get terrain and obstacle values 178 | // (if multiple geom/obstacle values are selected, only use index >0) 179 | if (tileInfo.geometry != -1 || ui->comboBox_Terrain->currentIndex() > 0) 180 | tileInfo.geometry = ui->comboBox_Terrain->itemData(ui->comboBox_Terrain->currentIndex()).toUInt(); 181 | 182 | if (tileInfo.obstacle != -1 || ui->comboBox_Obstacle->currentIndex() > 0) 183 | //tileInfo.obstacle = stringVal(ui->comboBox_Obstacle->currentText()); 184 | tileInfo.obstacle = ui->comboBox_Obstacle->itemData(ui->comboBox_Obstacle->currentIndex()).toUInt(); 185 | 186 | // get bumper checkbox status 187 | tileInfo.bumperNorth = getCheckBox(ui->checkBox_North); 188 | tileInfo.bumperSouth = getCheckBox(ui->checkBox_South); 189 | tileInfo.bumperEast = getCheckBox(ui->checkBox_East); 190 | tileInfo.bumperWest = getCheckBox(ui->checkBox_West); 191 | 192 | // get height slider status 193 | int newHeight = ui->horizontalSlider_Height->value(); 194 | bool relativeHeight = tileInfo.height == -1; 195 | 196 | // get layer selection status 197 | bool layer1 = ui->radioButton_Layer1->isChecked(); 198 | bool layer2 = ui->radioButton_Layer2->isChecked(); 199 | 200 | // after the tile edit window is done, apply the changes to the tiles 201 | for (int v = selY; v < selY + selLength; v++) { 202 | for (int h = selX; h < selX + selWidth; h++) { 203 | maptile_t *newTile = &(level->tiles[v][h]); 204 | 205 | if (newTile->geometry == 0 && tileInfo.geometry == -1) { 206 | continue; 207 | } else if (tileInfo.geometry == 0) { 208 | *newTile = noTile; 209 | continue; 210 | } 211 | 212 | if (tileInfo.geometry >= 0) 213 | newTile->geometry = tileInfo.geometry; 214 | if (tileInfo.obstacle >= 0) { 215 | // handle multiple types for water hazard based on terrain value 216 | if (tileInfo.obstacle == water 217 | && newTile->geometry >= slopes && newTile->geometry < endSlopes) 218 | newTile->obstacle = water - 1 + newTile->geometry; 219 | // handle multiple types for bounce pads 220 | else if (tileInfo.obstacle == bounceFlat 221 | && newTile->geometry >= slopes && newTile->geometry < slopesDouble) 222 | newTile->obstacle = bounce + newTile->geometry - slopes; 223 | // handle multiple types for conveyor belts 224 | else if (tileInfo.obstacle >= belts && tileInfo.obstacle < beltSlopes 225 | && newTile->geometry >= slopes && newTile->geometry < slopesDouble) 226 | newTile->obstacle = conveyorMap[tileInfo.obstacle - belts][newTile->geometry - slopes]; 227 | else 228 | newTile->obstacle = tileInfo.obstacle; 229 | } 230 | 231 | if (tileInfo.bumperNorth >= 0) 232 | newTile->flags.bumperNorth = tileInfo.bumperNorth; 233 | if (tileInfo.bumperSouth >= 0) 234 | newTile->flags.bumperSouth = tileInfo.bumperSouth; 235 | if (tileInfo.bumperEast >= 0) 236 | newTile->flags.bumperEast = tileInfo.bumperEast; 237 | if (tileInfo.bumperWest>= 0) 238 | newTile->flags.bumperWest = tileInfo.bumperWest; 239 | 240 | if (relativeHeight) 241 | newTile->height += newHeight; 242 | else 243 | newTile->height = newHeight; 244 | 245 | if (layer2) newTile->flags.layer = 1; 246 | else if (layer1) newTile->flags.layer = 0; 247 | 248 | newTile->flags.dummy = 0; 249 | } 250 | } 251 | 252 | QDialog::accept(); 253 | } 254 | 255 | /* 256 | Set a checkbox's tristate value and check state based on the 257 | appropriate value in tileinfo. 258 | */ 259 | void setCheckBox(QCheckBox *box, int state) { 260 | box->setTristate(state == -1); 261 | switch (state) { 262 | case -1: 263 | box->setCheckState(Qt::PartiallyChecked); 264 | break; 265 | case 1: 266 | box->setCheckState(Qt::Checked); 267 | break; 268 | default: 269 | box->setCheckState(Qt::Unchecked); 270 | } 271 | } 272 | 273 | /* 274 | Get a checkbox's value for tileinfo based on its check state 275 | */ 276 | uint getCheckBox(const QCheckBox *box) { 277 | return (box->checkState() == Qt::PartiallyChecked) 278 | ? -1 : box->checkState() / 2; 279 | } 280 | -------------------------------------------------------------------------------- /src/tileeditwindow.h: -------------------------------------------------------------------------------- 1 | /* 2 | This code is released under the terms of the MIT license. 3 | See COPYING.txt for details. 4 | */ 5 | 6 | #ifndef TILEEDITWINDOW_H 7 | #define TILEEDITWINDOW_H 8 | 9 | #include 10 | 11 | #include "level.h" 12 | 13 | namespace Ui { 14 | class TileEditWindow; 15 | } 16 | 17 | class TileEditWindow : public QDialog 18 | { 19 | Q_OBJECT 20 | 21 | public: 22 | explicit TileEditWindow(QWidget *parent = 0); 23 | ~TileEditWindow(); 24 | 25 | int startEdit(leveldata_t *level, QRect sel); 26 | 27 | private: 28 | Ui::TileEditWindow *ui; 29 | 30 | leveldata_t *level; 31 | tileinfo_t tileInfo; 32 | int selX, selY, selLength, selWidth; 33 | bool relativeHeight; 34 | 35 | private slots: 36 | void accept(); 37 | }; 38 | 39 | #endif // TILEEDITWINDOW_H 40 | -------------------------------------------------------------------------------- /src/tileeditwindow.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | TileEditWindow 4 | 5 | 6 | Qt::ApplicationModal 7 | 8 | 9 | 10 | 0 11 | 0 12 | 400 13 | 273 14 | 15 | 16 | 17 | 18 | 0 19 | 0 20 | 21 | 22 | 23 | 24 | 400 25 | 250 26 | 27 | 28 | 29 | 30 | 16777215 31 | 16777215 32 | 33 | 34 | 35 | Edit Tiles 36 | 37 | 38 | 39 | :/images/main16.png:/images/main16.png 40 | 41 | 42 | true 43 | 44 | 45 | 46 | 47 | 48 | QLayout::SetDefaultConstraint 49 | 50 | 51 | 0 52 | 53 | 54 | 55 | 56 | Terrain 57 | 58 | 59 | false 60 | 61 | 62 | 63 | 64 | 65 | 31 66 | 67 | 68 | Qt::Horizontal 69 | 70 | 71 | 72 | 73 | 74 | 75 | false 76 | 77 | 78 | 79 | 80 | 81 | 82 | Height 83 | 84 | 85 | 86 | 87 | 88 | 89 | 31 90 | 91 | 92 | 93 | 94 | 95 | 96 | Qt::Horizontal 97 | 98 | 99 | QSizePolicy::Fixed 100 | 101 | 102 | 103 | 40 104 | 20 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | Obstacles 116 | 117 | 118 | 119 | 120 | 121 | Qt::Horizontal 122 | 123 | 124 | 125 | 40 126 | 20 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | false 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 44 143 | 16777215 144 | 145 | 146 | 147 | Bumpers 148 | 149 | 150 | 151 | 152 | 153 | 154 | North 155 | 156 | 157 | 158 | 159 | 160 | 161 | South 162 | 163 | 164 | 165 | 166 | 167 | 168 | East 169 | 170 | 171 | 172 | 173 | 174 | 175 | West 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | Rendering 186 | 187 | 188 | Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter 189 | 190 | 191 | 192 | 193 | 194 | true 195 | 196 | 197 | Layer 1 198 | 199 | 200 | true 201 | 202 | 203 | 204 | 205 | 206 | 207 | true 208 | 209 | 210 | 211 | 0 212 | 0 213 | 214 | 215 | 216 | Qt::LeftToRight 217 | 218 | 219 | Layer 2 220 | 221 | 222 | true 223 | 224 | 225 | 226 | 227 | 228 | 229 | Qt::Horizontal 230 | 231 | 232 | 233 | 40 234 | 20 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | Qt::Horizontal 243 | 244 | 245 | 246 | 40 247 | 20 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | true 256 | 257 | 258 | Keep 259 | 260 | 261 | false 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | QDialogButtonBox::Cancel|QDialogButtonBox::Ok 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | spinBox_Height 289 | valueChanged(int) 290 | horizontalSlider_Height 291 | setValue(int) 292 | 293 | 294 | 123 295 | 68 296 | 297 | 298 | 265 299 | 68 300 | 301 | 302 | 303 | 304 | horizontalSlider_Height 305 | valueChanged(int) 306 | spinBox_Height 307 | setValue(int) 308 | 309 | 310 | 265 311 | 68 312 | 313 | 314 | 123 315 | 68 316 | 317 | 318 | 319 | 320 | buttonBox 321 | accepted() 322 | TileEditWindow 323 | accept() 324 | 325 | 326 | 199 327 | 250 328 | 329 | 330 | 199 331 | 136 332 | 333 | 334 | 335 | 336 | buttonBox 337 | rejected() 338 | TileEditWindow 339 | reject() 340 | 341 | 342 | 199 343 | 250 344 | 345 | 346 | 199 347 | 136 348 | 349 | 350 | 351 | 352 | 353 | -------------------------------------------------------------------------------- /src/version.h: -------------------------------------------------------------------------------- 1 | /* 2 | This code is released under the terms of the MIT license. 3 | See COPYING.txt for details. 4 | */ 5 | 6 | #ifndef VERSION_H 7 | #define VERSION_H 8 | 9 | #define INFO_TITLE "Kirby's Dream Course Editor" 10 | #define INFO_MYNAME "Devin Acker (Revenant)\0" 11 | #define INFO_LEGAL "Copyright 2013-2015 by Revenant\0" 12 | 13 | #define INFO_DESC "Kirby's Dream Course editor\0" 14 | #define INFO_VERS "1.13c\0" 15 | #define INFO_VNUM 1, 13, 2, 0 16 | #define INFO_NAME "KDCEditor\0" 17 | #define INFO_FILE "KDCEditor.exe\0" 18 | 19 | #endif // VERSION_H 20 | -------------------------------------------------------------------------------- /src/windows.rc: -------------------------------------------------------------------------------- 1 | /* 2 | This code is released under the terms of the MIT license. 3 | See COPYING.txt for details. 4 | */ 5 | 6 | #include 7 | #include "version.h" 8 | 9 | 0 ICON "images/main.ico" 10 | 11 | VS_VERSION_INFO VERSIONINFO 12 | FILEVERSION INFO_VNUM 13 | PRODUCTVERSION INFO_VNUM 14 | FILEFLAGSMASK 0x3fL 15 | FILEFLAGS 0 16 | FILEOS VOS_NT_WINDOWS32 17 | FILETYPE VFT_APP 18 | FILESUBTYPE VFT2_UNKNOWN 19 | { 20 | BLOCK "VarFileInfo" 21 | { 22 | VALUE "Translation", 0x409, 1200 23 | } 24 | BLOCK "StringFileInfo" 25 | { 26 | BLOCK "040904b0" 27 | { 28 | VALUE "CompanyName", INFO_MYNAME 29 | VALUE "FileDescription", INFO_DESC 30 | VALUE "FileVersion", INFO_VERS 31 | VALUE "InternalName", INFO_NAME 32 | VALUE "LegalCopyright", INFO_LEGAL 33 | VALUE "OriginalFilename", INFO_FILE 34 | VALUE "ProductName", INFO_DESC 35 | VALUE "ProductVersion", INFO_VERS 36 | } 37 | } 38 | } 39 | --------------------------------------------------------------------------------