1 /** 2 * Copyright © Novelate 2020 3 * License: MIT (https://github.com/Novelate/NovelateEngine/blob/master/LICENSE) 4 * Author: Jacob Jensen (bausshf) 5 * Website: https://novelate.com/ 6 * ------ 7 * Novelate is a free and open-source visual novel engine and framework written in the D programming language. 8 * It can be used freely for both personal and commercial projects. 9 * ------ 10 * Module Description: 11 * The core module for Novelate exposes misc. core functionality of the engine. 12 */ 13 module novelate.core; 14 15 import std.file : readText, write, exists; 16 import std.array : replace, split, array; 17 import std..string : strip, stripLeft, stripRight, format; 18 import std.algorithm : filter; 19 import std.conv : to; 20 21 import novelate.scripting.parser; 22 import novelate.config; 23 import novelate.screens; 24 import novelate.state; 25 import novelate.fonts; 26 import novelate.events; 27 import novelate.colormanager; 28 import novelate.external; 29 30 /// Enumeration of layer types. There are 10 available layers ranging from 0 - 9 as indexes. 7 is the largest named frame. 31 enum LayerType : size_t 32 { 33 /// The background layer. 34 background = 0, 35 /// The object background. 36 objectBackground = 1, 37 /// The object foreground. 38 objectForeground = 2, 39 /// The character layer. 40 character = 3, 41 /// The front character layer. 42 characterFront = 4, 43 /// The dialogue box layer. 44 dialogueBox = 5, 45 /// The dialogue box interaction layer. Generally used for text, options etc. 46 dialogueBoxInteraction = 6, 47 /// The front layer. 48 front = 7 49 } 50 51 /** 52 * Changes the resolution of the game. This should preferebly only be of the following resolutions: 800x600, 1024x768 or 1280x720. However any resolutions are acceptable but may have side-effects attached such as invalid rendering etc. All set resolutions are saved to a res.ini file in the data folder allowing for resolutions to be kept across instances of the game. 53 * Params: 54 * width = The width of the resolution. 55 * height = The height of the resolution. 56 * fullScreen = A boolean determining whether the game is full-screen or not. 57 */ 58 void changeResolution(size_t width, size_t height, bool fullScreen) 59 { 60 _width = width; 61 _height = height; 62 63 if (_window && _window.isOpen) 64 { 65 _window.close(); 66 67 _window = null; 68 } 69 70 write(config.dataFolder ~ "/res.ini", format("Width=%s\r\nHeight=%s\r\nFullScreen=%s", width, height, fullScreen)); 71 72 _window = ExternalWindow.create(_title, width, height, fullScreen); 73 74 _window.fps = _fps; 75 76 if (_activeScreens) 77 { 78 foreach (k,v; _activeScreens) 79 { 80 v.refresh(_width, _height); 81 } 82 } 83 84 fireEvent!(EventType.onResolutionChange); 85 } 86 87 /// Loads the credits video. Currently does nothing. 88 void loadCreditsVideo() 89 { 90 fireEvent!(EventType.onLoadingCreditsVideo); 91 92 // ... 93 } 94 95 /// Enumeration of standard screens. 96 enum StandardScreen : string 97 { 98 /// No screen. 99 none = "none", 100 /// The main menu. 101 mainMenu = "mainMenu", 102 /// The load screen. 103 load = "load", 104 /// The save screen. 105 save = "save", 106 /// The about screen. 107 about = "about", 108 /// The characters screen. 109 characters = "characters", 110 /// The game play screen (scene) 111 scene = "scene" 112 } 113 114 /** 115 * Adds a screen to the game. 116 * Params: 117 * screen = The screen to add. 118 */ 119 void addScreen(Screen screen) 120 { 121 if (!screen) 122 { 123 return; 124 } 125 126 screen.setWidthAndHeight(_width, _height); 127 128 _activeScreens[screen.name] = screen; 129 } 130 131 /** 132 * Removes a screen from the game. 133 * Params: 134 * screenName = The name of the screen. 135 */ 136 void removeScreen(string screenName) 137 { 138 if (!_activeScreens) 139 { 140 return; 141 } 142 143 _activeScreens.remove(screenName); 144 } 145 146 /** 147 * Changes the active screen. 148 * Params: 149 * screenName = The screen to change to. You should use the "Screen" enum for accuracy of the screen name. 150 * data = The data passed onto the screen. 151 */ 152 void changeActiveScreen(string screenName, string[] data = null) 153 { 154 if (!_activeScreens) 155 { 156 return; 157 } 158 159 auto screen = _activeScreens.get(screenName, null); 160 161 if (!screen) 162 { 163 return; 164 } 165 166 if (screen.shouldClearLayers(data)) 167 { 168 screen.clearAllLayersButBackground(); 169 } 170 171 screen.update(data); 172 173 _activeScreen = screen; 174 _activeScreenName = screen.name; 175 176 fireEvent!(EventType.onScreenChange); 177 } 178 179 /// Initializes the game. 180 void initialize() 181 { 182 initExternalBinding(); 183 184 parseFile("main.txt"); 185 186 loadFonts(config.dataFolder ~ "/fonts"); 187 188 _title = config.gameTitle; 189 190 if (config.gameSlogan && config.gameSlogan.length) 191 { 192 _title ~= " - " ~ config.gameSlogan; 193 } 194 195 fullScreen = false; 196 197 if (exists(config.dataFolder ~ "/res.ini")) 198 { 199 auto lines = readText(config.dataFolder ~ "/res.ini").replace("\r", "").split("\n"); 200 201 foreach (line; lines) 202 { 203 if (!line || !line.strip.length) 204 { 205 continue; 206 } 207 208 auto data = line.split("="); 209 210 if (data.length != 2) 211 { 212 continue; 213 } 214 215 switch (data[0]) 216 { 217 case "Width": _width = to!size_t(data[1]); break; 218 case "Height": _height = to!size_t(data[1]); break; 219 case "FullScreen": fullScreen = to!bool(data[1]); break; 220 221 default: break; 222 } 223 } 224 } 225 226 addScreen(new MainMenuScreen); 227 addScreen(new PlayScreen); 228 229 playScene = config.startScene; 230 } 231 232 /// Runs the game/event/UI loop. 233 void run() 234 { 235 auto backgroundColor = colorFromRGBA(0,0,0,0xff); 236 237 changeResolution(_width, _height, fullScreen); 238 239 changeActiveScreen(StandardScreen.mainMenu); 240 241 auto manager = new ExternalEventManager; 242 manager.addHandler(ExternalEventType.closed, { 243 _window.close(); 244 }); 245 246 manager.addHandler(ExternalEventType.mouseMoved, { 247 if (_activeScreen) 248 { 249 _activeScreen.mouseMove(ExternalEventState.mouseMoveEvent.x, ExternalEventState.mouseMoveEvent.y); 250 } 251 }); 252 manager.addHandler(ExternalEventType.mouseButtonPressed, { 253 if (_activeScreen) 254 { 255 _activeScreen.mousePress(ExternalEventState.mouseButtonEvent.button); 256 } 257 }); 258 manager.addHandler(ExternalEventType.mouseButtonReleased, { 259 if (_activeScreen) 260 { 261 _activeScreen.mouseRelease(ExternalEventState.mouseButtonEvent.button); 262 } 263 }); 264 265 manager.addHandler(ExternalEventType.keyPressed, { 266 if (_activeScreen) 267 { 268 _activeScreen.keyPress(ExternalEventState.keyEvent.code); 269 } 270 }); 271 manager.addHandler(ExternalEventType.keyReleased, { 272 if (_activeScreen) 273 { 274 _activeScreen.keyRelease(ExternalEventState.keyEvent.code); 275 } 276 }); 277 278 while (running && _window && _window.isOpen) 279 { 280 if (exitGame) 281 { 282 exitGame = false; 283 running = false; 284 _window.close(); 285 goto exit; 286 } 287 288 if (endGame) 289 { 290 if (config.creditsVideo) 291 { 292 // Should be a component ... 293 loadCreditsVideo(); 294 } 295 else 296 { 297 changeActiveScreen(StandardScreen.mainMenu); 298 } 299 300 endGame = false; 301 playScene = config.startScene; 302 } 303 304 if (nextScene) 305 { 306 auto sceneScreen = _activeScreens[StandardScreen.scene]; 307 308 if (sceneScreen) 309 { 310 sceneScreen.update([nextScene]); 311 } 312 313 nextScene = null; 314 } 315 316 if (!_window.processEvents(manager)) 317 { 318 goto exit; 319 } 320 321 if (_window.canUpdate) 322 { 323 _window.clear(backgroundColor); 324 325 if (_activeScreen) 326 { 327 _activeScreen.render(_window); 328 } 329 330 fireEvent!(EventType.onRender); 331 332 _window.render(); 333 } 334 } 335 exit: 336 quit(); 337 }