From 6352e8e3196f78388b6c771073f9e03eaa612673 Mon Sep 17 00:00:00 2001 From: Kae <80987908+Novaenia@users.noreply.github.com> Date: Tue, 20 Jun 2023 14:33:09 +1000 Subject: [PATCH] everything everywhere all at once --- .gitignore | 12 + README.md | 4 +- cmake/FindDirectX.cmake | 115 + cmake/FindDiscordApi.cmake | 31 + cmake/FindGLEW.cmake | 67 + cmake/FindJeMalloc.cmake | 44 + cmake/FindLua52.cmake | 80 + cmake/FindOggVorbis.cmake | 57 + cmake/FindPkgMacros.cmake | 142 + cmake/FindSDL2.cmake | 165 + cmake/FindSteamApi.cmake | 31 + cmake/GetGitRevisionDescription.cmake | 130 + cmake/GetGitRevisionDescription.cmake.in | 38 + cmake/TargetArch.cmake | 134 + doc/OPENSOURCE.md | 768 + doc/lua/activeitem.md | 187 + doc/lua/activeitemanimation.md | 43 + doc/lua/actormovementcontroller.md | 417 + doc/lua/animator.md | 243 + doc/lua/celestial.md | 238 + doc/lua/commandprocessor.md | 7 + doc/lua/config.md | 9 + doc/lua/containerpane.md | 19 + doc/lua/entity.md | 69 + doc/lua/item.md | 159 + doc/lua/localanimator.md | 68 + doc/lua/message.md | 21 + doc/lua/monster.md | 172 + doc/lua/movementcontroller.md | 248 + doc/lua/npc.md | 283 + doc/lua/object.md | 229 + doc/lua/objectanimator.md | 19 + doc/lua/physics.md | 19 + doc/lua/player.md | 529 + doc/lua/playercompanions.md | 13 + doc/lua/projectile.md | 67 + doc/lua/quest.md | 203 + doc/lua/root.md | 328 + doc/lua/scriptedanimator.md | 23 + doc/lua/scriptpane.md | 49 + doc/lua/stagehand.md | 37 + doc/lua/statuscontroller.md | 263 + doc/lua/statuseffect.md | 69 + doc/lua/tech.md | 63 + doc/lua/updatablescript.md | 13 + doc/lua/utility.md | 170 + doc/lua/vehicle.md | 91 + doc/lua/widget.md | 391 + doc/lua/world.md | 1020 + scripts/distclean.sh | 6 + scripts/findskippedmatids.sh | 16 + scripts/format-source.sh | 11 + scripts/gitlab-ci/assemble.sh | 98 + scripts/gitlab-ci/linux/build.sh | 22 + scripts/gitlab-ci/linux/run-client.sh | 5 + scripts/gitlab-ci/linux/run-server.sh | 22 + scripts/gitlab-ci/linux/sbinit.config | 8 + scripts/gitlab-ci/linux/test.sh | 8 + .../macos/Starbound.app/Contents/Info.plist | 27 + .../Contents/Resources/starbound.icns | Bin 0 -> 47576 bytes scripts/gitlab-ci/macos/build.sh | 25 + scripts/gitlab-ci/macos/run-server.sh | 11 + scripts/gitlab-ci/macos/sbinit.config | 8 + scripts/gitlab-ci/macos/test.sh | 9 + scripts/gitlab-ci/repack_steamfree.sh | 43 + scripts/gitlab-ci/windows32/build.bat | 25 + scripts/gitlab-ci/windows32/sbinit.config | 14 + scripts/gitlab-ci/windows32/test.bat | 8 + scripts/gitlab-ci/windows64/build.bat | 30 + scripts/gitlab-ci/windows64/sbinit.config | 14 + scripts/gitlab-ci/windows64/test.bat | 8 + scripts/ide/atom-build.json | 16 + scripts/ide/clang_complete | 12 + scripts/ide/linter-clang-flags | 1 + scripts/ide/linter-clang-includes | 12 + scripts/join-multiline-source-strings.sh | 11 + scripts/linux/autotag_objects.sh | 21 + scripts/linux/find_invalid_tiles.sh | 6 + scripts/linux/object_tag_editor.sh | 5 + scripts/linux/sbinit.config | 20 + scripts/linux/setup.sh | 27 + scripts/linux/ycm_extra_conf.py | 149 + scripts/osx/build.command | 6 + scripts/osx/copy-to-steam.sh | 9 + scripts/osx/launch-steam.sh | 7 + scripts/osx/run.command | 6 + scripts/osx/sbinit.config | 23 + scripts/osx/setup.command | 17 + scripts/osx/setup.sh | 29 + scripts/osx/ycm_extra_conf.py | 160 + scripts/packing.config | 69 + scripts/steam_appid.txt | 1 + scripts/windows/build.bat | 7 + scripts/windows/copy-to-steam32.bat | 8 + scripts/windows/copy-to-steam64.bat | 8 + scripts/windows/find_invalid_tiles.bat | 7 + scripts/windows/sbinit.config | 24 + scripts/windows/setup32.bat | 44 + scripts/windows/setup64.bat | 42 + scripts/windows/showlog.bat | 1 + scripts/windows/update_tilesets.bat | 6 + source/CMakeLists.txt | 533 + source/application/CMakeLists.txt | 65 + source/application/StarApplication.cpp | 31 + source/application/StarApplication.hpp | 76 + .../application/StarApplicationController.hpp | 67 + .../StarDesktopService_pc_steam.cpp | 11 + .../StarDesktopService_pc_steam.hpp | 19 + source/application/StarInputEvent.cpp | 166 + source/application/StarInputEvent.hpp | 274 + source/application/StarMainApplication.hpp | 34 + .../application/StarMainApplication_sdl.cpp | 692 + .../StarP2PNetworkingService_pc.cpp | 616 + .../StarP2PNetworkingService_pc.hpp | 146 + .../application/StarPlatformServices_pc.cpp | 175 + .../application/StarPlatformServices_pc.hpp | 83 + source/application/StarRenderer.cpp | 60 + source/application/StarRenderer.hpp | 147 + source/application/StarRenderer_opengl20.cpp | 828 + source/application/StarRenderer_opengl20.hpp | 199 + .../StarStatisticsService_pc_steam.cpp | 130 + .../StarStatisticsService_pc_steam.hpp | 41 + source/application/StarTextureAtlas.hpp | 417 + ...arUserGeneratedContentService_pc_steam.cpp | 60 + ...arUserGeneratedContentService_pc_steam.hpp | 26 + .../application/discord/activity_manager.cpp | 177 + source/application/discord/activity_manager.h | 42 + .../discord/application_manager.cpp | 63 + .../application/discord/application_manager.h | 29 + source/application/discord/core.cpp | 162 + source/application/discord/core.h | 58 + source/application/discord/discord.h | 14 + source/application/discord/event.h | 59 + source/application/discord/ffi.h | 790 + source/application/discord/image_manager.cpp | 57 + source/application/discord/image_manager.h | 28 + source/application/discord/lobby_manager.cpp | 547 + source/application/discord/lobby_manager.h | 88 + .../application/discord/network_manager.cpp | 103 + source/application/discord/network_manager.h | 39 + .../application/discord/overlay_manager.cpp | 97 + source/application/discord/overlay_manager.h | 32 + .../discord/relationship_manager.cpp | 90 + .../discord/relationship_manager.h | 32 + .../application/discord/storage_manager.cpp | 148 + source/application/discord/storage_manager.h | 45 + source/application/discord/store_manager.cpp | 160 + source/application/discord/store_manager.h | 38 + source/application/discord/types.cpp | 707 + source/application/discord/types.h | 426 + source/application/discord/user_manager.cpp | 58 + source/application/discord/user_manager.h | 29 + source/base/CMakeLists.txt | 37 + source/base/StarAnimatedPartSet.cpp | 305 + source/base/StarAnimatedPartSet.hpp | 163 + source/base/StarAssetSource.hpp | 35 + source/base/StarAssets.cpp | 1113 + source/base/StarAssets.hpp | 380 + source/base/StarBlocksAlongLine.hpp | 108 + source/base/StarCellularLightArray.hpp | 590 + source/base/StarCellularLighting.cpp | 160 + source/base/StarCellularLighting.hpp | 96 + source/base/StarCellularLiquid.hpp | 658 + source/base/StarConfiguration.cpp | 54 + source/base/StarConfiguration.hpp | 39 + source/base/StarDirectoryAssetSource.cpp | 96 + source/base/StarDirectoryAssetSource.hpp | 41 + source/base/StarMixer.cpp | 486 + source/base/StarMixer.hpp | 167 + source/base/StarPackedAssetSource.cpp | 158 + source/base/StarPackedAssetSource.hpp | 45 + source/base/StarVersion.cpp.in | 9 + source/base/StarVersion.hpp | 16 + source/base/StarVersionOptionParser.cpp | 46 + source/base/StarVersionOptionParser.hpp | 27 + source/base/StarWorldGeometry.cpp | 358 + source/base/StarWorldGeometry.hpp | 263 + source/client/CMakeLists.txt | 31 + source/client/StarClientApplication.cpp | 903 + source/client/StarClientApplication.hpp | 117 + source/client/starbound-largelogo.ico | Bin 0 -> 370070 bytes source/client/starbound.exe.manifest | 18 + source/client/starbound.ico | Bin 0 -> 370070 bytes source/client/starbound.rc | 25 + source/core/CMakeLists.txt | 201 + source/core/StarAStar.hpp | 276 + source/core/StarAlgorithm.hpp | 667 + source/core/StarArray.hpp | 254 + source/core/StarAtomicSharedPtr.hpp | 121 + source/core/StarAudio.cpp | 562 + source/core/StarAudio.hpp | 95 + source/core/StarBTree.hpp | 937 + source/core/StarBTreeDatabase.cpp | 1188 + source/core/StarBTreeDatabase.hpp | 344 + source/core/StarBiMap.hpp | 419 + source/core/StarBlockAllocator.hpp | 268 + source/core/StarBuffer.cpp | 287 + source/core/StarBuffer.hpp | 122 + source/core/StarByteArray.cpp | 258 + source/core/StarByteArray.hpp | 259 + source/core/StarBytes.hpp | 109 + source/core/StarCasting.hpp | 93 + source/core/StarColor.cpp | 627 + source/core/StarColor.hpp | 171 + source/core/StarCompression.cpp | 223 + source/core/StarCompression.hpp | 59 + source/core/StarConfig.hpp | 113 + source/core/StarDataStream.cpp | 311 + source/core/StarDataStream.hpp | 393 + source/core/StarDataStreamDevices.cpp | 167 + source/core/StarDataStreamDevices.hpp | 249 + source/core/StarDataStreamExtra.hpp | 393 + source/core/StarDynamicLib.hpp | 38 + source/core/StarDynamicLib_unix.cpp | 46 + source/core/StarDynamicLib_windows.cpp | 43 + source/core/StarEither.hpp | 243 + source/core/StarEncode.cpp | 226 + source/core/StarEncode.hpp | 27 + source/core/StarException.hpp | 100 + source/core/StarException_unix.cpp | 136 + source/core/StarException_windows.cpp | 253 + source/core/StarFile.cpp | 240 + source/core/StarFile.hpp | 149 + source/core/StarFile_unix.cpp | 295 + source/core/StarFile_windows.cpp | 412 + source/core/StarFlatHashMap.hpp | 545 + source/core/StarFlatHashSet.hpp | 497 + source/core/StarFlatHashTable.hpp | 557 + source/core/StarFont.cpp | 123 + source/core/StarFont.hpp | 48 + source/core/StarFormat.hpp | 100 + source/core/StarFormattedJson.cpp | 674 + source/core/StarFormattedJson.hpp | 133 + source/core/StarHash.hpp | 100 + source/core/StarHostAddress.cpp | 281 + source/core/StarHostAddress.hpp | 89 + source/core/StarIODevice.cpp | 153 + source/core/StarIODevice.hpp | 133 + source/core/StarIdMap.hpp | 151 + source/core/StarImage.cpp | 525 + source/core/StarImage.hpp | 313 + source/core/StarImageProcessing.cpp | 550 + source/core/StarImageProcessing.hpp | 153 + source/core/StarInterpolation.hpp | 454 + source/core/StarIterator.hpp | 437 + source/core/StarJson.cpp | 1015 + source/core/StarJson.hpp | 358 + source/core/StarJsonBuilder.cpp | 168 + source/core/StarJsonBuilder.hpp | 104 + source/core/StarJsonExtra.cpp | 458 + source/core/StarJsonExtra.hpp | 384 + source/core/StarJsonParser.hpp | 733 + source/core/StarJsonPatch.cpp | 97 + source/core/StarJsonPatch.hpp | 41 + source/core/StarJsonPath.cpp | 76 + source/core/StarJsonPath.hpp | 332 + source/core/StarJsonRpc.cpp | 115 + source/core/StarJsonRpc.hpp | 54 + source/core/StarLexicalCast.hpp | 74 + source/core/StarLine.hpp | 289 + source/core/StarList.hpp | 1138 + source/core/StarListener.cpp | 49 + source/core/StarListener.hpp | 67 + source/core/StarLockFile.hpp | 42 + source/core/StarLockFile_unix.cpp | 94 + source/core/StarLockFile_windows.cpp | 80 + source/core/StarLogging.cpp | 192 + source/core/StarLogging.hpp | 193 + source/core/StarLruCache.hpp | 149 + source/core/StarLua.cpp | 1459 ++ source/core/StarLua.hpp | 2163 ++ source/core/StarLuaConverters.cpp | 42 + source/core/StarLuaConverters.hpp | 264 + source/core/StarMap.hpp | 318 + source/core/StarMathCommon.hpp | 328 + source/core/StarMatrix3.hpp | 456 + source/core/StarMaybe.hpp | 400 + source/core/StarMemory.cpp | 93 + source/core/StarMemory.hpp | 20 + source/core/StarMultiArray.hpp | 510 + source/core/StarMultiArrayInterpolator.hpp | 539 + source/core/StarMultiTable.hpp | 169 + source/core/StarNetElement.cpp | 22 + source/core/StarNetElement.hpp | 62 + source/core/StarNetElementBasicFields.cpp | 64 + source/core/StarNetElementBasicFields.hpp | 331 + source/core/StarNetElementContainers.hpp | 452 + source/core/StarNetElementDynamicGroup.hpp | 317 + source/core/StarNetElementFloatFields.hpp | 246 + source/core/StarNetElementGroup.cpp | 108 + source/core/StarNetElementGroup.hpp | 66 + source/core/StarNetElementSignal.hpp | 145 + source/core/StarNetElementSyncGroup.cpp | 88 + source/core/StarNetElementSyncGroup.hpp | 54 + source/core/StarNetElementSystem.hpp | 19 + source/core/StarNetElementTop.hpp | 82 + source/core/StarNetImpl.hpp | 157 + source/core/StarObserverStream.hpp | 98 + source/core/StarOptionParser.cpp | 162 + source/core/StarOptionParser.hpp | 84 + source/core/StarOrderedMap.hpp | 657 + source/core/StarOrderedSet.hpp | 430 + source/core/StarParametricFunction.hpp | 288 + source/core/StarPeriodic.hpp | 98 + source/core/StarPeriodicFunction.hpp | 82 + source/core/StarPerlin.cpp | 12 + source/core/StarPerlin.hpp | 718 + source/core/StarPoly.hpp | 750 + source/core/StarPythonic.hpp | 603 + source/core/StarRandom.cpp | 346 + source/core/StarRandom.hpp | 216 + source/core/StarRandomPoint.hpp | 79 + source/core/StarRect.hpp | 1068 + source/core/StarRefPtr.hpp | 303 + source/core/StarRpcPromise.hpp | 175 + source/core/StarSectorArray2D.hpp | 378 + source/core/StarSecureRandom.hpp | 14 + source/core/StarSecureRandom_unix.cpp | 10 + source/core/StarSecureRandom_windows.cpp | 21 + source/core/StarSet.hpp | 322 + source/core/StarSha256.cpp | 260 + source/core/StarSha256.hpp | 44 + source/core/StarShellParser.cpp | 208 + source/core/StarShellParser.hpp | 66 + source/core/StarSignalHandler.hpp | 37 + source/core/StarSignalHandler_unix.cpp | 92 + source/core/StarSignalHandler_windows.cpp | 190 + source/core/StarSmallVector.hpp | 447 + source/core/StarSocket.cpp | 272 + source/core/StarSocket.hpp | 98 + source/core/StarSpatialHash2D.hpp | 337 + source/core/StarSpline.hpp | 158 + source/core/StarStaticRandom.hpp | 131 + source/core/StarStaticVector.hpp | 403 + source/core/StarString.cpp | 1137 + source/core/StarString.hpp | 462 + source/core/StarString_windows.cpp | 34 + source/core/StarString_windows.hpp | 15 + source/core/StarStrongTypedef.hpp | 105 + source/core/StarTcp.cpp | 221 + source/core/StarTcp.hpp | 75 + source/core/StarThread.cpp | 121 + source/core/StarThread.hpp | 425 + source/core/StarThread_unix.cpp | 390 + source/core/StarThread_windows.cpp | 544 + source/core/StarTickRateMonitor.cpp | 87 + source/core/StarTickRateMonitor.hpp | 78 + source/core/StarTime.cpp | 208 + source/core/StarTime.hpp | 108 + source/core/StarTime_unix.cpp | 96 + source/core/StarTime_windows.cpp | 68 + source/core/StarTtlCache.hpp | 203 + source/core/StarUdp.cpp | 84 + source/core/StarUdp.hpp | 41 + source/core/StarUnicode.cpp | 273 + source/core/StarUnicode.hpp | 229 + source/core/StarUuid.cpp | 72 + source/core/StarUuid.hpp | 44 + source/core/StarVariant.hpp | 927 + source/core/StarVector.hpp | 925 + source/core/StarVlqEncoding.hpp | 106 + source/core/StarWeightedPool.hpp | 196 + source/core/StarWorkerPool.cpp | 166 + source/core/StarWorkerPool.hpp | 222 + source/core/StarXXHash.hpp | 142 + source/extern/CMakeLists.txt | 53 + source/extern/lauxlib.h | 256 + source/extern/lua.h | 486 + source/extern/lua.hpp | 9 + source/extern/lua/lapi.c | 1275 + source/extern/lua/lapi.h | 24 + source/extern/lua/lauxlib.c | 972 + source/extern/lua/lbaselib.c | 520 + source/extern/lua/lbitlib.c | 230 + source/extern/lua/lcode.c | 955 + source/extern/lua/lcode.h | 87 + source/extern/lua/lcorolib.c | 168 + source/extern/lua/lctype.c | 55 + source/extern/lua/lctype.h | 95 + source/extern/lua/ldblib.c | 456 + source/extern/lua/ldebug.c | 671 + source/extern/lua/ldebug.h | 39 + source/extern/lua/ldo.c | 717 + source/extern/lua/ldo.h | 46 + source/extern/lua/ldump.c | 215 + source/extern/lua/lfunc.c | 151 + source/extern/lua/lfunc.h | 61 + source/extern/lua/lgc.c | 1175 + source/extern/lua/lgc.h | 138 + source/extern/lua/linit.c | 68 + source/extern/lua/liolib.c | 752 + source/extern/lua/llex.c | 598 + source/extern/lua/llex.h | 86 + source/extern/lua/llimits.h | 313 + source/extern/lua/lmathlib.c | 407 + source/extern/lua/lmem.c | 100 + source/extern/lua/lmem.h | 69 + source/extern/lua/loadlib.c | 787 + source/extern/lua/lobject.c | 479 + source/extern/lua/lobject.h | 553 + source/extern/lua/lopcodes.c | 124 + source/extern/lua/lopcodes.h | 295 + source/extern/lua/loslib.c | 370 + source/extern/lua/lparser.c | 1647 ++ source/extern/lua/lparser.h | 120 + source/extern/lua/lprefix.h | 45 + source/extern/lua/lstate.c | 340 + source/extern/lua/lstate.h | 225 + source/extern/lua/lstring.c | 226 + source/extern/lua/lstring.h | 47 + source/extern/lua/lstrlib.c | 1514 ++ source/extern/lua/ltable.c | 655 + source/extern/lua/ltable.h | 53 + source/extern/lua/ltablib.c | 359 + source/extern/lua/ltm.c | 144 + source/extern/lua/ltm.h | 75 + source/extern/lua/lundump.c | 277 + source/extern/lua/lundump.h | 33 + source/extern/lua/lutf8lib.c | 256 + source/extern/lua/lvm.c | 1274 + source/extern/lua/lvm.h | 68 + source/extern/lua/lzio.c | 78 + source/extern/lua/lzio.h | 67 + source/extern/luaconf.h | 750 + source/extern/lualib.h | 58 + source/extern/tinyformat.h | 1054 + source/extern/xxhash.c | 888 + source/extern/xxhash.h | 293 + source/frontend/CMakeLists.txt | 105 + source/frontend/StarActionBar.cpp | 317 + source/frontend/StarActionBar.hpp | 61 + source/frontend/StarAiInterface.cpp | 402 + source/frontend/StarAiInterface.hpp | 145 + source/frontend/StarBookmarkInterface.cpp | 81 + source/frontend/StarBookmarkInterface.hpp | 33 + source/frontend/StarCharCreation.cpp | 448 + source/frontend/StarCharCreation.hpp | 61 + source/frontend/StarCharSelection.cpp | 104 + source/frontend/StarCharSelection.hpp | 38 + source/frontend/StarChat.cpp | 382 + source/frontend/StarChat.hpp | 100 + source/frontend/StarChatBubbleManager.cpp | 344 + source/frontend/StarChatBubbleManager.hpp | 100 + source/frontend/StarChatBubbleSeparation.cpp | 74 + source/frontend/StarChatBubbleSeparation.hpp | 181 + source/frontend/StarCinematic.cpp | 562 + source/frontend/StarCinematic.hpp | 143 + .../frontend/StarClientCommandProcessor.cpp | 375 + .../frontend/StarClientCommandProcessor.hpp | 73 + source/frontend/StarCodexInterface.cpp | 159 + source/frontend/StarCodexInterface.hpp | 67 + source/frontend/StarConfirmationDialog.cpp | 96 + source/frontend/StarConfirmationDialog.hpp | 38 + source/frontend/StarContainerInteractor.cpp | 92 + source/frontend/StarContainerInteractor.hpp | 46 + source/frontend/StarContainerInterface.cpp | 303 + source/frontend/StarContainerInterface.hpp | 61 + source/frontend/StarCraftingInterface.cpp | 773 + source/frontend/StarCraftingInterface.hpp | 85 + source/frontend/StarErrorScreen.cpp | 97 + source/frontend/StarErrorScreen.hpp | 51 + source/frontend/StarGraphicsMenu.cpp | 194 + source/frontend/StarGraphicsMenu.hpp | 36 + source/frontend/StarInterfaceCursor.cpp | 62 + source/frontend/StarInterfaceCursor.hpp | 35 + source/frontend/StarInventory.cpp | 426 + source/frontend/StarInventory.hpp | 67 + source/frontend/StarItemTooltip.cpp | 221 + source/frontend/StarItemTooltip.hpp | 28 + source/frontend/StarJoinRequestDialog.cpp | 52 + source/frontend/StarJoinRequestDialog.hpp | 30 + source/frontend/StarKeybindingsMenu.cpp | 244 + source/frontend/StarKeybindingsMenu.hpp | 49 + source/frontend/StarMainInterface.cpp | 1472 ++ source/frontend/StarMainInterface.hpp | 214 + source/frontend/StarMainInterfaceTypes.cpp | 129 + source/frontend/StarMainInterfaceTypes.hpp | 156 + source/frontend/StarMainMixer.cpp | 114 + source/frontend/StarMainMixer.hpp | 34 + source/frontend/StarMerchantInterface.cpp | 391 + source/frontend/StarMerchantInterface.hpp | 84 + source/frontend/StarModsMenu.cpp | 119 + source/frontend/StarModsMenu.hpp | 39 + source/frontend/StarNameplatePainter.cpp | 118 + source/frontend/StarNameplatePainter.hpp | 50 + source/frontend/StarOptionsMenu.cpp | 169 + source/frontend/StarOptionsMenu.hpp | 68 + source/frontend/StarPopupInterface.cpp | 30 + source/frontend/StarPopupInterface.hpp | 22 + source/frontend/StarQuestIndicatorPainter.cpp | 67 + source/frontend/StarQuestIndicatorPainter.hpp | 36 + source/frontend/StarQuestInterface.cpp | 447 + source/frontend/StarQuestInterface.hpp | 95 + source/frontend/StarQuestTracker.cpp | 186 + source/frontend/StarQuestTracker.hpp | 60 + source/frontend/StarRadioMessagePopup.cpp | 167 + source/frontend/StarRadioMessagePopup.hpp | 65 + source/frontend/StarScriptPane.cpp | 198 + source/frontend/StarScriptPane.hpp | 49 + source/frontend/StarSimpleTooltip.cpp | 23 + source/frontend/StarSimpleTooltip.hpp | 16 + source/frontend/StarSongbookInterface.cpp | 61 + source/frontend/StarSongbookInterface.hpp | 24 + source/frontend/StarStatusPane.cpp | 92 + source/frontend/StarStatusPane.hpp | 41 + source/frontend/StarTeamBar.cpp | 393 + source/frontend/StarTeamBar.hpp | 115 + source/frontend/StarTeleportDialog.cpp | 173 + source/frontend/StarTeleportDialog.hpp | 38 + source/frontend/StarTitleScreen.cpp | 435 + source/frontend/StarTitleScreen.hpp | 132 + source/frontend/StarWidgetLuaBindings.cpp | 411 + source/frontend/StarWidgetLuaBindings.hpp | 17 + source/frontend/StarWireInterface.cpp | 332 + source/frontend/StarWireInterface.hpp | 48 + source/game/CMakeLists.txt | 502 + source/game/StarActorMovementController.cpp | 1473 ++ source/game/StarActorMovementController.hpp | 363 + source/game/StarAiDatabase.cpp | 124 + source/game/StarAiDatabase.hpp | 62 + source/game/StarAiTypes.cpp | 18 + source/game/StarAiTypes.hpp | 46 + source/game/StarAmbient.cpp | 175 + source/game/StarAmbient.hpp | 71 + source/game/StarAnimation.cpp | 121 + source/game/StarAnimation.hpp | 60 + source/game/StarArmorWearer.cpp | 310 + source/game/StarArmorWearer.hpp | 89 + source/game/StarBehaviorDatabase.cpp | 298 + source/game/StarBehaviorDatabase.hpp | 155 + source/game/StarBehaviorState.cpp | 387 + source/game/StarBehaviorState.hpp | 119 + source/game/StarBiome.cpp | 94 + source/game/StarBiome.hpp | 62 + source/game/StarBiomeDatabase.cpp | 233 + source/game/StarBiomeDatabase.hpp | 50 + source/game/StarBiomePlacement.cpp | 298 + source/game/StarBiomePlacement.hpp | 102 + source/game/StarCelestialCoordinate.cpp | 184 + source/game/StarCelestialCoordinate.hpp | 96 + source/game/StarCelestialDatabase.cpp | 828 + source/game/StarCelestialDatabase.hpp | 228 + source/game/StarCelestialGraphics.cpp | 288 + source/game/StarCelestialGraphics.hpp | 60 + source/game/StarCelestialParameters.cpp | 131 + source/game/StarCelestialParameters.hpp | 52 + source/game/StarCelestialTypes.cpp | 152 + source/game/StarCelestialTypes.hpp | 71 + source/game/StarChatAction.cpp | 35 + source/game/StarChatAction.hpp | 40 + source/game/StarChatProcessor.cpp | 257 + source/game/StarChatProcessor.hpp | 86 + source/game/StarChatTypes.cpp | 69 + source/game/StarChatTypes.hpp | 62 + source/game/StarClientContext.cpp | 94 + source/game/StarClientContext.hpp | 61 + source/game/StarCodex.cpp | 71 + source/game/StarCodex.hpp | 39 + source/game/StarCodexDatabase.cpp | 41 + source/game/StarCodexDatabase.hpp | 26 + source/game/StarCollectionDatabase.cpp | 128 + source/game/StarCollectionDatabase.hpp | 62 + source/game/StarCollisionBlock.cpp | 14 + source/game/StarCollisionBlock.hpp | 115 + source/game/StarCollisionGenerator.cpp | 233 + source/game/StarCollisionGenerator.hpp | 50 + source/game/StarCommandProcessor.cpp | 1021 + source/game/StarCommandProcessor.hpp | 77 + source/game/StarDamage.cpp | 311 + source/game/StarDamage.hpp | 124 + source/game/StarDamageDatabase.cpp | 69 + source/game/StarDamageDatabase.hpp | 46 + source/game/StarDamageManager.cpp | 263 + source/game/StarDamageManager.hpp | 99 + source/game/StarDamageTypes.cpp | 100 + source/game/StarDamageTypes.hpp | 61 + source/game/StarDanceDatabase.cpp | 64 + source/game/StarDanceDatabase.hpp | 47 + source/game/StarDrawable.cpp | 273 + source/game/StarDrawable.hpp | 187 + source/game/StarDungeonGenerator.cpp | 1602 ++ source/game/StarDungeonGenerator.hpp | 714 + source/game/StarDungeonImagePart.cpp | 131 + source/game/StarDungeonImagePart.hpp | 42 + source/game/StarDungeonTMXPart.cpp | 466 + source/game/StarDungeonTMXPart.hpp | 215 + source/game/StarEffectEmitter.cpp | 110 + source/game/StarEffectEmitter.hpp | 44 + source/game/StarEffectSourceDatabase.cpp | 193 + source/game/StarEffectSourceDatabase.hpp | 74 + source/game/StarEmoteProcessor.cpp | 61 + source/game/StarEmoteProcessor.hpp | 27 + source/game/StarEntityFactory.cpp | 173 + source/game/StarEntityFactory.hpp | 55 + source/game/StarEntityMap.cpp | 334 + source/game/StarEntityMap.hpp | 189 + source/game/StarEntityRendering.cpp | 49 + source/game/StarEntityRendering.hpp | 41 + source/game/StarEntityRenderingTypes.cpp | 127 + source/game/StarEntityRenderingTypes.hpp | 89 + source/game/StarEntitySplash.cpp | 50 + source/game/StarEntitySplash.hpp | 30 + source/game/StarFallingBlocksAgent.cpp | 81 + source/game/StarFallingBlocksAgent.hpp | 50 + source/game/StarForceRegions.cpp | 199 + source/game/StarForceRegions.hpp | 95 + source/game/StarGameTimers.cpp | 162 + source/game/StarGameTimers.hpp | 77 + source/game/StarGameTypes.cpp | 97 + source/game/StarGameTypes.hpp | 169 + source/game/StarHumanoid.cpp | 1245 + source/game/StarHumanoid.hpp | 367 + source/game/StarImageMetadataDatabase.cpp | 251 + source/game/StarImageMetadataDatabase.hpp | 40 + source/game/StarInteractionTypes.cpp | 64 + source/game/StarInteractionTypes.hpp | 54 + source/game/StarInterpolationTracker.cpp | 63 + source/game/StarInterpolationTracker.hpp | 42 + source/game/StarInventoryTypes.cpp | 91 + source/game/StarInventoryTypes.hpp | 60 + source/game/StarItem.cpp | 288 + source/game/StarItem.hpp | 180 + source/game/StarItemBag.cpp | 372 + source/game/StarItemBag.hpp | 120 + source/game/StarItemDatabase.cpp | 709 + source/game/StarItemDatabase.hpp | 205 + source/game/StarItemDescriptor.cpp | 160 + source/game/StarItemDescriptor.hpp | 82 + source/game/StarItemDrop.cpp | 353 + source/game/StarItemDrop.hpp | 115 + source/game/StarItemRecipe.cpp | 46 + source/game/StarItemRecipe.hpp | 38 + source/game/StarLightSource.cpp | 32 + source/game/StarLightSource.hpp | 31 + source/game/StarLiquidTypes.cpp | 75 + source/game/StarLiquidTypes.hpp | 59 + source/game/StarLiquidsDatabase.cpp | 156 + source/game/StarLiquidsDatabase.hpp | 105 + source/game/StarMaterialDatabase.cpp | 576 + source/game/StarMaterialDatabase.hpp | 272 + source/game/StarMaterialRenderProfile.cpp | 170 + source/game/StarMaterialRenderProfile.hpp | 125 + source/game/StarMaterialTypes.hpp | 100 + source/game/StarMicroDungeon.cpp | 80 + source/game/StarMicroDungeon.hpp | 31 + source/game/StarMonster.cpp | 859 + source/game/StarMonster.hpp | 208 + source/game/StarMonsterDatabase.cpp | 502 + source/game/StarMonsterDatabase.hpp | 201 + source/game/StarMovementController.cpp | 1109 + source/game/StarMovementController.hpp | 297 + source/game/StarNameGenerator.cpp | 150 + source/game/StarNameGenerator.hpp | 42 + source/game/StarNetPacketSocket.cpp | 406 + source/game/StarNetPacketSocket.hpp | 169 + source/game/StarNetPackets.cpp | 1264 + source/game/StarNetPackets.hpp | 896 + source/game/StarNetworkedAnimator.cpp | 1027 + source/game/StarNetworkedAnimator.hpp | 331 + source/game/StarNpc.cpp | 1095 + source/game/StarNpc.hpp | 257 + source/game/StarNpcDatabase.cpp | 394 + source/game/StarNpcDatabase.hpp | 86 + source/game/StarObject.cpp | 1298 + source/game/StarObject.hpp | 268 + source/game/StarObjectDatabase.cpp | 593 + source/game/StarObjectDatabase.hpp | 224 + source/game/StarParallax.cpp | 239 + source/game/StarParallax.hpp | 74 + source/game/StarParticle.cpp | 305 + source/game/StarParticle.hpp | 124 + source/game/StarParticleDatabase.cpp | 58 + source/game/StarParticleDatabase.hpp | 47 + source/game/StarParticleManager.cpp | 115 + source/game/StarParticleManager.hpp | 43 + source/game/StarPlant.cpp | 1072 + source/game/StarPlant.hpp | 178 + source/game/StarPlantDatabase.cpp | 367 + source/game/StarPlantDatabase.hpp | 137 + source/game/StarPlantDrop.cpp | 380 + source/game/StarPlantDrop.hpp | 83 + source/game/StarPlatformerAStar.cpp | 540 + source/game/StarPlatformerAStar.hpp | 80 + source/game/StarPlatformerAStarTypes.cpp | 21 + source/game/StarPlatformerAStarTypes.hpp | 150 + source/game/StarPlayer.cpp | 2374 ++ source/game/StarPlayer.hpp | 592 + source/game/StarPlayerBlueprints.cpp | 37 + source/game/StarPlayerBlueprints.hpp | 31 + source/game/StarPlayerCodexes.cpp | 87 + source/game/StarPlayerCodexes.hpp | 41 + source/game/StarPlayerCompanions.cpp | 136 + source/game/StarPlayerCompanions.hpp | 66 + source/game/StarPlayerDeployment.cpp | 100 + source/game/StarPlayerDeployment.hpp | 46 + source/game/StarPlayerFactory.cpp | 67 + source/game/StarPlayerFactory.hpp | 72 + source/game/StarPlayerInventory.cpp | 1068 + source/game/StarPlayerInventory.hpp | 225 + source/game/StarPlayerLog.cpp | 127 + source/game/StarPlayerLog.hpp | 57 + source/game/StarPlayerStorage.cpp | 232 + source/game/StarPlayerStorage.hpp | 51 + source/game/StarPlayerTech.cpp | 94 + source/game/StarPlayerTech.hpp | 46 + source/game/StarPlayerTypes.cpp | 70 + source/game/StarPlayerTypes.hpp | 61 + source/game/StarPlayerUniverseMap.cpp | 287 + source/game/StarPlayerUniverseMap.hpp | 120 + source/game/StarProjectile.cpp | 989 + source/game/StarProjectile.hpp | 191 + source/game/StarProjectileDatabase.cpp | 171 + source/game/StarProjectileDatabase.hpp | 125 + source/game/StarQuestDescriptor.cpp | 444 + source/game/StarQuestDescriptor.hpp | 144 + source/game/StarQuestManager.cpp | 407 + source/game/StarQuestManager.hpp | 81 + source/game/StarQuestTemplateDatabase.cpp | 84 + source/game/StarQuestTemplateDatabase.hpp | 83 + source/game/StarQuests.cpp | 764 + source/game/StarQuests.hpp | 199 + source/game/StarRadioMessageDatabase.cpp | 76 + source/game/StarRadioMessageDatabase.hpp | 44 + source/game/StarRoot.cpp | 701 + source/game/StarRoot.hpp | 332 + source/game/StarRootLoader.cpp | 192 + source/game/StarRootLoader.hpp | 71 + source/game/StarServerClientContext.cpp | 287 + source/game/StarServerClientContext.hpp | 122 + source/game/StarSky.cpp | 655 + source/game/StarSky.hpp | 191 + source/game/StarSkyParameters.cpp | 218 + source/game/StarSkyParameters.hpp | 54 + source/game/StarSkyRenderData.cpp | 175 + source/game/StarSkyRenderData.hpp | 49 + source/game/StarSkyTypes.cpp | 114 + source/game/StarSkyTypes.hpp | 89 + source/game/StarSongbook.cpp | 729 + source/game/StarSongbook.hpp | 114 + source/game/StarSpawnTypeDatabase.cpp | 146 + source/game/StarSpawnTypeDatabase.hpp | 94 + source/game/StarSpawner.cpp | 351 + source/game/StarSpawner.hpp | 119 + source/game/StarSpeciesDatabase.cpp | 238 + source/game/StarSpeciesDatabase.hpp | 109 + source/game/StarStagehand.cpp | 191 + source/game/StarStagehand.hpp | 79 + source/game/StarStagehandDatabase.cpp | 35 + source/game/StarStagehandDatabase.hpp | 26 + source/game/StarStatCollection.cpp | 189 + source/game/StarStatCollection.hpp | 70 + source/game/StarStatSet.cpp | 332 + source/game/StarStatSet.hpp | 153 + source/game/StarStatistics.cpp | 235 + source/game/StarStatistics.hpp | 68 + source/game/StarStatisticsDatabase.cpp | 93 + source/game/StarStatisticsDatabase.hpp | 51 + source/game/StarStatusController.cpp | 823 + source/game/StarStatusController.hpp | 241 + source/game/StarStatusEffectDatabase.cpp | 54 + source/game/StarStatusEffectDatabase.hpp | 44 + source/game/StarStatusTypes.cpp | 131 + source/game/StarStatusTypes.hpp | 92 + source/game/StarStoredFunctions.cpp | 314 + source/game/StarStoredFunctions.hpp | 101 + source/game/StarSystemWorld.cpp | 638 + source/game/StarSystemWorld.hpp | 229 + source/game/StarSystemWorldClient.cpp | 184 + source/game/StarSystemWorldClient.hpp | 58 + source/game/StarSystemWorldServer.cpp | 462 + source/game/StarSystemWorldServer.hpp | 93 + source/game/StarSystemWorldServerThread.cpp | 175 + source/game/StarSystemWorldServerThread.hpp | 75 + source/game/StarTeamClient.cpp | 245 + source/game/StarTeamClient.hpp | 88 + source/game/StarTeamManager.cpp | 308 + source/game/StarTeamManager.hpp | 82 + source/game/StarTechController.cpp | 536 + source/game/StarTechController.hpp | 171 + source/game/StarTechDatabase.cpp | 62 + source/game/StarTechDatabase.hpp | 52 + source/game/StarTenantDatabase.cpp | 103 + source/game/StarTenantDatabase.hpp | 75 + source/game/StarTerrainDatabase.cpp | 155 + source/game/StarTerrainDatabase.hpp | 66 + source/game/StarTileDamage.cpp | 330 + source/game/StarTileDamage.hpp | 156 + source/game/StarTileModification.cpp | 66 + source/game/StarTileModification.hpp | 51 + source/game/StarTileSectorArray.hpp | 489 + source/game/StarTilesetDatabase.cpp | 361 + source/game/StarTilesetDatabase.hpp | 142 + source/game/StarToolUser.cpp | 858 + source/game/StarToolUser.hpp | 149 + source/game/StarTreasure.cpp | 219 + source/game/StarTreasure.hpp | 93 + source/game/StarUniverseClient.cpp | 553 + source/game/StarUniverseClient.hpp | 144 + source/game/StarUniverseConnection.cpp | 259 + source/game/StarUniverseConnection.hpp | 114 + source/game/StarUniverseServer.cpp | 2141 ++ source/game/StarUniverseServer.hpp | 251 + source/game/StarUniverseSettings.cpp | 106 + source/game/StarUniverseSettings.hpp | 52 + source/game/StarVehicle.cpp | 648 + source/game/StarVehicle.hpp | 172 + source/game/StarVehicleDatabase.cpp | 66 + source/game/StarVehicleDatabase.hpp | 29 + source/game/StarVersioningDatabase.cpp | 219 + source/game/StarVersioningDatabase.hpp | 87 + source/game/StarWarping.cpp | 227 + source/game/StarWarping.hpp | 91 + source/game/StarWeather.cpp | 365 + source/game/StarWeather.hpp | 129 + source/game/StarWeatherTypes.cpp | 123 + source/game/StarWeatherTypes.hpp | 48 + source/game/StarWireProcessor.cpp | 132 + source/game/StarWireProcessor.hpp | 44 + source/game/StarWiring.cpp | 44 + source/game/StarWiring.hpp | 71 + source/game/StarWorldClient.cpp | 1941 ++ source/game/StarWorldClient.hpp | 327 + source/game/StarWorldClientState.cpp | 94 + source/game/StarWorldClientState.hpp | 60 + source/game/StarWorldGeneration.cpp | 1584 ++ source/game/StarWorldGeneration.hpp | 164 + source/game/StarWorldImpl.hpp | 515 + source/game/StarWorldLayout.cpp | 987 + source/game/StarWorldLayout.hpp | 181 + source/game/StarWorldParameters.cpp | 721 + source/game/StarWorldParameters.hpp | 190 + source/game/StarWorldRenderData.hpp | 62 + source/game/StarWorldServer.cpp | 2341 ++ source/game/StarWorldServer.hpp | 380 + source/game/StarWorldServerThread.cpp | 271 + source/game/StarWorldServerThread.hpp | 94 + source/game/StarWorldStorage.cpp | 903 + source/game/StarWorldStorage.hpp | 308 + source/game/StarWorldStructure.cpp | 267 + source/game/StarWorldStructure.hpp | 96 + source/game/StarWorldTemplate.cpp | 784 + source/game/StarWorldTemplate.hpp | 207 + source/game/StarWorldTiles.cpp | 247 + source/game/StarWorldTiles.hpp | 330 + .../game/interfaces/StarActivatableItem.hpp | 21 + .../game/interfaces/StarAggressiveEntity.hpp | 17 + .../game/interfaces/StarAnchorableEntity.cpp | 21 + .../game/interfaces/StarAnchorableEntity.hpp | 39 + source/game/interfaces/StarBeamItem.cpp | 268 + source/game/interfaces/StarBeamItem.hpp | 78 + source/game/interfaces/StarChattyEntity.hpp | 19 + .../game/interfaces/StarContainerEntity.cpp | 15 + .../game/interfaces/StarContainerEntity.hpp | 50 + .../game/interfaces/StarDamageBarEntity.cpp | 11 + .../game/interfaces/StarDamageBarEntity.hpp | 27 + source/game/interfaces/StarDurabilityItem.hpp | 18 + .../game/interfaces/StarEffectSourceItem.hpp | 18 + source/game/interfaces/StarEmoteEntity.hpp | 18 + source/game/interfaces/StarEntity.cpp | 194 + source/game/interfaces/StarEntity.hpp | 229 + source/game/interfaces/StarFireableItem.cpp | 300 + source/game/interfaces/StarFireableItem.hpp | 91 + .../game/interfaces/StarInspectableEntity.hpp | 37 + .../game/interfaces/StarInteractiveEntity.cpp | 25 + .../game/interfaces/StarInteractiveEntity.hpp | 34 + .../game/interfaces/StarLoungingEntities.cpp | 63 + .../game/interfaces/StarLoungingEntities.hpp | 71 + source/game/interfaces/StarNametagEntity.hpp | 20 + .../StarNonRotatedDrawablesItem.hpp | 18 + source/game/interfaces/StarPhysicsEntity.cpp | 85 + source/game/interfaces/StarPhysicsEntity.hpp | 61 + source/game/interfaces/StarPointableItem.cpp | 13 + source/game/interfaces/StarPointableItem.hpp | 22 + source/game/interfaces/StarPortraitEntity.hpp | 19 + .../game/interfaces/StarPreviewTileTool.hpp | 20 + .../game/interfaces/StarPreviewableItem.hpp | 19 + source/game/interfaces/StarScriptedEntity.hpp | 25 + .../interfaces/StarStatusEffectEntity.hpp | 18 + .../game/interfaces/StarStatusEffectItem.hpp | 18 + source/game/interfaces/StarSwingableItem.cpp | 53 + source/game/interfaces/StarSwingableItem.hpp | 36 + source/game/interfaces/StarTileEntity.cpp | 101 + source/game/interfaces/StarTileEntity.hpp | 97 + source/game/interfaces/StarToolUserEntity.hpp | 104 + source/game/interfaces/StarToolUserItem.cpp | 59 + source/game/interfaces/StarToolUserItem.hpp | 49 + .../game/interfaces/StarWarpTargetEntity.hpp | 20 + source/game/interfaces/StarWireEntity.hpp | 28 + source/game/interfaces/StarWorld.cpp | 153 + source/game/interfaces/StarWorld.hpp | 259 + source/game/items/StarActiveItem.cpp | 522 + source/game/items/StarActiveItem.hpp | 101 + source/game/items/StarArmors.cpp | 203 + source/game/items/StarArmors.hpp | 127 + source/game/items/StarAugmentItem.cpp | 29 + source/game/items/StarAugmentItem.hpp | 27 + source/game/items/StarBlueprintItem.cpp | 54 + source/game/items/StarBlueprintItem.hpp | 32 + source/game/items/StarCodexItem.cpp | 49 + source/game/items/StarCodexItem.hpp | 30 + source/game/items/StarConsumableItem.cpp | 110 + source/game/items/StarConsumableItem.hpp | 38 + source/game/items/StarCurrency.cpp | 42 + source/game/items/StarCurrency.hpp | 33 + source/game/items/StarInspectionTool.cpp | 173 + source/game/items/StarInspectionTool.hpp | 74 + source/game/items/StarInstrumentItem.cpp | 88 + source/game/items/StarInstrumentItem.hpp | 57 + source/game/items/StarLiquidItem.cpp | 151 + source/game/items/StarLiquidItem.hpp | 47 + source/game/items/StarMaterialItem.cpp | 175 + source/game/items/StarMaterialItem.hpp | 50 + source/game/items/StarObjectItem.cpp | 107 + source/game/items/StarObjectItem.hpp | 39 + source/game/items/StarThrownItem.cpp | 56 + source/game/items/StarThrownItem.hpp | 32 + source/game/items/StarTools.cpp | 709 + source/game/items/StarTools.hpp | 244 + source/game/items/StarUnlockItem.cpp | 69 + source/game/items/StarUnlockItem.hpp | 35 + source/game/objects/StarContainerObject.cpp | 557 + source/game/objects/StarContainerObject.hpp | 112 + source/game/objects/StarFarmableObject.cpp | 187 + source/game/objects/StarFarmableObject.hpp | 44 + source/game/objects/StarLoungeableObject.cpp | 107 + source/game/objects/StarLoungeableObject.hpp | 40 + source/game/objects/StarPhysicsObject.cpp | 114 + source/game/objects/StarPhysicsObject.hpp | 49 + source/game/objects/StarTeleporterObject.cpp | 16 + source/game/objects/StarTeleporterObject.hpp | 13 + .../scripting/StarBehaviorLuaBindings.cpp | 37 + .../scripting/StarBehaviorLuaBindings.hpp | 17 + .../scripting/StarCelestialLuaBindings.cpp | 219 + .../scripting/StarCelestialLuaBindings.hpp | 16 + .../game/scripting/StarConfigLuaBindings.cpp | 15 + .../game/scripting/StarConfigLuaBindings.hpp | 13 + .../game/scripting/StarEntityLuaBindings.cpp | 77 + .../game/scripting/StarEntityLuaBindings.hpp | 22 + .../scripting/StarFireableItemLuaBindings.cpp | 168 + .../scripting/StarFireableItemLuaBindings.hpp | 33 + source/game/scripting/StarItemLuaBindings.cpp | 246 + source/game/scripting/StarItemLuaBindings.hpp | 45 + .../StarLuaActorMovementComponent.hpp | 440 + .../scripting/StarLuaAnimationComponent.hpp | 145 + source/game/scripting/StarLuaComponents.cpp | 155 + source/game/scripting/StarLuaComponents.hpp | 333 + .../game/scripting/StarLuaGameConverters.cpp | 601 + .../game/scripting/StarLuaGameConverters.hpp | 151 + source/game/scripting/StarLuaRoot.cpp | 161 + source/game/scripting/StarLuaRoot.hpp | 68 + .../StarMovementControllerLuaBindings.cpp | 83 + .../StarMovementControllerLuaBindings.hpp | 15 + .../StarNetworkedAnimatorLuaBindings.cpp | 112 + .../StarNetworkedAnimatorLuaBindings.hpp | 18 + .../game/scripting/StarPlayerLuaBindings.cpp | 446 + .../game/scripting/StarPlayerLuaBindings.hpp | 15 + source/game/scripting/StarRootLuaBindings.cpp | 338 + source/game/scripting/StarRootLuaBindings.hpp | 60 + .../StarScriptedAnimatorLuaBindings.cpp | 27 + .../StarScriptedAnimatorLuaBindings.hpp | 14 + .../StarStatusControllerLuaBindings.cpp | 271 + .../StarStatusControllerLuaBindings.hpp | 59 + .../StarUniverseServerLuaBindings.cpp | 110 + .../StarUniverseServerLuaBindings.hpp | 29 + .../game/scripting/StarUtilityLuaBindings.cpp | 252 + .../game/scripting/StarUtilityLuaBindings.hpp | 28 + .../game/scripting/StarWorldLuaBindings.cpp | 1981 ++ .../game/scripting/StarWorldLuaBindings.hpp | 182 + source/game/terrain/StarCacheSelector.cpp | 26 + source/game/terrain/StarCacheSelector.hpp | 23 + source/game/terrain/StarConstantSelector.cpp | 16 + source/game/terrain/StarConstantSelector.hpp | 20 + .../game/terrain/StarDisplacementSelector.cpp | 69 + .../game/terrain/StarDisplacementSelector.hpp | 37 + .../game/terrain/StarFlatSurfaceSelector.cpp | 18 + .../game/terrain/StarFlatSurfaceSelector.hpp | 22 + .../terrain/StarIslandSurfaceSelector.cpp | 50 + .../terrain/StarIslandSurfaceSelector.hpp | 39 + source/game/terrain/StarKarstCave.cpp | 114 + source/game/terrain/StarKarstCave.hpp | 64 + source/game/terrain/StarMaxSelector.cpp | 27 + source/game/terrain/StarMaxSelector.hpp | 20 + source/game/terrain/StarMinMaxSelector.cpp | 32 + source/game/terrain/StarMinMaxSelector.hpp | 20 + source/game/terrain/StarMixSelector.cpp | 29 + source/game/terrain/StarMixSelector.hpp | 22 + source/game/terrain/StarPerlinSelector.cpp | 30 + source/game/terrain/StarPerlinSelector.hpp | 24 + .../game/terrain/StarRidgeBlocksSelector.cpp | 35 + .../game/terrain/StarRidgeBlocksSelector.hpp | 32 + source/game/terrain/StarRotateSelector.cpp | 27 + source/game/terrain/StarRotateSelector.hpp | 24 + source/game/terrain/StarWormCave.cpp | 157 + source/game/terrain/StarWormCave.hpp | 42 + source/json_tool/CMakeLists.txt | 19 + source/json_tool/editor_gui.cpp | 206 + source/json_tool/editor_gui.hpp | 56 + source/json_tool/json_tool.cpp | 471 + source/json_tool/json_tool.hpp | 150 + source/mod_uploader/CMakeLists.txt | 32 + source/mod_uploader/StarModUploader.cpp | 394 + source/mod_uploader/StarModUploader.hpp | 59 + source/mod_uploader/StarSPlainTextEdit.cpp | 21 + source/mod_uploader/StarSPlainTextEdit.hpp | 28 + source/mod_uploader/StarStringConversion.hpp | 20 + source/mod_uploader/main.cpp | 29 + source/platform/CMakeLists.txt | 11 + source/platform/StarDesktopService.hpp | 17 + source/platform/StarP2PNetworkingService.hpp | 65 + source/platform/StarStatisticsService.hpp | 40 + .../StarUserGeneratedContentService.hpp | 26 + source/rendering/CMakeLists.txt | 35 + source/rendering/StarAnchorTypes.cpp | 14 + source/rendering/StarAnchorTypes.hpp | 24 + source/rendering/StarAssetTextureGroup.cpp | 85 + source/rendering/StarAssetTextureGroup.hpp | 50 + source/rendering/StarDrawablePainter.cpp | 57 + source/rendering/StarDrawablePainter.hpp | 27 + source/rendering/StarEnvironmentPainter.cpp | 443 + source/rendering/StarEnvironmentPainter.hpp | 81 + source/rendering/StarFontTextureGroup.cpp | 40 + source/rendering/StarFontTextureGroup.hpp | 40 + source/rendering/StarTextPainter.cpp | 402 + source/rendering/StarTextPainter.hpp | 109 + source/rendering/StarTilePainter.cpp | 539 + source/rendering/StarTilePainter.hpp | 140 + source/rendering/StarWorldCamera.cpp | 38 + source/rendering/StarWorldCamera.hpp | 117 + source/rendering/StarWorldPainter.cpp | 296 + source/rendering/StarWorldPainter.hpp | 67 + source/server/CMakeLists.txt | 25 + source/server/StarServerQueryThread.cpp | 278 + source/server/StarServerQueryThread.hpp | 108 + source/server/StarServerRconClient.cpp | 145 + source/server/StarServerRconClient.hpp | 51 + source/server/StarServerRconThread.cpp | 62 + source/server/StarServerRconThread.hpp | 36 + source/server/main.cpp | 94 + source/test/CMakeLists.txt | 85 + source/test/StarTestUniverse.cpp | 76 + source/test/StarTestUniverse.hpp | 28 + source/test/algorithm_test.cpp | 135 + source/test/assets_test.cpp | 24 + source/test/block_allocator_test.cpp | 16 + source/test/blocks_along_line_test.cpp | 45 + source/test/btree_database_test.cpp | 256 + source/test/btree_test.cpp | 646 + source/test/byte_array_test.cpp | 33 + source/test/clock_test.cpp | 29 + source/test/color_test.cpp | 37 + source/test/container_test.cpp | 13 + source/test/core_tests_main.cpp | 7 + source/test/encode_test.cpp | 36 + source/test/file_test.cpp | 51 + source/test/flat_hash_test.cpp | 220 + source/test/formatted_json_test.cpp | 371 + source/test/function_test.cpp | 57 + source/test/game_tests_main.cpp | 43 + source/test/gtest/gtest-all.cc | 9592 ++++++++ source/test/gtest/gtest.h | 20061 ++++++++++++++++ source/test/hash_test.cpp | 15 + source/test/host_address_test.cpp | 24 + source/test/item_test.cpp | 119 + source/test/json_test.cpp | 703 + source/test/line_test.cpp | 270 + source/test/lua_json_test.cpp | 425 + source/test/lua_test.cpp | 838 + source/test/math_test.cpp | 28 + source/test/multi_table_test.cpp | 61 + source/test/net_states_test.cpp | 1058 + source/test/ordered_map_test.cpp | 93 + source/test/ordered_set_test.cpp | 47 + source/test/periodic_test.cpp | 30 + source/test/poly_test.cpp | 51 + source/test/random_test.cpp | 174 + source/test/rect_test.cpp | 265 + source/test/ref_ptr_test.cpp | 83 + source/test/root_test.cpp | 82 + source/test/serialization_test.cpp | 28 + source/test/server_test.cpp | 13 + source/test/sha_test.cpp | 146 + source/test/shell_parse.cpp | 85 + source/test/small_vector_test.cpp | 59 + source/test/spawn_test.cpp | 48 + source/test/stat_test.cpp | 83 + source/test/static_vector_test.cpp | 56 + source/test/string_test.cpp | 235 + source/test/strong_typedef_test.cpp | 26 + source/test/thread_test.cpp | 36 + source/test/tile_array_test.cpp | 99 + source/test/universe_connection_test.cpp | 152 + source/test/variant_test.cpp | 68 + source/test/vlq_test.cpp | 30 + source/test/worker_pool_test.cpp | 34 + source/test/world_geometry_test.cpp | 44 + source/utility/CMakeLists.txt | 77 + source/utility/asset_packer.cpp | 79 + source/utility/asset_unpacker.cpp | 53 + source/utility/dump_versioned_json.cpp | 20 + .../utility/dungeon_generation_benchmark.cpp | 61 + source/utility/fix_embedded_tilesets.cpp | 101 + source/utility/game_repl.cpp | 53 + source/utility/generation_benchmark.cpp | 81 + source/utility/make_versioned_json.cpp | 20 + source/utility/map_grep.cpp | 116 + source/utility/planet_mapgen.cpp | 117 + source/utility/render_terrain_selector.cpp | 96 + source/utility/tileset_updater.cpp | 326 + source/utility/tileset_updater.hpp | 102 + source/utility/update_tilesets.cpp | 262 + source/utility/word_count.cpp | 191 + source/utility/world_benchmark.cpp | 101 + source/windowing/CMakeLists.txt | 76 + source/windowing/StarButtonGroup.cpp | 90 + source/windowing/StarButtonGroup.hpp | 59 + source/windowing/StarButtonWidget.cpp | 380 + source/windowing/StarButtonWidget.hpp | 138 + source/windowing/StarCanvasWidget.cpp | 238 + source/windowing/StarCanvasWidget.hpp | 106 + source/windowing/StarFlowLayout.cpp | 36 + source/windowing/StarFlowLayout.hpp | 25 + source/windowing/StarFuelWidget.cpp | 111 + source/windowing/StarFuelWidget.hpp | 41 + source/windowing/StarGuiContext.cpp | 427 + source/windowing/StarGuiContext.hpp | 145 + source/windowing/StarGuiReader.cpp | 88 + source/windowing/StarGuiReader.hpp | 22 + source/windowing/StarGuiTypes.cpp | 29 + source/windowing/StarGuiTypes.hpp | 51 + source/windowing/StarImageStretchWidget.cpp | 14 + source/windowing/StarImageStretchWidget.hpp | 25 + source/windowing/StarImageWidget.cpp | 130 + source/windowing/StarImageWidget.hpp | 51 + source/windowing/StarItemGridWidget.cpp | 289 + source/windowing/StarItemGridWidget.hpp | 101 + source/windowing/StarItemSlotWidget.cpp | 205 + source/windowing/StarItemSlotWidget.hpp | 76 + source/windowing/StarKeyBindings.cpp | 212 + source/windowing/StarKeyBindings.hpp | 119 + source/windowing/StarLabelWidget.cpp | 112 + source/windowing/StarLabelWidget.hpp | 52 + source/windowing/StarLargeCharPlateWidget.cpp | 160 + source/windowing/StarLargeCharPlateWidget.hpp | 64 + source/windowing/StarLayout.cpp | 13 + source/windowing/StarLayout.hpp | 16 + source/windowing/StarListWidget.cpp | 262 + source/windowing/StarListWidget.hpp | 70 + source/windowing/StarPane.cpp | 369 + source/windowing/StarPane.hpp | 130 + source/windowing/StarPaneManager.cpp | 374 + source/windowing/StarPaneManager.hpp | 104 + source/windowing/StarPortraitWidget.cpp | 117 + source/windowing/StarPortraitWidget.hpp | 44 + source/windowing/StarProgressWidget.cpp | 71 + source/windowing/StarProgressWidget.hpp | 42 + .../windowing/StarRegisteredPaneManager.hpp | 126 + source/windowing/StarScrollArea.cpp | 445 + source/windowing/StarScrollArea.hpp | 132 + source/windowing/StarSliderBar.cpp | 183 + source/windowing/StarSliderBar.hpp | 57 + source/windowing/StarStackWidget.cpp | 35 + source/windowing/StarStackWidget.hpp | 25 + source/windowing/StarTabSet.cpp | 92 + source/windowing/StarTabSet.hpp | 51 + source/windowing/StarTextBoxWidget.cpp | 393 + source/windowing/StarTextBoxWidget.hpp | 85 + source/windowing/StarVerticalLayout.cpp | 101 + source/windowing/StarVerticalLayout.hpp | 35 + source/windowing/StarWidget.cpp | 474 + source/windowing/StarWidget.hpp | 178 + source/windowing/StarWidgetParsing.cpp | 815 + source/windowing/StarWidgetParsing.hpp | 76 + 1172 files changed, 265928 insertions(+), 1 deletion(-) create mode 100644 cmake/FindDirectX.cmake create mode 100644 cmake/FindDiscordApi.cmake create mode 100644 cmake/FindGLEW.cmake create mode 100644 cmake/FindJeMalloc.cmake create mode 100644 cmake/FindLua52.cmake create mode 100644 cmake/FindOggVorbis.cmake create mode 100644 cmake/FindPkgMacros.cmake create mode 100644 cmake/FindSDL2.cmake create mode 100644 cmake/FindSteamApi.cmake create mode 100644 cmake/GetGitRevisionDescription.cmake create mode 100644 cmake/GetGitRevisionDescription.cmake.in create mode 100644 cmake/TargetArch.cmake create mode 100644 doc/OPENSOURCE.md create mode 100644 doc/lua/activeitem.md create mode 100644 doc/lua/activeitemanimation.md create mode 100644 doc/lua/actormovementcontroller.md create mode 100644 doc/lua/animator.md create mode 100644 doc/lua/celestial.md create mode 100644 doc/lua/commandprocessor.md create mode 100644 doc/lua/config.md create mode 100644 doc/lua/containerpane.md create mode 100644 doc/lua/entity.md create mode 100644 doc/lua/item.md create mode 100644 doc/lua/localanimator.md create mode 100644 doc/lua/message.md create mode 100644 doc/lua/monster.md create mode 100644 doc/lua/movementcontroller.md create mode 100644 doc/lua/npc.md create mode 100644 doc/lua/object.md create mode 100644 doc/lua/objectanimator.md create mode 100644 doc/lua/physics.md create mode 100644 doc/lua/player.md create mode 100644 doc/lua/playercompanions.md create mode 100644 doc/lua/projectile.md create mode 100644 doc/lua/quest.md create mode 100644 doc/lua/root.md create mode 100644 doc/lua/scriptedanimator.md create mode 100644 doc/lua/scriptpane.md create mode 100644 doc/lua/stagehand.md create mode 100644 doc/lua/statuscontroller.md create mode 100644 doc/lua/statuseffect.md create mode 100644 doc/lua/tech.md create mode 100644 doc/lua/updatablescript.md create mode 100644 doc/lua/utility.md create mode 100644 doc/lua/vehicle.md create mode 100644 doc/lua/widget.md create mode 100644 doc/lua/world.md create mode 100644 scripts/distclean.sh create mode 100644 scripts/findskippedmatids.sh create mode 100644 scripts/format-source.sh create mode 100644 scripts/gitlab-ci/assemble.sh create mode 100644 scripts/gitlab-ci/linux/build.sh create mode 100644 scripts/gitlab-ci/linux/run-client.sh create mode 100644 scripts/gitlab-ci/linux/run-server.sh create mode 100644 scripts/gitlab-ci/linux/sbinit.config create mode 100644 scripts/gitlab-ci/linux/test.sh create mode 100644 scripts/gitlab-ci/macos/Starbound.app/Contents/Info.plist create mode 100644 scripts/gitlab-ci/macos/Starbound.app/Contents/Resources/starbound.icns create mode 100644 scripts/gitlab-ci/macos/build.sh create mode 100644 scripts/gitlab-ci/macos/run-server.sh create mode 100644 scripts/gitlab-ci/macos/sbinit.config create mode 100644 scripts/gitlab-ci/macos/test.sh create mode 100644 scripts/gitlab-ci/repack_steamfree.sh create mode 100644 scripts/gitlab-ci/windows32/build.bat create mode 100644 scripts/gitlab-ci/windows32/sbinit.config create mode 100644 scripts/gitlab-ci/windows32/test.bat create mode 100644 scripts/gitlab-ci/windows64/build.bat create mode 100644 scripts/gitlab-ci/windows64/sbinit.config create mode 100644 scripts/gitlab-ci/windows64/test.bat create mode 100644 scripts/ide/atom-build.json create mode 100644 scripts/ide/clang_complete create mode 100644 scripts/ide/linter-clang-flags create mode 100644 scripts/ide/linter-clang-includes create mode 100644 scripts/join-multiline-source-strings.sh create mode 100644 scripts/linux/autotag_objects.sh create mode 100644 scripts/linux/find_invalid_tiles.sh create mode 100644 scripts/linux/object_tag_editor.sh create mode 100644 scripts/linux/sbinit.config create mode 100644 scripts/linux/setup.sh create mode 100644 scripts/linux/ycm_extra_conf.py create mode 100644 scripts/osx/build.command create mode 100644 scripts/osx/copy-to-steam.sh create mode 100644 scripts/osx/launch-steam.sh create mode 100644 scripts/osx/run.command create mode 100644 scripts/osx/sbinit.config create mode 100644 scripts/osx/setup.command create mode 100644 scripts/osx/setup.sh create mode 100644 scripts/osx/ycm_extra_conf.py create mode 100644 scripts/packing.config create mode 100644 scripts/steam_appid.txt create mode 100644 scripts/windows/build.bat create mode 100644 scripts/windows/copy-to-steam32.bat create mode 100644 scripts/windows/copy-to-steam64.bat create mode 100644 scripts/windows/find_invalid_tiles.bat create mode 100644 scripts/windows/sbinit.config create mode 100644 scripts/windows/setup32.bat create mode 100644 scripts/windows/setup64.bat create mode 100644 scripts/windows/showlog.bat create mode 100644 scripts/windows/update_tilesets.bat create mode 100644 source/CMakeLists.txt create mode 100644 source/application/CMakeLists.txt create mode 100644 source/application/StarApplication.cpp create mode 100644 source/application/StarApplication.hpp create mode 100644 source/application/StarApplicationController.hpp create mode 100644 source/application/StarDesktopService_pc_steam.cpp create mode 100644 source/application/StarDesktopService_pc_steam.hpp create mode 100644 source/application/StarInputEvent.cpp create mode 100644 source/application/StarInputEvent.hpp create mode 100644 source/application/StarMainApplication.hpp create mode 100644 source/application/StarMainApplication_sdl.cpp create mode 100644 source/application/StarP2PNetworkingService_pc.cpp create mode 100644 source/application/StarP2PNetworkingService_pc.hpp create mode 100644 source/application/StarPlatformServices_pc.cpp create mode 100644 source/application/StarPlatformServices_pc.hpp create mode 100644 source/application/StarRenderer.cpp create mode 100644 source/application/StarRenderer.hpp create mode 100644 source/application/StarRenderer_opengl20.cpp create mode 100644 source/application/StarRenderer_opengl20.hpp create mode 100644 source/application/StarStatisticsService_pc_steam.cpp create mode 100644 source/application/StarStatisticsService_pc_steam.hpp create mode 100644 source/application/StarTextureAtlas.hpp create mode 100644 source/application/StarUserGeneratedContentService_pc_steam.cpp create mode 100644 source/application/StarUserGeneratedContentService_pc_steam.hpp create mode 100644 source/application/discord/activity_manager.cpp create mode 100644 source/application/discord/activity_manager.h create mode 100644 source/application/discord/application_manager.cpp create mode 100644 source/application/discord/application_manager.h create mode 100644 source/application/discord/core.cpp create mode 100644 source/application/discord/core.h create mode 100644 source/application/discord/discord.h create mode 100644 source/application/discord/event.h create mode 100644 source/application/discord/ffi.h create mode 100644 source/application/discord/image_manager.cpp create mode 100644 source/application/discord/image_manager.h create mode 100644 source/application/discord/lobby_manager.cpp create mode 100644 source/application/discord/lobby_manager.h create mode 100644 source/application/discord/network_manager.cpp create mode 100644 source/application/discord/network_manager.h create mode 100644 source/application/discord/overlay_manager.cpp create mode 100644 source/application/discord/overlay_manager.h create mode 100644 source/application/discord/relationship_manager.cpp create mode 100644 source/application/discord/relationship_manager.h create mode 100644 source/application/discord/storage_manager.cpp create mode 100644 source/application/discord/storage_manager.h create mode 100644 source/application/discord/store_manager.cpp create mode 100644 source/application/discord/store_manager.h create mode 100644 source/application/discord/types.cpp create mode 100644 source/application/discord/types.h create mode 100644 source/application/discord/user_manager.cpp create mode 100644 source/application/discord/user_manager.h create mode 100644 source/base/CMakeLists.txt create mode 100644 source/base/StarAnimatedPartSet.cpp create mode 100644 source/base/StarAnimatedPartSet.hpp create mode 100644 source/base/StarAssetSource.hpp create mode 100644 source/base/StarAssets.cpp create mode 100644 source/base/StarAssets.hpp create mode 100644 source/base/StarBlocksAlongLine.hpp create mode 100644 source/base/StarCellularLightArray.hpp create mode 100644 source/base/StarCellularLighting.cpp create mode 100644 source/base/StarCellularLighting.hpp create mode 100644 source/base/StarCellularLiquid.hpp create mode 100644 source/base/StarConfiguration.cpp create mode 100644 source/base/StarConfiguration.hpp create mode 100644 source/base/StarDirectoryAssetSource.cpp create mode 100644 source/base/StarDirectoryAssetSource.hpp create mode 100644 source/base/StarMixer.cpp create mode 100644 source/base/StarMixer.hpp create mode 100644 source/base/StarPackedAssetSource.cpp create mode 100644 source/base/StarPackedAssetSource.hpp create mode 100644 source/base/StarVersion.cpp.in create mode 100644 source/base/StarVersion.hpp create mode 100644 source/base/StarVersionOptionParser.cpp create mode 100644 source/base/StarVersionOptionParser.hpp create mode 100644 source/base/StarWorldGeometry.cpp create mode 100644 source/base/StarWorldGeometry.hpp create mode 100644 source/client/CMakeLists.txt create mode 100644 source/client/StarClientApplication.cpp create mode 100644 source/client/StarClientApplication.hpp create mode 100644 source/client/starbound-largelogo.ico create mode 100644 source/client/starbound.exe.manifest create mode 100644 source/client/starbound.ico create mode 100644 source/client/starbound.rc create mode 100644 source/core/CMakeLists.txt create mode 100644 source/core/StarAStar.hpp create mode 100644 source/core/StarAlgorithm.hpp create mode 100644 source/core/StarArray.hpp create mode 100644 source/core/StarAtomicSharedPtr.hpp create mode 100644 source/core/StarAudio.cpp create mode 100644 source/core/StarAudio.hpp create mode 100644 source/core/StarBTree.hpp create mode 100644 source/core/StarBTreeDatabase.cpp create mode 100644 source/core/StarBTreeDatabase.hpp create mode 100644 source/core/StarBiMap.hpp create mode 100644 source/core/StarBlockAllocator.hpp create mode 100644 source/core/StarBuffer.cpp create mode 100644 source/core/StarBuffer.hpp create mode 100644 source/core/StarByteArray.cpp create mode 100644 source/core/StarByteArray.hpp create mode 100644 source/core/StarBytes.hpp create mode 100644 source/core/StarCasting.hpp create mode 100644 source/core/StarColor.cpp create mode 100644 source/core/StarColor.hpp create mode 100644 source/core/StarCompression.cpp create mode 100644 source/core/StarCompression.hpp create mode 100644 source/core/StarConfig.hpp create mode 100644 source/core/StarDataStream.cpp create mode 100644 source/core/StarDataStream.hpp create mode 100644 source/core/StarDataStreamDevices.cpp create mode 100644 source/core/StarDataStreamDevices.hpp create mode 100644 source/core/StarDataStreamExtra.hpp create mode 100644 source/core/StarDynamicLib.hpp create mode 100644 source/core/StarDynamicLib_unix.cpp create mode 100644 source/core/StarDynamicLib_windows.cpp create mode 100644 source/core/StarEither.hpp create mode 100644 source/core/StarEncode.cpp create mode 100644 source/core/StarEncode.hpp create mode 100644 source/core/StarException.hpp create mode 100644 source/core/StarException_unix.cpp create mode 100644 source/core/StarException_windows.cpp create mode 100644 source/core/StarFile.cpp create mode 100644 source/core/StarFile.hpp create mode 100644 source/core/StarFile_unix.cpp create mode 100644 source/core/StarFile_windows.cpp create mode 100644 source/core/StarFlatHashMap.hpp create mode 100644 source/core/StarFlatHashSet.hpp create mode 100644 source/core/StarFlatHashTable.hpp create mode 100644 source/core/StarFont.cpp create mode 100644 source/core/StarFont.hpp create mode 100644 source/core/StarFormat.hpp create mode 100644 source/core/StarFormattedJson.cpp create mode 100644 source/core/StarFormattedJson.hpp create mode 100644 source/core/StarHash.hpp create mode 100644 source/core/StarHostAddress.cpp create mode 100644 source/core/StarHostAddress.hpp create mode 100644 source/core/StarIODevice.cpp create mode 100644 source/core/StarIODevice.hpp create mode 100644 source/core/StarIdMap.hpp create mode 100644 source/core/StarImage.cpp create mode 100644 source/core/StarImage.hpp create mode 100644 source/core/StarImageProcessing.cpp create mode 100644 source/core/StarImageProcessing.hpp create mode 100644 source/core/StarInterpolation.hpp create mode 100644 source/core/StarIterator.hpp create mode 100644 source/core/StarJson.cpp create mode 100644 source/core/StarJson.hpp create mode 100644 source/core/StarJsonBuilder.cpp create mode 100644 source/core/StarJsonBuilder.hpp create mode 100644 source/core/StarJsonExtra.cpp create mode 100644 source/core/StarJsonExtra.hpp create mode 100644 source/core/StarJsonParser.hpp create mode 100644 source/core/StarJsonPatch.cpp create mode 100644 source/core/StarJsonPatch.hpp create mode 100644 source/core/StarJsonPath.cpp create mode 100644 source/core/StarJsonPath.hpp create mode 100644 source/core/StarJsonRpc.cpp create mode 100644 source/core/StarJsonRpc.hpp create mode 100644 source/core/StarLexicalCast.hpp create mode 100644 source/core/StarLine.hpp create mode 100644 source/core/StarList.hpp create mode 100644 source/core/StarListener.cpp create mode 100644 source/core/StarListener.hpp create mode 100644 source/core/StarLockFile.hpp create mode 100644 source/core/StarLockFile_unix.cpp create mode 100644 source/core/StarLockFile_windows.cpp create mode 100644 source/core/StarLogging.cpp create mode 100644 source/core/StarLogging.hpp create mode 100644 source/core/StarLruCache.hpp create mode 100644 source/core/StarLua.cpp create mode 100644 source/core/StarLua.hpp create mode 100644 source/core/StarLuaConverters.cpp create mode 100644 source/core/StarLuaConverters.hpp create mode 100644 source/core/StarMap.hpp create mode 100644 source/core/StarMathCommon.hpp create mode 100644 source/core/StarMatrix3.hpp create mode 100644 source/core/StarMaybe.hpp create mode 100644 source/core/StarMemory.cpp create mode 100644 source/core/StarMemory.hpp create mode 100644 source/core/StarMultiArray.hpp create mode 100644 source/core/StarMultiArrayInterpolator.hpp create mode 100644 source/core/StarMultiTable.hpp create mode 100644 source/core/StarNetElement.cpp create mode 100644 source/core/StarNetElement.hpp create mode 100644 source/core/StarNetElementBasicFields.cpp create mode 100644 source/core/StarNetElementBasicFields.hpp create mode 100644 source/core/StarNetElementContainers.hpp create mode 100644 source/core/StarNetElementDynamicGroup.hpp create mode 100644 source/core/StarNetElementFloatFields.hpp create mode 100644 source/core/StarNetElementGroup.cpp create mode 100644 source/core/StarNetElementGroup.hpp create mode 100644 source/core/StarNetElementSignal.hpp create mode 100644 source/core/StarNetElementSyncGroup.cpp create mode 100644 source/core/StarNetElementSyncGroup.hpp create mode 100644 source/core/StarNetElementSystem.hpp create mode 100644 source/core/StarNetElementTop.hpp create mode 100644 source/core/StarNetImpl.hpp create mode 100644 source/core/StarObserverStream.hpp create mode 100644 source/core/StarOptionParser.cpp create mode 100644 source/core/StarOptionParser.hpp create mode 100644 source/core/StarOrderedMap.hpp create mode 100644 source/core/StarOrderedSet.hpp create mode 100644 source/core/StarParametricFunction.hpp create mode 100644 source/core/StarPeriodic.hpp create mode 100644 source/core/StarPeriodicFunction.hpp create mode 100644 source/core/StarPerlin.cpp create mode 100644 source/core/StarPerlin.hpp create mode 100644 source/core/StarPoly.hpp create mode 100644 source/core/StarPythonic.hpp create mode 100644 source/core/StarRandom.cpp create mode 100644 source/core/StarRandom.hpp create mode 100644 source/core/StarRandomPoint.hpp create mode 100644 source/core/StarRect.hpp create mode 100644 source/core/StarRefPtr.hpp create mode 100644 source/core/StarRpcPromise.hpp create mode 100644 source/core/StarSectorArray2D.hpp create mode 100644 source/core/StarSecureRandom.hpp create mode 100644 source/core/StarSecureRandom_unix.cpp create mode 100644 source/core/StarSecureRandom_windows.cpp create mode 100644 source/core/StarSet.hpp create mode 100644 source/core/StarSha256.cpp create mode 100644 source/core/StarSha256.hpp create mode 100644 source/core/StarShellParser.cpp create mode 100644 source/core/StarShellParser.hpp create mode 100644 source/core/StarSignalHandler.hpp create mode 100644 source/core/StarSignalHandler_unix.cpp create mode 100644 source/core/StarSignalHandler_windows.cpp create mode 100644 source/core/StarSmallVector.hpp create mode 100644 source/core/StarSocket.cpp create mode 100644 source/core/StarSocket.hpp create mode 100644 source/core/StarSpatialHash2D.hpp create mode 100644 source/core/StarSpline.hpp create mode 100644 source/core/StarStaticRandom.hpp create mode 100644 source/core/StarStaticVector.hpp create mode 100644 source/core/StarString.cpp create mode 100644 source/core/StarString.hpp create mode 100644 source/core/StarString_windows.cpp create mode 100644 source/core/StarString_windows.hpp create mode 100644 source/core/StarStrongTypedef.hpp create mode 100644 source/core/StarTcp.cpp create mode 100644 source/core/StarTcp.hpp create mode 100644 source/core/StarThread.cpp create mode 100644 source/core/StarThread.hpp create mode 100644 source/core/StarThread_unix.cpp create mode 100644 source/core/StarThread_windows.cpp create mode 100644 source/core/StarTickRateMonitor.cpp create mode 100644 source/core/StarTickRateMonitor.hpp create mode 100644 source/core/StarTime.cpp create mode 100644 source/core/StarTime.hpp create mode 100644 source/core/StarTime_unix.cpp create mode 100644 source/core/StarTime_windows.cpp create mode 100644 source/core/StarTtlCache.hpp create mode 100644 source/core/StarUdp.cpp create mode 100644 source/core/StarUdp.hpp create mode 100644 source/core/StarUnicode.cpp create mode 100644 source/core/StarUnicode.hpp create mode 100644 source/core/StarUuid.cpp create mode 100644 source/core/StarUuid.hpp create mode 100644 source/core/StarVariant.hpp create mode 100644 source/core/StarVector.hpp create mode 100644 source/core/StarVlqEncoding.hpp create mode 100644 source/core/StarWeightedPool.hpp create mode 100644 source/core/StarWorkerPool.cpp create mode 100644 source/core/StarWorkerPool.hpp create mode 100644 source/core/StarXXHash.hpp create mode 100644 source/extern/CMakeLists.txt create mode 100644 source/extern/lauxlib.h create mode 100644 source/extern/lua.h create mode 100644 source/extern/lua.hpp create mode 100644 source/extern/lua/lapi.c create mode 100644 source/extern/lua/lapi.h create mode 100644 source/extern/lua/lauxlib.c create mode 100644 source/extern/lua/lbaselib.c create mode 100644 source/extern/lua/lbitlib.c create mode 100644 source/extern/lua/lcode.c create mode 100644 source/extern/lua/lcode.h create mode 100644 source/extern/lua/lcorolib.c create mode 100644 source/extern/lua/lctype.c create mode 100644 source/extern/lua/lctype.h create mode 100644 source/extern/lua/ldblib.c create mode 100644 source/extern/lua/ldebug.c create mode 100644 source/extern/lua/ldebug.h create mode 100644 source/extern/lua/ldo.c create mode 100644 source/extern/lua/ldo.h create mode 100644 source/extern/lua/ldump.c create mode 100644 source/extern/lua/lfunc.c create mode 100644 source/extern/lua/lfunc.h create mode 100644 source/extern/lua/lgc.c create mode 100644 source/extern/lua/lgc.h create mode 100644 source/extern/lua/linit.c create mode 100644 source/extern/lua/liolib.c create mode 100644 source/extern/lua/llex.c create mode 100644 source/extern/lua/llex.h create mode 100644 source/extern/lua/llimits.h create mode 100644 source/extern/lua/lmathlib.c create mode 100644 source/extern/lua/lmem.c create mode 100644 source/extern/lua/lmem.h create mode 100644 source/extern/lua/loadlib.c create mode 100644 source/extern/lua/lobject.c create mode 100644 source/extern/lua/lobject.h create mode 100644 source/extern/lua/lopcodes.c create mode 100644 source/extern/lua/lopcodes.h create mode 100644 source/extern/lua/loslib.c create mode 100644 source/extern/lua/lparser.c create mode 100644 source/extern/lua/lparser.h create mode 100644 source/extern/lua/lprefix.h create mode 100644 source/extern/lua/lstate.c create mode 100644 source/extern/lua/lstate.h create mode 100644 source/extern/lua/lstring.c create mode 100644 source/extern/lua/lstring.h create mode 100644 source/extern/lua/lstrlib.c create mode 100644 source/extern/lua/ltable.c create mode 100644 source/extern/lua/ltable.h create mode 100644 source/extern/lua/ltablib.c create mode 100644 source/extern/lua/ltm.c create mode 100644 source/extern/lua/ltm.h create mode 100644 source/extern/lua/lundump.c create mode 100644 source/extern/lua/lundump.h create mode 100644 source/extern/lua/lutf8lib.c create mode 100644 source/extern/lua/lvm.c create mode 100644 source/extern/lua/lvm.h create mode 100644 source/extern/lua/lzio.c create mode 100644 source/extern/lua/lzio.h create mode 100644 source/extern/luaconf.h create mode 100644 source/extern/lualib.h create mode 100644 source/extern/tinyformat.h create mode 100644 source/extern/xxhash.c create mode 100644 source/extern/xxhash.h create mode 100644 source/frontend/CMakeLists.txt create mode 100644 source/frontend/StarActionBar.cpp create mode 100644 source/frontend/StarActionBar.hpp create mode 100644 source/frontend/StarAiInterface.cpp create mode 100644 source/frontend/StarAiInterface.hpp create mode 100644 source/frontend/StarBookmarkInterface.cpp create mode 100644 source/frontend/StarBookmarkInterface.hpp create mode 100644 source/frontend/StarCharCreation.cpp create mode 100644 source/frontend/StarCharCreation.hpp create mode 100644 source/frontend/StarCharSelection.cpp create mode 100644 source/frontend/StarCharSelection.hpp create mode 100644 source/frontend/StarChat.cpp create mode 100644 source/frontend/StarChat.hpp create mode 100644 source/frontend/StarChatBubbleManager.cpp create mode 100644 source/frontend/StarChatBubbleManager.hpp create mode 100644 source/frontend/StarChatBubbleSeparation.cpp create mode 100644 source/frontend/StarChatBubbleSeparation.hpp create mode 100644 source/frontend/StarCinematic.cpp create mode 100644 source/frontend/StarCinematic.hpp create mode 100644 source/frontend/StarClientCommandProcessor.cpp create mode 100644 source/frontend/StarClientCommandProcessor.hpp create mode 100644 source/frontend/StarCodexInterface.cpp create mode 100644 source/frontend/StarCodexInterface.hpp create mode 100644 source/frontend/StarConfirmationDialog.cpp create mode 100644 source/frontend/StarConfirmationDialog.hpp create mode 100644 source/frontend/StarContainerInteractor.cpp create mode 100644 source/frontend/StarContainerInteractor.hpp create mode 100644 source/frontend/StarContainerInterface.cpp create mode 100644 source/frontend/StarContainerInterface.hpp create mode 100644 source/frontend/StarCraftingInterface.cpp create mode 100644 source/frontend/StarCraftingInterface.hpp create mode 100644 source/frontend/StarErrorScreen.cpp create mode 100644 source/frontend/StarErrorScreen.hpp create mode 100644 source/frontend/StarGraphicsMenu.cpp create mode 100644 source/frontend/StarGraphicsMenu.hpp create mode 100644 source/frontend/StarInterfaceCursor.cpp create mode 100644 source/frontend/StarInterfaceCursor.hpp create mode 100644 source/frontend/StarInventory.cpp create mode 100644 source/frontend/StarInventory.hpp create mode 100644 source/frontend/StarItemTooltip.cpp create mode 100644 source/frontend/StarItemTooltip.hpp create mode 100644 source/frontend/StarJoinRequestDialog.cpp create mode 100644 source/frontend/StarJoinRequestDialog.hpp create mode 100644 source/frontend/StarKeybindingsMenu.cpp create mode 100644 source/frontend/StarKeybindingsMenu.hpp create mode 100644 source/frontend/StarMainInterface.cpp create mode 100644 source/frontend/StarMainInterface.hpp create mode 100644 source/frontend/StarMainInterfaceTypes.cpp create mode 100644 source/frontend/StarMainInterfaceTypes.hpp create mode 100644 source/frontend/StarMainMixer.cpp create mode 100644 source/frontend/StarMainMixer.hpp create mode 100644 source/frontend/StarMerchantInterface.cpp create mode 100644 source/frontend/StarMerchantInterface.hpp create mode 100644 source/frontend/StarModsMenu.cpp create mode 100644 source/frontend/StarModsMenu.hpp create mode 100644 source/frontend/StarNameplatePainter.cpp create mode 100644 source/frontend/StarNameplatePainter.hpp create mode 100644 source/frontend/StarOptionsMenu.cpp create mode 100644 source/frontend/StarOptionsMenu.hpp create mode 100644 source/frontend/StarPopupInterface.cpp create mode 100644 source/frontend/StarPopupInterface.hpp create mode 100644 source/frontend/StarQuestIndicatorPainter.cpp create mode 100644 source/frontend/StarQuestIndicatorPainter.hpp create mode 100644 source/frontend/StarQuestInterface.cpp create mode 100644 source/frontend/StarQuestInterface.hpp create mode 100644 source/frontend/StarQuestTracker.cpp create mode 100644 source/frontend/StarQuestTracker.hpp create mode 100644 source/frontend/StarRadioMessagePopup.cpp create mode 100644 source/frontend/StarRadioMessagePopup.hpp create mode 100644 source/frontend/StarScriptPane.cpp create mode 100644 source/frontend/StarScriptPane.hpp create mode 100644 source/frontend/StarSimpleTooltip.cpp create mode 100644 source/frontend/StarSimpleTooltip.hpp create mode 100644 source/frontend/StarSongbookInterface.cpp create mode 100644 source/frontend/StarSongbookInterface.hpp create mode 100644 source/frontend/StarStatusPane.cpp create mode 100644 source/frontend/StarStatusPane.hpp create mode 100644 source/frontend/StarTeamBar.cpp create mode 100644 source/frontend/StarTeamBar.hpp create mode 100644 source/frontend/StarTeleportDialog.cpp create mode 100644 source/frontend/StarTeleportDialog.hpp create mode 100644 source/frontend/StarTitleScreen.cpp create mode 100644 source/frontend/StarTitleScreen.hpp create mode 100644 source/frontend/StarWidgetLuaBindings.cpp create mode 100644 source/frontend/StarWidgetLuaBindings.hpp create mode 100644 source/frontend/StarWireInterface.cpp create mode 100644 source/frontend/StarWireInterface.hpp create mode 100644 source/game/CMakeLists.txt create mode 100644 source/game/StarActorMovementController.cpp create mode 100644 source/game/StarActorMovementController.hpp create mode 100644 source/game/StarAiDatabase.cpp create mode 100644 source/game/StarAiDatabase.hpp create mode 100644 source/game/StarAiTypes.cpp create mode 100644 source/game/StarAiTypes.hpp create mode 100644 source/game/StarAmbient.cpp create mode 100644 source/game/StarAmbient.hpp create mode 100644 source/game/StarAnimation.cpp create mode 100644 source/game/StarAnimation.hpp create mode 100644 source/game/StarArmorWearer.cpp create mode 100644 source/game/StarArmorWearer.hpp create mode 100644 source/game/StarBehaviorDatabase.cpp create mode 100644 source/game/StarBehaviorDatabase.hpp create mode 100644 source/game/StarBehaviorState.cpp create mode 100644 source/game/StarBehaviorState.hpp create mode 100644 source/game/StarBiome.cpp create mode 100644 source/game/StarBiome.hpp create mode 100644 source/game/StarBiomeDatabase.cpp create mode 100644 source/game/StarBiomeDatabase.hpp create mode 100644 source/game/StarBiomePlacement.cpp create mode 100644 source/game/StarBiomePlacement.hpp create mode 100644 source/game/StarCelestialCoordinate.cpp create mode 100644 source/game/StarCelestialCoordinate.hpp create mode 100644 source/game/StarCelestialDatabase.cpp create mode 100644 source/game/StarCelestialDatabase.hpp create mode 100644 source/game/StarCelestialGraphics.cpp create mode 100644 source/game/StarCelestialGraphics.hpp create mode 100644 source/game/StarCelestialParameters.cpp create mode 100644 source/game/StarCelestialParameters.hpp create mode 100644 source/game/StarCelestialTypes.cpp create mode 100644 source/game/StarCelestialTypes.hpp create mode 100644 source/game/StarChatAction.cpp create mode 100644 source/game/StarChatAction.hpp create mode 100644 source/game/StarChatProcessor.cpp create mode 100644 source/game/StarChatProcessor.hpp create mode 100644 source/game/StarChatTypes.cpp create mode 100644 source/game/StarChatTypes.hpp create mode 100644 source/game/StarClientContext.cpp create mode 100644 source/game/StarClientContext.hpp create mode 100644 source/game/StarCodex.cpp create mode 100644 source/game/StarCodex.hpp create mode 100644 source/game/StarCodexDatabase.cpp create mode 100644 source/game/StarCodexDatabase.hpp create mode 100644 source/game/StarCollectionDatabase.cpp create mode 100644 source/game/StarCollectionDatabase.hpp create mode 100644 source/game/StarCollisionBlock.cpp create mode 100644 source/game/StarCollisionBlock.hpp create mode 100644 source/game/StarCollisionGenerator.cpp create mode 100644 source/game/StarCollisionGenerator.hpp create mode 100644 source/game/StarCommandProcessor.cpp create mode 100644 source/game/StarCommandProcessor.hpp create mode 100644 source/game/StarDamage.cpp create mode 100644 source/game/StarDamage.hpp create mode 100644 source/game/StarDamageDatabase.cpp create mode 100644 source/game/StarDamageDatabase.hpp create mode 100644 source/game/StarDamageManager.cpp create mode 100644 source/game/StarDamageManager.hpp create mode 100644 source/game/StarDamageTypes.cpp create mode 100644 source/game/StarDamageTypes.hpp create mode 100644 source/game/StarDanceDatabase.cpp create mode 100644 source/game/StarDanceDatabase.hpp create mode 100644 source/game/StarDrawable.cpp create mode 100644 source/game/StarDrawable.hpp create mode 100644 source/game/StarDungeonGenerator.cpp create mode 100644 source/game/StarDungeonGenerator.hpp create mode 100644 source/game/StarDungeonImagePart.cpp create mode 100644 source/game/StarDungeonImagePart.hpp create mode 100644 source/game/StarDungeonTMXPart.cpp create mode 100644 source/game/StarDungeonTMXPart.hpp create mode 100644 source/game/StarEffectEmitter.cpp create mode 100644 source/game/StarEffectEmitter.hpp create mode 100644 source/game/StarEffectSourceDatabase.cpp create mode 100644 source/game/StarEffectSourceDatabase.hpp create mode 100644 source/game/StarEmoteProcessor.cpp create mode 100644 source/game/StarEmoteProcessor.hpp create mode 100644 source/game/StarEntityFactory.cpp create mode 100644 source/game/StarEntityFactory.hpp create mode 100644 source/game/StarEntityMap.cpp create mode 100644 source/game/StarEntityMap.hpp create mode 100644 source/game/StarEntityRendering.cpp create mode 100644 source/game/StarEntityRendering.hpp create mode 100644 source/game/StarEntityRenderingTypes.cpp create mode 100644 source/game/StarEntityRenderingTypes.hpp create mode 100644 source/game/StarEntitySplash.cpp create mode 100644 source/game/StarEntitySplash.hpp create mode 100644 source/game/StarFallingBlocksAgent.cpp create mode 100644 source/game/StarFallingBlocksAgent.hpp create mode 100644 source/game/StarForceRegions.cpp create mode 100644 source/game/StarForceRegions.hpp create mode 100644 source/game/StarGameTimers.cpp create mode 100644 source/game/StarGameTimers.hpp create mode 100644 source/game/StarGameTypes.cpp create mode 100644 source/game/StarGameTypes.hpp create mode 100644 source/game/StarHumanoid.cpp create mode 100644 source/game/StarHumanoid.hpp create mode 100644 source/game/StarImageMetadataDatabase.cpp create mode 100644 source/game/StarImageMetadataDatabase.hpp create mode 100644 source/game/StarInteractionTypes.cpp create mode 100644 source/game/StarInteractionTypes.hpp create mode 100644 source/game/StarInterpolationTracker.cpp create mode 100644 source/game/StarInterpolationTracker.hpp create mode 100644 source/game/StarInventoryTypes.cpp create mode 100644 source/game/StarInventoryTypes.hpp create mode 100644 source/game/StarItem.cpp create mode 100644 source/game/StarItem.hpp create mode 100644 source/game/StarItemBag.cpp create mode 100644 source/game/StarItemBag.hpp create mode 100644 source/game/StarItemDatabase.cpp create mode 100644 source/game/StarItemDatabase.hpp create mode 100644 source/game/StarItemDescriptor.cpp create mode 100644 source/game/StarItemDescriptor.hpp create mode 100644 source/game/StarItemDrop.cpp create mode 100644 source/game/StarItemDrop.hpp create mode 100644 source/game/StarItemRecipe.cpp create mode 100644 source/game/StarItemRecipe.hpp create mode 100644 source/game/StarLightSource.cpp create mode 100644 source/game/StarLightSource.hpp create mode 100644 source/game/StarLiquidTypes.cpp create mode 100644 source/game/StarLiquidTypes.hpp create mode 100644 source/game/StarLiquidsDatabase.cpp create mode 100644 source/game/StarLiquidsDatabase.hpp create mode 100644 source/game/StarMaterialDatabase.cpp create mode 100644 source/game/StarMaterialDatabase.hpp create mode 100644 source/game/StarMaterialRenderProfile.cpp create mode 100644 source/game/StarMaterialRenderProfile.hpp create mode 100644 source/game/StarMaterialTypes.hpp create mode 100644 source/game/StarMicroDungeon.cpp create mode 100644 source/game/StarMicroDungeon.hpp create mode 100644 source/game/StarMonster.cpp create mode 100644 source/game/StarMonster.hpp create mode 100644 source/game/StarMonsterDatabase.cpp create mode 100644 source/game/StarMonsterDatabase.hpp create mode 100644 source/game/StarMovementController.cpp create mode 100644 source/game/StarMovementController.hpp create mode 100644 source/game/StarNameGenerator.cpp create mode 100644 source/game/StarNameGenerator.hpp create mode 100644 source/game/StarNetPacketSocket.cpp create mode 100644 source/game/StarNetPacketSocket.hpp create mode 100644 source/game/StarNetPackets.cpp create mode 100644 source/game/StarNetPackets.hpp create mode 100644 source/game/StarNetworkedAnimator.cpp create mode 100644 source/game/StarNetworkedAnimator.hpp create mode 100644 source/game/StarNpc.cpp create mode 100644 source/game/StarNpc.hpp create mode 100644 source/game/StarNpcDatabase.cpp create mode 100644 source/game/StarNpcDatabase.hpp create mode 100644 source/game/StarObject.cpp create mode 100644 source/game/StarObject.hpp create mode 100644 source/game/StarObjectDatabase.cpp create mode 100644 source/game/StarObjectDatabase.hpp create mode 100644 source/game/StarParallax.cpp create mode 100644 source/game/StarParallax.hpp create mode 100644 source/game/StarParticle.cpp create mode 100644 source/game/StarParticle.hpp create mode 100644 source/game/StarParticleDatabase.cpp create mode 100644 source/game/StarParticleDatabase.hpp create mode 100644 source/game/StarParticleManager.cpp create mode 100644 source/game/StarParticleManager.hpp create mode 100644 source/game/StarPlant.cpp create mode 100644 source/game/StarPlant.hpp create mode 100644 source/game/StarPlantDatabase.cpp create mode 100644 source/game/StarPlantDatabase.hpp create mode 100644 source/game/StarPlantDrop.cpp create mode 100644 source/game/StarPlantDrop.hpp create mode 100644 source/game/StarPlatformerAStar.cpp create mode 100644 source/game/StarPlatformerAStar.hpp create mode 100644 source/game/StarPlatformerAStarTypes.cpp create mode 100644 source/game/StarPlatformerAStarTypes.hpp create mode 100644 source/game/StarPlayer.cpp create mode 100644 source/game/StarPlayer.hpp create mode 100644 source/game/StarPlayerBlueprints.cpp create mode 100644 source/game/StarPlayerBlueprints.hpp create mode 100644 source/game/StarPlayerCodexes.cpp create mode 100644 source/game/StarPlayerCodexes.hpp create mode 100644 source/game/StarPlayerCompanions.cpp create mode 100644 source/game/StarPlayerCompanions.hpp create mode 100644 source/game/StarPlayerDeployment.cpp create mode 100644 source/game/StarPlayerDeployment.hpp create mode 100644 source/game/StarPlayerFactory.cpp create mode 100644 source/game/StarPlayerFactory.hpp create mode 100644 source/game/StarPlayerInventory.cpp create mode 100644 source/game/StarPlayerInventory.hpp create mode 100644 source/game/StarPlayerLog.cpp create mode 100644 source/game/StarPlayerLog.hpp create mode 100644 source/game/StarPlayerStorage.cpp create mode 100644 source/game/StarPlayerStorage.hpp create mode 100644 source/game/StarPlayerTech.cpp create mode 100644 source/game/StarPlayerTech.hpp create mode 100644 source/game/StarPlayerTypes.cpp create mode 100644 source/game/StarPlayerTypes.hpp create mode 100644 source/game/StarPlayerUniverseMap.cpp create mode 100644 source/game/StarPlayerUniverseMap.hpp create mode 100644 source/game/StarProjectile.cpp create mode 100644 source/game/StarProjectile.hpp create mode 100644 source/game/StarProjectileDatabase.cpp create mode 100644 source/game/StarProjectileDatabase.hpp create mode 100644 source/game/StarQuestDescriptor.cpp create mode 100644 source/game/StarQuestDescriptor.hpp create mode 100644 source/game/StarQuestManager.cpp create mode 100644 source/game/StarQuestManager.hpp create mode 100644 source/game/StarQuestTemplateDatabase.cpp create mode 100644 source/game/StarQuestTemplateDatabase.hpp create mode 100644 source/game/StarQuests.cpp create mode 100644 source/game/StarQuests.hpp create mode 100644 source/game/StarRadioMessageDatabase.cpp create mode 100644 source/game/StarRadioMessageDatabase.hpp create mode 100644 source/game/StarRoot.cpp create mode 100644 source/game/StarRoot.hpp create mode 100644 source/game/StarRootLoader.cpp create mode 100644 source/game/StarRootLoader.hpp create mode 100644 source/game/StarServerClientContext.cpp create mode 100644 source/game/StarServerClientContext.hpp create mode 100644 source/game/StarSky.cpp create mode 100644 source/game/StarSky.hpp create mode 100644 source/game/StarSkyParameters.cpp create mode 100644 source/game/StarSkyParameters.hpp create mode 100644 source/game/StarSkyRenderData.cpp create mode 100644 source/game/StarSkyRenderData.hpp create mode 100644 source/game/StarSkyTypes.cpp create mode 100644 source/game/StarSkyTypes.hpp create mode 100644 source/game/StarSongbook.cpp create mode 100644 source/game/StarSongbook.hpp create mode 100644 source/game/StarSpawnTypeDatabase.cpp create mode 100644 source/game/StarSpawnTypeDatabase.hpp create mode 100644 source/game/StarSpawner.cpp create mode 100644 source/game/StarSpawner.hpp create mode 100644 source/game/StarSpeciesDatabase.cpp create mode 100644 source/game/StarSpeciesDatabase.hpp create mode 100644 source/game/StarStagehand.cpp create mode 100644 source/game/StarStagehand.hpp create mode 100644 source/game/StarStagehandDatabase.cpp create mode 100644 source/game/StarStagehandDatabase.hpp create mode 100644 source/game/StarStatCollection.cpp create mode 100644 source/game/StarStatCollection.hpp create mode 100644 source/game/StarStatSet.cpp create mode 100644 source/game/StarStatSet.hpp create mode 100644 source/game/StarStatistics.cpp create mode 100644 source/game/StarStatistics.hpp create mode 100644 source/game/StarStatisticsDatabase.cpp create mode 100644 source/game/StarStatisticsDatabase.hpp create mode 100644 source/game/StarStatusController.cpp create mode 100644 source/game/StarStatusController.hpp create mode 100644 source/game/StarStatusEffectDatabase.cpp create mode 100644 source/game/StarStatusEffectDatabase.hpp create mode 100644 source/game/StarStatusTypes.cpp create mode 100644 source/game/StarStatusTypes.hpp create mode 100644 source/game/StarStoredFunctions.cpp create mode 100644 source/game/StarStoredFunctions.hpp create mode 100644 source/game/StarSystemWorld.cpp create mode 100644 source/game/StarSystemWorld.hpp create mode 100644 source/game/StarSystemWorldClient.cpp create mode 100644 source/game/StarSystemWorldClient.hpp create mode 100644 source/game/StarSystemWorldServer.cpp create mode 100644 source/game/StarSystemWorldServer.hpp create mode 100644 source/game/StarSystemWorldServerThread.cpp create mode 100644 source/game/StarSystemWorldServerThread.hpp create mode 100644 source/game/StarTeamClient.cpp create mode 100644 source/game/StarTeamClient.hpp create mode 100644 source/game/StarTeamManager.cpp create mode 100644 source/game/StarTeamManager.hpp create mode 100644 source/game/StarTechController.cpp create mode 100644 source/game/StarTechController.hpp create mode 100644 source/game/StarTechDatabase.cpp create mode 100644 source/game/StarTechDatabase.hpp create mode 100644 source/game/StarTenantDatabase.cpp create mode 100644 source/game/StarTenantDatabase.hpp create mode 100644 source/game/StarTerrainDatabase.cpp create mode 100644 source/game/StarTerrainDatabase.hpp create mode 100644 source/game/StarTileDamage.cpp create mode 100644 source/game/StarTileDamage.hpp create mode 100644 source/game/StarTileModification.cpp create mode 100644 source/game/StarTileModification.hpp create mode 100644 source/game/StarTileSectorArray.hpp create mode 100644 source/game/StarTilesetDatabase.cpp create mode 100644 source/game/StarTilesetDatabase.hpp create mode 100644 source/game/StarToolUser.cpp create mode 100644 source/game/StarToolUser.hpp create mode 100644 source/game/StarTreasure.cpp create mode 100644 source/game/StarTreasure.hpp create mode 100644 source/game/StarUniverseClient.cpp create mode 100644 source/game/StarUniverseClient.hpp create mode 100644 source/game/StarUniverseConnection.cpp create mode 100644 source/game/StarUniverseConnection.hpp create mode 100644 source/game/StarUniverseServer.cpp create mode 100644 source/game/StarUniverseServer.hpp create mode 100644 source/game/StarUniverseSettings.cpp create mode 100644 source/game/StarUniverseSettings.hpp create mode 100644 source/game/StarVehicle.cpp create mode 100644 source/game/StarVehicle.hpp create mode 100644 source/game/StarVehicleDatabase.cpp create mode 100644 source/game/StarVehicleDatabase.hpp create mode 100644 source/game/StarVersioningDatabase.cpp create mode 100644 source/game/StarVersioningDatabase.hpp create mode 100644 source/game/StarWarping.cpp create mode 100644 source/game/StarWarping.hpp create mode 100644 source/game/StarWeather.cpp create mode 100644 source/game/StarWeather.hpp create mode 100644 source/game/StarWeatherTypes.cpp create mode 100644 source/game/StarWeatherTypes.hpp create mode 100644 source/game/StarWireProcessor.cpp create mode 100644 source/game/StarWireProcessor.hpp create mode 100644 source/game/StarWiring.cpp create mode 100644 source/game/StarWiring.hpp create mode 100644 source/game/StarWorldClient.cpp create mode 100644 source/game/StarWorldClient.hpp create mode 100644 source/game/StarWorldClientState.cpp create mode 100644 source/game/StarWorldClientState.hpp create mode 100644 source/game/StarWorldGeneration.cpp create mode 100644 source/game/StarWorldGeneration.hpp create mode 100644 source/game/StarWorldImpl.hpp create mode 100644 source/game/StarWorldLayout.cpp create mode 100644 source/game/StarWorldLayout.hpp create mode 100644 source/game/StarWorldParameters.cpp create mode 100644 source/game/StarWorldParameters.hpp create mode 100644 source/game/StarWorldRenderData.hpp create mode 100644 source/game/StarWorldServer.cpp create mode 100644 source/game/StarWorldServer.hpp create mode 100644 source/game/StarWorldServerThread.cpp create mode 100644 source/game/StarWorldServerThread.hpp create mode 100644 source/game/StarWorldStorage.cpp create mode 100644 source/game/StarWorldStorage.hpp create mode 100644 source/game/StarWorldStructure.cpp create mode 100644 source/game/StarWorldStructure.hpp create mode 100644 source/game/StarWorldTemplate.cpp create mode 100644 source/game/StarWorldTemplate.hpp create mode 100644 source/game/StarWorldTiles.cpp create mode 100644 source/game/StarWorldTiles.hpp create mode 100644 source/game/interfaces/StarActivatableItem.hpp create mode 100644 source/game/interfaces/StarAggressiveEntity.hpp create mode 100644 source/game/interfaces/StarAnchorableEntity.cpp create mode 100644 source/game/interfaces/StarAnchorableEntity.hpp create mode 100644 source/game/interfaces/StarBeamItem.cpp create mode 100644 source/game/interfaces/StarBeamItem.hpp create mode 100644 source/game/interfaces/StarChattyEntity.hpp create mode 100644 source/game/interfaces/StarContainerEntity.cpp create mode 100644 source/game/interfaces/StarContainerEntity.hpp create mode 100644 source/game/interfaces/StarDamageBarEntity.cpp create mode 100644 source/game/interfaces/StarDamageBarEntity.hpp create mode 100644 source/game/interfaces/StarDurabilityItem.hpp create mode 100644 source/game/interfaces/StarEffectSourceItem.hpp create mode 100644 source/game/interfaces/StarEmoteEntity.hpp create mode 100644 source/game/interfaces/StarEntity.cpp create mode 100644 source/game/interfaces/StarEntity.hpp create mode 100644 source/game/interfaces/StarFireableItem.cpp create mode 100644 source/game/interfaces/StarFireableItem.hpp create mode 100644 source/game/interfaces/StarInspectableEntity.hpp create mode 100644 source/game/interfaces/StarInteractiveEntity.cpp create mode 100644 source/game/interfaces/StarInteractiveEntity.hpp create mode 100644 source/game/interfaces/StarLoungingEntities.cpp create mode 100644 source/game/interfaces/StarLoungingEntities.hpp create mode 100644 source/game/interfaces/StarNametagEntity.hpp create mode 100644 source/game/interfaces/StarNonRotatedDrawablesItem.hpp create mode 100644 source/game/interfaces/StarPhysicsEntity.cpp create mode 100644 source/game/interfaces/StarPhysicsEntity.hpp create mode 100644 source/game/interfaces/StarPointableItem.cpp create mode 100644 source/game/interfaces/StarPointableItem.hpp create mode 100644 source/game/interfaces/StarPortraitEntity.hpp create mode 100644 source/game/interfaces/StarPreviewTileTool.hpp create mode 100644 source/game/interfaces/StarPreviewableItem.hpp create mode 100644 source/game/interfaces/StarScriptedEntity.hpp create mode 100644 source/game/interfaces/StarStatusEffectEntity.hpp create mode 100644 source/game/interfaces/StarStatusEffectItem.hpp create mode 100644 source/game/interfaces/StarSwingableItem.cpp create mode 100644 source/game/interfaces/StarSwingableItem.hpp create mode 100644 source/game/interfaces/StarTileEntity.cpp create mode 100644 source/game/interfaces/StarTileEntity.hpp create mode 100644 source/game/interfaces/StarToolUserEntity.hpp create mode 100644 source/game/interfaces/StarToolUserItem.cpp create mode 100644 source/game/interfaces/StarToolUserItem.hpp create mode 100644 source/game/interfaces/StarWarpTargetEntity.hpp create mode 100644 source/game/interfaces/StarWireEntity.hpp create mode 100644 source/game/interfaces/StarWorld.cpp create mode 100644 source/game/interfaces/StarWorld.hpp create mode 100644 source/game/items/StarActiveItem.cpp create mode 100644 source/game/items/StarActiveItem.hpp create mode 100644 source/game/items/StarArmors.cpp create mode 100644 source/game/items/StarArmors.hpp create mode 100644 source/game/items/StarAugmentItem.cpp create mode 100644 source/game/items/StarAugmentItem.hpp create mode 100644 source/game/items/StarBlueprintItem.cpp create mode 100644 source/game/items/StarBlueprintItem.hpp create mode 100644 source/game/items/StarCodexItem.cpp create mode 100644 source/game/items/StarCodexItem.hpp create mode 100644 source/game/items/StarConsumableItem.cpp create mode 100644 source/game/items/StarConsumableItem.hpp create mode 100644 source/game/items/StarCurrency.cpp create mode 100644 source/game/items/StarCurrency.hpp create mode 100644 source/game/items/StarInspectionTool.cpp create mode 100644 source/game/items/StarInspectionTool.hpp create mode 100644 source/game/items/StarInstrumentItem.cpp create mode 100644 source/game/items/StarInstrumentItem.hpp create mode 100644 source/game/items/StarLiquidItem.cpp create mode 100644 source/game/items/StarLiquidItem.hpp create mode 100644 source/game/items/StarMaterialItem.cpp create mode 100644 source/game/items/StarMaterialItem.hpp create mode 100644 source/game/items/StarObjectItem.cpp create mode 100644 source/game/items/StarObjectItem.hpp create mode 100644 source/game/items/StarThrownItem.cpp create mode 100644 source/game/items/StarThrownItem.hpp create mode 100644 source/game/items/StarTools.cpp create mode 100644 source/game/items/StarTools.hpp create mode 100644 source/game/items/StarUnlockItem.cpp create mode 100644 source/game/items/StarUnlockItem.hpp create mode 100644 source/game/objects/StarContainerObject.cpp create mode 100644 source/game/objects/StarContainerObject.hpp create mode 100644 source/game/objects/StarFarmableObject.cpp create mode 100644 source/game/objects/StarFarmableObject.hpp create mode 100644 source/game/objects/StarLoungeableObject.cpp create mode 100644 source/game/objects/StarLoungeableObject.hpp create mode 100644 source/game/objects/StarPhysicsObject.cpp create mode 100644 source/game/objects/StarPhysicsObject.hpp create mode 100644 source/game/objects/StarTeleporterObject.cpp create mode 100644 source/game/objects/StarTeleporterObject.hpp create mode 100644 source/game/scripting/StarBehaviorLuaBindings.cpp create mode 100644 source/game/scripting/StarBehaviorLuaBindings.hpp create mode 100644 source/game/scripting/StarCelestialLuaBindings.cpp create mode 100644 source/game/scripting/StarCelestialLuaBindings.hpp create mode 100644 source/game/scripting/StarConfigLuaBindings.cpp create mode 100644 source/game/scripting/StarConfigLuaBindings.hpp create mode 100644 source/game/scripting/StarEntityLuaBindings.cpp create mode 100644 source/game/scripting/StarEntityLuaBindings.hpp create mode 100644 source/game/scripting/StarFireableItemLuaBindings.cpp create mode 100644 source/game/scripting/StarFireableItemLuaBindings.hpp create mode 100644 source/game/scripting/StarItemLuaBindings.cpp create mode 100644 source/game/scripting/StarItemLuaBindings.hpp create mode 100644 source/game/scripting/StarLuaActorMovementComponent.hpp create mode 100644 source/game/scripting/StarLuaAnimationComponent.hpp create mode 100644 source/game/scripting/StarLuaComponents.cpp create mode 100644 source/game/scripting/StarLuaComponents.hpp create mode 100644 source/game/scripting/StarLuaGameConverters.cpp create mode 100644 source/game/scripting/StarLuaGameConverters.hpp create mode 100644 source/game/scripting/StarLuaRoot.cpp create mode 100644 source/game/scripting/StarLuaRoot.hpp create mode 100644 source/game/scripting/StarMovementControllerLuaBindings.cpp create mode 100644 source/game/scripting/StarMovementControllerLuaBindings.hpp create mode 100644 source/game/scripting/StarNetworkedAnimatorLuaBindings.cpp create mode 100644 source/game/scripting/StarNetworkedAnimatorLuaBindings.hpp create mode 100644 source/game/scripting/StarPlayerLuaBindings.cpp create mode 100644 source/game/scripting/StarPlayerLuaBindings.hpp create mode 100644 source/game/scripting/StarRootLuaBindings.cpp create mode 100644 source/game/scripting/StarRootLuaBindings.hpp create mode 100644 source/game/scripting/StarScriptedAnimatorLuaBindings.cpp create mode 100644 source/game/scripting/StarScriptedAnimatorLuaBindings.hpp create mode 100644 source/game/scripting/StarStatusControllerLuaBindings.cpp create mode 100644 source/game/scripting/StarStatusControllerLuaBindings.hpp create mode 100644 source/game/scripting/StarUniverseServerLuaBindings.cpp create mode 100644 source/game/scripting/StarUniverseServerLuaBindings.hpp create mode 100644 source/game/scripting/StarUtilityLuaBindings.cpp create mode 100644 source/game/scripting/StarUtilityLuaBindings.hpp create mode 100644 source/game/scripting/StarWorldLuaBindings.cpp create mode 100644 source/game/scripting/StarWorldLuaBindings.hpp create mode 100644 source/game/terrain/StarCacheSelector.cpp create mode 100644 source/game/terrain/StarCacheSelector.hpp create mode 100644 source/game/terrain/StarConstantSelector.cpp create mode 100644 source/game/terrain/StarConstantSelector.hpp create mode 100644 source/game/terrain/StarDisplacementSelector.cpp create mode 100644 source/game/terrain/StarDisplacementSelector.hpp create mode 100644 source/game/terrain/StarFlatSurfaceSelector.cpp create mode 100644 source/game/terrain/StarFlatSurfaceSelector.hpp create mode 100644 source/game/terrain/StarIslandSurfaceSelector.cpp create mode 100644 source/game/terrain/StarIslandSurfaceSelector.hpp create mode 100644 source/game/terrain/StarKarstCave.cpp create mode 100644 source/game/terrain/StarKarstCave.hpp create mode 100644 source/game/terrain/StarMaxSelector.cpp create mode 100644 source/game/terrain/StarMaxSelector.hpp create mode 100644 source/game/terrain/StarMinMaxSelector.cpp create mode 100644 source/game/terrain/StarMinMaxSelector.hpp create mode 100644 source/game/terrain/StarMixSelector.cpp create mode 100644 source/game/terrain/StarMixSelector.hpp create mode 100644 source/game/terrain/StarPerlinSelector.cpp create mode 100644 source/game/terrain/StarPerlinSelector.hpp create mode 100644 source/game/terrain/StarRidgeBlocksSelector.cpp create mode 100644 source/game/terrain/StarRidgeBlocksSelector.hpp create mode 100644 source/game/terrain/StarRotateSelector.cpp create mode 100644 source/game/terrain/StarRotateSelector.hpp create mode 100644 source/game/terrain/StarWormCave.cpp create mode 100644 source/game/terrain/StarWormCave.hpp create mode 100644 source/json_tool/CMakeLists.txt create mode 100644 source/json_tool/editor_gui.cpp create mode 100644 source/json_tool/editor_gui.hpp create mode 100644 source/json_tool/json_tool.cpp create mode 100644 source/json_tool/json_tool.hpp create mode 100644 source/mod_uploader/CMakeLists.txt create mode 100644 source/mod_uploader/StarModUploader.cpp create mode 100644 source/mod_uploader/StarModUploader.hpp create mode 100644 source/mod_uploader/StarSPlainTextEdit.cpp create mode 100644 source/mod_uploader/StarSPlainTextEdit.hpp create mode 100644 source/mod_uploader/StarStringConversion.hpp create mode 100644 source/mod_uploader/main.cpp create mode 100644 source/platform/CMakeLists.txt create mode 100644 source/platform/StarDesktopService.hpp create mode 100644 source/platform/StarP2PNetworkingService.hpp create mode 100644 source/platform/StarStatisticsService.hpp create mode 100644 source/platform/StarUserGeneratedContentService.hpp create mode 100644 source/rendering/CMakeLists.txt create mode 100644 source/rendering/StarAnchorTypes.cpp create mode 100644 source/rendering/StarAnchorTypes.hpp create mode 100644 source/rendering/StarAssetTextureGroup.cpp create mode 100644 source/rendering/StarAssetTextureGroup.hpp create mode 100644 source/rendering/StarDrawablePainter.cpp create mode 100644 source/rendering/StarDrawablePainter.hpp create mode 100644 source/rendering/StarEnvironmentPainter.cpp create mode 100644 source/rendering/StarEnvironmentPainter.hpp create mode 100644 source/rendering/StarFontTextureGroup.cpp create mode 100644 source/rendering/StarFontTextureGroup.hpp create mode 100644 source/rendering/StarTextPainter.cpp create mode 100644 source/rendering/StarTextPainter.hpp create mode 100644 source/rendering/StarTilePainter.cpp create mode 100644 source/rendering/StarTilePainter.hpp create mode 100644 source/rendering/StarWorldCamera.cpp create mode 100644 source/rendering/StarWorldCamera.hpp create mode 100644 source/rendering/StarWorldPainter.cpp create mode 100644 source/rendering/StarWorldPainter.hpp create mode 100644 source/server/CMakeLists.txt create mode 100644 source/server/StarServerQueryThread.cpp create mode 100644 source/server/StarServerQueryThread.hpp create mode 100644 source/server/StarServerRconClient.cpp create mode 100644 source/server/StarServerRconClient.hpp create mode 100644 source/server/StarServerRconThread.cpp create mode 100644 source/server/StarServerRconThread.hpp create mode 100644 source/server/main.cpp create mode 100644 source/test/CMakeLists.txt create mode 100644 source/test/StarTestUniverse.cpp create mode 100644 source/test/StarTestUniverse.hpp create mode 100644 source/test/algorithm_test.cpp create mode 100644 source/test/assets_test.cpp create mode 100644 source/test/block_allocator_test.cpp create mode 100644 source/test/blocks_along_line_test.cpp create mode 100644 source/test/btree_database_test.cpp create mode 100644 source/test/btree_test.cpp create mode 100644 source/test/byte_array_test.cpp create mode 100644 source/test/clock_test.cpp create mode 100644 source/test/color_test.cpp create mode 100644 source/test/container_test.cpp create mode 100644 source/test/core_tests_main.cpp create mode 100644 source/test/encode_test.cpp create mode 100644 source/test/file_test.cpp create mode 100644 source/test/flat_hash_test.cpp create mode 100644 source/test/formatted_json_test.cpp create mode 100644 source/test/function_test.cpp create mode 100644 source/test/game_tests_main.cpp create mode 100644 source/test/gtest/gtest-all.cc create mode 100644 source/test/gtest/gtest.h create mode 100644 source/test/hash_test.cpp create mode 100644 source/test/host_address_test.cpp create mode 100644 source/test/item_test.cpp create mode 100644 source/test/json_test.cpp create mode 100644 source/test/line_test.cpp create mode 100644 source/test/lua_json_test.cpp create mode 100644 source/test/lua_test.cpp create mode 100644 source/test/math_test.cpp create mode 100644 source/test/multi_table_test.cpp create mode 100644 source/test/net_states_test.cpp create mode 100644 source/test/ordered_map_test.cpp create mode 100644 source/test/ordered_set_test.cpp create mode 100644 source/test/periodic_test.cpp create mode 100644 source/test/poly_test.cpp create mode 100644 source/test/random_test.cpp create mode 100644 source/test/rect_test.cpp create mode 100644 source/test/ref_ptr_test.cpp create mode 100644 source/test/root_test.cpp create mode 100644 source/test/serialization_test.cpp create mode 100644 source/test/server_test.cpp create mode 100644 source/test/sha_test.cpp create mode 100644 source/test/shell_parse.cpp create mode 100644 source/test/small_vector_test.cpp create mode 100644 source/test/spawn_test.cpp create mode 100644 source/test/stat_test.cpp create mode 100644 source/test/static_vector_test.cpp create mode 100644 source/test/string_test.cpp create mode 100644 source/test/strong_typedef_test.cpp create mode 100644 source/test/thread_test.cpp create mode 100644 source/test/tile_array_test.cpp create mode 100644 source/test/universe_connection_test.cpp create mode 100644 source/test/variant_test.cpp create mode 100644 source/test/vlq_test.cpp create mode 100644 source/test/worker_pool_test.cpp create mode 100644 source/test/world_geometry_test.cpp create mode 100644 source/utility/CMakeLists.txt create mode 100644 source/utility/asset_packer.cpp create mode 100644 source/utility/asset_unpacker.cpp create mode 100644 source/utility/dump_versioned_json.cpp create mode 100644 source/utility/dungeon_generation_benchmark.cpp create mode 100644 source/utility/fix_embedded_tilesets.cpp create mode 100644 source/utility/game_repl.cpp create mode 100644 source/utility/generation_benchmark.cpp create mode 100644 source/utility/make_versioned_json.cpp create mode 100644 source/utility/map_grep.cpp create mode 100644 source/utility/planet_mapgen.cpp create mode 100644 source/utility/render_terrain_selector.cpp create mode 100644 source/utility/tileset_updater.cpp create mode 100644 source/utility/tileset_updater.hpp create mode 100644 source/utility/update_tilesets.cpp create mode 100644 source/utility/word_count.cpp create mode 100644 source/utility/world_benchmark.cpp create mode 100644 source/windowing/CMakeLists.txt create mode 100644 source/windowing/StarButtonGroup.cpp create mode 100644 source/windowing/StarButtonGroup.hpp create mode 100644 source/windowing/StarButtonWidget.cpp create mode 100644 source/windowing/StarButtonWidget.hpp create mode 100644 source/windowing/StarCanvasWidget.cpp create mode 100644 source/windowing/StarCanvasWidget.hpp create mode 100644 source/windowing/StarFlowLayout.cpp create mode 100644 source/windowing/StarFlowLayout.hpp create mode 100644 source/windowing/StarFuelWidget.cpp create mode 100644 source/windowing/StarFuelWidget.hpp create mode 100644 source/windowing/StarGuiContext.cpp create mode 100644 source/windowing/StarGuiContext.hpp create mode 100644 source/windowing/StarGuiReader.cpp create mode 100644 source/windowing/StarGuiReader.hpp create mode 100644 source/windowing/StarGuiTypes.cpp create mode 100644 source/windowing/StarGuiTypes.hpp create mode 100644 source/windowing/StarImageStretchWidget.cpp create mode 100644 source/windowing/StarImageStretchWidget.hpp create mode 100644 source/windowing/StarImageWidget.cpp create mode 100644 source/windowing/StarImageWidget.hpp create mode 100644 source/windowing/StarItemGridWidget.cpp create mode 100644 source/windowing/StarItemGridWidget.hpp create mode 100644 source/windowing/StarItemSlotWidget.cpp create mode 100644 source/windowing/StarItemSlotWidget.hpp create mode 100644 source/windowing/StarKeyBindings.cpp create mode 100644 source/windowing/StarKeyBindings.hpp create mode 100644 source/windowing/StarLabelWidget.cpp create mode 100644 source/windowing/StarLabelWidget.hpp create mode 100644 source/windowing/StarLargeCharPlateWidget.cpp create mode 100644 source/windowing/StarLargeCharPlateWidget.hpp create mode 100644 source/windowing/StarLayout.cpp create mode 100644 source/windowing/StarLayout.hpp create mode 100644 source/windowing/StarListWidget.cpp create mode 100644 source/windowing/StarListWidget.hpp create mode 100644 source/windowing/StarPane.cpp create mode 100644 source/windowing/StarPane.hpp create mode 100644 source/windowing/StarPaneManager.cpp create mode 100644 source/windowing/StarPaneManager.hpp create mode 100644 source/windowing/StarPortraitWidget.cpp create mode 100644 source/windowing/StarPortraitWidget.hpp create mode 100644 source/windowing/StarProgressWidget.cpp create mode 100644 source/windowing/StarProgressWidget.hpp create mode 100644 source/windowing/StarRegisteredPaneManager.hpp create mode 100644 source/windowing/StarScrollArea.cpp create mode 100644 source/windowing/StarScrollArea.hpp create mode 100644 source/windowing/StarSliderBar.cpp create mode 100644 source/windowing/StarSliderBar.hpp create mode 100644 source/windowing/StarStackWidget.cpp create mode 100644 source/windowing/StarStackWidget.hpp create mode 100644 source/windowing/StarTabSet.cpp create mode 100644 source/windowing/StarTabSet.hpp create mode 100644 source/windowing/StarTextBoxWidget.cpp create mode 100644 source/windowing/StarTextBoxWidget.hpp create mode 100644 source/windowing/StarVerticalLayout.cpp create mode 100644 source/windowing/StarVerticalLayout.hpp create mode 100644 source/windowing/StarWidget.cpp create mode 100644 source/windowing/StarWidget.hpp create mode 100644 source/windowing/StarWidgetParsing.cpp create mode 100644 source/windowing/StarWidgetParsing.hpp diff --git a/.gitignore b/.gitignore index 46f42f8..48a2018 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,15 @@ +build/ +dist/ +attic/ +lib/ +tiled/ +assets/user/ +assets/devel/ +mods/*.pak +mods/* +*.sln +*.suo +*.vcxproj* CMakeLists.txt.user CMakeCache.txt CMakeFiles diff --git a/README.md b/README.md index d7c0bbb..cd1f4e6 100644 --- a/README.md +++ b/README.md @@ -1 +1,3 @@ -# OpenStarbound \ No newline at end of file +# OpenStarbound + +Open-source Starbound fork \ No newline at end of file diff --git a/cmake/FindDirectX.cmake b/cmake/FindDirectX.cmake new file mode 100644 index 0000000..bd2e706 --- /dev/null +++ b/cmake/FindDirectX.cmake @@ -0,0 +1,115 @@ +#------------------------------------------------------------------- +# This file is part of the CMake build system for OGRE +# (Object-oriented Graphics Rendering Engine) +# For the latest info, see http://www.ogre3d.org/ +# +# The contents of this file are placed in the public domain. Feel +# free to make use of it in any way you like. +#------------------------------------------------------------------- + +# ----------------------------------------------------------------------------- +# Find DirectX SDK +# Define: +# DirectX_FOUND +# DirectX_INCLUDE_DIR +# DirectX_LIBRARY +# DirectX_ROOT_DIR + +if(WIN32) # The only platform it makes sense to check for DirectX SDK + include(FindPkgMacros) + findpkg_begin(DirectX) + + # Get path, convert backslashes as ${ENV_DXSDK_DIR} + getenv_path(DXSDK_DIR) + getenv_path(DIRECTX_HOME) + getenv_path(DIRECTX_ROOT) + getenv_path(DIRECTX_BASE) + + # construct search paths + set(DirectX_PREFIX_PATH + "${DXSDK_DIR}" "${ENV_DXSDK_DIR}" + "${DIRECTX_HOME}" "${ENV_DIRECTX_HOME}" + "${DIRECTX_ROOT}" "${ENV_DIRECTX_ROOT}" + "${DIRECTX_BASE}" "${ENV_DIRECTX_BASE}" + "C:/apps_x86/Microsoft DirectX SDK*" + "C:/Program Files (x86)/Microsoft DirectX SDK*" + "C:/apps/Microsoft DirectX SDK*" + "C:/Program Files/Microsoft DirectX SDK*" + "$ENV{ProgramFiles}/Microsoft DirectX SDK*" + ) + create_search_paths(DirectX) + # redo search if prefix path changed + clear_if_changed(DirectX_PREFIX_PATH + DirectX_LIBRARY + DirectX_INCLUDE_DIR + DirectX_ROOT_DIR + ) + + find_path(DirectX_INCLUDE_DIR NAMES d3d9.h HINTS ${DirectX_INC_SEARCH_PATH}) + # dlls are in DirectX_ROOT_DIR/Developer Runtime/x64|x86 + # lib files are in DirectX_ROOT_DIR/Lib/x64|x86 + if(${CMAKE_TARGET_ARCHITECTURE} STREQUAL "x86_64") + set(DirectX_LIBPATH_SUFFIX "x64") + else() + set(DirectX_LIBPATH_SUFFIX "x86") + endif() + find_library(DirectX_LIBRARY NAMES d3d9 HINTS ${DirectX_LIB_SEARCH_PATH} PATH_SUFFIXES ${DirectX_LIBPATH_SUFFIX}) + find_library(DirectX_D3DX9_LIBRARY NAMES d3dx9 HINTS ${DirectX_LIB_SEARCH_PATH} PATH_SUFFIXES ${DirectX_LIBPATH_SUFFIX}) + find_library(DirectX_DXERR9_LIBRARY NAMES dxerr HINTS ${DirectX_LIB_SEARCH_PATH} PATH_SUFFIXES ${DirectX_LIBPATH_SUFFIX}) + find_library(DirectX_DXGUID_LIBRARY NAMES dxguid HINTS ${DirectX_LIB_SEARCH_PATH} PATH_SUFFIXES ${DirectX_LIBPATH_SUFFIX}) + find_library(DirectX_DINPUT8_LIBRARY NAMES dinput8 HINTS ${DirectX_LIB_SEARCH_PATH} PATH_SUFFIXES ${DirectX_LIBPATH_SUFFIX}) + find_library(DirectX_XINPUT_LIBRARY NAMES xinput HINTS ${DirectX_LIB_SEARCH_PATH} PATH_SUFFIXES ${DirectX_LIBPATH_SUFFIX}) + find_library(DirectX_DXGI_LIBRARY NAMES dxgi HINTS ${DirectX_LIB_SEARCH_PATH} PATH_SUFFIXES ${DirectX_LIBPATH_SUFFIX}) + find_library(DirectX_D3DCOMPILER_LIBRARY NAMES d3dcompiler HINTS ${DirectX_LIB_SEARCH_PATH} PATH_SUFFIXES ${DirectX_LIBPATH_SUFFIX}) + + + if(DirectX_INCLUDE_DIR) + set(DirectX_ROOT_DIR "${DirectX_INCLUDE_DIR}/..") + endif(DirectX_INCLUDE_DIR) + + findpkg_finish(DirectX) + + set(DirectX_LIBRARIES + ${DirectX_LIBRARIES} + ${DirectX_D3DX9_LIBRARY} + ${DirectX_DXERR9_LIBRARY} + ${DirectX_DXGUID_LIBRARY} + ${DirectX_DINPUT8_LIBRARY} + ${DirectX_DXGI_LIBRARY} + ${DirectX_D3DCOMPILER_LIBRARY} + ) + + # look for D3D10.1 components + + if (DirectX_FOUND) + find_path(DirectX_D3D10_INCLUDE_DIR NAMES d3d10_1shader.h HINTS ${DirectX_INCLUDE_DIR} NO_DEFAULT_PATH) + get_filename_component(DirectX_LIBRARY_DIR "${DirectX_LIBRARY}" PATH) + message(STATUS "DX lib dir: ${DirectX_LIBRARY_DIR}") + + find_library(DirectX_D3D10_LIBRARY NAMES d3d10 HINTS ${DirectX_LIBRARY_DIR} NO_DEFAULT_PATH) + find_library(DirectX_D3DX10_LIBRARY NAMES d3dx10 HINTS ${DirectX_LIBRARY_DIR} NO_DEFAULT_PATH) + + + if (DirectX_D3D10_INCLUDE_DIR AND DirectX_D3D10_LIBRARY AND DirectX_D3DX10_LIBRARY) + set(DirectX_D3D10_FOUND TRUE) + set(DirectX_D3D10_INCLUDE_DIRS ${DirectX_D3D10_INCLUDE_DIR}) + set(DirectX_D3D10_LIBRARIES ${DirectX_D3D10_LIBRARY} ${DirectX_D3DX10_LIBRARY}) + endif () + endif () + + # look for D3D11 components + if (DirectX_FOUND) + find_path(DirectX_D3D11_INCLUDE_DIR NAMES D3D11Shader.h HINTS ${DirectX_INCLUDE_DIR} NO_DEFAULT_PATH) + get_filename_component(DirectX_LIBRARY_DIR "${DirectX_LIBRARY}" PATH) + message(STATUS "DX lib dir: ${DirectX_LIBRARY_DIR}") + find_library(DirectX_D3D11_LIBRARY NAMES d3d11 d3d11_beta HINTS ${DirectX_LIBRARY_DIR} NO_DEFAULT_PATH) + find_library(DirectX_D3DX11_LIBRARY NAMES d3dx11 HINTS ${DirectX_LIBRARY_DIR} NO_DEFAULT_PATH) + + if (DirectX_D3D11_INCLUDE_DIR AND DirectX_D3D11_LIBRARY AND DirectX_D3DX11_LIBRARY) + set(DirectX_D3D11_FOUND TRUE) + set(DirectX_D3D11_INCLUDE_DIRS ${DirectX_D3D11_INCLUDE_DIR}) + set(DirectX_D3D11_LIBRARIES ${DirectX_D3D11_LIBRARY} ${DirectX_D3DX11_LIBRARY}) + endif () + endif () + +endif(WIN32) diff --git a/cmake/FindDiscordApi.cmake b/cmake/FindDiscordApi.cmake new file mode 100644 index 0000000..0cd3dd8 --- /dev/null +++ b/cmake/FindDiscordApi.cmake @@ -0,0 +1,31 @@ +# Variables defined by this module: +# +# DISCORD_API_FOUND System has discord api libs/headers +# DISCORD_API_LIBRARY The discord api library +# DISCORD_API_INCLUDE_DIR The location of discord api headers + +find_path(DISCORD_API_ROOT_DIR + NAMES include/discord_game_sdk.h +) + +find_library(DISCORD_API_LIBRARY + NAMES discord_game_sdk + HINTS ${DISCORD_API_ROOT_DIR}/lib +) + +find_path(DISCORD_API_INCLUDE_DIR + NAMES discord_game_sdk.h + HINTS ${DISCORD_API_ROOT_DIR}/include +) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(DiscordApi DEFAULT_MSG + DISCORD_API_LIBRARY + DISCORD_API_INCLUDE_DIR +) + +mark_as_advanced( + DISCORD_API_ROOT_DIR + DISCORD_API_LIBRARY + DISCORD_API_INCLUDE_DIR +) diff --git a/cmake/FindGLEW.cmake b/cmake/FindGLEW.cmake new file mode 100644 index 0000000..07bd01b --- /dev/null +++ b/cmake/FindGLEW.cmake @@ -0,0 +1,67 @@ +# Copyright (c) 2007 NVIDIA Corporation +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. +# +# +# Try to find GLEW library and include pathi. Once done this will define: +# GLEW_FOUND +# GLEW_INCLUDE_DIR +# GLEW_LIBRARY + +IF (WIN32) + FIND_PATH( GLEW_INCLUDE_DIR GL/glew.h + $ENV{PROGRAMFILES}/GLEW/include + ${PROJECT_SOURCE_DIR}/src/nvgl/glew/include + DOC "The directory where GL/glew.h resides") + FIND_LIBRARY( GLEW_LIBRARY + NAMES glew GLEW glew32 glew32s + PATHS + $ENV{PROGRAMFILES}/GLEW/lib + ${PROJECT_SOURCE_DIR}/src/nvgl/glew/bin + ${PROJECT_SOURCE_DIR}/src/nvgl/glew/lib + DOC "The GLEW library") +ELSE (WIN32) + FIND_PATH( GLEW_INCLUDE_DIR GL/glew.h + /usr/include + /usr/local/include + /sw/include + /opt/local/include + DOC "The directory where GL/glew.h resides") + FIND_LIBRARY( GLEW_LIBRARY + NAMES GLEW glew + PATHS + /usr/lib64 + /usr/lib + /usr/local/lib64 + /usr/local/lib + /sw/lib + /opt/local/lib + DOC "The GLEW library") +ENDIF (WIN32) + +IF (GLEW_INCLUDE_DIR) + SET( GLEW_FOUND 1 CACHE STRING "Set to 1 if GLEW is found, 0 otherwise") +ELSE (GLEW_INCLUDE_DIR) + SET( GLEW_FOUND 0 CACHE STRING "Set to 1 if GLEW is found, 0 otherwise") +ENDIF (GLEW_INCLUDE_DIR) + +MARK_AS_ADVANCED( GLEW_FOUND ) diff --git a/cmake/FindJeMalloc.cmake b/cmake/FindJeMalloc.cmake new file mode 100644 index 0000000..54abd29 --- /dev/null +++ b/cmake/FindJeMalloc.cmake @@ -0,0 +1,44 @@ +# - Try to find jemalloc headers and libraries. +# +# Usage of this module as follows: +# +# find_package(JeMalloc) +# +# Variables used by this module, they can change the default behaviour and need +# to be set before calling find_package: +# +# JEMALLOC_ROOT_DIR Set this variable to the root installation of +# jemalloc if the module has problems finding +# the proper installation path. +# +# Variables defined by this module: +# +# JEMALLOC_FOUND System has jemalloc libs/headers +# JEMALLOC_LIBRARIES The jemalloc library/libraries +# JEMALLOC_INCLUDE_DIR The location of jemalloc headers + +find_path(JEMALLOC_ROOT_DIR + NAMES include/jemalloc/jemalloc.h +) + +find_library(JEMALLOC_LIBRARY + NAMES jemalloc + HINTS ${JEMALLOC_ROOT_DIR}/lib +) + +find_path(JEMALLOC_INCLUDE_DIR + NAMES jemalloc/jemalloc.h + HINTS ${JEMALLOC_ROOT_DIR}/include +) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(JeMalloc DEFAULT_MSG + JEMALLOC_LIBRARY + JEMALLOC_INCLUDE_DIR +) + +mark_as_advanced( + JEMALLOC_ROOT_DIR + JEMALLOC_LIBRARY + JEMALLOC_INCLUDE_DIR +) diff --git a/cmake/FindLua52.cmake b/cmake/FindLua52.cmake new file mode 100644 index 0000000..69286b6 --- /dev/null +++ b/cmake/FindLua52.cmake @@ -0,0 +1,80 @@ +# Locate Lua library +# This module defines +# LUA52_FOUND, if false, do not try to link to Lua +# LUA_LIBRARIES +# LUA_INCLUDE_DIR, where to find lua.h +# LUA_VERSION_STRING, the version of Lua found (since CMake 2.8.8) +# +# Note that the expected include convention is +# #include "lua.h" +# and not +# #include +# This is because, the lua location is not standardized and may exist +# in locations other than lua/ + +#============================================================================= +# Copyright 2007-2009 Kitware, Inc. +# +# Distributed under the OSI-approved BSD License (the "License"); +# see accompanying file Copyright.txt for details. +# +# This software is distributed WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the License for more information. +#============================================================================= +# (To distribute this file outside of CMake, substitute the full +# License text for the above reference.) + +find_path(LUA_INCLUDE_DIR lua.h + HINTS + ENV LUA_DIR + PATH_SUFFIXES include/lua52 include/lua5.2 include/lua-5.2 include/lua include + PATHS + ~/Library/Frameworks + /Library/Frameworks + /sw # Fink + /opt/local # DarwinPorts + /opt/csw # Blastwave + /opt +) + +find_library(LUA_LIBRARY + NAMES lua52 lua5.2 lua-5.2 lua + HINTS + ENV LUA_DIR + PATH_SUFFIXES lib + PATHS + ~/Library/Frameworks + /Library/Frameworks + /sw + /opt/local + /opt/csw + /opt +) + +if(LUA_LIBRARY) + # include the math library for Unix + if(UNIX AND NOT APPLE AND NOT BEOS) + find_library(LUA_MATH_LIBRARY m) + set( LUA_LIBRARIES "${LUA_LIBRARY};${LUA_MATH_LIBRARY}" CACHE STRING "Lua Libraries") + # For Windows and Mac, don't need to explicitly include the math library + else() + set( LUA_LIBRARIES "${LUA_LIBRARY}" CACHE STRING "Lua Libraries") + endif() +endif() + +if(LUA_INCLUDE_DIR AND EXISTS "${LUA_INCLUDE_DIR}/lua.h") + file(STRINGS "${LUA_INCLUDE_DIR}/lua.h" lua_version_str REGEX "^#define[ \t]+LUA_RELEASE[ \t]+\"Lua .+\"") + + string(REGEX REPLACE "^#define[ \t]+LUA_RELEASE[ \t]+\"Lua ([^\"]+)\".*" "\\1" LUA_VERSION_STRING "${lua_version_str}") + unset(lua_version_str) +endif() + +# handle the QUIETLY and REQUIRED arguments and set LUA_FOUND to TRUE if +# all listed variables are TRUE +FIND_PACKAGE_HANDLE_STANDARD_ARGS(Lua52 + REQUIRED_VARS LUA_LIBRARIES LUA_INCLUDE_DIR + VERSION_VAR LUA_VERSION_STRING) + +mark_as_advanced(LUA_INCLUDE_DIR LUA_LIBRARIES LUA_LIBRARY LUA_MATH_LIBRARY) + diff --git a/cmake/FindOggVorbis.cmake b/cmake/FindOggVorbis.cmake new file mode 100644 index 0000000..4f984db --- /dev/null +++ b/cmake/FindOggVorbis.cmake @@ -0,0 +1,57 @@ +# - Try to find the OggVorbis libraries +# Once done this will define +# +# OGGVORBIS_FOUND - system has OggVorbis +# OGGVORBIS_VERSION - set either to 1 or 2 +# OGGVORBIS_INCLUDE_DIR - the OggVorbis include directory +# OGGVORBIS_LIBRARIES - The libraries needed to use OggVorbis +# OGG_LIBRARY - The Ogg library +# VORBIS_LIBRARY - The Vorbis library +# VORBISFILE_LIBRARY - The VorbisFile library + +# Copyright (c) 2006, Richard Laerkaeng, +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + + +include (CheckLibraryExists) + +find_path(VORBIS_INCLUDE_DIR vorbis/vorbisfile.h) +find_path(OGG_INCLUDE_DIR ogg/ogg.h) + +find_library(OGG_LIBRARY NAMES ogg) +find_library(VORBIS_LIBRARY NAMES vorbis) +find_library(VORBISFILE_LIBRARY NAMES vorbisfile) + +mark_as_advanced(VORBIS_INCLUDE_DIR OGG_INCLUDE_DIR + OGG_LIBRARY VORBIS_LIBRARY VORBISFILE_LIBRARY) + + +if (VORBIS_INCLUDE_DIR AND VORBIS_LIBRARY AND VORBISFILE_LIBRARY) + set(OGGVORBIS_FOUND TRUE) + + set(OGGVORBIS_LIBRARIES ${OGG_LIBRARY} ${VORBIS_LIBRARY} ${VORBISFILE_LIBRARY}) + + set(_CMAKE_REQUIRED_LIBRARIES_TMP ${CMAKE_REQUIRED_LIBRARIES}) + set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} ${OGGVORBIS_LIBRARIES}) + set(CMAKE_REQUIRED_LIBRARIES ${_CMAKE_REQUIRED_LIBRARIES_TMP}) + +else (VORBIS_INCLUDE_DIR AND VORBIS_LIBRARY AND VORBISFILE_LIBRARY) + set (OGGVORBIS_VERSION) + set(OGGVORBIS_FOUND FALSE) +endif (VORBIS_INCLUDE_DIR AND VORBIS_LIBRARY AND VORBISFILE_LIBRARY) + + +if (OGGVORBIS_FOUND) + if (NOT OggVorbis_FIND_QUIETLY) + message(STATUS "Found OggVorbis: ${OGGVORBIS_LIBRARIES}") + endif (NOT OggVorbis_FIND_QUIETLY) +else (OGGVORBIS_FOUND) + if (OggVorbis_FIND_REQUIRED) + message(FATAL_ERROR "Could NOT find OggVorbis libraries") + endif (OggVorbis_FIND_REQUIRED) + if (NOT OggVorbis_FIND_QUITELY) + message(STATUS "Could NOT find OggVorbis libraries") + endif (NOT OggVorbis_FIND_QUITELY) +endif (OGGVORBIS_FOUND) diff --git a/cmake/FindPkgMacros.cmake b/cmake/FindPkgMacros.cmake new file mode 100644 index 0000000..facd594 --- /dev/null +++ b/cmake/FindPkgMacros.cmake @@ -0,0 +1,142 @@ +#------------------------------------------------------------------- +# This file is part of the CMake build system for OGRE +# (Object-oriented Graphics Rendering Engine) +# For the latest info, see http://www.ogre3d.org/ +# +# The contents of this file are placed in the public domain. Feel +# free to make use of it in any way you like. +#------------------------------------------------------------------- + +################################################################## +# Provides some common functionality for the FindPackage modules +################################################################## + +# Begin processing of package +macro(findpkg_begin PREFIX) + if (NOT ${PREFIX}_FIND_QUIETLY) + message(STATUS "Looking for ${PREFIX}...") + endif () +endmacro(findpkg_begin) + +# Display a status message unless FIND_QUIETLY is set +macro(pkg_message PREFIX) + if (NOT ${PREFIX}_FIND_QUIETLY) + message(STATUS ${ARGN}) + endif () +endmacro(pkg_message) + +# Get environment variable, define it as ENV_$var and make sure backslashes are converted to forward slashes +macro(getenv_path VAR) + set(ENV_${VAR} $ENV{${VAR}}) + # replace won't work if var is blank + if (ENV_${VAR}) + string( REGEX REPLACE "\\\\" "/" ENV_${VAR} ${ENV_${VAR}} ) + endif () +endmacro(getenv_path) + +# Construct search paths for includes and libraries from a PREFIX_PATH +macro(create_search_paths PREFIX) + foreach(dir ${${PREFIX}_PREFIX_PATH}) + set(${PREFIX}_INC_SEARCH_PATH ${${PREFIX}_INC_SEARCH_PATH} + ${dir}/include ${dir}/include/${PREFIX} ${dir}/Headers) + set(${PREFIX}_LIB_SEARCH_PATH ${${PREFIX}_LIB_SEARCH_PATH} + ${dir}/lib ${dir}/lib/${PREFIX} ${dir}/Libs) + endforeach(dir) + set(${PREFIX}_FRAMEWORK_SEARCH_PATH ${${PREFIX}_PREFIX_PATH}) +endmacro(create_search_paths) + +# clear cache variables if a certain variable changed +macro(clear_if_changed TESTVAR) + # test against internal check variable + if (NOT "${${TESTVAR}}" STREQUAL "${${TESTVAR}_INT_CHECK}") + message(STATUS "${TESTVAR} changed.") + foreach(var ${ARGN}) + set(${var} "NOTFOUND" CACHE STRING "x" FORCE) + endforeach(var) + endif () + set(${TESTVAR}_INT_CHECK ${${TESTVAR}} CACHE INTERNAL "x" FORCE) +endmacro(clear_if_changed) + +# Try to get some hints from pkg-config, if available +macro(use_pkgconfig PREFIX PKGNAME) + find_package(PkgConfig) + if (PKG_CONFIG_FOUND) + pkg_check_modules(${PREFIX} ${PKGNAME}) + endif () +endmacro (use_pkgconfig) + +# Couple a set of release AND debug libraries (or frameworks) +macro(make_library_set PREFIX) + if (${PREFIX}_FWK) + set(${PREFIX} ${${PREFIX}_FWK}) + elseif (${PREFIX}_REL AND ${PREFIX}_DBG) + set(${PREFIX} optimized ${${PREFIX}_REL} debug ${${PREFIX}_DBG}) + elseif (${PREFIX}_REL) + set(${PREFIX} ${${PREFIX}_REL}) + elseif (${PREFIX}_DBG) + set(${PREFIX} ${${PREFIX}_DBG}) + endif () +endmacro(make_library_set) + +# Generate debug names from given release names +macro(get_debug_names PREFIX) + foreach(i ${${PREFIX}}) + set(${PREFIX}_DBG ${${PREFIX}_DBG} ${i}d ${i}D ${i}_d ${i}_D ${i}_debug ${i}) + endforeach(i) +endmacro(get_debug_names) + +# Add the parent dir from DIR to VAR +macro(add_parent_dir VAR DIR) + get_filename_component(${DIR}_TEMP "${${DIR}}/.." ABSOLUTE) + set(${VAR} ${${VAR}} ${${DIR}_TEMP}) +endmacro(add_parent_dir) + +# Do the final processing for the package find. +macro(findpkg_finish PREFIX) + # skip if already processed during this run + if (NOT ${PREFIX}_FOUND) + if (${PREFIX}_INCLUDE_DIR AND ${PREFIX}_LIBRARY) + set(${PREFIX}_FOUND TRUE) + set(${PREFIX}_INCLUDE_DIRS ${${PREFIX}_INCLUDE_DIR}) + set(${PREFIX}_LIBRARIES ${${PREFIX}_LIBRARY}) + if (NOT ${PREFIX}_FIND_QUIETLY) + message(STATUS "Found ${PREFIX}: ${${PREFIX}_LIBRARIES}") + endif () + else () + if (NOT ${PREFIX}_FIND_QUIETLY) + message(STATUS "Could not locate ${PREFIX}") + endif () + if (${PREFIX}_FIND_REQUIRED) + message(FATAL_ERROR "Required library ${PREFIX} not found! Install the library (including dev packages) and try again. If the library is already installed, set the missing variables manually in cmake.") + endif () + endif () + + mark_as_advanced(${PREFIX}_INCLUDE_DIR ${PREFIX}_LIBRARY ${PREFIX}_LIBRARY_REL ${PREFIX}_LIBRARY_DBG ${PREFIX}_LIBRARY_FWK) + endif () +endmacro(findpkg_finish) + + +# Slightly customised framework finder +MACRO(findpkg_framework fwk) + IF(APPLE) + SET(${fwk}_FRAMEWORK_PATH + ${${fwk}_FRAMEWORK_SEARCH_PATH} + ${CMAKE_FRAMEWORK_PATH} + ~/Library/Frameworks + /Library/Frameworks + /System/Library/Frameworks + /Network/Library/Frameworks + /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS3.0.sdk/System/Library/Frameworks/ + ) + FOREACH(dir ${${fwk}_FRAMEWORK_PATH}) + SET(fwkpath ${dir}/${fwk}.framework) + IF(EXISTS ${fwkpath}) + SET(${fwk}_FRAMEWORK_INCLUDES ${${fwk}_FRAMEWORK_INCLUDES} + ${fwkpath}/Headers ${fwkpath}/PrivateHeaders) + if (NOT ${fwk}_LIBRARY_FWK) + SET(${fwk}_LIBRARY_FWK "-framework ${fwk}") + endif () + ENDIF(EXISTS ${fwkpath}) + ENDFOREACH(dir) + ENDIF(APPLE) +ENDMACRO(findpkg_framework) diff --git a/cmake/FindSDL2.cmake b/cmake/FindSDL2.cmake new file mode 100644 index 0000000..c5e4dc9 --- /dev/null +++ b/cmake/FindSDL2.cmake @@ -0,0 +1,165 @@ + +# This module defines +# SDL2_LIBRARY, the name of the library to link against +# SDL2_FOUND, if false, do not try to link to SDL2 +# SDL2_INCLUDE_DIR, where to find SDL.h +# +# This module responds to the the flag: +# SDL2_BUILDING_LIBRARY +# If this is defined, then no SDL2main will be linked in because +# only applications need main(). +# Otherwise, it is assumed you are building an application and this +# module will attempt to locate and set the the proper link flags +# as part of the returned SDL2_LIBRARY variable. +# +# Don't forget to include SDLmain.h and SDLmain.m your project for the +# OS X framework based version. (Other versions link to -lSDL2main which +# this module will try to find on your behalf.) Also for OS X, this +# module will automatically add the -framework Cocoa on your behalf. +# +# +# Additional Note: If you see an empty SDL2_LIBRARY_TEMP in your configuration +# and no SDL2_LIBRARY, it means CMake did not find your SDL2 library +# (SDL2.dll, libsdl2.so, SDL2.framework, etc). +# Set SDL2_LIBRARY_TEMP to point to your SDL2 library, and configure again. +# Similarly, if you see an empty SDL2MAIN_LIBRARY, you should set this value +# as appropriate. These values are used to generate the final SDL2_LIBRARY +# variable, but when these values are unset, SDL2_LIBRARY does not get created. +# +# +# $SDL2DIR is an environment variable that would +# correspond to the ./configure --prefix=$SDL2DIR +# used in building SDL2. +# l.e.galup 9-20-02 +# +# Modified by Eric Wing. +# Added code to assist with automated building by using environmental variables +# and providing a more controlled/consistent search behavior. +# Added new modifications to recognize OS X frameworks and +# additional Unix paths (FreeBSD, etc). +# Also corrected the header search path to follow "proper" SDL guidelines. +# Added a search for SDL2main which is needed by some platforms. +# Added a search for threads which is needed by some platforms. +# Added needed compile switches for MinGW. +# +# On OSX, this will prefer the Framework version (if found) over others. +# People will have to manually change the cache values of +# SDL2_LIBRARY to override this selection or set the CMake environment +# CMAKE_INCLUDE_PATH to modify the search paths. +# +# Note that the header path has changed from SDL2/SDL.h to just SDL.h +# This needed to change because "proper" SDL convention +# is #include "SDL.h", not . This is done for portability +# reasons because not all systems place things in SDL2/ (see FreeBSD). + +#============================================================================= +# Copyright 2003-2009 Kitware, Inc. +# +# Distributed under the OSI-approved BSD License (the "License"); +# see accompanying file Copyright.txt for details. +# +# This software is distributed WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the License for more information. +#============================================================================= +# (To distribute this file outside of CMake, substitute the full +# License text for the above reference.) + +SET(SDL2_SEARCH_PATHS + ~/Library/Frameworks + /Library/Frameworks + /usr/local + /usr + /sw # Fink + /opt/local # DarwinPorts + /opt/csw # Blastwave + /opt + ${SDL2_PATH} +) + +FIND_PATH(SDL2_INCLUDE_DIR SDL.h + HINTS + $ENV{SDL2DIR} + PATH_SUFFIXES include/SDL2 include + PATHS ${SDL2_SEARCH_PATHS} +) + +FIND_LIBRARY(SDL2_LIBRARY_TEMP + NAMES SDL2 + HINTS + $ENV{SDL2DIR} + PATH_SUFFIXES lib64 lib + PATHS ${SDL2_SEARCH_PATHS} +) + +IF(NOT SDL2_BUILDING_LIBRARY) + IF(NOT ${SDL2_INCLUDE_DIR} MATCHES ".framework") + # Non-OS X framework versions expect you to also dynamically link to + # SDL2main. This is mainly for Windows and OS X. Other (Unix) platforms + # seem to provide SDL2main for compatibility even though they don't + # necessarily need it. + FIND_LIBRARY(SDL2MAIN_LIBRARY + NAMES SDL2main + HINTS + $ENV{SDL2DIR} + PATH_SUFFIXES lib64 lib + PATHS ${SDL2_SEARCH_PATHS} + ) + ENDIF(NOT ${SDL2_INCLUDE_DIR} MATCHES ".framework") +ENDIF(NOT SDL2_BUILDING_LIBRARY) + +# SDL2 may require threads on your system. +# The Apple build may not need an explicit flag because one of the +# frameworks may already provide it. +# But for non-OSX systems, I will use the CMake Threads package. +IF(NOT APPLE) + FIND_PACKAGE(Threads) +ENDIF(NOT APPLE) + +# MinGW needs an additional library, mwindows +# It's total link flags should look like -lmingw32 -lSDL2main -lSDL2 -lmwindows +# (Actually on second look, I think it only needs one of the m* libraries.) +IF(MINGW) + SET(MINGW32_LIBRARY mingw32 CACHE STRING "mwindows for MinGW") +ENDIF(MINGW) + +IF(SDL2_LIBRARY_TEMP) + # For SDL2main + IF(NOT SDL2_BUILDING_LIBRARY) + IF(SDL2MAIN_LIBRARY) + SET(SDL2_LIBRARY_TEMP ${SDL2MAIN_LIBRARY} ${SDL2_LIBRARY_TEMP}) + ENDIF(SDL2MAIN_LIBRARY) + ENDIF(NOT SDL2_BUILDING_LIBRARY) + + # For OS X, SDL2 uses Carbon, Cocoa, CoreAudio, AudioUnit, ForceFeedback, + # IOKit, and iconv as a backend so it must link to those. CMake doesn't + # display the -framework Cocoa string in the UI even though it actually is + # there if I modify a pre-used variable. I think it has something to do with + # the CACHE STRING. So I use a temporary variable until the end so I can set + # the "real" variable in one-shot. + IF(APPLE) + SET(SDL2_LIBRARY_TEMP ${SDL2_LIBRARY_TEMP} "-framework Carbon -framework Cocoa -framework CoreAudio -framework AudioUnit -framework AudioToolbox -framework ForceFeedback -framework IOKit -framework CoreVideo -liconv") + ENDIF(APPLE) + + # For threads, as mentioned Apple doesn't need this. + # In fact, there seems to be a problem if I used the Threads package + # and try using this line, so I'm just skipping it entirely for OS X. + IF(NOT APPLE) + SET(SDL2_LIBRARY_TEMP ${SDL2_LIBRARY_TEMP} ${CMAKE_THREAD_LIBS_INIT}) + ENDIF(NOT APPLE) + + # For MinGW library + IF(MINGW) + SET(SDL2_LIBRARY_TEMP ${MINGW32_LIBRARY} ${SDL2_LIBRARY_TEMP}) + ENDIF(MINGW) + + # Set the final string here so the GUI reflects the final state. + SET(SDL2_LIBRARY ${SDL2_LIBRARY_TEMP} CACHE STRING "Where the SDL2 Library can be found") + # Set the temp variable to INTERNAL so it is not seen in the CMake GUI + SET(SDL2_LIBRARY_TEMP "${SDL2_LIBRARY_TEMP}" CACHE INTERNAL "") +ENDIF(SDL2_LIBRARY_TEMP) + +INCLUDE(FindPackageHandleStandardArgs) + +FIND_PACKAGE_HANDLE_STANDARD_ARGS(SDL2 REQUIRED_VARS SDL2_LIBRARY SDL2_INCLUDE_DIR) + diff --git a/cmake/FindSteamApi.cmake b/cmake/FindSteamApi.cmake new file mode 100644 index 0000000..2830c6a --- /dev/null +++ b/cmake/FindSteamApi.cmake @@ -0,0 +1,31 @@ +# Variables defined by this module: +# +# STEAM_API_FOUND System has steam api libs/headers +# STEAM_API_LIBRARY The steam api library +# STEAM_API_INCLUDE_DIR The location of steam api headers + +find_path(STEAM_API_ROOT_DIR + NAMES include/steam/steam_api.h +) + +find_library(STEAM_API_LIBRARY + NAMES steam_api + HINTS ${STEAM_API_ROOT_DIR}/lib +) + +find_path(STEAM_API_INCLUDE_DIR + NAMES steam/steam_api.h + HINTS ${STEAM_API_ROOT_DIR}/include +) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(SteamApi DEFAULT_MSG + STEAM_API_LIBRARY + STEAM_API_INCLUDE_DIR +) + +mark_as_advanced( + STEAM_API_ROOT_DIR + STEAM_API_LIBRARY + STEAM_API_INCLUDE_DIR +) diff --git a/cmake/GetGitRevisionDescription.cmake b/cmake/GetGitRevisionDescription.cmake new file mode 100644 index 0000000..c8d27f2 --- /dev/null +++ b/cmake/GetGitRevisionDescription.cmake @@ -0,0 +1,130 @@ +# - Returns a version string from Git +# +# These functions force a re-configure on each git commit so that you can +# trust the values of the variables in your build system. +# +# get_git_head_revision( [ ...]) +# +# Returns the refspec and sha hash of the current head revision +# +# git_describe( [ ...]) +# +# Returns the results of git describe on the source tree, and adjusting +# the output so that it tests false if an error occurs. +# +# git_get_exact_tag( [ ...]) +# +# Returns the results of git describe --exact-match on the source tree, +# and adjusting the output so that it tests false if there was no exact +# matching tag. +# +# Requires CMake 2.6 or newer (uses the 'function' command) +# +# Original Author: +# 2009-2010 Ryan Pavlik +# http://academic.cleardefinition.com +# Iowa State University HCI Graduate Program/VRAC +# +# Copyright Iowa State University 2009-2010. +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) + +if(__get_git_revision_description) + return() +endif() +set(__get_git_revision_description YES) + +# We must run the following at "include" time, not at function call time, +# to find the path to this module rather than the path to a calling list file +get_filename_component(_gitdescmoddir ${CMAKE_CURRENT_LIST_FILE} PATH) + +function(get_git_head_revision _refspecvar _hashvar) + set(GIT_PARENT_DIR "${CMAKE_CURRENT_SOURCE_DIR}") + set(GIT_DIR "${GIT_PARENT_DIR}/.git") + while(NOT EXISTS "${GIT_DIR}") # .git dir not found, search parent directories + set(GIT_PREVIOUS_PARENT "${GIT_PARENT_DIR}") + get_filename_component(GIT_PARENT_DIR ${GIT_PARENT_DIR} PATH) + if(GIT_PARENT_DIR STREQUAL GIT_PREVIOUS_PARENT) + # We have reached the root directory, we are not in git + set(${_refspecvar} "GITDIR-NOTFOUND" PARENT_SCOPE) + set(${_hashvar} "GITDIR-NOTFOUND" PARENT_SCOPE) + return() + endif() + set(GIT_DIR "${GIT_PARENT_DIR}/.git") + endwhile() + # check if this is a submodule + if(NOT IS_DIRECTORY ${GIT_DIR}) + file(READ ${GIT_DIR} submodule) + string(REGEX REPLACE "gitdir: (.*)\n$" "\\1" GIT_DIR_RELATIVE ${submodule}) + get_filename_component(SUBMODULE_DIR ${GIT_DIR} PATH) + get_filename_component(GIT_DIR ${SUBMODULE_DIR}/${GIT_DIR_RELATIVE} ABSOLUTE) + endif() + set(GIT_DATA "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/git-data") + if(NOT EXISTS "${GIT_DATA}") + file(MAKE_DIRECTORY "${GIT_DATA}") + endif() + + if(NOT EXISTS "${GIT_DIR}/HEAD") + return() + endif() + set(HEAD_FILE "${GIT_DATA}/HEAD") + configure_file("${GIT_DIR}/HEAD" "${HEAD_FILE}" COPYONLY) + + configure_file("${_gitdescmoddir}/GetGitRevisionDescription.cmake.in" + "${GIT_DATA}/grabRef.cmake" + @ONLY) + include("${GIT_DATA}/grabRef.cmake") + + set(${_refspecvar} "${HEAD_REF}" PARENT_SCOPE) + set(${_hashvar} "${HEAD_HASH}" PARENT_SCOPE) +endfunction() + +function(git_describe _var) + if(NOT GIT_FOUND) + find_package(Git QUIET) + endif() + get_git_head_revision(refspec hash) + if(NOT GIT_FOUND) + set(${_var} "GIT-NOTFOUND" PARENT_SCOPE) + return() + endif() + if(NOT hash) + set(${_var} "HEAD-HASH-NOTFOUND" PARENT_SCOPE) + return() + endif() + + # TODO sanitize + #if((${ARGN}" MATCHES "&&") OR + # (ARGN MATCHES "||") OR + # (ARGN MATCHES "\\;")) + # message("Please report the following error to the project!") + # message(FATAL_ERROR "Looks like someone's doing something nefarious with git_describe! Passed arguments ${ARGN}") + #endif() + + #message(STATUS "Arguments to execute_process: ${ARGN}") + + execute_process(COMMAND + "${GIT_EXECUTABLE}" + describe + ${hash} + ${ARGN} + WORKING_DIRECTORY + "${CMAKE_SOURCE_DIR}" + RESULT_VARIABLE + res + OUTPUT_VARIABLE + out + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) + if(NOT res EQUAL 0) + set(out "${out}-${res}-NOTFOUND") + endif() + + set(${_var} "${out}" PARENT_SCOPE) +endfunction() + +function(git_get_exact_tag _var) + git_describe(out --exact-match ${ARGN}) + set(${_var} "${out}" PARENT_SCOPE) +endfunction() diff --git a/cmake/GetGitRevisionDescription.cmake.in b/cmake/GetGitRevisionDescription.cmake.in new file mode 100644 index 0000000..888ce13 --- /dev/null +++ b/cmake/GetGitRevisionDescription.cmake.in @@ -0,0 +1,38 @@ +# +# Internal file for GetGitRevisionDescription.cmake +# +# Requires CMake 2.6 or newer (uses the 'function' command) +# +# Original Author: +# 2009-2010 Ryan Pavlik +# http://academic.cleardefinition.com +# Iowa State University HCI Graduate Program/VRAC +# +# Copyright Iowa State University 2009-2010. +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) + +set(HEAD_HASH) + +file(READ "@HEAD_FILE@" HEAD_CONTENTS LIMIT 1024) + +string(STRIP "${HEAD_CONTENTS}" HEAD_CONTENTS) +if(HEAD_CONTENTS MATCHES "ref") + # named branch + string(REPLACE "ref: " "" HEAD_REF "${HEAD_CONTENTS}") + if(EXISTS "@GIT_DIR@/${HEAD_REF}") + configure_file("@GIT_DIR@/${HEAD_REF}" "@GIT_DATA@/head-ref" COPYONLY) + elseif(EXISTS "@GIT_DIR@/logs/${HEAD_REF}") + configure_file("@GIT_DIR@/logs/${HEAD_REF}" "@GIT_DATA@/head-ref" COPYONLY) + set(HEAD_HASH "${HEAD_REF}") + endif() +else() + # detached HEAD + configure_file("@GIT_DIR@/HEAD" "@GIT_DATA@/head-ref" COPYONLY) +endif() + +if(NOT HEAD_HASH) + file(READ "@GIT_DATA@/head-ref" HEAD_HASH LIMIT 1024) + string(STRIP "${HEAD_HASH}" HEAD_HASH) +endif() diff --git a/cmake/TargetArch.cmake b/cmake/TargetArch.cmake new file mode 100644 index 0000000..3761e4d --- /dev/null +++ b/cmake/TargetArch.cmake @@ -0,0 +1,134 @@ +# Based on the Qt 5 processor detection code, so should be very accurate +# https://qt.gitorious.org/qt/qtbase/blobs/master/src/corelib/global/qprocessordetection.h +# Currently handles arm (v5, v6, v7), x86 (32/64), ia64, and ppc (32/64) + +# Regarding POWER/PowerPC, just as is noted in the Qt source, +# "There are many more known variants/revisions that we do not handle/detect." + +set(archdetect_c_code " +#if defined(__arm__) || defined(__TARGET_ARCH_ARM) + #if defined(__ARM_ARCH_7__) \\ + || defined(__ARM_ARCH_7A__) \\ + || defined(__ARM_ARCH_7R__) \\ + || defined(__ARM_ARCH_7M__) \\ + || (defined(__TARGET_ARCH_ARM) && __TARGET_ARCH_ARM-0 >= 7) + #error cmake_ARCH armv7 + #elif defined(__ARM_ARCH_6__) \\ + || defined(__ARM_ARCH_6J__) \\ + || defined(__ARM_ARCH_6T2__) \\ + || defined(__ARM_ARCH_6Z__) \\ + || defined(__ARM_ARCH_6K__) \\ + || defined(__ARM_ARCH_6ZK__) \\ + || defined(__ARM_ARCH_6M__) \\ + || (defined(__TARGET_ARCH_ARM) && __TARGET_ARCH_ARM-0 >= 6) + #error cmake_ARCH armv6 + #elif defined(__ARM_ARCH_5TEJ__) \\ + || (defined(__TARGET_ARCH_ARM) && __TARGET_ARCH_ARM-0 >= 5) + #error cmake_ARCH armv5 + #else + #error cmake_ARCH arm + #endif +#elif defined(__i386) || defined(__i386__) || defined(_M_IX86) + #error cmake_ARCH i386 +#elif defined(__x86_64) || defined(__x86_64__) || defined(__amd64) || defined(_M_X64) + #error cmake_ARCH x86_64 +#elif defined(__ia64) || defined(__ia64__) || defined(_M_IA64) + #error cmake_ARCH ia64 +#elif defined(__ppc__) || defined(__ppc) || defined(__powerpc__) \\ + || defined(_ARCH_COM) || defined(_ARCH_PWR) || defined(_ARCH_PPC) \\ + || defined(_M_MPPC) || defined(_M_PPC) + #if defined(__ppc64__) || defined(__powerpc64__) || defined(__64BIT__) + #error cmake_ARCH ppc64 + #else + #error cmake_ARCH ppc + #endif +#endif + +#error cmake_ARCH unknown +") + +# Set ppc_support to TRUE before including this file or ppc and ppc64 +# will be treated as invalid architectures since they are no longer supported by Apple + +function(target_architecture output_var) + if(APPLE AND CMAKE_OSX_ARCHITECTURES) + # On OS X we use CMAKE_OSX_ARCHITECTURES *if* it was set + # First let's normalize the order of the values + + # Note that it's not possible to compile PowerPC applications if you are using + # the OS X SDK version 10.6 or later - you'll need 10.4/10.5 for that, so we + # disable it by default + # See this page for more information: + # http://stackoverflow.com/questions/5333490/how-can-we-restore-ppc-ppc64-as-well-as-full-10-4-10-5-sdk-support-to-xcode-4 + + # Architecture defaults to i386 or ppc on OS X 10.5 and earlier, depending on the CPU type detected at runtime. + # On OS X 10.6+ the default is x86_64 if the CPU supports it, i386 otherwise. + + foreach(osx_arch ${CMAKE_OSX_ARCHITECTURES}) + if("${osx_arch}" STREQUAL "ppc" AND ppc_support) + set(osx_arch_ppc TRUE) + elseif("${osx_arch}" STREQUAL "i386") + set(osx_arch_i386 TRUE) + elseif("${osx_arch}" STREQUAL "x86_64") + set(osx_arch_x86_64 TRUE) + elseif("${osx_arch}" STREQUAL "ppc64" AND ppc_support) + set(osx_arch_ppc64 TRUE) + else() + message(FATAL_ERROR "Invalid OS X arch name: ${osx_arch}") + endif() + endforeach() + + # Now add all the architectures in our normalized order + if(osx_arch_ppc) + list(APPEND ARCH ppc) + endif() + + if(osx_arch_i386) + list(APPEND ARCH i386) + endif() + + if(osx_arch_x86_64) + list(APPEND ARCH x86_64) + endif() + + if(osx_arch_ppc64) + list(APPEND ARCH ppc64) + endif() + else() + file(WRITE "${CMAKE_BINARY_DIR}/arch.c" "${archdetect_c_code}") + + enable_language(C) + + # Detect the architecture in a rather creative way... + # This compiles a small C program which is a series of ifdefs that selects a + # particular #error preprocessor directive whose message string contains the + # target architecture. The program will always fail to compile (both because + # file is not a valid C program, and obviously because of the presence of the + # #error preprocessor directives... but by exploiting the preprocessor in this + # way, we can detect the correct target architecture even when cross-compiling, + # since the program itself never needs to be run (only the compiler/preprocessor) + try_run( + run_result_unused + compile_result_unused + "${CMAKE_BINARY_DIR}" + "${CMAKE_BINARY_DIR}/arch.c" + COMPILE_OUTPUT_VARIABLE ARCH + CMAKE_FLAGS CMAKE_OSX_ARCHITECTURES=${CMAKE_OSX_ARCHITECTURES} + ) + + # Parse the architecture name from the compiler output + string(REGEX MATCH "cmake_ARCH ([a-zA-Z0-9_]+)" ARCH "${ARCH}") + + # Get rid of the value marker leaving just the architecture name + string(REPLACE "cmake_ARCH " "" ARCH "${ARCH}") + + # If we are compiling with an unknown architecture this variable should + # already be set to "unknown" but in the case that it's empty (i.e. due + # to a typo in the code), then set it to unknown + if (NOT ARCH) + set(ARCH unknown) + endif() + endif() + + set(${output_var} "${ARCH}" PARENT_SCOPE) +endfunction() diff --git a/doc/OPENSOURCE.md b/doc/OPENSOURCE.md new file mode 100644 index 0000000..83c3661 --- /dev/null +++ b/doc/OPENSOURCE.md @@ -0,0 +1,768 @@ +Open Source Software used in Starbound and Accompanying Utilities +================================================================= + +[The Lua Scripting Language](www.lua.org) +----------------------------------------- +> The MIT License (MIT) +> Copyright (C) 1994-2015 Lua.org, PUC-Rio. +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of +> this software and associated documentation files (the "Software"), to deal in +> the Software without restriction, including without limitation the rights to +> use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +> of the Software, and to permit persons to whom the Software is furnished to do +> so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +[SDL - Simple DirectMedia Layer](libsdl.org) +-------------------------------------------- +> Simple DirectMedia Layer +> Copyright (C) 1997-2016 Sam Lantinga +> +> This software is provided 'as-is', without any express or implied +> warranty. In no event will the authors be held liable for any damages +> arising from the use of this software. +> +> Permission is granted to anyone to use this software for any purpose, +> including commercial applications, and to alter it and redistribute it +> freely, subject to the following restrictions: +> +> 1. The origin of this software must not be misrepresented; you must not +> claim that you wrote the original software. If you use this software +> in a product, an acknowledgment in the product documentation would be +> appreciated but is not required. +> 2. Altered source versions must be plainly marked as such, and must not be +> misrepresented as being the original software. +> 3. This notice may not be removed or altered from any source distribution. + +[libpng PNG reference library](libpng.org) +------------------------------------------ +> COPYRIGHT NOTICE, DISCLAIMER, and LICENSE: +> +> If you modify libpng you may insert additional notices immediately following +> this sentence. +> +> This code is released under the libpng license. +> +> libpng versions 1.0.7, July 1, 2000, through 1.6.18, July 23, 2015, are +> Copyright (c) 2000-2002, 2004, 2006-2015 Glenn Randers-Pehrson, and are +> distributed according to the same disclaimer and license as libpng-1.0.6 +> with the following individuals added to the list of Contributing Authors: +> +> Simon-Pierre Cadieux +> Mans Rullgard +> Cosmin Truta +> Gilles Vollant +> James Yu +> +> and with the following additions to the disclaimer: +> +> There is no warranty against interference with your enjoyment of the +> library or against infringement. There is no warranty that our +> efforts or the library will fulfill any of your particular purposes +> or needs. This library is provided with all faults, and the entire +> risk of satisfactory quality, performance, accuracy, and effort is with +> the user. +> +> libpng versions 0.97, January 1998, through 1.0.6, March 20, 2000, are +> Copyright (c) 1998-2000 Glenn Randers-Pehrson, and are distributed according +> to the same disclaimer and license as libpng-0.96, with the following +> individuals added to the list of Contributing Authors: +> +> Tom Lane +> Glenn Randers-Pehrson +> Eric S. Raymond +> Willem van Schaik +> +> libpng versions 0.89, June 1996, through 0.96, May 1997, are +> Copyright (c) 1996-1997 Andreas Dilger, and are +> distributed according to the same disclaimer and license as libpng-0.88, +> with the following individuals added to the list of Contributing Authors: +> +> John Bowler +> Kevin Bracey +> Sam Bushell +> Magnus Holmgren +> Greg Roelofs +> Tom Tanner +> +> libpng versions 0.5, May 1995, through 0.88, January 1996, are +> Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc. +> +> For the purposes of this copyright and license, "Contributing Authors" +> is defined as the following set of individuals: +> +> Andreas Dilger +> Dave Martindale +> Guy Eric Schalnat +> Paul Schmidt +> Tim Wegner +> +> The PNG Reference Library is supplied "AS IS". The Contributing Authors +> and Group 42, Inc. disclaim all warranties, expressed or implied, +> including, without limitation, the warranties of merchantability and of +> fitness for any purpose. The Contributing Authors and Group 42, Inc. +> assume no liability for direct, indirect, incidental, special, exemplary, +> or consequential damages, which may result from the use of the PNG +> Reference Library, even if advised of the possibility of such damage. +> +> Permission is hereby granted to use, copy, modify, and distribute this +> source code, or portions hereof, for any purpose, without fee, subject +> to the following restrictions: +> +> 1. The origin of this source code must not be misrepresented. +> +> 2. Altered versions must be plainly marked as such and must not +> be misrepresented as being the original source. +> +> 3. This Copyright notice may not be removed or altered from any +> source or altered source distribution. +> +> The Contributing Authors and Group 42, Inc. specifically permit, without +> fee, and encourage the use of this source code as a component to +> supporting the PNG file format in commercial products. If you use this +> source code in a product, acknowledgment is not required but would be +> appreciated. + +[zlib compresison library](www.xiph.org) +--------------------------------------------------------- +> Copyright (C) 1995-2013 Jean-loup Gailly and Mark Adler +> +> This software is provided 'as-is', without any express or implied +> warranty. In no event will the authors be held liable for any damages +> arising from the use of this software. +> +> Permission is granted to anyone to use this software for any purpose, +> including commercial applications, and to alter it and redistribute it +> freely, subject to the following restrictions: +> +> 1. The origin of this software must not be misrepresented; you must not +> claim that you wrote the original software. If you use this software +> in a product, an acknowledgment in the product documentation would be +> appreciated but is not required. +> 2. Altered source versions must be plainly marked as such, and must not be +> misrepresented as being the original software. +> 3. This notice may not be removed or altered from any source distribution. +> +> Jean-loup Gailly Mark Adler +> jloup@gzip.org madler@alumni.caltech.edu + +[libogg and libvorbis multimedia libraries](www.xiph.org) +--------------------------------------------------------- +> Copyright (c) 2002-2015 Xiph.org Foundation +> +> Redistribution and use in source and binary forms, with or without +> modification, are permitted provided that the following conditions +> are met: +> +> - Redistributions of source code must retain the above copyright +> notice, this list of conditions and the following disclaimer. +> +> - Redistributions in binary form must reproduce the above copyright +> notice, this list of conditions and the following disclaimer in the +> documentation and/or other materials provided with the distribution. +> +> - Neither the name of the Xiph.org Foundation nor the names of its +> contributors may be used to endorse or promote products derived from +> this software without specific prior written permission. +> +> THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +> ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +> LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +> A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION +> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +> SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +> LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +> DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +> THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +> (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +> OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +[FreeType Font Engine](www.freetype.org) +---------------------------------------- +> Portions of this software are copyright © 2015 The FreeType +> Project (www.freetype.org). All rights reserved. + +[jemalloc custom allocator](www.cannonware.com/jemalloc/) +--------------------------------------------------------- +> Copyright (C) 2002-2015 Jason Evans . +> All rights reserved. +> Copyright (C) 2007-2012 Mozilla Foundation. All rights reserved. +> Copyright (C) 2009-2015 Facebook, Inc. All rights reserved. +> +> Redistribution and use in source and binary forms, with or without +> modification, are permitted provided that the following conditions are met: +> 1. Redistributions of source code must retain the above copyright notice(s), +> this list of conditions and the following disclaimer. +> 2. Redistributions in binary form must reproduce the above copyright notice(s), +> this list of conditions and the following disclaimer in the documentation +> and/or other materials provided with the distribution. +> +> THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) ``AS IS'' AND ANY EXPRESS +> OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +> MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +> EVENT SHALL THE COPYRIGHT HOLDER(S) BE LIABLE FOR ANY DIRECT, INDIRECT, +> INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +> LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +> PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +> LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +> OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +> ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +[The xxHash algorithm implementation by Yann Collet](github.com/Cyan4973/xxHash) +-------------------------------------------------------------------------------- +> Copyright (c) 2012-2014, Yann Collet +> All rights reserved. +> +> Redistribution and use in source and binary forms, with or without modification, +> are permitted provided that the following conditions are met: +> +> * Redistributions of source code must retain the above copyright notice, this +> list of conditions and the following disclaimer. +> +> * Redistributions in binary form must reproduce the above copyright notice, this +> list of conditions and the following disclaimer in the documentation and/or +> other materials provided with the distribution. +> +> THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +> ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +> WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +> DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +> ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +> (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +> LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +> ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +> (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +> SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +[Modified Tinyformat library by Chris Foster](github.com/c42f/tinyformat) +------------------------------------------------------------------------- +> Copyright (C) 2011, Chris Foster [chris42f (at) gmail (d0t) com] +> +> Boost Software License - Version 1.0 +> +> Permission is hereby granted, free of charge, to any person or organization +> obtaining a copy of the software and accompanying documentation covered by +> this license (the "Software") to use, reproduce, display, distribute, +> execute, and transmit the Software, and to prepare derivative works of the +> Software, and to permit third-parties to whom the Software is furnished to +> do so, all subject to the following: +> +> The copyright notices in the Software and this entire statement, including +> the above license grant, this restriction and the following disclaimer, +> must be included in all copies of the Software, in whole or in part, and +> all derivative works of the Software, unless such copies or derivative +> works are solely in the form of machine-executable object code generated by +> a source language processor. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +> SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +> FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +> ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +> DEALINGS IN THE SOFTWARE. + +[The OpenGL Extension Wrangler Library](glew.sourceforge.net) +------------------------------------------------------------- +> The OpenGL Extension Wrangler Library +> Copyright (C) 2008-2015, Nigel Stewart +> Copyright (C) 2002-2008, Milan Ikits +> Copyright (C) 2002-2008, Marcelo E. Magallon +> Copyright (C) 2002, Lev Povalahev +> All rights reserved. +> +> Redistribution and use in source and binary forms, with or without +> modification, are permitted provided that the following conditions are met: +> +> * Redistributions of source code must retain the above copyright notice, +> this list of conditions and the following disclaimer. +> * Redistributions in binary form must reproduce the above copyright notice, +> this list of conditions and the following disclaimer in the documentation +> and/or other materials provided with the distribution. +> * The name of the author may be used to endorse or promote products +> derived from this software without specific prior written permission. +> +> THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +> AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +> IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +> ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +> LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +> CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +> SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +> INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +> CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +> ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +> THE POSSIBILITY OF SUCH DAMAGE. + +Open Source Software used by the Mod Upoading Tool +================================================== + +[Qt GUI Toolkit](www.qt.io) +--------------------------- +> GNU LESSER GENERAL PUBLIC LICENSE +> Version 2.1, February 1999 +> +> Copyright (C) 1991, 1999 Free Software Foundation, Inc. +> 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +> Everyone is permitted to copy and distribute verbatim copies +> of this license document, but changing it is not allowed. +> +> [This is the first released version of the Lesser GPL. It also counts +> as the successor of the GNU Library Public License, version 2, hence +> the version number 2.1.] +> +> Preamble +> +> The licenses for most software are designed to take away your +> freedom to share and change it. By contrast, the GNU General Public +> Licenses are intended to guarantee your freedom to share and change +> free software--to make sure the software is free for all its users. +> +> This license, the Lesser General Public License, applies to some +> specially designated software packages--typically libraries--of the +> Free Software Foundation and other authors who decide to use it. You +> can use it too, but we suggest you first think carefully about whether +> this license or the ordinary General Public License is the better +> strategy to use in any particular case, based on the explanations below. +> +> When we speak of free software, we are referring to freedom of use, +> not price. Our General Public Licenses are designed to make sure that +> you have the freedom to distribute copies of free software (and charge +> for this service if you wish); that you receive source code or can get +> it if you want it; that you can change the software and use pieces of +> it in new free programs; and that you are informed that you can do +> these things. +> +> To protect your rights, we need to make restrictions that forbid +> distributors to deny you these rights or to ask you to surrender these +> rights. These restrictions translate to certain responsibilities for +> you if you distribute copies of the library or if you modify it. +> +> For example, if you distribute copies of the library, whether gratis +> or for a fee, you must give the recipients all the rights that we gave +> you. You must make sure that they, too, receive or can get the source +> code. If you link other code with the library, you must provide +> complete object files to the recipients, so that they can relink them +> with the library after making changes to the library and recompiling +> it. And you must show them these terms so they know their rights. +> +> We protect your rights with a two-step method: (1) we copyright the +> library, and (2) we offer you this license, which gives you legal +> permission to copy, distribute and/or modify the library. +> +> To protect each distributor, we want to make it very clear that +> there is no warranty for the free library. Also, if the library is +> modified by someone else and passed on, the recipients should know +> that what they have is not the original version, so that the original +> author's reputation will not be affected by problems that might be +> introduced by others. +> +> Finally, software patents pose a constant threat to the existence of +> any free program. We wish to make sure that a company cannot +> effectively restrict the users of a free program by obtaining a +> restrictive license from a patent holder. Therefore, we insist that +> any patent license obtained for a version of the library must be +> consistent with the full freedom of use specified in this license. +> +> Most GNU software, including some libraries, is covered by the +> ordinary GNU General Public License. This license, the GNU Lesser +> General Public License, applies to certain designated libraries, and +> is quite different from the ordinary General Public License. We use +> this license for certain libraries in order to permit linking those +> libraries into non-free programs. +> +> When a program is linked with a library, whether statically or using +> a shared library, the combination of the two is legally speaking a +> combined work, a derivative of the original library. The ordinary +> General Public License therefore permits such linking only if the +> entire combination fits its criteria of freedom. The Lesser General +> Public License permits more lax criteria for linking other code with +> the library. +> +> We call this license the "Lesser" General Public License because it +> does Less to protect the user's freedom than the ordinary General +> Public License. It also provides other free software developers Less +> of an advantage over competing non-free programs. These disadvantages +> are the reason we use the ordinary General Public License for many +> libraries. However, the Lesser license provides advantages in certain +> special circumstances. +> +> For example, on rare occasions, there may be a special need to +> encourage the widest possible use of a certain library, so that it becomes +> a de-facto standard. To achieve this, non-free programs must be +> allowed to use the library. A more frequent case is that a free +> library does the same job as widely used non-free libraries. In this +> case, there is little to gain by limiting the free library to free +> software only, so we use the Lesser General Public License. +> +> In other cases, permission to use a particular library in non-free +> programs enables a greater number of people to use a large body of +> free software. For example, permission to use the GNU C Library in +> non-free programs enables many more people to use the whole GNU +> operating system, as well as its variant, the GNU/Linux operating +> system. +> +> Although the Lesser General Public License is Less protective of the +> users' freedom, it does ensure that the user of a program that is +> linked with the Library has the freedom and the wherewithal to run +> that program using a modified version of the Library. +> +> The precise terms and conditions for copying, distribution and +> modification follow. Pay close attention to the difference between a +> "work based on the library" and a "work that uses the library". The +> former contains code derived from the library, whereas the latter must +> be combined with the library in order to run. +> +> GNU LESSER GENERAL PUBLIC LICENSE +> TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION +> +> 0. This License Agreement applies to any software library or other +> program which contains a notice placed by the copyright holder or +> other authorized party saying it may be distributed under the terms of +> this Lesser General Public License (also called "this License"). +> Each licensee is addressed as "you". +> +> A "library" means a collection of software functions and/or data +> prepared so as to be conveniently linked with application programs +> (which use some of those functions and data) to form executables. +> +> The "Library", below, refers to any such software library or work +> which has been distributed under these terms. A "work based on the +> Library" means either the Library or any derivative work under +> copyright law: that is to say, a work containing the Library or a +> portion of it, either verbatim or with modifications and/or translated +> straightforwardly into another language. (Hereinafter, translation is +> included without limitation in the term "modification".) +> +> "Source code" for a work means the preferred form of the work for +> making modifications to it. For a library, complete source code means +> all the source code for all modules it contains, plus any associated +> interface definition files, plus the scripts used to control compilation +> and installation of the library. +> +> Activities other than copying, distribution and modification are not +> covered by this License; they are outside its scope. The act of +> running a program using the Library is not restricted, and output from +> such a program is covered only if its contents constitute a work based +> on the Library (independent of the use of the Library in a tool for +> writing it). Whether that is true depends on what the Library does +> and what the program that uses the Library does. +> +> 1. You may copy and distribute verbatim copies of the Library's +> complete source code as you receive it, in any medium, provided that +> you conspicuously and appropriately publish on each copy an +> appropriate copyright notice and disclaimer of warranty; keep intact +> all the notices that refer to this License and to the absence of any +> warranty; and distribute a copy of this License along with the +> Library. +> +> You may charge a fee for the physical act of transferring a copy, +> and you may at your option offer warranty protection in exchange for a +> fee. +> +> 2. You may modify your copy or copies of the Library or any portion +> of it, thus forming a work based on the Library, and copy and +> distribute such modifications or work under the terms of Section 1 +> above, provided that you also meet all of these conditions: +> +> a) The modified work must itself be a software library. +> +> b) You must cause the files modified to carry prominent notices +> stating that you changed the files and the date of any change. +> +> c) You must cause the whole of the work to be licensed at no +> charge to all third parties under the terms of this License. +> +> d) If a facility in the modified Library refers to a function or a +> table of data to be supplied by an application program that uses +> the facility, other than as an argument passed when the facility +> is invoked, then you must make a good faith effort to ensure that, +> in the event an application does not supply such function or +> table, the facility still operates, and performs whatever part of +> its purpose remains meaningful. +> +> (For example, a function in a library to compute square roots has +> a purpose that is entirely well-defined independent of the +> application. Therefore, Subsection 2d requires that any +> application-supplied function or table used by this function must +> be optional: if the application does not supply it, the square +> root function must still compute square roots.) +> +> These requirements apply to the modified work as a whole. If +> identifiable sections of that work are not derived from the Library, +> and can be reasonably considered independent and separate works in +> themselves, then this License, and its terms, do not apply to those +> sections when you distribute them as separate works. But when you +> distribute the same sections as part of a whole which is a work based +> on the Library, the distribution of the whole must be on the terms of +> this License, whose permissions for other licensees extend to the +> entire whole, and thus to each and every part regardless of who wrote +> it. +> +> Thus, it is not the intent of this section to claim rights or contest +> your rights to work written entirely by you; rather, the intent is to +> exercise the right to control the distribution of derivative or +> collective works based on the Library. +> +> In addition, mere aggregation of another work not based on the Library +> with the Library (or with a work based on the Library) on a volume of +> a storage or distribution medium does not bring the other work under +> the scope of this License. +> +> 3. You may opt to apply the terms of the ordinary GNU General Public +> License instead of this License to a given copy of the Library. To do +> this, you must alter all the notices that refer to this License, so +> that they refer to the ordinary GNU General Public License, version 2, +> instead of to this License. (If a newer version than version 2 of the +> ordinary GNU General Public License has appeared, then you can specify +> that version instead if you wish.) Do not make any other change in +> these notices. +> +> Once this change is made in a given copy, it is irreversible for +> that copy, so the ordinary GNU General Public License applies to all +> subsequent copies and derivative works made from that copy. +> +> This option is useful when you wish to copy part of the code of +> the Library into a program that is not a library. +> +> 4. You may copy and distribute the Library (or a portion or +> derivative of it, under Section 2) in object code or executable form +> under the terms of Sections 1 and 2 above provided that you accompany +> it with the complete corresponding machine-readable source code, which +> must be distributed under the terms of Sections 1 and 2 above on a +> medium customarily used for software interchange. +> +> If distribution of object code is made by offering access to copy +> from a designated place, then offering equivalent access to copy the +> source code from the same place satisfies the requirement to +> distribute the source code, even though third parties are not +> compelled to copy the source along with the object code. +> +> 5. A program that contains no derivative of any portion of the +> Library, but is designed to work with the Library by being compiled or +> linked with it, is called a "work that uses the Library". Such a +> work, in isolation, is not a derivative work of the Library, and +> therefore falls outside the scope of this License. +> +> However, linking a "work that uses the Library" with the Library +> creates an executable that is a derivative of the Library (because it +> contains portions of the Library), rather than a "work that uses the +> library". The executable is therefore covered by this License. +> Section 6 states terms for distribution of such executables. +> +> When a "work that uses the Library" uses material from a header file +> that is part of the Library, the object code for the work may be a +> derivative work of the Library even though the source code is not. +> Whether this is true is especially significant if the work can be +> linked without the Library, or if the work is itself a library. The +> threshold for this to be true is not precisely defined by law. +> +> If such an object file uses only numerical parameters, data +> structure layouts and accessors, and small macros and small inline +> functions (ten lines or less in length), then the use of the object +> file is unrestricted, regardless of whether it is legally a derivative +> work. (Executables containing this object code plus portions of the +> Library will still fall under Section 6.) +> +> Otherwise, if the work is a derivative of the Library, you may +> distribute the object code for the work under the terms of Section 6. +> Any executables containing that work also fall under Section 6, +> whether or not they are linked directly with the Library itself. +> +> 6. As an exception to the Sections above, you may also combine or +> link a "work that uses the Library" with the Library to produce a +> work containing portions of the Library, and distribute that work +> under terms of your choice, provided that the terms permit +> modification of the work for the customer's own use and reverse +> engineering for debugging such modifications. +> +> You must give prominent notice with each copy of the work that the +> Library is used in it and that the Library and its use are covered by +> this License. You must supply a copy of this License. If the work +> during execution displays copyright notices, you must include the +> copyright notice for the Library among them, as well as a reference +> directing the user to the copy of this License. Also, you must do one +> of these things: +> +> a) Accompany the work with the complete corresponding +> machine-readable source code for the Library including whatever +> changes were used in the work (which must be distributed under +> Sections 1 and 2 above); and, if the work is an executable linked +> with the Library, with the complete machine-readable "work that +> uses the Library", as object code and/or source code, so that the +> user can modify the Library and then relink to produce a modified +> executable containing the modified Library. (It is understood +> that the user who changes the contents of definitions files in the +> Library will not necessarily be able to recompile the application +> to use the modified definitions.) +> +> b) Use a suitable shared library mechanism for linking with the +> Library. A suitable mechanism is one that (1) uses at run time a +> copy of the library already present on the user's computer system, +> rather than copying library functions into the executable, and (2) +> will operate properly with a modified version of the library, if +> the user installs one, as long as the modified version is +> interface-compatible with the version that the work was made with. +> +> c) Accompany the work with a written offer, valid for at +> least three years, to give the same user the materials +> specified in Subsection 6a, above, for a charge no more +> than the cost of performing this distribution. +> +> d) If distribution of the work is made by offering access to copy +> from a designated place, offer equivalent access to copy the above +> specified materials from the same place. +> +> e) Verify that the user has already received a copy of these +> materials or that you have already sent this user a copy. +> +> For an executable, the required form of the "work that uses the +> Library" must include any data and utility programs needed for +> reproducing the executable from it. However, as a special exception, +> the materials to be distributed need not include anything that is +> normally distributed (in either source or binary form) with the major +> components (compiler, kernel, and so on) of the operating system on +> which the executable runs, unless that component itself accompanies +> the executable. +> +> It may happen that this requirement contradicts the license +> restrictions of other proprietary libraries that do not normally +> accompany the operating system. Such a contradiction means you cannot +> use both them and the Library together in an executable that you +> distribute. +> +> 7. You may place library facilities that are a work based on the +> Library side-by-side in a single library together with other library +> facilities not covered by this License, and distribute such a combined +> library, provided that the separate distribution of the work based on +> the Library and of the other library facilities is otherwise +> permitted, and provided that you do these two things: +> +> a) Accompany the combined library with a copy of the same work +> based on the Library, uncombined with any other library +> facilities. This must be distributed under the terms of the +> Sections above. +> +> b) Give prominent notice with the combined library of the fact +> that part of it is a work based on the Library, and explaining +> where to find the accompanying uncombined form of the same work. +> +> 8. You may not copy, modify, sublicense, link with, or distribute +> 0the Library except as expressly provided under this License. Any +> attempt otherwise to copy, modify, sublicense, link with, or +> distribute the Library is void, and will automatically terminate your +> rights under this License. However, parties who have received copies, +> or rights, from you under this License will not have their licenses +> terminated so long as such parties remain in full compliance. +> +> 9. You are not required to accept this License, since you have not +> signed it. However, nothing else grants you permission to modify or +> distribute the Library or its derivative works. These actions are +> prohibited by law if you do not accept this License. Therefore, by +> modifying or distributing the Library (or any work based on the +> Library), you indicate your acceptance of this License to do so, and +> all its terms and conditions for copying, distributing or modifying +> the Library or works based on it. +> +> 10. Each time you redistribute the Library (or any work based on the +> Library), the recipient automatically receives a license from the +> original licensor to copy, distribute, link with or modify the Library +> subject to these terms and conditions. You may not impose any further +> restrictions on the recipients' exercise of the rights granted herein. +> You are not responsible for enforcing compliance by third parties with +> this License. +> +> 11. If, as a consequence of a court judgment or allegation of patent +> infringement or for any other reason (not limited to patent issues), +> conditions are imposed on you (whether by court order, agreement or +> otherwise) that contradict the conditions of this License, they do not +> excuse you from the conditions of this License. If you cannot +> distribute so as to satisfy simultaneously your obligations under this +> License and any other pertinent obligations, then as a consequence you +> may not distribute the Library at all. For example, if a patent +> license would not permit royalty-free redistribution of the Library by +> all those who receive copies directly or indirectly through you, then +> the only way you could satisfy both it and this License would be to +> refrain entirely from distribution of the Library. +> +> If any portion of this section is held invalid or unenforceable under any +> particular circumstance, the balance of the section is intended to apply, +> and the section as a whole is intended to apply in other circumstances. +> +> It is not the purpose of this section to induce you to infringe any +> patents or other property right claims or to contest validity of any +> such claims; this section has the sole purpose of protecting the +> integrity of the free software distribution system which is +> implemented by public license practices. Many people have made +> generous contributions to the wide range of software distributed +> through that system in reliance on consistent application of that +> system; it is up to the author/donor to decide if he or she is willing +> to distribute software through any other system and a licensee cannot +> impose that choice. +> +> This section is intended to make thoroughly clear what is believed to +> be a consequence of the rest of this License. +> +> 12. If the distribution and/or use of the Library is restricted in +> certain countries either by patents or by copyrighted interfaces, the +> original copyright holder who places the Library under this License may add +> an explicit geographical distribution limitation excluding those countries, +> so that distribution is permitted only in or among countries not thus +> excluded. In such case, this License incorporates the limitation as if +> written in the body of this License. +> +> 13. The Free Software Foundation may publish revised and/or new +> versions of the Lesser General Public License from time to time. +> Such new versions will be similar in spirit to the present version, +> but may differ in detail to address new problems or concerns. +> +> Each version is given a distinguishing version number. If the Library +> specifies a version number of this License which applies to it and +> "any later version", you have the option of following the terms and +> conditions either of that version or of any later version published by +> the Free Software Foundation. If the Library does not specify a +> license version number, you may choose any version ever published by +> the Free Software Foundation. +> +> 14. If you wish to incorporate parts of the Library into other free +> programs whose distribution conditions are incompatible with these, +> write to the author to ask for permission. For software which is +> copyrighted by the Free Software Foundation, write to the Free +> Software Foundation; we sometimes make exceptions for this. Our +> decision will be guided by the two goals of preserving the free status +> of all derivatives of our free software and of promoting the sharing +> and reuse of software generally. +> +> NO WARRANTY +> +> 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +> WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +> EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +> OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +> KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +> IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +> PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +> LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +> THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. +> +> 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +> WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +> AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +> FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +> CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +> LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +> RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +> FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +> SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +> DAMAGES. diff --git a/doc/lua/activeitem.md b/doc/lua/activeitem.md new file mode 100644 index 0000000..bce3f27 --- /dev/null +++ b/doc/lua/activeitem.md @@ -0,0 +1,187 @@ +The activeItem table contains bindings which provide functionality for the ActiveItem and for the item's 'owner' (a ToolUser entity currently holding the item). + +--- + +#### `EntityId` activeItem.ownerEntityId() + +Returns the entity id of the owner entity. + +--- + +#### `DamageTeam` activeItem.ownerTeam() + +Returns the damage team of the owner entity. + +--- + +#### `Vec2F` activeItem.ownerAimPosition() + +Returns the world aim position of the owner entity. + +--- + +#### `float` activeItem.ownerPowerMultiplier() + +Returns the power multiplier of the owner entity. + +--- + +#### `String` activeItem.fireMode() + +Returns the current fire mode of the item, which can be "none", "primary" or "alt". Single-handed items held in the off hand will receive right click as "primary" rather than "alt". + +--- + +#### `String` activeItem.hand() + +Returns the name of the hand that the item is currently held in, which can be "primary" or "alt". + +--- + +#### `Vec2F` activeItem.handPosition([`Vec2F` offset]) + +Takes an input position (defaults to [0, 0]) relative to the item and returns a position relative to the owner entity. + +--- + +#### `LuaTable` activeItem.aimAngleAndDirection(`float` aimVerticalOffset, `Vec2F` targetPosition) + +Returns a table containing the `float` aim angle and `int` facing direction that would be used for the item to aim at the specified target position with the specified vertical offset. This takes into account the position of the shoulder, distance of the hand from the body, and a lot of other complex factors and should be used to control aimable weapons or tools based on the owner's aim position. + +--- + +#### `float` activeItem.aimAngle(`float` aimVerticalOffset, `Vec2F` targetPosition) + +Similar to activeItem.aimAngleAndDirection but only returns the aim angle that would be calculated with the entity's current facing direction. Necessary if, for example, an item needs to aim behind the owner. + +--- + +#### `void` activeItem.setHoldingItem(`bool` holdingItem) + +Sets whether the owner is visually holding the item. + +--- + +#### `void` activeItem.setBackArmFrame([`String` armFrame]) + +Sets the arm image frame that the item should use when held behind the player, or clears it to the default rotation arm frame if no frame is specified. + +--- + +#### `void` activeItem.setFrontArmFrame([`String` armFrame]) + +Sets the arm image frame that the item should use when held in front of the player, or clears it to the default rotation arm frame if no frame is specified. + +--- + +#### `void` activeItem.setTwoHandedGrip(`bool` twoHandedGrip) + +Sets whether the item should be visually held with both hands. Does not alter the functional handedness requirement of the item. + +--- + +#### `void` activeItem.setRecoil(`bool` recoil) + +Sets whether the item is in a recoil state, which will translate both the item and the arm holding it slightly toward the back of the character. + +--- + +#### `void` activeItem.setOutsideOfHand(`bool` outsideOfHand) + +Sets whether the item should be visually rendered outside the owner's hand. Items outside of the hand will be rendered in front of the arm when held in front and behind the arm when held behind. + +--- + +#### `void` activeItem.setArmAngle(`float` angle) + +Sets the angle to which the owner's arm holding the item should be rotated. + +--- + +#### `void` activeItem.setFacingDirection(`float` direction) + +Sets the item's requested facing direction, which controls the owner's facing. Positive direction values will face right while negative values will face left. If the owner holds two items which request opposing facing directions, the direction requested by the item in the primary hand will take precedence. + +--- + +#### `void` activeItem.setDamageSources([`List` damageSources]) + +Sets a list of active damage sources with coordinates relative to the owner's position or clears them if unspecified. + +--- + +#### `void` activeItem.setItemDamageSources([`List` damageSources]) + +Sets a list of active damage sources with coordinates relative to the item's hand position or clears them if unspecified. + +--- + +#### `void` activeItem.setShieldPolys([`List` shieldPolys]) + +Sets a list of active shield polygons with coordinates relative to the owner's position or clears them if unspecified. + +--- + +#### `void` activeItem.setItemShieldPolys([`List` shieldPolys]) + +Sets a list of active shield polygons with coordinates relative to the item's hand position or clears them if unspecified. + +--- + +#### `void` activeItem.setForceRegions([`List` forceRegions]) + +Sets a list of active physics force regions with coordinates relative to the owner's position or clears them if unspecified. + +--- + +#### `void` activeItem.setItemForceRegions([`List` forceRegions]) + +Sets a list of active physics force regions with coordinates relative to the item's hand position or clears them if unspecified. + +--- + +#### `void` activeItem.setCursor([`String` cursor]) + +Sets the item's overriding cursor image or clears it if unspecified. + +--- + +#### `void` activeItem.setScriptedAnimationParameter(`String` parameter, `Json` value) + +Sets a parameter to be used by the item's scripted animator. + +--- + +#### `void` activeItem.setInventoryIcon(`String` image) + +Sets the inventory icon of the item. + +--- + +#### `void` activeItem.setInstanceValue(`String` parameter, `Json` value) + +Sets an instance value (parameter) of the item. + +--- + +#### `LuaValue` activeItem.callOtherHandScript(`String` functionName, [`LuaValue` args ...]) + +Attempts to call the specified function name with the specified argument values in the context of an ActiveItem held in the opposing hand and synchronously returns the result if successful. + +--- + +#### `void` activeItem.interact(`String` interactionType, `Json` config, [`EntityId` sourceEntityId]) + +Triggers an interact action on the owner as if they had initiated an interaction and the result had returned the specified interaction type and configuration. Can be used to e.g. open GUI windows normally triggered by player interaction with entities. + +--- + +#### `void` activeItem.emote(`String` emote) + +Triggers the owner to perform the specified emote. + +--- + +#### `void` activeItem.setCameraFocusEntity([`EntityId` entity]) + +If the owner is a player, sets that player's camera to be centered on the position of the specified entity, or recenters the camera on the player's position if no entity id is specified. diff --git a/doc/lua/activeitemanimation.md b/doc/lua/activeitemanimation.md new file mode 100644 index 0000000..d5c0fdc --- /dev/null +++ b/doc/lua/activeitemanimation.md @@ -0,0 +1,43 @@ +The activeItemAnimation table contains bindings available to client-side animation scripts for active items. + +--- + +#### `Vec2F` activeItemAnimation.ownerPosition() + +Returns the current entity position of the item's owner. + +--- + +#### `Vec2F` activeItemAnimation.ownerAimPosition() + +Returns the current world aim position of the item's owner. + +--- + +#### `float` activeItemAnimation.ownerArmAngle() + +Returns the current angle of the arm holding the item. + +--- + +#### `int` activeItemAnimation.ownerFacingDirection() + +Returns the current facing direction of the item's owner. Will return 1 for right or -1 for left. + +--- + +#### `Vec2F` activeItemAnimation.handPosition([`Vec2F` offset]) + +Takes an input position (defaults to [0, 0]) relative to the item and returns a position relative to the owner entity. + +--- + +#### `Vec2F` activeItemAnimation.partPoint(`String` partName, `String` propertyName) + +Returns a transformation of the specified `Vec2F` parameter configured on the specified animation part, relative to the owner's position. + +--- + +#### `PolyF` activeItemAnimation.partPoly(`String` partName, `String` propertyName) + +Returns a transformation of the specified `PolyF` parameter configured on the specified animation part, relative to the owner's position. diff --git a/doc/lua/actormovementcontroller.md b/doc/lua/actormovementcontroller.md new file mode 100644 index 0000000..4d35f78 --- /dev/null +++ b/doc/lua/actormovementcontroller.md @@ -0,0 +1,417 @@ +# actor mcontroller + +The `mcontroller` table sometimes contains functions relating to the actor movement controller. + +This section of mcontroller documentation refers to the ActorMovementController lua bindings. There is other documentation referring to the mcontroller table for base MovementControllers. + +* monsters +* npcs +* tech +* companion system scripts +* status effects +* quest scripts +* active items + +--- + +#### `RectF` mcontroller.boundBox() + +Returns a rect containing the entire collision of the movement controller, in local coordinates. + +--- + +#### `PolyF` mcontroller.collisionPoly() + +Returns the collision poly of the movement controller, in local coordinates. + +--- + +#### `PolyF` mcontroller.collisionBody() + +Returns the collision poly of the movement controller, in world coordinates. + +--- + +#### `float` mcontroller.mass() + +Returns the configured mass of the movement controller. + +--- + +#### `Vec2F` mcontroller.position() + +Returns the current position of the movement controller. + +--- + +#### `float` mcontroller.xPosition() + +Returns the current horizontal position of the movement controller. + +--- + +#### `float` mcontroller.yPosition() + +Returns the current vertical position of the movement controller. + +--- + +#### `Vec2F` mcontroller.velocity() + +Returns the current velocity of the movement controller. + +--- + +#### `float` mcontroller.xVelocity() + +Returns the current horizontal speed of the movement controller. + +--- + +#### `float` mcontroller.yVelocity() + +Returns the current vertical speed of the movement controller. + +--- + +#### `float` mcontroller.rotation() + +Returns the current rotation of the movement controller in radians. + +--- + +#### `bool` mcontroller.isColliding() + +Returns whether the movement controller is currently colliding with world geometry or a PhysicsMovingCollision. + +--- + +#### `bool` mcontroller.isNullColliding() + +Returns whether the movement controller is currently colliding with null world geometry. Null collision occurs in unloaded sectors. + +--- + +#### `bool` mcontroller.isCollisionStuck() + +Returns whether the movement controller is currently stuck colliding. Movement controllers can stick if the `stickyCollision` movement parameter is set. + +--- + +#### `float` mcontroller.stickingDirection() + +Returns the angle that the movement controller is currently stuck at, in radians. + +--- + +#### `float` mcontroller.liquidPercentage() + +Returns the percentage of the collision poly currently submerged in liquid; + +--- + +#### `LiquidId` mcontroller.liquidId() + +Returns the liquid ID of the liquid that the movement controller is currently submerged in. If this is several liquids this returns the most plentiful one. + +--- + +#### `bool` mcontroller.onGround() + +Returns whether the movement controller is currently on ground. + +--- + +#### `bool` mcontroller.zeroG() + +Returns `true` if the movement controller is at a world position without gravity or if gravity has been disabled. + +--- + +#### `bool` mcontroller.atWorldLimit([`bool` bottomOnly]) + +Returns `true` if the movement controller is touching the bottom or the top (unless bottomOnly is specified) of the world. + +--- + +##### `void` mcontroller.setAnchorState(`EntityId` anchorableEntity, size_t anchorPosition) + +Anchors the movement controller to an anchorable entity at the given anchor index. + +--- + +##### `void` mcontroller.resetAnchorState() + +Reset the anchor state. + +--- + +##### `EntityId`, `int` mcontroller.anchorState() + +Returns ID of anchored entity and index of the anchor position. + +--- + +#### `void` mcontroller.setPosition(`Vec2F` position) + +Sets the position of the movement controller. + +--- + +#### `void` mcontroller.setXPosition(`float` x) + +Sets the horizontal position of the movement controller. + +--- + +#### `void` mcontroller.setYPosition(`float` y) + +Sets the vertical position of the movement controller. + +--- + +#### `void` mcontroller.translate(`Vec2F` direction) + +Moves the movement controller by the vector provided. + +--- + +#### `void` mcontroller.setVelocity(`Vec2F` velocity) + +Sets the velocity of the movement controller. + +--- + +#### `void` mcontroller.setXVelocity(`Vec2F` xVelocity) + +Sets the horizontal velocity of the movement controller. + +--- + +#### `void` mcontroller.setYVelocity(`Vec2F` yVelocity) + +Sets the vertical velocity of the movement controller. + +--- + +#### `void` mcontroller.addMomentum(`Vec2F` momentum) + +Adds (momentum / mass) velocity to the movement controller. + +--- + +#### `void` mcontroller.setRotation(`float` angle) + +Sets the rotation of the movement controller. Angle is in radians. + +--- + +##### `ActorMovementParameters` mcontroller.baseParameters() + +Returns the base movement parameters. + +--- + +##### `bool` mcontroller.walking() + +Returns whether the actor movement controller is currently walking. + +--- + +##### `bool` mcontroller.running() + +Returns whether the actor movement controller is currently running. + +--- + +##### `int` mcontroller.movingDirection() + +Returns the direction that the actor movement controller is currently moving in. -1 for left, 1 for right. + +--- + +##### `int` mcontroller.facingDirection() + +Returns the facing direction. -1 for left, 1 for right. + +--- + +##### `bool` mcontroller.crouching() + +Returns whether the controller is currently crouching. + +--- + +##### `bool` mcontroller.flying() + +Returns whether the controller is currently flying. + +--- + +##### `bool` mcontroller.falling() + +Returns whether the controller is currently falling. + +--- + +##### `bool` mcontroller.canJump() + +Returns whether the controller can currently jump. + +--- + +##### `bool` mcontroller.jumping() + +Returns whether the controller is currently jumping. + +--- + +##### `bool` mcontroller.groundMovement() + +Returns whether the controller is currently in a ground movement state. Movement controllers can be in ground movement even when onGround returns false. + +--- + +##### `bool` mcontroller.liquidMovement() + +Returns whether the controller is currently in liquid movement mode. + +--- + +## controls + +The actor movement controller has a set of controls. Controls can be set anywhere and are accumulated and evaluated after all scripts are run. Controls will either override previously set controls, or combine them. + +Controls are either cleared before every script update, or can be set to be manually cleared. + +--- + +##### `void` mcontroller.controlRotation(`float` rotation) + +Rotates the controller. Each control adds to the previous one. + +--- + +##### `void` mcontroller.controlAcceleration(`Vec2F` acceleration) + +Controls acceleration. Each control adds to the previous one. + +--- + +##### `void` mcontroller.controlForce() + +Controls force. Each control adds to the previous one. + +--- + +##### `void` mcontroller.controlApproachVelocity(`Vec2F` targetVelocity, `float` maxControlForce) + +Approaches the targetVelocity using the force provided. If the current velocity is higher than the provided targetVelocity, the targetVelocity will still be approached, effectively slowing down the entity. + +Each control overrides the previous one. + +--- + +#### `void` mcontroller.controlApproachVelocityAlongAngle(`float` angle, `float` targetVelocity, `float` maxControlForce, `bool` positiveOnly = false) + +Approaches the targetVelocity but only along the provided angle, not affecting velocity in the perpendicular axis. If positiveOnly, then it will not slow down the movementController if it is already moving faster than targetVelocity. + +Each control overrides the previous one. + +--- + +#### `void` mcontroller.controlApproachXVelocity(`float` targetVelocity, `float` maxControlForce) + +Approaches an X velocity. Same as using approachVelocityAlongAngle with angle 0. + +Each control overrides the previous one. + +--- + +#### `void` mcontroller.controlApproachYVelocity(`float` targetVelocity, `float` maxControlForce) + +Approaches a Y velocity. Same as using approachVelocityAlongAngle with angle (Pi / 2). + +Each control overrides the previous one. + +--- + +##### `void` mcontroller.controlParameters(`ActorMovementParameters` parameters) + +Changes movement parameters. Parameters are merged into the base parameters. + +Each control is merged into the previous one. + +--- + +##### `void` mcontroller.controlModifiers(`ActorMovementModifiers` modifiers) + +Changes movement modifiers. Modifiers are merged into the base modifiers. + +Each control is merged into the previous one. + +--- + +##### `void` mcontroller.controlMove(`float` direction, `bool` run) + +Controls movement in a direction. + +Each control replaces the previous one. + +--- + +##### `void` mcontroller.controlFace(`float` direction) + +Controls the facing direction. + +Each control replaces the previous one. + +--- + +##### `void` mcontroller.controlDown() + +Controls dropping through platforms. + +--- + +##### `void` mcontroller.controlCrouch() + +Controls crouching. + +--- + +##### `void` mcontroller.controlJump() + +Controls starting a jump. Only has an effect if canJump is true. + +--- + +##### `void` mcontroller.controlHoldJump() + +Keeps holding jump. Will not trigger a new jump, and can be held in the air. + +--- + +##### `void` mcontroller.controlFly(`Vec2F` direction) + +Controls flying in the specified direction (or {0, 0} to stop) with the configured flightSpeed parameter. + +Each control overrides the previous one. + +--- + +##### `bool` mcontroller.autoClearControls() + +Returns whether the controller is currently set to auto clear controls before each script update. + +--- + +##### `void` mcontroller.setAutoClearControls(`bool` enabled) + +Set whether to automatically clear controls before each script update. + +--- + +##### `void` mcontroller.clearControls() + +Manually clear all controls. diff --git a/doc/lua/animator.md b/doc/lua/animator.md new file mode 100644 index 0000000..aae5ad3 --- /dev/null +++ b/doc/lua/animator.md @@ -0,0 +1,243 @@ +# animator + +The *animator* table contains functions that relate to an attached networked animator. Networked animators are found in: + +* tech +* monsters +* vehicles +* status effects +* active items + +--- + +#### `bool` animator.setAnimationState(`String` stateType, `String` State, `bool` startNew = false) + +Sets an animation state. If startNew is true, restart the animation loop if it's already active. Returns whether the state was set. + +--- + +#### `String` animator.animationState(`String` stateType) + +Returns the current state for a state type. + +--- + +#### `Json` animator.animationStateProperty(`String` stateType, `String` propertyName) + +Returns the value of the specified property for a state type. + +--- + +#### `void` animator.setGlobalTag(`String` tagName, `String` tagValue) + +Sets a global animator tag. A global tag replaces any tag with the specified tagValue across all animation parts. + +--- + +#### `void` animator.setPartTag(`String` partType, `String` tagName, `String` tagValue) + +Sets a local animator tag. A part tag replaces any tag with the specified tagValue in the partType animation part only. + +--- + +#### `void` animator.setFlipped(`bool` flipped) + +Sets whether the animator should be flipped horizontally. + +--- + +#### `void` animator.setAnimationRate(`float` rate) + +Sets the animation rate of the animator. + +--- + +#### `void` animator.rotateGroup(`String` rotationGroup, `float` targetAngle, `bool` immediate) + +Rotates a rotation group to the specified angle. If immediate, ignore rotation speed. + +*NOTE:* Rotation groups have largely been replaced by transformation groups and should only be used in a context where maintaining a rotation speed is important. When possible use transformation groups. + +--- + +#### `float` animator.currentRotationAngle(`String` rotationGroup) + +Returns the current angle for a rotation group. + +--- + +#### `bool` animator.hasTransformationGroup(`String` transformationGroup) + +Returns whether the animator contains the specified transformation group. + +--- + +#### `void` animator.translateTransformationGroup(`String` transformationGroup, `Vec2F` translate) + +Translates the specified transformation group. + +--- + +#### `void` animator.rotateTransformationGroup(`String` transformationGroup, `float` rotation, [`Vec2F` rotationCenter]) + +Rotates the specified transformation group by the specified angle in radians, optionally around the specified center point. + +--- + +#### `void` animator.scaleTransformationGroup(`String` transformationGroup, `float` scale, [`Vec2F` scaleCenter]) + +#### `void` animator.scaleTransformationGroup(`String` transformationGroup, `Vec2F` scale, [`Vec2F` scaleCenter]) + +Scales the specified transformation group by the specified scale. Optionally scale it from a scaleCenter. + +--- + +#### `void` animator.transformTransformationGroup(`String` transformationGroup, `float` a, `float` b, `float` c, `float` d, `float` tx, `float` ty) + +Applies a custom Mat3 transform to the specified transformationGroup. The applied matrix will be: + +[a, b, tx, + c, d, ty, + 0, 0, 1] + +--- + +#### `void` animator.resetTransformationGroup(`String` transformationGroup) + +Resets a transformationGroup to the identity transform. + +[1, 0, 0 + 0, 1, 0, + 0, 1, 1] + +--- + +#### `void` animator.setParticleEmitterActive(`String` emitterName, `bool` active) + +Sets a particle emitter to be active or inactive. + +--- + +#### `void` animator.setParticleEmitterEmissionRate(`String` emitterName, `float` emissionRate) + +Sets the rate at which a particle emitter emits particles while active. + +--- + +#### `void` animator.setParticleEmitterBurstCount(`String` emitterName, `unsigned` burstCount) + +Sets the amount of each particle the emitter will emit when using burstParticleEmitter. + +--- + +#### `void` animator.setParticleEmitterOffsetRegion(`String` emitterName, `RectF` offsetRegion) + +Sets an offset region for the particle emitter. Any particles spawned will have a randomized offset within the region added to their position. + +--- + +#### `void` animator.burstParticleEmitter(`String` emitterName) + +Spawns the entire set of particles `burstCount` times, where `burstCount` can be configured in the animator or set by setParticleEmitterBurstCount. + +--- + +#### `void` animator.setLightActive(`String` lightName, bool active) + +Sets a light to be active/inactive. + +--- + +#### `void` animator.setLightPosition(`String` lightName, Vec2F position) + +Sets the position of a light. + +--- + +#### `void` animator.setLightColor(`String` lightName, Color color) + +Sets the color of a light. Brighter color gives a higher light intensity. + +--- + +#### `void` animator.setLightPointAngle(`String` lightName, float angle) + +Sets the angle of a pointLight. + +--- + +#### `bool` animator.hasSound(`String` soundName) + +Returns whether the animator has a sound by the name of `soundName` + +--- + +#### `void` animator.setSoundPool(`String` soundName, `List` soundPool) + +Sets the list of sound assets to pick from when playing a sound. + +--- + +#### `void` animator.setSoundPosition(`String` soundName, `Vec2F` position) + +Sets the position that a sound is played at. + +--- + +#### `void` animator.playSound(`String` soundName, [`int` loops = 0]) + +Plays a sound. Optionally loop `loops` times. 0 plays the sound once (no loops), -1 loops indefinitely. + +--- + +#### `void` animator.setSoundVolume(`String` soundName, `float` volume, [`float` rampTime = 0.0]) + +Sets the volume of a sound. Optionally smoothly transition the volume over `rampTime` seconds. + +--- + +#### `void` animator.setSoundPitch(`String` soundName, `float` pitch, [`float` rampTime = 0.0]) + +Sets the relative pitch of a sound. Optionally smoothly transition the pitch over `rampTime` seconds. + +--- + +#### `void` animator.stopAllSounds(`String` soundName) + +Stops all instances of the specified sound. + +--- + +#### `void` animator.setEffectActive(`String` effect, `bool` enabled) + +Sets a configured effect to be active/inactive. + +--- + +#### `Vec2F` animator.partPoint(`String` partName, `String` propertyName) + +Returns a `Vec2F` configured in a part's properties with all of the part's transformations applied to it. + +--- + +#### `PolyF` animator.partPoly(`String` partName, `String` propertyName) + +Returns a `PolyF` configured in a part's properties with all the part's transformations applied to it. + +--- + +#### `Json` animator.partProperty(`String` partName, `String` propertyName) + +Returns an animation part property without applying any transformations. + +--- + +#### `Json` animator.transformPoint(`String` partName, `Vec2F` point) + +Applies the specified part's transformation on the given point. + +--- + +#### `Json` animator.transformPoly(`String` partName, `PolyF` poly) + +Applies the specified part's transformation on the given poly. diff --git a/doc/lua/celestial.md b/doc/lua/celestial.md new file mode 100644 index 0000000..e8fb3f4 --- /dev/null +++ b/doc/lua/celestial.md @@ -0,0 +1,238 @@ +# celestial + +The *celestial* table contains functions that relate to the client sky, flying the player ship, system positions for planets, system objects, and the celestial database. It is available in the following contexts: + +* script pane + +--- + +#### `bool` celestial.skyFlying() + +Returns whether the client sky is currently flying. +--- + +#### `String` celestial.skyFlyingType() + +Returns the type of flying the client sky is currently performing. +--- + +#### `String` celestial.skyWarpPhase() + +Returns the current warp phase of the client sky, if warping. +--- + +#### `float` celestial.skyWarpProgress() + +Returns a value between 0 and 1 for how far through warping the sky is currently. +--- + +#### `bool` celestial.skyInHyperspace() + +Returns whether the sky is currently under hyperspace flight. +--- + +#### `void` celestial.flyShip(`Vec3I` system, `SystemLocation` destination) + +Flies the player ship to the specified SystemLocation in the specified system. + +SystemLocation is either of the following types: Null, CelestialCoordinate, Object, Vec2F + +The locations are specified as a pair of type and value + +``` +local system = celestial.currentSystem().location +local location = nil -- Null +location = {"coordinate", {location = system, planet = 1, satellite = 0}} -- CelestialCoordinate +location = {"object", "11112222333344445555666677778888"} -- Object (UUID) +location = {0.0, 0.0} -- Vec2F (position in space) +celestial.flyShip(system, location) +``` + +--- + +#### `bool` celestial.flying() + +Returns whether the player ship is flying +--- + +#### `Vec2F` celestial.shipSystemPosition() + +Returns the current position of the ship in the system. +--- + +#### `SystemLocation` celestial.shipDestination() + +Returns the current destination of the player ship. +--- + +#### `SystemLocation` celestial.shipLocation() + +Returns the current system location of the player ship. +--- + +#### `CelestialCoordinate` celestial.currentSystem() + +Returns the CelestialCoordinate for system the ship is currently in. +--- + +#### `float` celestial.planetSize(`CelestialCoordinate` planet) + +Returns the diameter of the specified planet in system space. +--- + +#### `Vec2F` celestial.planetPosition(`CelestialCoordinate` planet) + +Returns the position of the specified planet in system space. +--- + +#### `CelestialParameters` celestial.planetParameters(`CelestialCoordinate` planet) + +Returns the celestial parameters for the specified planet. +--- + +#### `VisitableParameters` celestial.visitableParameters(`CelestialCoordinate` planet) + +Returns the visitable parameters for the specified visitable planet. For unvisitable planets, returns nil. +--- + +#### `String` celestial.planetName(`CelestialCoordinate` planet) + +Returns the name of the specified planet. +--- + +#### `uint64_t` celestial.planetSeed(`CelestialCoordinate` planet) + +Returns the seed for the specified planet. +--- + +#### `float` celestial.clusterSize(`CelestialCoordinate` planet) + +Returns the diameter of the specified planet and its orbiting moons. +--- + +#### `List` celestial.planetOres(`CelestialCoordinate` planet) + +Returns a list of ores available on the specified planet. +--- + +#### `Vec2F` celestial.systemPosition(`SystemLocation` location) + +Returns the position of the specified location in the *current system*. +--- + +#### `Vec2F` celestial.orbitPosition(`Orbit` orbit) + +Returns the calculated position of the provided orbit. + +``` +local orbit = { + target = planet, -- the orbit target + direction = 1, -- orbit direction + enterTime = 0, -- time the orbit was entered, universe epoch time + enterPosition = {1, 0} -- the position that the orbit was entered at, relative to the target +} +``` +--- + +#### `List` celestial.systemObjects() + +Returns a list of the Uuids for objects in the current system. +--- + +#### `String` celestial.objectType(`Uuid` uuid) + +Returns the type of the specified object. +--- + +#### `Json` celestial.objectParameters(`Uuid` uuid) + +Returns the parameters for the specified object. +--- + +#### `WorldId` celestial.objectWarpActionWorld(`Uuid` uuid) + +Returns the warp action world ID for the specified object. +--- + +#### `Json` celestial.objectOrbit(`Uuid` uuid) + +Returns the orbit of the specified object, if any. +--- + +#### `Maybe` celestial.objectPosition(`Uuid` uuid) + +Returns the position of the specified object, if any. +--- + +#### `Json` celestial.objectTypeConfig(`String` typeName) + +Returns the configuration of the specified object type. +--- + +#### `Uuid` celestial.systemSpawnObject(`String` typeName, [`Vec2F` position], [`Uuid` uuid], [`Json` parameters]) + +Spawns an object of typeName at position. Optionally with the specified UUID and parameters. + +If no position is specified, one is automatically chosen in a spawnable range. + +Objects are limited to be spawned outside a distance of `/systemworld.config:clientSpawnObjectPadding` from any planet surface (including moons), star surface, planetary orbit (including moons), or permanent objects orbits, and at most within `clientSpawnObjectPadding` from the outermost orbit. +--- + +#### `List` celestial.playerShips() + +Returns a list of the player ships in the current system. +--- + +#### `playerShipPosition` celestial.playerShipPosition(`Uuid` uuid) + +Returns the position of the specified player ship. +--- + +#### `Maybe` celestial.hasChildren(`CelestialCoordinate` coordinate) + +Returns definitively whether the coordinate has orbiting children. `nil` return means the coordinate is not loaded. +--- + +#### `List` celestial.children(`CelestialCoordinate` coordinate) + +Returns the children for the specified celestial coordinate. For systems, return planets, for planets, return moons. +--- + +#### `List` celestial.childOrbits(`CelestialCoordinate` coordinate) + +Returns the child orbits for the specified celestial coordinate. +--- + +#### `List` celestial.scanSystems(`RectI` region, [`Set` includedTypes]) + +Returns a list of systems in the given region. If includedTypes is specified, this will return only systems whose typeName parameter is included in the set. This scans for systems asynchronously, meaning it may not return all systems if they have not been generated or sent to the client. Use `scanRegionFullyLoaded` to see if this is the case. +--- + +#### `List>` celestial.scanConstellationLines(`RectI` region) + +Returns the constellation lines for the specified universe region. +--- + +#### `bool` celestial.scanRegionFullyLoaded(`RectI` region) + +Returns whether the specified universe region has been fully loaded. +--- + +#### `List>` celestial.centralBodyImages(`CelestialCoordinate` system) + +Returns the images with scales for the central body (star) for the specified system coordinate. +--- + +#### `List>` celestial.planetaryObjectImages(`CelestialCoordinate` coordinate) + +Returns the smallImages with scales for the specified planet or moon. +--- + +#### `List>` celestial.worldImages(`CelestialCoordinate` coordinate) + +Returns the generated world images with scales for the specified planet or moon. +--- + +#### `List>` celestial.starImages(`CelestialCoordinate` system, `float` twinkleTime) + +Returns the star image for the specified system. Requires a twinkle time to provide the correct image frame. diff --git a/doc/lua/commandprocessor.md b/doc/lua/commandprocessor.md new file mode 100644 index 0000000..941f499 --- /dev/null +++ b/doc/lua/commandprocessor.md @@ -0,0 +1,7 @@ +The command processor has a single binding for performing admin checks, available on the *CommandProcessor* table. + +--- + +#### `String` CommandProcessor.adminCheck(`ConnectionId` connectionId, `String` actionDescription) + +Checks whether the specified connection id is authorized to perform admin actions and returns `nil` if authorization is succesful. If unauthorized, returns a `String` error message to display to the client requesting the action, which may include the specified action description, such as "Insufficient privileges to do the time warp again." diff --git a/doc/lua/config.md b/doc/lua/config.md new file mode 100644 index 0000000..8268fe1 --- /dev/null +++ b/doc/lua/config.md @@ -0,0 +1,9 @@ +# config + +The `config` lua bindings relate to anything that has a configuration and needs to access configuration parameters. + +--- + +#### `Json` config.getParameter(`String` parameter, `Json` default) + +Returns the value for the specified config parameter. If there is no value set, returns the default. diff --git a/doc/lua/containerpane.md b/doc/lua/containerpane.md new file mode 100644 index 0000000..43c55fa --- /dev/null +++ b/doc/lua/containerpane.md @@ -0,0 +1,19 @@ +These pane bindings are available to container interface panes and include functions not specifically related to widgets within the pane. + +--- + +#### `EntityId` pane.containerEntityId() + +Returns the entity id of the container that this pane is connected to. + +--- + +#### `EntityId` pane.playerEntityId() + +Returns the entity id of the player that opened this pane. + +--- + +#### `void` pane.dismiss() + +Closes the pane. diff --git a/doc/lua/entity.md b/doc/lua/entity.md new file mode 100644 index 0000000..dc3ebd7 --- /dev/null +++ b/doc/lua/entity.md @@ -0,0 +1,69 @@ +# entity + +The *entity* table contains functions that are common among all entities. Every function refers to the entity the script context is running on. + +Accessible in: + +* companion system scripts +* quests +* tech +* primary status script +* status effects +* monsters +* npcs +* objects +* active items + +--- + +#### `EntityId` entity.id() + +Returns the id number of the entity. + +--- + +#### `LuaTable` entity.damageTeam() + +Returns a table of the entity's damage team type and team number. Ex: {type = "enemy", team = 0} + +--- + +#### `bool` entity.isValidTarget(`EntityId` entityId) + +Returns whether the provided entity is a valid target of the current entity. An entity is a valid target if they can be damaged, and in the case of monsters and NPCs if they are aggressive. + +--- + +#### `Vec2F` entity.distanceToEntity(`EntityId` entityId) + +Returns the vector distance from the current entity to the provided entity. + +--- + +#### `bool` entity.entityInSight(`EntityId` entityId) + +Returns whether the provided entity is in line of sight of the current entity. + +--- + +#### `Vec2F` entity.position() + +Returns the position of the current entity. + +--- + +#### `String` entity.entityType() + +Returns the type of the current entity. + +--- + +#### `String` entity.uniqueId() + +Returns the unique ID of the entity. Returns nil if there is no unique ID. + +--- + +#### `bool` entity.persistent() + +Returns `true` if the entity is persistent (will be saved to disk on sector unload) or `false` otherwise. diff --git a/doc/lua/item.md b/doc/lua/item.md new file mode 100644 index 0000000..df2d01f --- /dev/null +++ b/doc/lua/item.md @@ -0,0 +1,159 @@ +# item + +The `item` table is available in all scripted items and contains functions relating to the item itself. + +--- + +#### `String` item.name() + +Returns the name of the item. + +--- + +#### `size_t` item.count() + +Returns the stack count of the item. + +--- + +#### `size_t` item.setCount(`size_t` count) + +Sets the item count. Returns any overflow. + +--- + +#### `size_t` item.maxStack() + +Returns the max number of this item that will fit in a stack. + +--- + +#### `bool` item.matches(`ItemDescriptor` desc, [`bool` exactMatch]) + +Returns whether the item matches the specified item. If exactMatch is `true` then both the items' names and parameters are compared, otherwise only the items' names. + +--- + +#### `bool` item.consume(`size_t` count) + +Consumes items from the stack. Returns whether the full count was successfuly consumed. + +--- + +#### `bool` item.empty() + +Returns whether the item stack is empty. + +--- + +#### `ItemDescriptor` item.descriptor() + +Returns an item descriptor for the item. + +--- + +#### `String` item.description() + +Returns the description for the item. + +--- + +#### `String` item.friendlyName() + +Returns the short description for the item. + +--- + +#### `int` item.rarity() + +Returns the rarity for the item. + +* 0 = common +* 1 = uncommon +* 2 = rare +* 3 = legendary +* 4 = essential + +--- + +#### `String` item.rarityString() + +Returns the rarity as a string. + +--- + +#### `size_t` item.price() + +Returns the item price. + +--- + +#### `unsigned` item.fuelAmount() + +Returns the item fuel amount. + +--- + +#### `Json` item.iconDrawables() + +Returns a list of the item's icon drawables. + +--- + +#### `Json` item.dropDrawables() + +Returns a list of the item's itemdrop drawables. + +--- + +#### `String` item.largeImage() + +Returns the item's configured large image, if any. + +--- + +#### `String` item.tooltipKind() + +Returns the item's tooltip kind. + +--- + +#### `String` item.category() + +Returns the item's category + +--- + +#### `String` item.pickupSound() + +Returns the item's pickup sound. + +--- + +#### `bool` item.twoHanded() + +Returns whether the item is two handed. + +--- + +#### `float` item.timeToLive() + +Returns the items's time to live. + +--- + +#### `Json` item.learnBlueprintsOnPickup() + +Returns a list of the blueprints learned on picking up this item. + +--- + +#### `bool` item.hasItemTag(`String` itemTag) + +Returns whether the set of item tags for this item contains the specified tag. + +--- + +#### `Json` item.pickupQuestTemplates() + +Returns a list of quests acquired on picking up this item. diff --git a/doc/lua/localanimator.md b/doc/lua/localanimator.md new file mode 100644 index 0000000..c36fb93 --- /dev/null +++ b/doc/lua/localanimator.md @@ -0,0 +1,68 @@ +The *localAnimator* table provides bindings used by client side animation scripts (e.g. on objects and active items) to set drawables/lights and perform rendering actions. + +--- + +#### `void` localAnimator.playAudio(`String` sound, [`int` loops], [`float` volume]) + +Immediately plays the specified sound, optionally with the specified loop count and volume. + +--- + +#### `void` localAnimator.spawnParticle(`Json` particleConfig, `Vec2F` position) + +Immediately spawns a particle with the specified name or configuration at the specified position. + +--- + +#### `void` localAnimator.addDrawable(`Drawable` drawable, [`String` renderLayer]) + +Adds the specified drawable to the animator's list of drawables to be rendered. If a render layer is specified, this drawable will be drawn on that layer instead of the parent entity's render layer. Drawables set in this way are retained between script ticks and must be cleared manually using localAnimator.clearDrawables(). + +The drawable object must specify exactly one of the following keys to define its type: + +* [`pair` __line__] - Defines this drawable as a line between the specified two points. +* [`List` __poly__] - Defines the drawable as a polygon composed of the specified points. +* [`String` __image__] - Defines the drawable as an image with the specified asset path. + +The following additional keys may be specified for any drawable type: + +* [`Vec2F` __position__] - Relative position of the drawable. +* [`Color` __color__] - Color for the drawable. Defaults to white. +* [`bool` __fullbright__] - Specifies whether the drawable is fullbright (ignores world lighting). + +The following additional key may be specified for line drawables: + +* [`float` __width__] - Specifies the width of the line to be rendered. + +The following transformation options may be specified for image drawables. Note that if a __transformation__ is specified, it will be used instead of other specific transformation operations. + +* [`Mat3F` __transformation__] +* [`bool` __centered__] +* [`float` __rotation__] +* [`bool` __mirrored__] +* [`float` __scale__] + +--- + +#### `void` localAnimator.clearDrawables() + +Clears the list of drawables to be rendered. + +--- + +#### `void` localAnimator.addLightSource(`Json` lightSource) + +Adds the specified light source to the animator's list of light sources to be rendered. Light sources set in this way are retained between script ticks and must be cleared manually using localAnimator.clearLightSources(). The configuration object for the light source accepts the following keys: + +* `Vec2F` __position__ +* `Color` __color__ +* [`bool` __pointLight__] +* [`float` __pointBeam__] +* [`float` __beamAngle__] +* [`float` __beamAmbience__] + +--- + +#### `void` localAnimator.clearLightSources() + +Clears the list of light sources to be rendered. diff --git a/doc/lua/message.md b/doc/lua/message.md new file mode 100644 index 0000000..46262f3 --- /dev/null +++ b/doc/lua/message.md @@ -0,0 +1,21 @@ +The message table contains a single function, setHandler, which allows entities to receive messages sent using world.sendEntityMessage. Entities which can receive messages include: + +* monster +* NPC +* object +* vehicle +* stagehand +* projectile + +Additionally, messages can be handled by a variety of script contexts that run on the player: + +* activeitem +* quest +* playercompanions +* status + +--- + +#### `void` message.setHandler(`String` messageName, `LuaFunction` handler) + +Messages of the specified message type received by this script context will call the specified function. The first two arguments passed to the handler function will be the `String` messageName and a `bool` indicating whether the message is from a local entity, followed by any arguments sent with the message. diff --git a/doc/lua/monster.md b/doc/lua/monster.md new file mode 100644 index 0000000..dd212e5 --- /dev/null +++ b/doc/lua/monster.md @@ -0,0 +1,172 @@ +The monster table contains bindings specific to monsters which are available in addition to their common tables. + +--- + +#### `String` monster.type() + +Returns the monster's configured monster type. + +--- + +#### `String` monster.seed() + +Returns a string representation of the monster's random seed. + +--- + +#### `Json` monster.uniqueParameters() + +Returns a table of the monster's unique (override) parameters. + +--- + +#### `unsigned` monster.familyIndex() + +Returns the monster's family index. + +--- + +#### `float` monster.level() + +Returns the monster's level. + +--- + +#### `void` monster.setDamageOnTouch(`bool` enabled) + +Enables or disables the monster's touch damage. + +--- + +#### `void` monster.setDamageSources([`List` damageSources]) + +Sets the monster's active damage sources (or clears them if unspecified). + +--- + +#### `void` monster.setDamageParts(`StringSet` damageParts) + +Sets the monster's active damage parts. Damage parts must be defined in the monster's configuration parameters. A damage part specifies a damage source and an animation part to anchor the damage source to. The anchor part's transformation will be applied to the damage source's damage poly, and if a vector, the damage source's knockback. + +```js +"animationDamageParts" : { + "beam" : { + "anchorPart" : "partName", // animation part to anchor the damage source to + "checkLineCollision" : false, // optional, if the damage source is a line, check for collision along the line + "bounces" : 0, // optional, if the damage source is a line and it checks for collision + "damageSource" : { + "line" : [ [0.0, 0.0], [5.0, 0.0] ], + "damage" : 10, + "damageSourceKind" : "default", + "teamType" : "enemy", + "teamNumber" : 2 + } + } +} +``` + +```lua +monster.setDamageParts({"beam"}) -- sets the "beam" damage part active +``` + +--- + +#### `void` monster.setAggressive(`bool` aggressive) + +Sets whether the monster is currently aggressive. + +--- + +#### `void` monster.setDropPool(`Json` dropPool) + +Sets the monster's drop pool, which determines the items that it will drop on death. This can be specified as the `String` name of a treasure pool, or as a `Map` to specify different drop pools for different damage types. If specified as a map, the pool should contain a "default" entry for unhandled damage types. + +--- + +#### `Vec2F` monster.toAbsolutePosition(`Vec2F` relativePosition) + +Returns an absolute world position calculated from the given relative position. + +--- + +#### `Vec2F` monster.mouthPosition() + +Returns the world position of the monster's mouth. + +--- + +#### `void` monster.flyTo(`Vec2F` position) + +Causes the monster to controlFly toward the given world position. + +--- + +#### `void` monster.setDeathParticleBurst([`String` particleEmitter) + +Sets the name of the particle emitter (configured in the animation) to burst when the monster dies, or clears it if unspecified. + +--- + +#### `void` monster.setDeathSound([`String` sound]) + +Sets the name of the sound (configured in the animation) to play when the monster dies, or clears it if unspecified. + +--- + +#### `void` monster.setPhysicsForces(`List` forceRegions) + +Sets a list of physics force regions that the monster will project, used for applying forces to other nearby entities. Set an empty list to clear the force regions. + +--- + +#### `void` monster.setName(`String` name) + +Sets the monster's name. + +--- + +#### `void` monster.setDisplayNametag(`bool` enabled) + +Sets whether the monster should display its nametag. + +--- + +#### `bool` monster.say(`String` line, [`Map` tags]) + +Causes the monster to say the line, optionally replacing any specified tags in the text. Returns `true` if anything is said (i.e. the line is not empty) and `false` otherwise. + +--- + +#### `bool` monster.sayPortrait(`String` line, `String` portrait, [`Map` tags]) + +Similar to monster.say, but uses a portrait chat bubble with the specified portrait image. + +--- + +#### `void` monster.setDamageTeam(`DamageTeam` team) + +Sets the monster's current damage team type and number. + +--- + +#### `void` monster.setUniqueId([`String` uniqueId]) + +Sets the monster's unique entity id, or clears it if unspecified. + +--- + +#### `void` monster.setDamageBar(`String` damageBarType) + +Sets the type of damage bar that the monster should display. Valid options are "default", "none" and "special". + +--- + +#### `void` monster.setInteractive(`bool` interactive) + +Sets whether the monster is currently interactive. + +--- + +#### `void` monster.setAnimationParameter(`String` key, `Json` value) + +Sets a networked scripted animator parameter to be used in a client side rendering script using animationConfig.getParameter. diff --git a/doc/lua/movementcontroller.md b/doc/lua/movementcontroller.md new file mode 100644 index 0000000..dc67115 --- /dev/null +++ b/doc/lua/movementcontroller.md @@ -0,0 +1,248 @@ +# mcontroller + +The `mcontroller` table contains functions relating to the movement controller. + +This section of mcontroller documentation refers to the MovementController lua bindings. Other documentation may refer to ActorMovementController lua bindings. MovementController is used in: + +* projectiles +* vehicles + +--- + +#### `MovementParameters` mcontroller.parameters() + +Returns a table containing the movement parameters for the movement controller. + +--- + +#### `void` mcontroller.applyParameters(`Json` parameters) + +Applies the given parameters to the movement controller. The provided parameters are merged into the current movement parameters. + +--- + +#### `void` mcontroller.resetParameters() + +Resets movement parameters to their original state. + +--- + +#### `float` mcontroller.mass() + +Returns the configured mass of the movement controller. + +--- + +#### `Vec2F` mcontroller.position() + +Returns the current position of the movement controller. + +--- + +#### `float` mcontroller.xPosition() + +Returns the current horizontal position of the movement controller. + +--- + +#### `float` mcontroller.yPosition() + +Returns the current vertical position of the movement controller. + +--- + +#### `Vec2F` mcontroller.velocity() + +Returns the current velocity of the movement controller. + +--- + +#### `float` mcontroller.xVelocity() + +Returns the current horizontal speed of the movement controller. + +--- + +#### `float` mcontroller.yVelocity() + +Returns the current vertical speed of the movement controller. + +--- + +#### `float` mcontroller.rotation() + +Returns the current rotation of the movement controller in radians. + +--- + +#### `PolyF` mcontroller.collisionPoly() + +Returns the collision poly of the movement controller, in local coordinates. + +--- + +#### `PolyF` mcontroller.collisionBody() + +Returns the collision poly of the movement controller, in world coordinates. + +--- + +#### `RectF` mcontroller.collisionBoundBox() + +Returns a rect containing the entire collision poly of the movement controller, in world coordinates. + +--- + +#### `RectF` mcontroller.localBoundBox() + +Returns a rect containing the entire collision of the movement controller, in local coordinates. + +--- + +#### `bool` mcontroller.isColliding() + +Returns whether the movement controller is currently colliding with world geometry or a PhysicsMovingCollision. + +--- + +#### `bool` mcontroller.isNullColliding() + +Returns whether the movement controller is currently colliding with null world geometry. Null collision occurs in unloaded sectors. + +--- + +#### `bool` mcontroller.isCollisionStuck() + +Returns whether the movement controller is currently stuck colliding. Movement controllers can stick if the `stickyCollision` movement parameter is set. + +--- + +#### `float` mcontroller.stickingDirection() + +Returns the angle that the movement controller is currently stuck at, in radians. + +--- + +#### `float` mcontroller.liquidPercentage() + +Returns the percentage of the collision poly currently submerged in liquid; + +--- + +#### `LiquidId` mcontroller.liquidId() + +Returns the liquid ID of the liquid that the movement controller is currently submerged in. If this is several liquids this returns the most plentiful one. + +--- + +#### `bool` mcontroller.onGround() + +Returns whether the movement controller is currently on ground. + +--- + +#### `bool` mcontroller.zeroG() + +Returns `true` if the movement controller is at a world position without gravity or if gravity has been disabled. + +--- + +#### `bool` mcontroller.atWorldLimit([`bool` bottomOnly]) + +Returns `true` if the movement controller is touching the bottom or the top (unless bottomOnly is specified) of the world. + +--- + +#### `void` mcontroller.setPosition(`Vec2F` position) + +Sets the position of the movement controller. + +--- + +#### `void` mcontroller.setXPosition(`float` x) + +Sets the horizontal position of the movement controller. + +--- + +#### `void` mcontroller.setYPosition(`float` y) + +Sets the vertical position of the movement controller. + +--- + +#### `void` mcontroller.translate(`Vec2F` direction) + +Moves the movement controller by the vector provided. + +--- + +#### `void` mcontroller.setVelocity(`Vec2F` velocity) + +Sets the velocity of the movement controller. + +--- + +#### `void` mcontroller.setXVelocity(`Vec2F` xVelocity) + +Sets the horizontal velocity of the movement controller. + +--- + +#### `void` mcontroller.setYVelocity(`Vec2F` yVelocity) + +Sets the vertical velocity of the movement controller. + +--- + +#### `void` mcontroller.addMomentum(`Vec2F` momentum) + +Adds (momentum / mass) velocity to the movement controller. + +--- + +#### `void` mcontroller.setRotation(`float` angle) + +Sets the rotation of the movement controller. Angle is in radians. + +--- + +#### `void` mcontroller.rotate(`float` angle) + +Rotates the movement controller by an angle relative to its current angle. Angle is in radians. + +--- + +#### `void` mcontroller.accelerate(`Vec2F` acceleration) + +Accelerates the movement controller by the given acceleration for one tick. + +--- + +#### `void` mcontroller.force(`Vec2F` force) + +Accelerates the movement controller by (force / mass) for one tick. + +--- + +#### `void` mcontroller.approachVelocity(`Vec2F` targetVelocity, `float` maxControlForce) + +Approaches the targetVelocity using the force provided. If the current velocity is higher than the provided targetVelocity, the targetVelocity will still be approached, effectively slowing down the entity. + +--- + +#### `void` mcontroller.approachVelocityAlongAngle(`float` angle, `float` targetVelocity, `float` maxControlForce, `bool` positiveOnly = false) + +Approaches the targetVelocity but only along the provided angle, not affecting velocity in the perpendicular axis. If positiveOnly, then it will not slow down the movementController if it is already moving faster than targetVelocity. + +--- + +#### `void` mcontroller.approachXVelocity(`float` targetVelocity, `float` maxControlForce) + +Approaches an X velocity. Same as using approachVelocityAlongAngle with angle 0. + +--- + +#### `void` mcontroller.approachYVelocity(`float` targetVelocity, `float` maxControlForce) + +Approaches a Y velocity. Same as using approachVelocityAlongAngle with angle (Pi / 2). diff --git a/doc/lua/npc.md b/doc/lua/npc.md new file mode 100644 index 0000000..c7160ff --- /dev/null +++ b/doc/lua/npc.md @@ -0,0 +1,283 @@ +# npc + +The `npc` table is for functions relating directly to the current npc. It is available only in NPC scripts. + +--- + +#### `Vec2F` npc.toAbsolutePosition(`Vec2F` offset) + +Returns the specified local position in world space. + +--- + +#### `String` npc.species() + +Returns the species of the npc. + +--- + +#### `String` npc.gender() + +Returns the gender of the npc + +--- + +#### `Json` npc.humanoidIdentity() + +Returns the specific humanoid identity of the npc, containing information such as hair style and idle pose. + +--- + +#### `String` npc.npcType() + +Returns the npc type of the npc. + +--- + +#### `uint64_t` npc.seed() + +Returns the seed used to generate this npc. + +--- + +#### `float` npc.level() + +Returns the level of the npc. + +--- + +#### `List` npc.dropPools() + +Returns the list of treasure pools that will spawn when the npc dies. + +--- + +#### `void` npc.setDropPools(`List` pools) + +Sets the list of treasure pools that will spawn when the npc dies. + +--- + +#### `float` npc.energy() + +Returns the current energy of the npc. Same as `status.resource("energy")` + +--- + +#### `float` npc.maxEnergy() + +Returns the current energy of the npc. Same as `status.maxResource("energy")` + +--- + +#### `bool` npc.say(`String` line, [`Map` tags], [`Json` config]) + +Makes the npc say a string. Optionally pass in tags to replace text tags. Optionally give config options for the chat message. + +Returns whether the chat message was successfully added. + +Available options: +``` +{ + drawBorder = true, + fontSize = 8, + color = {255, 255, 255}, + sound = "/sfx/humanoid/avian_chatter_male1.ogg" +} +``` + +--- + +#### `bool` npc.sayPortrait(`String` line, `String` portrait, [`Map` tags], [`Json` config]) + +Makes the npc say a line, with a portrait chat bubble. Optionally pass in tags to replace text tags. Optionally give config options for the chat message. +Returns whether the chat message was successfully added. + +Available options: +``` +{ + drawMoreIndicator = true, + sound = "/sfx/humanoid/avian_chatter_male1.ogg" +} +``` + +--- + +#### `void` npc.emote(`String` emote) + +Makes the npc show a facial emote. + +--- + +#### `void` npc.dance(`String` dance) + +Sets the current dance for the npc. Dances are defined in .dance files. + +--- + +#### `void` npc.setInteractive(`bool` interactive) + +Sets whether the npc should be interactive. + +--- + +#### `bool` npc.setLounging(`EntityId` loungeable, [`size_t` anchorIndex]) + +Sets the npc to lounge in a loungeable. Optionally specify which anchor (seat) to use. +Returns whether the npc successfully lounged. + +--- + +#### `void` npc.resetLounging() + +Makes the npc stop lounging. + +--- + +#### `bool` npc.isLounging() + +Returns whether the npc is currently lounging. + +--- + +#### `Maybe` npc.loungingIn() + +Returns the EntityId of the loungeable the NPC is currently lounging in. Returns nil if not lounging. + +--- + +#### `void` npc.setOfferedQuests(`JsonArray` quests) + +Sets the list of quests the NPC will offer. + +--- + +#### `void` npc.setTurnInQuests(`JsonArray` quests) + +Sets the list of quests the played can turn in at this npc. + +--- + +#### `bool` npc.setItemSlot(`String` slot, `ItemDescriptor` item) + +Sets the specified item slot to contain the specified item. + +Possible equipment items slots: +* head +* headCosmetic +* chest +* chestCosmetic +* legs +* legsCosmetic +* back +* backCosmetic +* primary +* alt + +--- + +#### `ItemDescriptor` npc.getItemSlot(`String` slot) + +Returns the item currently in the specified item slot. + +--- + +#### `void` npc.disableWornArmor(`bool` disable) + +Set whether the npc should not gain status effects from the equipped armor. Armor will still be visually equipped. + +--- + +#### `void` npc.beginPrimaryFire() + +Toggles `on` firing the item equipped in the `primary` item slot. + +--- + +#### `void` npc.beginAltFire() + +Toggles `on` firing the item equipped in the `alt` item slot. + +--- + +#### `void` npc.endPrimaryFire() + +Toggles `off` firing the item equipped in the `primary` item slot. + +--- + +#### `void` npc.endAltFire() + +Toggles `off` firing the item equipped in the `alt` item slot. + +--- + +#### `void` npc.setShifting(`bool` shifting) + +Sets whether tools should be used as though shift is held. + +--- + +#### `void` npc.setDamageOnTouch(`bool` enabled) + +Sets whether damage on touch should be enabled. + +--- + +#### `Vec2F` npc.aimPosition() + +Returns the current aim position in world space. + +--- + +#### `void` npc.setAimPosition(`Vec2F` position) + +Sets the aim position in world space. + +--- + +#### `void` npc.setDeathParticleBurst(`String` emitter) + +Sets a particle emitter to burst when the npc dies. + +--- + +#### `void` npc.setStatusText(`String` status) + +Sets the text to appear above the npc when it first appears on screen. + +--- + +#### `void` npc.setDisplayNametag(`bool` display) + +Sets whether the nametag should be displayed above the NPC. + +--- + +#### `void` npc.setPersistent(`bool` persistent) + +Sets whether this npc should persist after being unloaded. + +--- + +#### `void` npc.setKeepAlive(`bool` keepAlive) + +Sets whether to keep this npc alive. If true, the npc will never be unloaded as long as the world is loaded. + +--- + +#### `void` npc.setDamageTeam(`Json` damageTeam) + +Sets a damage team for the npc in the format: `{type = "enemy", team = 2}` + +--- + +#### `void` npc.setAggressive(`bool` aggressive) + +Sets whether the npc should be flagged as aggressive. + +--- + +#### `void` npc.setUniqueId(`String` uniqueId) + +Sets a unique ID for this npc that can be used to access it. A unique ID has to be unique for the world the npc is on, but not universally unique. diff --git a/doc/lua/object.md b/doc/lua/object.md new file mode 100644 index 0000000..00dba68 --- /dev/null +++ b/doc/lua/object.md @@ -0,0 +1,229 @@ +The object table contains bindings specific to objects which are available in addition to their common tables. + +--- + +#### `String` object.name() + +Returns the object's type name. + +--- + +#### `int` object.direction() + +Returns the object's facing direction. This will be 1 for right or -1 for left. + +--- + +#### `Vec2F` object.position() + +Returns the object's tile position. This is identical to entity.position(), so use that instead. + +--- + +#### `void` object.setInteractive(`bool` interactive) + +Sets whether the object is currently interactive. + +--- + +#### `String` object.uniqueId() + +Returns the object's unique entity id, or `nil` if no unique id is set. This should be identical to entity.uniqueId(), so use that instead. + +--- + +#### `void` object.setUniqueId([`String` uniqueId]) + +Sets the objects unique entity id, or clears it if unspecified. + +--- + +#### `RectF` object.boundBox() + +Returns the object's metaBoundBox in world space. + +--- + +#### `List` object.spaces() + +Returns a list of the tile spaces that the object occupies. + +--- + +#### `void` object.setProcessingDirectives(`String` directives) + +Sets the image processing directives that should be applied to the object's animation. + +--- + +#### `void` object.setSoundEffectEnabled(`bool` enabled) + +Enables or disables the object's persistent sound effect, if one is configured. + +--- + +#### `void` object.smash([`bool` smash]) + +Breaks the object. If smash is `true` then it will be smashed, causing it to (by default) drop no items. + +--- + +#### `float` object.level() + +Returns the "level" parameter if set, otherwise returns the current world's threat level. + +--- + +#### `Vec2F` object.toAbsolutePosition(`Vec2F` relativePosition) + +Returns an absolute world position calculated from the given relative position. + +--- + +#### `bool` object.say(`String` line, [`Map` tags], [`Json` config]) + +Causes the object to say the line, optionally replacing any specified tags in the text, and using the provided additional chat configuration. Returns `true` if anything is said (i.e. the line is not empty) and `false` otherwise. + +--- + +#### `bool` object.sayPortrait(`String` line, `String` portrait, [`Map` tags], [`Json` config]) + +Similar to object.say, but uses a portrait chat bubble with the specified portrait image. + +--- + +#### `bool` object.isTouching(`EntityId` entityId) + +Returns `true` if the specified entity's collision area overlaps the object's bound box and `false` otherwise. + +--- + +#### `void` object.setLightColor(`Color` color) + +Sets the color of light for the object to emit. This is not the same as animator.setLightColor and the animator light configuration should be used for more featureful light sources. + +--- + +#### `Color` object.getLightColor() + +Returns the object's currently configured light color. + +--- + +#### `unsigned` object.inputNodeCount() + +Returns the number of wire input nodes the object has. + +--- + +#### `unsigned` object.outputNodeCount() + +Returns the number of wire output nodes the object has. + +--- + +#### `Vec2I` object.getInputNodePosition(`unsigned` nodeIndex) + +Returns the relative position of the specified wire input node. + +--- + +#### `Vec2I` object.getOutputNodePosition(`unsigned` nodeIndex) + +Returns the relative position of the specified wire output node. + +--- + +#### `bool` object.getInputNodeLevel(`unsigned` nodeIndex) + +Returns the current level of the specified wire input node. + +--- + +#### `bool` object.getOutputNodeLevel(`unsigned` nodeIndex) + +Returns the current level of the specified wire output node. + +--- + +#### `bool` object.isInputNodeConnected(`unsigned` nodeIndex) + +Returns `true` if any wires are currently connected to the specified wire input node and `false` otherwise. + +--- + +#### `bool` object.isOutputNodeConnected(`unsigned` nodeIndex) + +Returns `true` if any wires are currently connected to the specified wire output node and `false` otherwise + +--- + +#### `Map` object.getInputNodeIds(`unsigned` nodeIndex) + +Returns a map of the entity id of each wire entity connected to the given wire input node and the index of that entity's output node to which the input node is connected. + +--- + +#### `Map` object.getOutputNodeIds(`unsigned` nodeIndex) + +Returns a map of the entity id of each wire entity connected to the given wire output node and the index of that entity's input node to which the output node is connected. + +--- + +#### `void` object.setOutputNodeLevel(`unsigned` nodeIndex, `bool` level) + +Sets the level of the specified wire output node. + +--- + +#### `void` object.setAllOutputNodes(`bool` level) + +Sets the level of all wire output nodes. + +--- + +#### `void` object.setOfferedQuests([`JsonArray` quests]) + +Sets the list of quests that the object will offer to start, or clears them if unspecified. + +--- + +#### `void` object.setTurnInQuests([`JsonArray` quests]) + +Sets the list of quests that the object will accept turn-in for, or clears them if unspecified. + +--- + +#### `void` object.setConfigParameter(`String` key, `Json` value) + +Sets the specified override configuration parameter for the object. + +--- + +#### `void` object.setAnimationParameter(`String` key, `Json` value) + +Sets the specified animation parameter for the object's scripted animator. + +--- + +#### `void` object.setMaterialSpaces([`JsonArray` spaces]) + +Sets the object's material spaces to the specified list, or clears them if unspecified. List entries should be in the form of `pair` specifying the relative position and material name of materials to be set. __Objects should only set material spaces within their occupied tile spaces to prevent Bad Things TM from happening.__ + +--- + +#### `void` object.setDamageSources([`List` damageSources]) + +Sets the object's active damage sources (or clears them if unspecified). + +--- + +#### `float` object.health() + +Returns the object's current health. + +--- + +#### `void` object.setHealth(`float` health) + +Sets the object's current health. diff --git a/doc/lua/objectanimator.md b/doc/lua/objectanimator.md new file mode 100644 index 0000000..1c09866 --- /dev/null +++ b/doc/lua/objectanimator.md @@ -0,0 +1,19 @@ +The objectAnimator table contains bindings available to client-side animation scripts for objects. + +--- + +#### `Json` objectAnimator.getParameter(`String` parameter, `Json` default) + +Returns the value for the specified object parameter. If there is no value set, returns the default. + +--- + +#### `int` objectAnimator.direction() + +Returns the object's facing direction. This will be 1 for right or -1 for left. + +--- + +#### `Vec2F` objectAnimator.position() + +Returns the object's tile position. diff --git a/doc/lua/physics.md b/doc/lua/physics.md new file mode 100644 index 0000000..b5a68d8 --- /dev/null +++ b/doc/lua/physics.md @@ -0,0 +1,19 @@ +The physics table is available to objects and used to control any collisions or force regions configured on those objects. + +--- + +#### `void` physics.setForceEnabled(`String` force, `bool` enabled) + +Enables or disables the specified physics force region. + +--- + +#### `void` physics.setCollisionPosition(`String` collision, `Vec2F` position) + +Moves the specified physics collision region to the specified position. + +--- + +#### `void` physics.setCollisionEnabled(`String` collision, `bool` enabled) + +Enables or disables the specified physics collision region. diff --git a/doc/lua/player.md b/doc/lua/player.md new file mode 100644 index 0000000..fd8ebd4 --- /dev/null +++ b/doc/lua/player.md @@ -0,0 +1,529 @@ +The player table contains functions with privileged access to the player which run in a few contexts on the client such as scripted interface panes, quests, and player companions. + +--- + +#### `EntityId` player.id() + +Returns the player's entity id. + +--- + +#### `String` player.uniqueId() + +Returns the player's unique id. + +--- + +#### `String` player.species() + +Returns the player's species. + +--- + +#### `String` player.gender() + +Returns the player's gender. + +--- + +#### `String` player.isAdmin() + +Returns whether the player is admin. + +--- + +#### `Json` player.getProperty(`String` name, `Json` default) + +Returns the value assigned to the specified generic player property. If there is no value set, returns default. + +--- + +#### `void` player.setProperty(`String` name, `Json` value) + +Sets a generic player property to the specified value. + +--- + +#### `void` player.addScannedObject(`String` name) + +Adds the specified object to the player's scanned objects. + +--- + +#### `void` player.removeScannedObject(`String` name) + +Removes the specified object from the player's scanned objects. + +--- + +#### `void` player.interact(`String` interactionType, `Json` config, [`EntityId` sourceEntityId]) + +Triggers an interact action on the player as if they had initiated an interaction and the result had returned the specified interaction type and configuration. Can be used to e.g. open GUI windows normally triggered by player interaction with entities. + +--- + +#### `Json` player.shipUpgrades() + +Returns a JSON object containing information about the player's current ship upgrades including "shipLevel", "maxFuel", "crewSize" and a list of "capabilities". + +--- + +#### `void` player.upgradeShip(`Json` shipUpgrades) + +Applies the specified ship upgrades to the player's ship. + +--- + +#### `void` player.setUniverseFlag(`String` flagName) + +Sets the specified universe flag on the player's current universe. + +--- + +#### `void` player.giveBlueprint(`ItemDecriptor` item) + +Teaches the player any recipes which can be used to craft the specified item. + +--- + +#### `void` player.blueprintKnown(`ItemDecriptor` item) + +Returns `true` if the player knows one or more recipes to create the specified item and `false` otherwise. + +--- + +#### `void` player.makeTechAvailable(`String` tech) + +Adds the specified tech to the player's list of available (unlockable) techs. + +--- + +#### `void` player.makeTechUnavailable(`String` tech) + +Removes the specified tech from player's list of available (unlockable) techs. + +--- + +#### `void` player.enableTech(`String` tech) + +Unlocks the specified tech, allowing it to be equipped through the tech GUI. + +--- + +#### `void` player.equipTech(`String` tech) + +Equips the specified tech. + +--- + +#### `void` player.unequipTech(`String` tech) + +Unequips the specified tech. + +--- + +#### `JsonArray` player.availableTechs() + +Returns a list of the techs currently available to the player. + +--- + +#### `JsonArray` player.enabledTechs() + +Returns a list of the techs currently unlocked by the player. + +--- + +#### `String` player.equippedTech(`String` slot) + +Returns the name of the tech the player has currently equipped in the specified slot, or `nil` if no tech is equipped in that slot. + +--- + +#### `unsigned` player.currency(`String` currencyName) + +Returns the player's current total reserves of the specified currency. + +--- + +#### `void` player.addCurrency(`String` currencyName, `unsigned` amount) + +Increases the player's reserve of the specified currency by the specified amount. + +--- + +#### `bool` player.consumeCurrency(`String` currencyName, `unsigned` amount) + +Attempts to consume the specified amount of the specified currency and returns `true` if successful and `false` otherwise. + +--- + +#### `void` player.cleanupItems() + +Triggers an immediate cleanup of the player's inventory, removing item stacks with 0 quantity. May rarely be required in special cases of making several sequential modifications to the player's inventory within a single tick. + +--- + +#### `void` player.giveItem(`ItemDescriptor` item) + +Adds the specified item to the player's inventory. + +--- + +#### `bool` player.hasItem(`ItemDescriptor` item, [`bool` exactMatch]) + +Returns `true` if the player's inventory contains an item matching the specified descriptor and `false` otherwise. If exactMatch is `true` then parameters as well as item name must match. + +--- + +#### `unsigned` player.hasCountOfItem(`ItemDescriptor` item, [`bool` exactMatch]) + +Returns the total number of items in the player's inventory matching the specified descriptor. If exactMatch is `true` then parameters as well as item name must match. + +--- + +#### `ItemDescriptor` player.consumeItem(`ItemDescriptor` item, [`bool` consumePartial], [`bool` exactMatch]) + +Attempts to consume the specified item from the player's inventory and returns the item consumed if successful. If consumePartial is `true`, matching stacks totalling fewer items than the requested count may be consumed, otherwise the operation will only be performed if the full count can be consumed. If exactMatch is `true` then parameters as well as item name must match. + +--- + +#### `Map` player.inventoryTags() + +Returns a summary of all tags of all items in the player's inventory. Keys in the returned map are tag names and their corresponding values are the total count of items including that tag. + +--- + +#### `JsonArray` player.itemsWithTag(`String` tag) + +Returns a list of `ItemDescriptor`s for all items in the player's inventory that include the specified tag. + +--- + +#### `void` player.consumeTaggedItem(`String` tag, `unsigned` count) + +Consumes items from the player's inventory that include the matching tag, up to the specified count of items. + +--- + +#### `bool` player.hasItemWithParameter(`String` parameter, `Json` value) + +Returns `true` if the player's inventory contains at least one item which has the specified parameter set to the specified value. + +--- + +#### `void` player.consumeItemWithParameter(`String` parameter, `Json` value, `unsigned` count) + +Consumes items from the player's inventory that have the specified parameter set to the specified value, upt to the specified count of items. + +--- + +#### `ItemDescriptor` player.getItemWithParameter(`String` parameter, `Json` value) + +Returns the first item in the player's inventory that has the specified parameter set to the specified value, or `nil` if no such item is found. + +--- + +#### `ItemDescriptor` player.primaryHandItem() + +Returns the player's currently equipped primary hand item, or `nil` if no item is equipped. + +--- + +#### `ItemDescriptor` player.altHandItem() + +Returns the player's currently equipped alt hand item, or `nil` if no item is equipped. + +--- + +#### `JsonArray` player.primaryHandItemTags() + +Returns a list of the tags on the currently equipped primary hand item, or `nil` if no item is equipped. + +--- + +#### `JsonArray` player.altHandItemTags() + +Returns a list of the tags on the currently equipped alt hand item, or `nil` if no item is equipped. + +--- + +#### `ItemDescriptor` player.essentialItem(`String` slotName) + +Returns the contents of the specified essential slot, or `nil` if the slot is empty. Essential slot names are "beamaxe", "wiretool", "painttool" and "inspectiontool". + +--- + +#### `void` player.giveEssentialItem(`String` slotName, `ItemDescriptor` item) + +Sets the contents of the specified essential slot to the specified item. + +--- + +#### `void` player.removeEssentialItem(`String` slotName) + +Removes the essential item in the specified slot. + +--- + +#### `ItemDescriptor` player.equippedItem(`String` slotName) + +Returns the contents of the specified equipment slot, or `nil` if the slot is empty. Equipment slot names are "head", "chest", "legs", "back", "headCosmetic", "chestCosmetic", "legsCosmetic" and "backCosmetic". + +--- + +#### `void` player.setEquippedItem(`String` slotName, `Json` item) + +Sets the item in the specified equipment slot to the specified item. + +--- + +#### `ItemDescriptor` player.swapSlotItem() + +Returns the contents of the player's swap (cursor) slot, or `nil` if the slot is empty. + +--- + +#### `void` player.setSwapSlotItem(`Json` item) + +Sets the item in the player's swap (cursor) slot to the specified item. + +--- + +#### `bool` player.canStartQuest(`Json` questDescriptor) + +Returns `true` if the player meets all of the prerequisites to start the specified quest and `false` otherwise. + +--- + +#### `QuestId` player.startQuest(`Json` questDescriptor, [`String` serverUuid], [`String` worldId]) + +Starts the specified quest, optionally using the specified server Uuid and world id, and returns the quest id of the started quest. + +--- + +#### `bool` player.hasQuest(`String` questId) + +Returns `true` if the player has a quest, in any state, with the specified quest id and `false` otherwise. + +--- + +#### `bool` player.hasAcceptedQuest(`String` questId) + +Returns `true` if the player has accepted a quest (which may be active, completed, or failed) with the specified quest id and `false` otherwise. + +--- + +#### `bool` player.hasActiveQuest(`String` questId) + +Returns `true` if the player has a currently active quest with the specified quest id and `false` otherwise. + +--- + +#### `bool` player.hasCompletedQuest(`String` questId) + +Returns `true` if the player has a completed quest with the specified quest id and `false` otherwise. + +--- + +#### `Maybe` player.currentQuestWorld() + +If the player's currently tracked quest has an associated world, returns the id of that world. + +--- + +#### `List>` player.questWorlds() + +Returns a list of world ids for worlds relevant to the player's current quests, along with a boolean indicating whether that quest is tracked. + +--- + +#### `Maybe` player.currentQuestLocation() + +If the player's currently tracked quest has an associated location (CelestialCoordinate, system orbit, UUID, or system position) returns that location. + +--- + +#### `List>` player.questLocations() + +Returns a list of locations for worlds relevant to the player's current quests, along with a boolean indicating whether that quest is tracked. + +--- + +#### `void` player.enableMission(`String` missionName) + +Adds the specified mission to the player's list of available missions. + +--- + +#### `void` player.completeMission(`String` missionName) + +Adds the specified mission to the player's list of completed missions. + +--- + +#### `void` player.hasCompletedMission(`String` missionName) + +Returns whether the player has completed the specified mission. + +--- + +#### `void` player.radioMessage(`Json` messageConfig, [`float` delay]) + +Triggers the specified radio message for the player, either immediately or with the specified delay. + +--- + +#### `String` player.worldId() + +Returns a `String` representation of the world id of the player's current world. + +--- + +#### `String` player.serverUuid() + +Returns a `String` representation of the player's Uuid on the server. + +--- + +#### `String` player.ownShipWorldId() + +Returns a `String` representation of the world id of the player's ship world. + +--- + +#### `bool` player.lounge(`EntityId` loungeableId, [`unsigned` anchorIndex]) + +Triggers the player to lounge in the specified loungeable entity at the specified lounge anchor index (default is 0). + +--- + +#### `bool` player.isLounging() + +Returns `true` if the player is currently occupying a loungeable entity and `false` otherwise. + +--- + +#### `EntityId` player.loungingIn() + +If the player is currently lounging, returns the entity id of what they are lounging in. + +--- + +#### `double` player.playTime() + +Returns the total played time for the player. + +--- + +#### `bool` player.introComplete() + +Returns `true` if the player is marked as having completed the intro instance and `false` otherwise. + +--- + +#### `void` player.setIntroComplete(`bool` complete) + +Sets whether the player is marked as having completed the intro instance. + +--- + +#### `void` player.warp(`String` warpAction, [`String` animation], [`bool` deploy]) + +Immediately warps the player to the specified warp target, optionally using the specified warp animation and deployment. + +--- + +#### `bool` player.canDeploy() + +Returns whether the player has a deployable mech. + +--- + +#### `bool` player.isDeployed() + +Returns whether the player is currently deployed. + +--- + +#### `RpcPromise` player.confirm(`Json` dialogConfig) + +Displays a confirmation dialog to the player with the specified dialog configuration and returns an `RpcPromise` which can be used to retrieve the player's response to that dialog. + +--- + +#### `void` player.playCinematic(`Json` cinematic, [`bool` unique]) + +Triggers the specified cinematic to be displayed for the player. If unique is `true` the cinematic will only be shown to that player once. + +--- + +#### `void` player.recordEvent(`String` event, `Json` fields) + +Triggers the specified event on the player with the specified fields. Used to record data e.g. for achievements. + +--- + +#### `bool` player.worldHasOrbitBookmark(`Json` coordinate) + +Returns whether the player has a bookmark for the specified celestial coordinate. + +--- + +#### `List>` player.orbitBookmarks() + +Returns a list of orbit bookmarks with their system coordinates. + +--- + +#### `List` player.systemBookmarks(`Json` systemCoordinate) + +Returns a list of orbit bookmarks in the specified system. + +--- + +#### `bool` player.addOrbitBookmark(`Json` systemCoordinate, `Json` bookmarkConfig) + +Adds the specified bookmark to the player's bookmark list and returns `true` if the bookmark was successfully added (and was not already known) and `false` otherwise. + +--- + +#### `bool` player.removeOrbitBookmark(`Json` systemCoordinate, `Json` bookmarkConfig) + +Removes the specified bookmark from the player's bookmark list and returns `true` if the bookmark was successfully removed and `false` otherwise. + +--- + +#### `List` player.teleportBookmarks() + +Lists all of the player's teleport bookmarks. + +--- + +#### `bool` player.addTeleportBookmark(`Json` bookmarkConfig) + +Adds the specified bookmark to the player's bookmark list and returns `true` if the bookmark was successfully added (and was not already known) and `false` otherwise. + +--- + +#### `bool` player.removeTeleportBoookmark(`Json` bookmarkConfig) + +Removes the specified teleport bookmark. + +--- + +#### `bool` player.isMapped(`Json` coordinate) + +Returns whether the player has previously visited the specified coordinate. + +--- + +#### `Json` player.mappedObjects(`Json` systemCoordinate) + +Returns uuid, type, and orbits for all system objects in the specified system; + +--- + +#### `List` player.collectables(`String` collectionName) + +Returns a list of names of the collectables the player has unlocked in the specified collection. diff --git a/doc/lua/playercompanions.md b/doc/lua/playercompanions.md new file mode 100644 index 0000000..298a85a --- /dev/null +++ b/doc/lua/playercompanions.md @@ -0,0 +1,13 @@ +The playerCompanions table contains bindings used to manage player companions such as pets and crew members. + +--- + +#### `JsonArray` playerCompanions.getCompanions(`String` companionType) + +Returns a list of configurations for all companions of the specified type. + +--- + +#### `void` playerCompanions.setCompanions(`String` companionType, `JsonArray` companions) + +Sets the player's companions of the specified type to the specified list of companion configurations. diff --git a/doc/lua/projectile.md b/doc/lua/projectile.md new file mode 100644 index 0000000..8552cab --- /dev/null +++ b/doc/lua/projectile.md @@ -0,0 +1,67 @@ +The projectile table contains bindings specific to projectiles which are available in addition to their common tables. + +--- + +#### `Json` projectile.getParameter(`String` parameter, `Json` default) + +Returns the value for the specified config parameter. If there is no value set, returns the default. + +--- + +#### `void` projectile.die() + +Destroys the projectile. + +--- + +#### `EntityId` projectile.sourceEntity() + +Returns the entity id of the projectile's source entity, or `nil` if no source entity is set. + +--- + +#### `float` projectile.powerMultiplier() + +Returns the projectile's power multiplier. + +--- + +#### `float` projectile.power() + +Returns the projectile's power (damage). + +--- + +#### `void` projectile.setPower(`float` power) + +Sets the projectile's power (damage). + +--- + +#### `float` projectile.timeToLive() + +Returns the projectile's current remaining time to live. + +--- + +#### `void` projectile.setTimeToLive(`float` timeToLive) + +Sets the projectile's current remaining time to live. Altering the time to live may cause visual disparity between the projectile's master and slave entities. + +--- + +#### `bool` projectile.collision() + +Returns `true` if the projectile has collided and `false` otherwise. + +--- + +#### `void` projectile.processAction(`Json` action) + +Immediately performs the specified action. Action should be specified in a format identical to a single entry in e.g. actionOnReap in the projectile's configuration. This function will not properly perform rendering actions as they will not be networked. + +--- + +#### 'void' projectile.setReferenceVelocity(Maybe<`Vec2F`> velocity) + +Sets the projectile's reference velocity (a base velocity to which movement is relative) diff --git a/doc/lua/quest.md b/doc/lua/quest.md new file mode 100644 index 0000000..68010c0 --- /dev/null +++ b/doc/lua/quest.md @@ -0,0 +1,203 @@ +# quest + +The `quest` table contains functions relating directly to the quest whose script its run in. + +--- + +#### `String` quest.state() + +Returns the current state of the quest. + +Possible states: +* "New" +* "Offer" +* "Active" +* "Complete" +* "Failed" + +--- + +#### `void` quest.complete() + +Immediately completes the quest. + +--- + +#### `void` quest.fail() + +Immediately fails the quest. + +--- + +#### `void` quest.setCanTurnIn(`bool` turnIn) + +Sets whether the quest can be turned in. + +--- + +#### `String` quest.questId() + +Returns the quest id. + +--- + +#### `String` quest.templateId() + +Returns the ID of the template used to make this quest. + +--- + +#### `uint64_t` quest.seed() + +Returns the seed used to generate the quest. + +--- + +#### `Json` quest.questDescriptor() + +Returns the quest descriptor including parameters. + +--- + +#### `Json` quest.questArcDescriptor() + +Returns the quest arc descriptor. + +--- + +#### `Vec2F` quest.questArcPosition() + +Returns the quest arc position. (?) + +--- + +#### `String` quest.worldId() + +Returns the world id for the quest arc. + +--- + +#### `String` quest.serverUuid() + +Returns the server uuid for the quest. + +--- + +#### `QuestParameters` quest.parameters() + +Returns all quest parameters. + +--- + +#### `void` quest.setParameter(`String` name, `Json` value) + +Sets a quest parameter. + +--- + +#### `void` quest.setIndicators(`List` indicators) + +Set a list of quest parameters to use as custom indicators. + +Example: +``` +-- define indicators +local entityIndicator = { + type = "entity", + uniqueId = "techscientist" +} +local itemIndicator = { + type = "item", + item = "perfectlygenericitem" +} +local itemTagIndicator = { + type = "itemTag", + tag = "weapon" +} +local itemListIndicator = { + type = "itemList", + items = [ "perfectlygenericitem", "standingturret" ] +} +local monsterTypeIndicator = { + type = "monsterType", + typeName = "peblit" +} + +-- set quest parameters for the indicators +quest.setParameter("entity", entityIndicator) +quest.setParameter("item", itemIndicator) +quest.setParameter("itemTag", itemTagIndicator) +quest.setParameter("itemList", itemListIndicator) +quest.setParameter("monsterType", monsterTypeIndicator) + +-- add the set quest parameters to the indicators list +quest.setIndicators({"entity", "item", "itemTag", "itemList", "monsterType"}) +``` + +--- + +#### `void` quest.setObjectiveList(`JsonArray` objectives) + +Set the objectives for the quest tracker. Objectives are in the format {text, completed} + +Example: +```lua +quest.setObjectiveList({ + {"Gather 4 cotton", true}, + {"Eat 2 cotton", false} +}) +``` + +--- + +#### `void` quest.setProgress(`float` progress) + +Sets the progress amount of the quest tracker progress bar. Set nil to hide. Progress is from 0.0 to 1.0. + +--- + +#### `void` quest.setCompassDirection(`float` angle) + +Set the angle of the quest tracker compass. Setting nil hides the compass. + +--- + +#### `void` quest.setTitle(`String` title) + +Sets the title of the quest in the quest log. + +--- + +#### `void` quest.setText(`String` text) + +Set the text for the quest in the quest log. + +--- + +#### `void` quest.setCompletionText(`String` completionText) + +Sets the text shown in the completion window when the quest is completed. + +--- + +#### `void` quest.setFailureText(`String` failureText) + +Sets the text shown in the completion window when the quest is failed. + +--- + +#### `void` quest.setPortrait(`String` portraitName, `JsonArray` portrait) + +Sets a portrait to a list of drawables. + +--- + +#### `void` quest.setPortraitTitle(`String` portraitName, `String` title) + +Sets a portrait title. + +--- + +#### `void` quest.addReward(`ItemDescriptor` reward) + +Add an item to the reward pool. \ No newline at end of file diff --git a/doc/lua/root.md b/doc/lua/root.md new file mode 100644 index 0000000..fd26004 --- /dev/null +++ b/doc/lua/root.md @@ -0,0 +1,328 @@ +The `root` table contains functions that reference the game's currently loaded assets and don't relate to any more specific context such as a particular world or universe. + +--- + +#### `Json` root.assetJson(`String` assetPath) + +Returns the contents of the specified JSON asset file. + +--- + +#### `Json` root.makeCurrentVersionedJson(`String` versioningIdentifier, `Json` content) + +Returns a versioned JSON representation of the given JSON content with the given identifier and the most recent version as specified in `versioning.config`. + +--- + +#### `Json` root.loadVersionedJson(`Json` versionedContent, `String` versioningIdentifier) + +Returns the given JSON content and identifier after applying appropriate versioning scripts to bring it up to the most recent version as specified in `versioning.config`. + +--- + +#### `double` root.evalFunction(`String` functionName, `double` input) + +Returns the evaluation of the specified univariate function (as defined in a `.functions` file) for the given input value. + +--- + +#### `double` root.evalFunction2(`String` functionName, `double` input1, `double` input2) + +Returns the evaluation of the specified bivariate function (as defined in a `.2functions` file) for the given input values. + +--- + +#### `Vec2U` root.imageSize(`String` imagePath) + +Returns the pixel dimensions of the specified image asset. + +--- + +#### `List` root.imageSpaces(`String` imagePath, `Vec2F` worldPosition, `float` spaceScan, `bool` flip) + +Returns a list of the world tile spaces the image would occupy if placed at the given position using the specified spaceScan value (the portion of a space that must be non-transparent for that space to count as filled). + +--- + +#### `RectU` root.nonEmptyRegion(`String` imagePath) + +Returns the rectangle containing the portion of the specified asset image that is non-transparent. + +--- + +#### `Json` root.npcConfig(`String` npcType) + +Returns a representation of the generated JSON configuration for an NPC of the given type. + +--- + +#### `Json` root.npcVariant(`String` species, `String` npcType, `float` level, [`unsigned` seed], [`Json` parameters]) + +Generates an NPC with the specified species, type, level, seed and parameters, and returns its configuration. + +--- + +#### `float` root.projectileGravityMultiplier(`String` projectileName) + +Returns the gravity multiplier of the given projectile's movement controller configuration as configured in `physics.config`. + +--- + +#### `Json` root.projectileConfig(`String` projectileName) + +Returns a representation of the JSON configuration for the given projectile. + +--- + +#### `Json` root.itemDescriptorsMatch(`ItemDescriptor` descriptor1, `ItemDescriptor` descriptor2, [`bool` exactMatch]) + +Returns `true` if the given item descriptors match. If exactMatch is `true` then both names and parameters will be compared, otherwise only names. + +--- + +#### `JsonArray` root.recipesForItem(`String` itemName) + +Returns a list of JSON configurations of all recipes which output the given item. + +--- + +#### `String` root.itemType(`String` itemName) + +Returns the item type name for the specified item. + +--- + +#### `JsonArray` root.itemTags(`String` itemName) + +Returns a list of the tags applied to the specified item. + +--- + +#### `bool` root.itemHasTag(`String` itemName, `String` tagName) + +Returns true if the given item's tags include the specified tag and false otherwise. + +--- + +#### `Json` root.itemConfig(`ItemDescriptor` descriptor, [`float` level], [`unsigned` seed]) + +Generates an item from the specified descriptor, level and seed and returns a JSON object containing the `directory`, `config` and `parameters` for that item. + +--- + +#### `ItemDescriptor` root.createItem(`ItemDescriptor` descriptor, [`float` level], [`unsigned` seed]) + +Generates an item from the specified descriptor, level and seed and returns a new item descriptor for the resulting item. + +--- + +#### `Json` root.tenantConfig(`String` tenantName) + +Returns the JSON configuration for the given tenant. + +--- + +#### `JsonArray` root.getMatchingTenants(`map` colonyTags) + +Returns an array of JSON configurations of tenants matching the given map of colony tags and corresponding object counts. + +--- + +#### `JsonArray` root.liquidStatusEffects(`LiquidId` liquid) + +Returns an array of status effects applied by the given liquid. + +--- + +#### `String` root.generateName(`String` assetPath, [`unsigned` seed]) + +Returns a randomly generated name using the specified name gen config and seed. + +--- + +#### `Json` root.questConfig(`String` questTemplateId) + +Returns the JSON configuration of the specified quest template. + +--- + +#### `JsonArray` root.npcPortrait(`String` portraitMode, `String` species, `String` npcType, `float` level, [`unsigned` seed], [`Json` parameters]) + +Generates an NPC with the specified type, level, seed and parameters and returns a portrait in the given portraitMode as a list of drawables. + +--- + +#### `JsonArray` root.monsterPortrait(`String` typeName, [`Json` parameters]) + +Generates a monster of the given type with the given parameters and returns its portrait as a list of drawables. + +--- + +#### `bool` root.isTreasurePool(`String` poolName) + +Returns true if the given treasure pool exists and false otherwise. Can be used to guard against errors attempting to generate invalid treasure. + +--- + +#### `JsonArray` root.createTreasure(`String` poolName, `float` level, [`unsigned` seed]) + +Generates an instance of the specified treasure pool, level and seed and returns the contents as a list of item descriptors. + +--- + +#### `String` root.materialMiningSound(`String` materialName, [`String` modName]) + +Returns the path of the mining sound asset for the given material and mod combination, or `nil` if no mining sound is set. + +--- + +#### `String` root.materialFootstepSound(`String` materialName, [`String` modName]) + +Returns the path of the footstep sound asset for the given material and mod combination, or `nil` if no footstep sound is set. + +--- + +#### `float` root.materialHealth(`String` materialName) + +Returns the configured health value for the specified material. + +--- + +#### `Json` root.materialConfig(`String` materialName) + +Returns a JSON object containing the `path` and base `config` for the specified material if it is a real material, or `nil` if it is a metamaterial or invalid. + +--- + +#### `Json` root.modConfig(`String` modName) + +Returns a JSON object containing the `path` and base `config` for the specified mod if it is a real mod, or `nil` if it is a metamod or invalid. + +--- + +#### `Json` root.liquidConfig(`LiquidId` liquidId) + +#### `Json` root.liquidConfig(`String` liquidName) + +Returns a JSON object containing the `path` and base `config` for the specified liquid name or id if it is a real liquid, or `nil` if the liquid is empty or invalid. + +--- + +#### `String` root.liquidName(`LiquidId` liquidId) + +Returns the string name of the liquid with the given ID. + +--- + +#### `LiquidId` root.liquidId(`String` liquidName) + +Returns the numeric ID of the liquid with the given name. + +--- + +#### `Json` root.monsterSkillParameter(`String` skillName, `String` parameterName) + +Returns the value of the specified parameter for the specified monster skill. + +--- + +#### `Json` root.monsterParameters(`String` monsterType, [uint64_t seed]) + +Returns the parameters for a monster type. + +--- + +#### `ActorMovementParameters` root.monsterMovementSettings(`String` monsterType, [uint64_t seed]) + +Returns the configured base movement parameters for the specified monster type. + +--- + +#### `Json` root.createBiome(`String` biomeName, `unsigned` seed, `float` verticalMidPoint, `float` threatLevel) + +Generates a biome with the specified name, seed, vertical midpoint and threat level, and returns a JSON object containing the configuration for the generated biome. + +--- + +#### `String` root.hasTech(`String` techName) + +Returns `true` if a tech with the specified name exists and `false` otherwise. + +--- + +#### `String` root.techType(`String` techName) + +Returns the type (tech slot) of the specified tech. + +--- + +#### `Json` root.techConfig(`String` techName) + +Returns the JSON configuration for the specified tech. + +--- + +#### `String` root.treeStemDirectory(`String` stemName) + +Returns the path within assets from which the specified tree stem type was loaded. + +--- + +#### `String` root.treeFoliageDirectory(`String` foliageName) + +Returns the path within assets from which the specified tree foliage type was loaded. + +--- + +#### `Collection` root.collection(`String` collectionName) + +Returns the metadata for the specified collection. + +--- + +#### `List` root.collectables(`String` collectionName) + +Returns a list of collectables for the specified collection. + +--- + +#### `String` root.elementalResistance(`String` elementalType) + +Returns the name of the stat used to calculate elemental resistance for the specified elemental type. + +--- + +#### `Json` root.dungeonMetadata(`String` dungeonName) + +Returns the metadata for the specified dungeon definition. + +--- + +#### `BehaviorState` root.behavior(`LuaTable` context, `Json` config, `JsonObject` parameters) + +Loads a behavior and returns the behavior state as userdata. + +context is the current lua context called from, in almost all cases _ENV. + +config can be either the `String` name of a behavior tree, or an entire behavior tree configuration to be built. + +parameters is overrides for parameters for the behavior tree. + +BehaviorState contains 2 methods: + +behavior:init(_ENV) -- initializes the behavior, loads required scripts, and returns a new behavior state +behavior:run(state, dt) -- runs the behavior, takes a behavior state for the first argument +behavior:clear(state) -- resets the internal state of the behavior + +Example: + +```lua +function init() + self.behavior = root.behavior("monster", {}) + self.behaviorState = self.behavior:init(_ENV) +end + +function update(dt) + self.behavior:run(self.behaviorState, dt) +end +``` diff --git a/doc/lua/scriptedanimator.md b/doc/lua/scriptedanimator.md new file mode 100644 index 0000000..f714bd1 --- /dev/null +++ b/doc/lua/scriptedanimator.md @@ -0,0 +1,23 @@ +# animationConfig + +The `animationConfig` table contains functions for getting configuration options from the base entity and its networked animator. + +It is available only in client side rendering scripts. + +--- + +#### `Json` animationConfig.animationParameter(`String` key) + +Returns a networked value set by the parent entity's master script. + +--- + +#### `Vec2F` animationConfig.partPoint(`String` partName, `String` propertyName) + +Returns a `Vec2F` configured in a part's properties with all of the part's transformations applied to it. + +--- + +#### `PolyF` animationConfig.partPoly(`String` partName, `String` propertyName) + +Returns a `PolyF` configured in a part's properties with all the part's transformations applied to it. diff --git a/doc/lua/scriptpane.md b/doc/lua/scriptpane.md new file mode 100644 index 0000000..696b1a6 --- /dev/null +++ b/doc/lua/scriptpane.md @@ -0,0 +1,49 @@ +These pane bindings are available to scripted interface panes and include functions not specifically related to widgets within the pane. + +--- + +#### `EntityId` pane.sourceEntity() + +Returns the entity id of the pane's source entity. + +--- + +#### `void` pane.dismiss() + +Closes the pane. + +--- + +#### `void` pane.playSound(`String` sound, [`int` loops], [`float` volume]) + +Plays the specified sound asset, optionally looping the specified number of times or at the specified volume. + +--- + +#### `bool` pane.stopAllSounds(`String` sound) + +Stops all instances of the given sound asset, and returns `true` if any sounds were stopped and `false` otherwise. + +--- + +#### `void` pane.setTitle(`String` title, `String` subtitle) + +Sets the window title and subtitle. + +--- + +#### `void` pane.setTitleIcon(`String` image) + +Sets the window icon. + +--- + +#### `void` pane.addWidget(`Json` widgetConfig, [`String` widgetName]) + +Creates a new widget with the specified config and adds it to the pane, optionally with the specified name. + +--- + +#### `void` pane.removeWidget(`String` widgetName) + +Removes the specified widget from the pane. diff --git a/doc/lua/stagehand.md b/doc/lua/stagehand.md new file mode 100644 index 0000000..00e11a2 --- /dev/null +++ b/doc/lua/stagehand.md @@ -0,0 +1,37 @@ +The stagehand table contains bindings specific to stagehands which are available in addition to their common tables. + +--- + +#### `EntityId` stagehand.id() + +Returns the stagehand's entity id. Identical to entity.id(), so use that instead. + +--- + +#### `Vec2F` stagehand.position() + +Returns the stagehand's position. This is identical to entity.position(), so use that instead. + +--- + +#### `void` stagehand.setPosition(`Vec2F` position) + +Moves the stagehand to the specified position. + +--- + +#### `void` stagehand.die() + +Destroys the stagehand. + +--- + +#### `String` stagehand.typeName() + +Returns the stagehand's type name. + +--- + +#### `void` stagehand.setUniqueId([`String` uniqueId]) + +Sets the stagehand's unique entity id, or clears it if unspecified. diff --git a/doc/lua/statuscontroller.md b/doc/lua/statuscontroller.md new file mode 100644 index 0000000..735a746 --- /dev/null +++ b/doc/lua/statuscontroller.md @@ -0,0 +1,263 @@ +# status + +The `status` table relates to the status controller attached to an entity. It is available in: + +* monsters +* npcs +* status effects +* companion system scripts +* quest scripts +* tech +* primary status scripts for: player, monster, npc + +--- + +#### `Json` status.statusProperty(`String` name, `Json` default) + +Returns the value assigned to the specified status property. If there is no value set, returns default. + +--- + +#### `void` status.setStatusProperty(`String` name, `Json` value) + +Sets a status property to the specified value. + +--- + +#### `float` status.stat(`String` statName) + +Returns the value for the specified stat. Defaults to 0.0 if the stat does not exist. + +--- + +#### `bool` status.statPositive(`String` statName) + +Returns whether the stat value is greater than 0. + +--- + +#### `List` status.resourceNames() + +Returns a list of the names of all the configured resources; + +--- + +#### `bool` status.isResource(`String` resourceName) + +Returns whether the specified resource exists in this status controller. + +--- + +#### `float` status.resource(`String` resourceName) + +Returns the value of the specified resource. + +--- + +#### `bool` status.resourcePositive(`String` resourceName) + +Returns whether the value of the specified resource is greater than 0. + +--- + +#### `void` status.setResource(`String` resourceName, `float` value) + +Sets a resource to the specified value. + +--- + +#### `void` status.modifyResource(`String` resourceName, `float` value) + +Adds the specified value to a resource. + +--- + +#### `float` status.giveResource(`String` resourceName, `float` value) + +Adds the specified value to a resource. Returns any overflow. + +--- + +#### `bool` status.consumeResource(`String` resourceName, `float` amount) + +Tries to consume the specified amount from a resource. Returns whether the full amount was able to be consumes. Does not modify the resource if unable to consume the full amount. + +--- + +#### `bool` status.overConsumeResource(`String` resourceName, `float` amount) + +Tries to consume the specified amount from a resource. If unable to consume the full amount, will consume all the remaining amount. Returns whether it was able to consume any at all of the resource. + +--- + +#### `bool` status.resourceLocked(`String` resourceName) + +Returns whether the resource is currently locked. + +--- + +#### `void` status.setResourceLocked(`String` resourceName, `bool` locked) + +Sets a resource to be locked/unlocked. A locked resource cannot be consumed. + +--- + +#### `void` status.resetResource(`String` resourceName) + +Resets a resource to its base value. + +--- + +#### `void` status.resetAllResources() + +Resets all resources to their base values. + +--- + +#### `float` status.resourceMax(`String` resourceName) + +Returns the max value for the specified resource. + +--- + +#### `float` status.resourcePercentage(`String` resourceName) + +Returns the percentage of max that the resource is currently at. From 0.0 to 1.0. + +--- + +#### `void` status.setResourcePercentage(`String` resourceName, `float` value) + +Sets a resource to a percentage of the max value for the resource. From 0.0 to 1.0. + +--- + +#### `void` status.modifyResourcePercentage(`String` resourceName, `float` value) + +Adds a percentage of the max resource value to the current value of the resource. + +--- + +#### `JsonArray` status.getPersistentEffects(`String` effectCategory) + +Returns a list of the currently active persistent effects in the specified effect category. + +--- + +#### `void` status.addPersistentEffect(`String` effectCategory, `Json` effect) + +Adds a status effect to the specified effect category. + +--- + +#### `void` status.addPersistentEffects(`String` effectCategory, `JsonArray` effects) + +Adds a list of effects to the specified effect category. + +--- + +#### `void` status.setPersistentEffects(`String` effectCategory, `JsonArray` effects) + +Sets the list of effects of the specified effect category. Replaces the current list active effects. + +--- + +#### `void` status.clearPersistentEffects(`String` effectCategory) + +Clears any status effects from the specified effect category. + +--- + +#### `void` status.clearAllPersistentEffects() + +Clears all persistent status effects from all effect categories. + +--- + +#### `void` status.addEphemeralEffect(`String` effectName, [`float` duration], [`EntityId` sourceEntity]) + +Adds the specified unique status effect. Optionally with a custom duration, and optionally with a source entity id accessible in the status effect. + +--- + +#### `void` status.addEphemeralEffects(`JsonArray` effects, [`EntityId` sourceEntity]) + +Adds a list of unique status effects. Optionally with a source entity id. + +Unique status effects can be specified either as a string, "myuniqueeffect", or as a table, {effect = "myuniqueeffect", duration = 5}. Remember that this function takes a `list` of these effect descriptors. This is a valid list of effects: { "myuniqueeffect", {effect = "myothereffect", duration = 5} } + +--- + +#### `void` status.removeEphemeralEffect(`String` effectName) + +Removes the specified unique status effect. + +--- + +#### `void` status.clearEphemeralEffects() + +Clears all ephemeral status effects. + +--- + +#### `List>`, `unsigned` status.damageTakenSince([`unsigned` since = 0]]) + +Returns two values: +* A list of damage notifications for the entity's damage taken since the specified heartbeat. +* The most recent heartbeat to be passed into the function again to get the damage notifications taken since this function call. + +Example: + +```lua +_,lastStep = status.damageTakenSince() -- Returns the full buffer of damage notifications, throw this away, we only want the current step + +-- stuff + +notifications,lastStep = status.damageTakenSince(lastStep) -- Get the damage notifications since the last call, and update the heartbeat +``` + +--- + +#### `List>`, `unsigned` status.inflictedHitsSince([`unsigned` since = 0]]) + +Returns two values: +* A list {{entityId, damageRequest}} for the entity's inflicted hits since the specified heartbeat. +* The most recent heartbeat to be passed into the function again to get the inflicted hits since this function call. + +--- + +#### `List`, `unsigned` status.inflictedDamageSince([`unsigned` since = 0]) + +Returns two values: +* A list of damage notifications for damage inflicted by the entity. +* The most recent heartbeat to be passed into the function again to get the list of damage notifications since the last call. + +--- + +#### `JsonArray` status.activeUniqueStatusEffectSummary() + +Returns a list of two element tables describing all unique status effects currently active on the status controller. Each entry consists of the `String` name of the effect and a `float` between 0 and 1 indicating the remaining portion of that effect's duration. + +--- + +#### `bool` status.uniqueStatusEffectActive(`String` effectName) + +Returns `true` if the specified unique status effect is currently active and `false` otherwise. + +--- + +#### `String` status.primaryDirectives() + +Returns the primary set of image processing directives applied to the animation of the entity using this status controller. + +--- + +#### `void` status.setPrimaryDirectives([`String` directives]) + +Sets the primary set of image processing directives that should be applied to the animation of the entity using this status controller. + +--- + +#### `void` status.applySelfDamageRequest(`DamageRequest` damageRequest) + +Directly applies the specified damage request to the entity using this status controller. diff --git a/doc/lua/statuseffect.md b/doc/lua/statuseffect.md new file mode 100644 index 0000000..930bed2 --- /dev/null +++ b/doc/lua/statuseffect.md @@ -0,0 +1,69 @@ +# effect + +The `effect` table relates to functions specifically for status effects. + +--- + +#### `float` effect.duration() + +Returns the remaining duration of the status effect. + +--- + +#### `void` effect.modifyDuration(`float` duration) + +Adds the specified duration to the current remaining duration. + +--- + +#### `void` effect.expire() + +Immediately expire the effect, setting the duration to 0. + +--- + +#### `EntityId` effect.sourceEntity() + +Returns the source entity id of the status effect, if any. + +--- + +#### `void` effect.setParentDirectives(`String` directives) + +Sets image processing directives for the entity the status effect is active on. + +--- + +#### `Json` effect.getParameter(`String` name, `Json` def) + +Returns the value associated with the parameter name in the effect configuration. If no value is set, returns the default specified. + +--- + +#### `StatModifierGroupId` effect.addStatModifierGroup(`List` modifiers) + +Adds a new stat modifier group and returns the ID created for the group. Stat modifier groups will stay active until the effect expires. + +Stat modifiers are of the format: + +```lua +{ + stat = "health", + + amount = 50 + --OR baseMultiplier = 1.5 + --OR effectiveMultiplier = 1.5 +} +``` + +--- + +#### `void` effect.setStatModifierGroup(`StatModifierGroupId`, groupId, `List` modifiers) + +Replaces the list of stat modifiers in a group with the specified modifiers. + +--- + +#### `void` effect.removeStatModifierGroup(`StatModifierGroupId` groupId) + +Removes the specified stat modifier group. diff --git a/doc/lua/tech.md b/doc/lua/tech.md new file mode 100644 index 0000000..bf25a19 --- /dev/null +++ b/doc/lua/tech.md @@ -0,0 +1,63 @@ +# tech + +The `tech` table contains functions exclusively available in tech scripts. + +--- + +#### `Vec2F` tech.aimPosition() + +Returns the current cursor aim position. + +--- + +#### `void` tech.setVisible(`bool` visible) + +Sets whether the tech should be visible. + +--- + +#### `void` tech.setParentState(`String` state) + +Set the animation state of the player. + +Valid states: +* "Stand" +* "Fly" +* "Fall" +* "Sit" +* "Lay" +* "Duck" +* "Walk" +* "Run" +* "Swim" + +--- + +#### `void` tech.setParentDirectives(`String` directives) + +Sets the image processing directives for the player. + +--- + +#### `void` tech.setParentHidden(`bool` hidden) + +Sets whether to make the player invisible. Will still show the tech. + +--- + +#### `void` tech.setParentOffset(`Vec2F` offset) + +Sets the position of the player relative to the tech. + +--- + +#### `bool` tech.parentLounging() + +Returns whether the player is lounging. + +--- + +#### `void` tech.setToolUsageSuppressed(`bool` suppressed) + +Sets whether to suppress tool usage on the player. When tool usage is suppressed no items can be used. + diff --git a/doc/lua/updatablescript.md b/doc/lua/updatablescript.md new file mode 100644 index 0000000..2dd55e1 --- /dev/null +++ b/doc/lua/updatablescript.md @@ -0,0 +1,13 @@ +Most entity script contexts include the *script* table, which provides bindings for getting and setting the script's update rate. Update deltas are specified in numbers of frames, so a script with an update delta of 1 would run every frame, or a script with an update delta of 60 would run once per second. An update delta of 0 means that the script's periodic update will never be called, but it can still perform actions through script calls, messaging, or event hooks. + +--- + +#### `void` script.setUpdateDelta(`unsigned` dt) + +Sets the script's update delta. + +--- + +#### `float` script.updateDt() + +Returns the duration in seconds between periodic updates to the script. diff --git a/doc/lua/utility.md b/doc/lua/utility.md new file mode 100644 index 0000000..27e9fc0 --- /dev/null +++ b/doc/lua/utility.md @@ -0,0 +1,170 @@ +The sb table contains miscellaneous utility functions that don't directly relate to any assets or content of the game. + +--- + +#### `double` sb.nrand([`double` standardDeviation], [`double` mean]) + +Returns a randomized value with a normal distribution using the specified standard deviation (default is 1.0) and mean (default is 0). + +--- + +#### `String` sb.makeUuid() + +Returns a `String` representation of a new, randomly-created `Uuid`. + +--- + +#### `void` sb.logInfo(`String` formatString, [`LuaValue` formatValues ...]) + +Logs the specified formatted string, optionally using the formatted replacement values, to the log file and console with the Info log level. + +--- + +#### `void` sb.logWarn(`String` formatString, [`LuaValue` formatValues ...]) + +Logs the specified formatted string, optionally using the formatted replacement values, to the log file and console with the Warn log level. + +--- + +#### `void` sb.logError(`String` formatString, [`LuaValue` formatValues ...]) + +Logs the specified formatted string, optionally using the formatted replacement values, to the log file and console with the Error log level. + +--- + +#### `void` sb.setLogMap(`String` key, `String` formatString, [`LuaValue` formatValues ...]) + +Sets an entry in the debug log map (visible while in debug mode) using the specified format string and optional formatted replacement values. + +--- + +#### `String` sb.printJson(`Json` value, [`bool` pretty]) + +Returns a human-readable string representation of the specified JSON value. If pretty is `true`, objects and arrays will have whitespace added for readability. + +--- + +#### `String` sb.print(`LuaValue` value) + +Returns a human-readable string representation of the specified `LuaValue`. + +--- + +#### `Variant` sb.interpolateSinEase(`double` offset, `Variant` value1, `Variant` value2) + +Returns an interpolated `Vec2F` or `double` between the two specified values using a sin ease function. + +--- + +#### `String` sb.replaceTags(`String` string, `Map` tags) + +Replaces all tags in the specified string with the specified tag replacement values. + +--- + +#### `Json` sb.jsonMerge(`Json` a, `Json` b) + +Returns the result of merging the contents of b on top of a. + +--- + +#### `Json` sb.jsonQuery(`Json` content, `String` path, `Json` default) + +Attempts to extract the value in the specified content at the specified path, and returns the found value or the specified default if no such value exists. + +--- + +#### `int` sb.staticRandomI32([`LuaValue` hashValues ...]) + +Returns a statically randomized 32-bit signed integer based on the given list of seed values. + +--- + +#### `int` sb.staticRandomI32Range(`int` min, `int` max, [`LuaValue` hashValues ...]) + +Returns a statically randomized 32-bit signed integer within the specified range based on the given list of seed values. + +--- + +#### `double` sb.staticRandomDouble([`LuaValue` hashValues ...]) + +Returns a statically randomized `double` based on the given list of seed values. + +--- + +#### `double` sb.staticRandomDoubleRange(`double` min, `double` max, [`LuaValue` hashValues ...]) + +Returns a statically randomized `double` within the specified range based on the given list of seed values. + +--- + +#### `RandomSource` sb.makeRandomSource([`unsigned` seed]) + +Creates and returns a Lua UserData value which can be used as a random source, initialized with the specified seed. The `RandomSource` has the following methods: + +##### `void` init([`unsigned` seed]) + +Reinitializes the random source, optionally using the specified seed. + +##### `void` addEntropy([`unsigned` seed]) + +Adds entropy to the random source, optionally using the specified seed. + +##### `unsigned` randu32() + +Returns a random 32-bit unsigned integer value. + +##### `unsigned` randu64() + +Returns a random 64-bit unsigned integer value. + +##### `int` randi32() + +Returns a random 32-bit signed integer value. + +##### `int` randi64() + +Returns a random 64-bit signed integer value. + +##### `float` randf([`float` min], [`float` max]) + +Returns a random `float` value within the specified range, or between 0 and 1 if no range is specified. + +##### `double` randf([`double` min], [`double` max]) + +Returns a random `double` value within the specified range, or between 0 and 1 if no range is specified. + +##### `unsigned` randf(`unsigned` minOrMax, [`unsigned` max]) + +Returns a random unsigned integer value between minOrMax and max, or between 0 and minOrMax if no max is specified. + +##### `int` randf([`int` min], [`int` max]) + +Returns a random signed integer value between minOrMax and max, or between 0 and minOrMax if no max is specified. + +##### `bool` randb() + +Returns a random `bool` value. + +--- + +#### `PerlinSource` sb.makePerlinSource(`Json` config) + +Creates and returns a Lua UserData value which can be used as a Perlin noise source. The configuration for the `PerlinSource` should be a JSON object and can include the following keys: + +* `unsigned` __seed__ - Seed value used to initialize the source. +* `String` __type__ - Type of noise to use. Valid types are "perlin", "billow" or "ridgedMulti". +* `int` __octaves__ - Number of octaves of noise to use. Defaults to 1. +* `double` __frequency__ - Defaults to 1.0. +* `double` __amplitude__ - Defaults to 1.0. +* `double` __bias__ - Defaults to 0.0. +* `double` __alpha__ - Defaults to 2.0. +* `double` __beta__ - Defaults to 2.0. +* `double` __offset__ - Defaults to 1.0. +* `double` __gain__ - Defaults to 2.0. + +The `PerlinSource` has only one method: + +##### `float` get(`float` x, [`float` y], [`float` z]) + +Returns a `float` value from the Perlin source using 1, 2, or 3 dimensions of input. diff --git a/doc/lua/vehicle.md b/doc/lua/vehicle.md new file mode 100644 index 0000000..c0c91cc --- /dev/null +++ b/doc/lua/vehicle.md @@ -0,0 +1,91 @@ +The vehicle table contains bindings specific to vehicles which are available in addition to their common tables. + +--- + +#### `bool` vehicle.controlHeld(`String` loungeName, `String` controlName) + +Returns `true` if the specified control is currently being held by an occupant of the specified lounge position and `false` otherwise. + +--- + +#### `Vec2F` vehicle.aimPosition(`String` loungeName) + +Returns the world aim position for the specified lounge position. + +--- + +#### `EntityId` vehicle.entityLoungingIn(`String` loungeName) + +Returns the entity id of the entity currently occupying the specified lounge position, or `nil` if the lounge position is unoccupied. + +--- + +#### `void` vehicle.setLoungeEnabled(`String` loungeName, `bool` enabled) + +Enables or disables the specified lounge position. + +--- + +#### `void` vehicle.setLoungeOrientation(`String` loungeName, `String` orientation) + +Sets the lounge orientation for the specified lounge position. Valid orientations are "sit", "stand" or "lay". + +--- + +#### `void` vehicle.setLoungeEmote(`String` loungeName, [`String` emote]) + +Sets the emote to be performed by entities occupying the specified lounge position, or clears it if no emote is specified. + +--- + +#### `void` vehicle.setLoungeDance(`String` loungeName, [`String` dance]) + +Sets the dance to be performed by entities occupying the specified lounge position, or clears it if no dance is specified. + +--- + +#### `void` vehicle.setLoungeStatusEffects(`String` loungeName, `JsonArray` statusEffects) + +Sets the list of status effects to be applied to entities occupying the specified lounge position. To clear the effects, set an empty list. + +--- + +#### `void` vehicle.setPersistent(`bool` persistent) + +Sets whether the vehicle is persistent, i.e. whether it will be stored when the world is unloaded and reloaded. + +--- + +#### `void` vehicle.setInteractive(`bool` interactive) + +Sets whether the vehicle is currently interactive. + +--- + +#### `void` vehicle.setDamageTeam(`DamageTeam` team) + +Sets the vehicle's current damage team type and number. + +--- + +#### `void` vehicle.setMovingCollisionEnabled(`String` collisionName, `bool` enabled) + +Enables or disables the specified collision region. + +--- + +#### `void` vehicle.setForceRegionEnabled(`String` regionName, `bool` enabled) + +Enables or disables the specified force region. + +--- + +#### `void` vehicle.setDamageSourceEnabled(`String` damageSourceName, `bool` enabled) + +Enables or disables the specified damage source. + +--- + +#### `void` vehicle.destroy() + +Destroys the vehicle. diff --git a/doc/lua/widget.md b/doc/lua/widget.md new file mode 100644 index 0000000..1b29678 --- /dev/null +++ b/doc/lua/widget.md @@ -0,0 +1,391 @@ +# widget + +The `widget` table contains functions to manipulate and get data about widgets in a scriptpane. + +The widgetName passed into most of these functions can contain period separators for getting children. + +Example: +``` +widget.getPosition("itemScrollArea.itemList.1.name") +``` + +## General callbacks + +These callbacks are available for all widgets. + +--- + +#### `void` widget.playSound(`String` audio, [`int` loops = 0], [`float` volume = 1.0f]) + +Plays a sound. + +--- + +#### `Vec2I` widget.getPosition(`String` widgetName) + +Returns the position of a widget. + +--- + +#### `void` widget.setPosition(`String` widgetName, `Vec2I` position) + +Sets the position of a widget. + +--- + +#### `Vec2I` widget.getSize(`String` widgetName) + +Returns the size of a widget. + +--- + +#### `void` widget.setSize(`String` widgetName, `Vec2I` size) + +Sets the size of a widget. + +--- + +#### `void` widget.setVisible(`String` widgetName, `bool` visible) + +Sets the visibility of a widget. + +--- + +#### `void` widget.active(`String` widgetName) + +Returns whether the widget is visible. + +--- + +#### `void` widget.focus(`String` widgetName) + +Sets focus on the specified widget. + +--- + +#### `void` widget.hasFocus(`String` widgetName) + +Returns whether the specified widget is currently focused. + +--- + +#### `void` widget.blur(`String` widgetName) + +Unsets focus on the specified focused widget. + +--- + +#### `Json` widget.getData(`String` widgetName) + +Returns the arbitrary data value set for the widget. + +--- + +#### `void` widget.setData(`String` widgetName, `Json` data) + +Sets arbitrary data for the widget. +--- + +#### `String` widget.getChildAt(`Vec2I` screenPosition) + +Returns the full name for any widget at screenPosition. + +--- + +#### `bool` widget.inMember(`String` widgetName, `Vec2I` screenPosition) + +Returns whether the widget contains the specified screenPosition. + +--- + +#### `void` widget.addChild(`String` widgetName, `Json` childConfig, [`String` childName]) + +Creates a new child widget with the specified config and adds it to the specified widget, optionally with the specified name. + +--- + +#### `void` widget.removeAllChildren(`String` widgetName) + +Removes all child widgets of the specified widget. + +--- + +#### `void` widget.removeChild(`String` widgetName, `String` childName) + +Removes the specified child widget from the specified widget. + +--- + +## Widget specific callbacks + +These callbacks only work for some widget types. + +--- + +#### `String` widget.getText(`String` widgetName) + +Returns the text set in a TextBoxWidget. + +--- + +#### `void` widget.setText(`String` widgetName, `String` text) + +Sets the text of: LabelWidget, ButtonWidget, TextBoxWidget + +--- + +#### `void` widget.setFontColor(`String` widgetName, `Color` color) + +Sets the font color of: LabelWidget, ButtonWidget, TextBoxWidget + +--- + +#### `void` widget.setImage(`String` widgetName, `String` imagePath) + +Sets the image of an ImageWidget. + +--- + +#### `void` widget.setImageScale(`String` widgetName, `float` imageScale) + +Sets the scale of an ImageWidget. + +--- + +#### `void` widget.setImageRotation(`String` widgetName, `float` imageRotation) + +Sets the rotation of an ImageWidget. + +--- + +#### `void` widget.setButtonEnabled(`String` widgetName, `bool` enabled) + +Sets whether the ButtonWidget should be enabled. + +--- + +#### `void` widget.setButtonImage(`String` widgetName, `String` baseImage) + +Sets the baseImage of a ButtonWidget. + +--- + +#### `void` widget.setButtonImages(`String` widgetName, `Json` imageSet) + +Sets the full image set of a ButtonWidget. + +``` +{ + base = "image.png", + hover = "image.png", + pressed = "image.png", + disabled = "image.png", +} +``` + +--- + +#### `void` widget.setButtonCheckedImages(`String` widgetName, `Json` imageSet) + +Similar to widget.setButtonImages, but sets the images used for the checked state of a checkable ButtonWidget. + +--- + +#### `void` widget.setButtonOverlayImage(`String` widgetName, `String` overlayImage) + +Sets the overlay image of a ButtonWidget. + +--- + +#### `bool` widget.getChecked(`String` widgetName) + +Returns whether the ButtonWidget is checked. + +--- + +#### `void` widget.setChecked(`String` widgetName, `bool` checked) + +Sets whether a ButtonWidget is checked + +--- + +#### `int` widget.getSelectedOption(`String` widgetName) + +Returns the index of the selected option in a ButtonGroupWidget. + +--- + +#### `int` widget.getSelectedData(`String` widgetName) + +Returns the data of the selected option in a ButtonGroupWidget. Nil if no option is selected. + +--- + +#### `void` widget.setSelectedOption(`String` widgetName, `int` index) + +Sets the selected option index of a ButtonGroupWidget. + +--- + +#### `void` widget.setOptionEnabled(`String` widgetName, `int` index, `bool` enabled) + +Sets whether a ButtonGroupWidget option should be enabled. + +--- + +#### `void` widget.setOptionVisible(`String` widgetName, `int` index, `bool`, visible) + +Sets whether a ButtonGroupWidget option should be visible. + +--- + +#### `void` widget.setProgress(`String` widgetName, `float` value) + +Sets the progress of a ProgressWidget. Value should be between 0.0 and 1.0. + +--- + +#### `void` widget.setSliderEnabled(`String` widgetName, `bool` enabled) + +Sets whether the SliderBarWidget should be enabled. + +--- + +#### `float` widget.getSliderValue(`String` widgetName) + +Gets the current value of a SliderBarWidget. + +--- + +#### `void` widget.setSliderValue(`String` widgetName, `int` newValue) + +Sets the current value of a SliderBarWidget. + +--- + +#### `void` widget.getSliderRange(`String` widgetName, `int` newMin, `int` newMax, [`int` newDelta]) + +Sets the minimum, maximum and (optionally) delta values of a SliderBarWidget. + +--- + +#### `void` widget.clearListItems(`String` widgetName) + +Clears all items in a ListWidget. + +--- + +#### `String` widget.addListItem(`String` widgetName) + +Adds a list item to a ListWidget using the configured template, and returns the name of the added list item. + +--- + +#### `void` widget.removeListItem(`String` widgetName, `size_t` at) + +Removes a list item from a ListWidget at a specific index. + +--- + +#### `String` widget.getListSelected(`String` widgetName) + +Returns the name of the currently selected widget in a ListWidget. + +--- + +#### `void` widget.setListSelected(`String` widgetName, `String` selected) + +Sets the selected widget of a ListWidget. + +--- + +#### `void` widget.registerMemberCallback(`String` widgetName, `String` callbackName, `LuaFunction` callback) + +Registers a member callback for a ListWidget's list items to use. + +--- + +#### `ItemBag` widget.itemGridItems(`String` widgetName) + +Returns the full item bag contents of an ItemGridWidget. + +--- + +#### `ItemDescriptor` widget.itemSlotItem(`String` widgetName) + +Returns the descriptor of the item in the specified item slot widget. + +--- + +#### `void` widget.setItemSlotItem(`String` widgetName, `Json` itemDescriptor) + +Sets the item in the specified item slot widget. + +--- + +#### `void` widget.setItemSlotProgress(`String` widgetName, `float` progress) + +Sets the progress overlay on the item slot to the specified value (between 0 and 1). + +--- + +#### `CanvasWidget` widget.bindCanvas(`String` widgetName) + +Binds the canvas widget with the specified name as userdata for easy access. The `CanvasWidget` has the following methods: + +##### `Vec2I` size() + +Returns the size of the canvas. + +##### `void` clear() + +Clears the canvas. + +##### `Vec2I` mousePosition() + +Returns the mouse position relative to the canvas. + +##### `void` drawImage(`String` image, `Vec2F` position, [`float` scale], [`Color` color], [`bool` centered]) + +Draws an image to the canvas. + +##### `void` drawImageDrawable(`String` image, `Vec2F` position, [`Variant` scale], [`Color` color], [`float` rotation]) + +Draws an image to the canvas, centered on position, with slightly different options. + +##### `void` drawImageRect(`String` texName, `RectF` texCoords, `RectF` screenCoords, [`Color` color]) + +Draws a rect section of a texture to a rect section of the canvas. + +##### `void` drawTiledImage(`String` image, `Vec2F` offset, `RectF` screenCoords, [`float` scale], [`Color` color]) + +Draws an image tiled (and wrapping) within the specified screen area. + +##### `void` drawLine(`Vec2F` start, `Vec2F` end, [`Color` color], [`float` lineWidth]) + +Draws a line on the canvas. + +##### `void` drawRect(`RectF` rect, `Color` color) + +Draws a filled rectangle on the canvas. + +##### `void` drawPoly(`PolyF` poly, `Color` color, [`float` lineWidth]) + +Draws a polygon on the canvas. + +##### `void` drawTriangles(`List` triangles, [`Color` color]) + +Draws a list of filled triangles to the canvas. + +##### `void` drawText(`String` text, `Json` textPositioning, `unsigned` fontSize, [`Color` color]) + +Draws text on the canvas. textPositioning is in the format: + +```lua +{ + position = {0, 0} + horizontalAnchor = "left", -- left, mid, right + verticalAnchor = "top", -- top, mid, bottom + wrapWidth = nil -- wrap width in pixels or nil +} +``` diff --git a/doc/lua/world.md b/doc/lua/world.md new file mode 100644 index 0000000..7bbb637 --- /dev/null +++ b/doc/lua/world.md @@ -0,0 +1,1020 @@ +The `world` table contains functions that perform actions within a specified such as querying or modifying entities, tiles, etc. in that world. + +--- + +#### `String` world.type() + +Returns a string describing the world's type. For terrestrial worlds this will be the primary biome, for instance worlds this will be the instance name, and for ship or generic worlds this will be 'unknown'. + +--- + +#### `bool` world.terrestrial() + +Returns a `true` if the current world is a terrestrial world, i.e. a planet, and `false` otherwise. + +--- + +#### `Vec2I` world.size() + +Returns a vector describing the size of the current world. + +--- + +#### `float` world.magnitude(`Vec2F` position1, `Vec2F` position2) + +Returns the magnitude of the distance between the specified world positions. Use this rather than simple vector subtraction to handle world wrapping. + +--- + +#### `Vec2F` world.distance(`Vec2F` position1, `Vec2F` position2) + +Returns the vector difference between the specified world positions. Use this rather than simple vector subtraction to handle world wrapping. + +--- + +#### `bool` world.polyContains(`PolyF` poly, `Vec2F` position) + +Returns `true` if the specified poly contains the specified position in world space and `false` otherwise. + +--- + +#### `Vec2F` world.xwrap(`Vec2F` position) + +Returns the specified position with its X coordinate wrapped around the world width. + +#### `float` world.xwrap(`float` xPosition) + +Returns the specified X position wrapped around the world width. + +--- + +#### `Variant` world.nearestTo(`Variant` sourcePosition, `Variant` targetPosition) + +Returns the point nearest to (i.e. on the same side of the world as) the source point. Either argument can be specified as a `Vec2F` point or as a `float` X position. The type of the targetPosition determines the return type. + +--- + +#### `bool` world.pointCollision(`Vec2F` point, [`CollisionSet` collisionKinds]) + +Returns `true` if the generated collision geometry at the specified point matches any of the specified collision kinds and `false` otherwise. + +--- + +#### `bool` world.pointTileCollision(`Vec2F` point, [`CollisionSet` collisionKinds]) + +Returns `true` if the tile at the specified point matches any of the specified collision kinds and `false` otherwise. + +--- + +#### `Tuple, Maybe>` world.lineCollision(`Vec2F` startPoint, `Vec2F` endPoint, [`CollisionSet` collisionKinds]) + +If the line between the specified points overlaps any generated collision geometry of the specified collision kinds, returns the point at which the line collides, or `nil` if the line does not collide. If intersecting a side of the poly, also returns the normal of the intersected side as a second return. + +--- + +#### `bool` world.lineTileCollision(`Vec2F` startPoint, `Vec2F` endPoint, [`CollisionSet` collisionKinds]) + +Returns `true` if the line between the specified points overlaps any tiles of the specified collision kinds and `false` otherwise. + +--- + +#### `Maybe>` world.lineTileCollisionPoint(`Vec2F` startPoint, `Vec2F` endPoint, [`CollisionSet` collisionKinds]) + +Returns a table of {`position`, `normal`} where `position` is the position that the line intersects the first collidable tile, and `normal` is the collision normal. Returns `nil` if no tile is intersected. + +--- + +#### `bool` world.rectCollision(`RectF` rect, [`CollisionSet` collisionKinds]) + +Returns `true` if the specified rectangle overlaps any generated collision geometry of the specified collision kinds and `false` otherwise. + +--- + +#### `bool` world.rectTileCollision(`RectF` rect, [`CollisionSet` collisionKinds]) + +Returns `true` if the specified rectangle overlaps any tiles of the specified collision kinds and `false` otherwise. + +--- + +#### `bool` world.polyCollision(`PolyF` poly, [`Vec2F` position], [`CollisionSet` collisionKinds]) + +Returns `true` if the specified polygon overlaps any generated collision geometry of the specified collision kinds and `false` otherwise. If a position is specified, the polygon coordinates will be treated as relative to that world position. + +--- + +#### `List` world.collisionBlocksAlongLine(`Vec2F` startPoint, `Vec2F` endPoint, [`CollisionSet` collisionKinds], [`int` maxReturnCount]) + +Returns an ordered list of tile positions along the line between the specified points that match any of the specified collision kinds. If maxReturnCount is specified, the function will only return up to that number of points. + +--- + +#### `List>` world.liquidAlongLine(`Vec2F` startPoint, `Vec2F` endPoint) + +Returns a list of pairs containing a position and a `LiquidLevel` for all tiles along the line between the specified points that contain any liquid. + +--- + +#### `Vec2F` world.resolvePolyCollision(`PolyF` poly, `Vec2F` position, `float` maximumCorrection, [`CollisionSet` collisionKinds]) + +Attempts to move the specified poly (relative to the specified position) such that it does not collide with any of the specified collision kinds. Will only move the poly up to the distance specified by maximumCorrection. Returns `nil` if the collision resolution fails. + +--- + +#### `bool` world.tileIsOccupied(`Vec2I` tilePosition, [`bool` foregroundLayer], [`bool` includeEphemeral]) + +Returns `true` if the specified tile position is occupied by a material or tile entity and `false` if it is empty. The check will be performed on the foreground tile layer if foregroundLayer is `true` (or unspecified) and the background tile layer if it is `false`. The check will include ephemeral tile entities such as preview objects if includeEphemeral is `true`, and will not include these entities if it is `false` (or unspecified). + +--- + +#### `bool` world.placeObject(`String` objectName, `Vec2I` tilePosition, [`int` direction], [`Json` parameters]) + +Attempts to place the specified object into the world at the specified position, preferring it to be right-facing if direction is positive (or unspecified) and left-facing if it is negative. If parameters are specified they will be applied to the object. Returns `true` if the object is placed successfully and `false` otherwise. + +--- + +#### `EntityId` world.spawnItem(`ItemDescriptor` item, `Vec2F` position, [`unsigned` count], [`Json` parameters], [`Vec2F` velocity], [`float` intangibleTime]) + +Attempts to spawn the specified item into the world as the specified position. If item is specified as a name, it will optionally apply the specified count and parameters. The item drop entity can also be spawned with an initial velocity and intangible time (delay before it can be picked up) if specified. Returns an `EntityId` of the item drop if successful and `nil` otherwise. + +--- + +#### `List` world.spawnTreasure(`Vec2F` position, `String` poolName, `float` level, [`unsigned` seed]) + +Attempts to spawn all items in an instance of the specified treasure pool with the specified level and seed at the specified world position. Returns a list of `EntityId`s of the item drops created if successful and `nil` otherwise. + +--- + +#### `EntityId` world.spawnMonster(`String` monsterType, `Vec2F` position, [`Json` parameters]) + +Attempts to spawn a monster of the specified type at the specified position. If parameters are specified they will be applied to the spawned monster. If they are unspecified, they default to an object setting aggressive to be randomly `true` or `false`. Level for the monster may be specified in parameters. Returns the `EntityId` of the spawned monster if successful and `nil` otherwise. + +--- + +#### `EntityId` world.spawnNpc(`Vec2F` position, `String` species, `String` npcType, `float` level, [`unsigned` seed], [`Json` parameters]) + +Attempts to spawn an NPC of the specified type, species, level with the specified seed and parameters at the specified position. Returns `EntityId` of the spawned NPC if successful and `nil` otherwise. + +--- + +#### `EntityId` world.spawnStagehand(`Vec2F` position, `String` type, [`Json` overrides]) + +Attempts to spawn a stagehand of the specified type at the specified position with the specified override parameters. Returns `EntityId` of the spawned stagehand if successful and `nil` otherwise. + +--- + +#### `EntityId` world.spawnProjectile(`String` projectileName, `Vec2F` position, [`EntityId` sourceEntityId], [`Vec2F` direction], [`bool` trackSourceEntity], [`Json` parameters]) + +Attempts to spawn a projectile of the specified type at the specified position with the specified source entity id, direction, and parameters. If trackSourceEntity is `true` then the projectile's position will be locked relative to its source entity's position. Returns the `EntityId` of the spawned projectile if successful and `nil` otherwise. + +--- + +#### `EntityId` world.spawnVehicle(`String` vehicleName, `Vec2F` position, [`Json` overrides]) + +Attempts to spawn a vehicle of the specified type at the specified position with the specified override parameters. Returns the `EntityId` of the spawned vehicle if successful and `nil` otherwise. + +--- + +#### `float` world.threatLevel() + +Returns the threat level of the current world. + +--- + +#### `double` world.time() + +Returns the absolute time of the current world. + +--- + +#### `unsigned` world.day() + +Returns the absolute numerical day of the current world. + +--- + +#### `double` world.timeOfDay() + +Returns a value between 0 and 1 indicating the time within the day of the current world. + +--- + +#### `float` world.dayLength() + +Returns the duration of a day on the current world. + +--- + +#### `Json` world.getProperty(`String` propertyName, [`Json` defaultValue]) + +Returns the JSON value of the specified world property, or defaultValue or `nil` if it is not set. + +--- + +#### `void` world.setProperty(`String` propertyName, `Json` value) + +Sets the specified world property to the specified value. + +--- + +#### `LiquidLevel` world.liquidAt(`Vec2I` position) + +Returns the `LiquidLevel` at the specified tile position, or `nil` if there is no liquid. + +#### `LiquidLevel` world.liquidAt(`RectF` region) + +Returns the average `LiquidLevel` of the most plentiful liquid within the specified region, or `nil` if there is no liquid. + +--- + +#### `float` world.gravity(`Vec2F` position) + +Returns the gravity at the specified position. This should be consistent for all non-dungeon tiles in a world but can be altered by dungeons. + +--- + +#### `bool` world.spawnLiquid(`Vec2F` position, `LiquidId` liquid, `float` quantity) + +Attempts to place the specified quantity of the specified liquid at the specified position. Returns `true` if successful and `false` otherwise. + +--- + +#### `LiquidLevel` world.destroyLiquid(`Vec2F` position) + +Removes any liquid at the specified position and returns the LiquidLevel containing the type and quantity of liquid removed, or `nil` if no liquid is removed. + +--- + +#### `bool` world.isTileProtected(`Vec2F` position) + +Returns `true` if the tile at the specified position is protected and `false` otherwise. + +--- + +#### `PlatformerAStar::Path` world.findPlatformerPath(`Vec2F` startPosition, `Vec2F` endPosition, `ActorMovementParameters` movementParameters, `PlatformerAStar::Parameters` searchParameters) + +Attempts to synchronously pathfind between the specified positions using the specified movement and pathfinding parameters. Returns the path as a list of nodes as successful, or `nil` if no path is found. + +--- + +#### `PlatformerAStar::PathFinder` world.platformerPathStart(`Vec2F` startPosition, `Vec2F` endPosition, `ActorMovementParameters` movementParameters, `PlatformerAStar::Parameters` searchParameters) + +Creates and returns a Lua UserData value which can be used for pathfinding over multiple frames. The `PathFinder` returned has the following two methods: + +##### `bool` explore([`int` nodeLimit]) + +Explores the path up to the specified node count limit. Returns `true` if the pathfinding is complete and `false` if it is still incomplete. If nodeLimit is unspecified, this will search up to the configured maximum number of nodes, making it equivalent to world.platformerPathStart. + +##### `PlatformerAStar::Path` result() + +Returns the completed path. + +--- + +#### `float` world.lightLevel(`Vec2F` position) + +Returns the current logical light level at the specified position. Requires recalculation of lighting, so this should be used sparingly. + +--- + +#### `float` world.windLevel(`Vec2F` position) + +Returns the current wind level at the specified position. + +--- + +#### `bool` world.breathable(`Vec2F` position) + +Returns `true` if the world is breathable at the specified position and `false` otherwise. + +--- + +#### `List` world.environmentStatusEffects(`Vec2F` position) + +Returns a list of the environmental status effects at the specified position. + +--- + +#### `bool` world.underground(`Vec2F` position) + +Returns `true` if the specified position is below the world's surface level and `false` otherwise. + +--- + +#### `bool` world.inSurfaceLayer(`Vec2I` position) + +Returns `true` if the world is terrestrial and the specified position is within its surface layer, and `false` otherwise. + +#### `float` world.surfaceLevel() + +Returns the surface layer base height. + +--- + +#### `int` world.oceanLevel(`Vec2I` position) + +If the specified position is within a region that has ocean (endless) liquid, returns the world Y level of that ocean's surface, or 0 if there is no ocean in the specified region. + +--- + +#### `Variant` world.material(`Vec2F` position, `String` layerName) + +Returns the name of the material at the specified position and layer. Layer can be specified as 'foreground' or 'background'. Returns `false` if the space is empty in that layer. Returns `nil` if the material is NullMaterial (e.g. if the position is in an unloaded sector). + +--- + +#### `String` world.mod(`Vec2F` position, `String` layerName) + +Returns the name of the mod at the specified position and layer, or `nil` if there is no mod. + +--- + +#### `float` world.materialHueShift(`Vec2F` position, `String` layerName) + +Returns the hue shift of the material at the specified position and layer. + +--- + +#### `float` world.modHueShift(`Vec2F` position, `String` layerName) + +Returns the hue shift of the mod at the specified position and layer. + +--- + +#### `unsigned` world.materialColor(`Vec2F` position, `String` layerName) + +Returns the color variant (painted color) of the material at the specified position and layer. + +--- + +#### `void` world.setMaterialColor(`Vec2F` position, `String` layerName, `unsigned` color) + +Sets the color variant of the material at the specified position and layer to the specified color. + +--- + +#### `bool` world.damageTiles(`List` positions, `String` layerName, `Vec2F` sourcePosition, `String` damageType, `float` damageAmount, [`unsigned` harvestLevel], [`EntityId` sourceEntity]) + +Damages all tiles in the specified layer and positions by the specified amount. The source position of the damage determines the initial direction of the damage particles. Damage types are: "plantish", "blockish", "beamish", "explosive", "fire", "tilling". Harvest level determines whether destroyed materials or mods will drop as items. Returns `true` if any damage was done and `false` otherwise. + +--- + +#### `bool` world.damageTileArea(`Vec2F` center, `float` radius, `String` layerName, `Vec2F` sourcePosition, `String` damageType, `float` damageAmount, [`unsigned` harvestLevel, [`EntityId` sourceEntity) + +Identical to world.damageTiles but applies to tiles in a circular radius around the specified center point. + +--- + +#### `List` world.radialTileQuery(`Vec2F` position, `float` radius, `String` layerName) + +Returns a list of existing tiles within `radius` of the given position, on the specified tile layer. + +--- + +#### `bool` world.placeMaterial(`Vec2I` position, `String` layerName, `String` materialName, [`int` hueShift], [`bool` allowOverlap]) + +Attempts to place the specified material in the specified position and layer. If allowOverlap is `true` the material can be placed in a space occupied by mobile entities, otherwise such placement attempts will fail. Returns `true` if the placement succeeds and `false` otherwise. + +--- + +#### `bool` world.placeMod(`Vec2I` position, `String` layerName, `String` modName, [`int` hueShift], [`bool` allowOverlap]) + +Attempts to place the specified mod in the specified position and layer. If allowOverlap is `true` the mod can be placed in a space occupied by mobile entities, otherwise such placement attempts will fail. Returns `true` if the placement succeeds and `false` otherwise. + +--- + +#### `List` world.entityQuery(`Vec2F` position, `Variant` world.monsterQuery(`Vec2F` position, `Variant` world.npcQuery(`Vec2F` position, `Variant` world.objectQuery(`Vec2F` position, `Variant` world.itemDropQuery(`Vec2F` position, `Variant` world.playerQuery(`Vec2F` position, `Variant` world.loungeableQuery(`Vec2F` position, `Variant` world.entityLineQuery(`Vec2F` startPosition, `Vec2F` endPosition, [`Json` options]) + +Similar to world.entityQuery but only returns entities that intersect the line between the specified positions. + +--- + +#### `List` world.objectLineQuery(`Vec2F` startPosition, `Vec2F` endPosition, [`Json` options]) + +Identical to world.entityLineQuery but only considers objects. + +--- + +#### `List` world.npcLineQuery(`Vec2F` startPosition, `Vec2F` endPosition, [`Json` options]) + +Identical to world.entityLineQuery but only considers NPCs. + +--- + +#### `EntityId` world.objectAt(`Vec2I` tilePosition) + +Returns the entity id of any object occupying the specified tile position, or `nil` if the position is not occupied by an object. + +--- + +#### `bool` world.entityExists(`EntityId` entityId) + +Returns `true` if an entity with the specified id exists in the world and `false` otherwise. + +--- + +#### `DamageTeam` world.entityDamageTeam(`EntityId` entityId) + +Returns the current damage team (team type and team number) of the specified entity, or `nil` if the entity doesn't exist. + +--- + +#### `bool` world.entityCanDamage(`EntityId` sourceId, `EntityId` targetId) + +Returns `true` if the specified source entity can damage the specified target entity using their current damage teams and `false` otherwise. + +--- + +#### `bool` world.entityAggressive(`EntityId` entity) + +Returns `true` if the specified entity is an aggressive monster or NPC and `false` otherwise. + +--- + +#### `String` world.entityType(`EntityId` entityId) + +Returns the entity type name of the specified entity, or `nil` if the entity doesn't exist. + +--- + +#### `Vec2F` world.entityPosition(`EntityId` entityId) + +Returns the current world position of the specified entity, or `nil` if the entity doesn't exist. + +--- + +#### `Vec2F` world.entityMouthPosition(`EntityId` entityId) + +Returns the current world mouth position of the specified player, monster, NPC or object, or `nil` if the entity doesn't exist or isn't a valid type. + +--- + +#### `Vec2F` world.entityVelocity(`EntityId` entityId) + +Returns the current velocity of the entity if it is a vehicle, monster, NPC or player and `nil` otherwise. + +--- + +#### `Vec2F` world.entityMetaBoundBOx(`EntityId` entityId) + +Returns the meta bound box of the entity, if any. + +--- + +#### `unsigned` world.entityCurrency(`EntityId` entityId, `String` currencyType) + +Returns the specified player entity's stock of the specified currency type, or `nil` if the entity is not a player. + +--- + +#### `unsigned` world.entityHasCountOfItem(`EntityId` entityId, `Json` itemDescriptor, [`bool` exactMatch]) + +Returns the nubmer of the specified item that the specified player entity is currently carrying, or `nil` if the entity is not a player. If exactMatch is `true` then parameters as well as item name must match. + +NOTE: This function currently does not work correctly over the network, making it inaccurate when not used from client side scripts such as status. + +--- + +#### `Vec2F` world.entityHealth(`EntityId` entityId) + +Returns a `Vec2F` containing the specified entity's current and maximum health if the entity is a player, monster or NPC and `nil` otherwise. + +--- + +#### `String` world.entitySpecies(`EntityId` entityId) + +Returns the name of the specified entity's species if it is a player or NPC and `nil` otherwise. + +--- + +#### `String` world.entityGender(`EntityId` entityId) + +Returns the name of the specified entity's gender if it is a player or NPC and `nil` otherwise. + +--- + +#### `String` world.entityName(`EntityId` entityId) + +Returns a `String` name of the specified entity which has different behavior for different entity types. For players, monsters and NPCs, this will be the configured name of the specific entity. For objects or vehicles, this will be the name of the object or vehicle type. For item drops, this will be the name of the contained item. + +--- + +#### `String` world.entityTypeName(`EntityId` entityId) + +Similar to world.entityName but returns the names of configured types for NPCs and monsters. + +--- + +#### `String` world.entityDescription(`EntityId` entityId, [`String` species]) + +Returns the configured description for the specified inspectable entity (currently only objects and plants support this). Will return a species-specific description if species is specified and a generic description otherwise. + +--- + +#### `JsonArray` world.entityPortrait(`EntityId` entityId, `String` portraitMode) + +Generates a portrait of the specified entity in the specified portrait mode and returns a list of drawables, or `nil` if the entity is not a portrait entity. + +--- + +#### `String` world.entityHandItem(`EntityId` entityId, `String` handName) + +Returns the name of the item held in the specified hand of the specified player or NPC, or `nil` if the entity is not holding an item or is not a player or NPC. Hand name should be specified as "primary" or "alt". + +--- + +#### `ItemDescriptor` world.entityHandItemDescriptor(`EntityId` entityId, `String` handName) + +Similar to world.entityHandItem but returns the full descriptor of the item rather than the name. + + +--- + +### `ItemDescriptor` world.itemDropItem(`EntityId` entityId) + +Returns the item descriptor of an item drop's contents. + +--- + +### `Maybe>` world.biomeBlocksAt(`Vec2I` position) + +Returns the list of biome specific blocks that can place in the biome at the specified position. + +--- + +#### `String` world.entityUniqueId(`EntityId` entityId) + +Returns the unique id of the specified entity, or `nil` if the entity does not have a unique id. + +--- + +#### `Json` world.getNpcScriptParameter(`EntityId` entityId, `String` parameterName, [`Json` defaultValue]) + +Returns the value of the specified NPC's variant script config parameter, or defaultValue or `nil` if the parameter is not set or the entity is not an NPC. + +--- + +#### `Json` world.getObjectParameter(`EntityId` entityId, `String` parameterName, [`Json` defaultValue]) + +Returns the value of the specified object's config parameter, or defaultValue or `nil` if the parameter is not set or the entity is not an object. + +--- + +#### `List` world.objectSpaces(`EntityId` entityId) + +Returns a list of tile positions that the specified object occupies, or `nil` if the entity is not an object. + +--- + +#### `int` world.farmableStage(`EntityId` entityId) + +Returns the current growth stage of the specified farmable object, or `nil` if the entity is not a farmable object. + +--- + +#### `int` world.containerSize(`EntityId` entityId) + +Returns the total capacity of the specified container, or `nil` if the entity is not a container. + +--- + +#### `bool` world.containerClose(`EntityId` entityId) + +Visually closes the specified container. Returns `true` if the entity is a container and `false` otherwise. + +--- + +#### `bool` world.containerOpen(`EntityId` entityId) + +Visually opens the specified container. Returns `true` if the entity is a container and `false` otherwise. + +--- + +#### `JsonArray` world.containerItems(`EntityId` entityId) + +Returns a list of pairs of item descriptors and container positions of all items in the specified container, or `nil` if the entity is not a container. + +--- + +#### `ItemDescriptor` world.containerItemAt(`EntityId` entityId, `unsigned` offset) + +Returns an item descriptor of the item at the specified position in the specified container, or `nil` if the entity is not a container or the offset is out of range. + +--- + +#### `bool` world.containerConsume(`EntityId` entityId, `ItemDescriptor` item) + +Attempts to consume items from the specified container that match the specified item descriptor and returns `true` if successful, `false` if unsuccessful, or `nil` if the entity is not a container. Only succeeds if the full count of the specified item can be consumed. + +--- + +#### `bool` world.containerConsumeAt(`EntityId` entityId, `unsigned` offset, `unsigned` count) + +Similar to world.containerConsume but only considers the specified slot within the container. + +--- + +#### `unsigned` world.containerAvailable(`EntityId` entityId, `ItemDescriptor` item) + +Returns the number of the specified item that are currently available to consume in the specified container, or `nil` if the entity is not a container. + +--- + +#### `JsonArray` world.containerTakeAll(`EntityId` entityId) + +Similar to world.containerItems but consumes all items in the container. + +--- + +#### `ItemDescriptor` world.containerTakeAt(`EntityId` entityId, `unsigned` offset) + +Similar to world.containerItemAt, but consumes all items in the specified slot of the container. + +--- + +#### `ItemDescriptor` world.containerTakeNumItemsAt(`EntityId` entityId, `unsigned` offset, `unsigned` count) + +Similar to world.containerTakeAt, but consumes up to (but not necessarily equal to) the specified count of items from the specified slot of the container and returns only the items consumed. + +--- + +#### `unsigned` world.containerItemsCanFit(`EntityId` entityId, `ItemDescriptor` item) + +Returns the number of times the specified item can fit in the specified container, or `nil` if the entity is not a container. + +--- + +#### `Json` world.containerItemsFitWhere(`EntityId` entityId, `ItemDescriptor` items) + +Returns a JsonObject containing a list of "slots" the specified item would fit and the count of "leftover" items that would remain after attempting to add the items. Returns `nil` if the entity is not a container. + +--- + +#### `ItemDescriptor` world.containerAddItems(`EntityId` entityId, `ItemDescriptor` items) + +Adds the specified items to the specified container. Returns the leftover items after filling the container, or all items if the entity is not a container. + +--- + +#### `ItemDescriptor` world.containerStackItems(`EntityId` entityId, `ItemDescriptor` items) + +Similar to world.containerAddItems but will only combine items with existing stacks and will not fill empty slots. + +--- + +#### `ItemDescriptor` world.containerPutItemsAt(`EntityId` entityId, `ItemDescriptor` items, `unsigned` offset) + +Similar to world.containerAddItems but only considers the specified slot in the container. + +--- + +#### `ItemDescriptor` world.containerItemApply(`EntityId` entityId, `ItemDescriptor` items, `unsigned` offset) + +Attempts to combine the specified items with the current contents (if any) of the specified container slot and returns any items unable to be placed into the slot. + +--- + +#### `ItemDescriptor` world.containerSwapItemsNoCombine(`EntityId` entityId, `ItemDescriptor` items, `unsigned` offset) + +Places the specified items into the specified container slot and returns the previous contents of the slot if successful, or the original items if unsuccessful. + +--- + +#### `ItemDescriptor` world.containerSwapItems(`EntityId` entityId, `ItemDescriptor` items, `unsigned` offset) + +A combination of world.containerItemApply and world.containerSwapItemsNoCombine that attempts to combine items before swapping and returns the leftovers if stacking was successful or the previous contents of the container slot if the items did not stack. + +--- + +#### `LuaValue` world.callScriptedEntity(`EntityId` entityId, `String` functionName, [`LuaValue` args ...]) + +Attempts to call the specified function name in the context of the specified scripted entity with the specified arguments and returns the result. This method is synchronous and thus can only be used on local master entities, i.e. scripts run on the server may only call scripted entities that are also server-side master and scripts run on the client may only call scripted entities that are client-side master on that client. For more featureful entity messaging, use world.sendEntityMessage. + +--- + +#### `RpcPromise` world.sendEntityMessage(`Variant` entityId, `String` messageType, [`LuaValue` args ...]) + +Sends an asynchronous message to an entity with the specified entity id or unique id with the specified message type and arguments and returns an `RpcPromise` which can be used to receive the result of the message when available. See the message table for information on entity message handling. This function __should not be called in any entity's init function__ as the sending entity will not have been fully loaded. + +--- + +#### `RpcPromise` world.findUniqueEntity(`String` uniqueId) + +Attempts to find an entity on the server by unique id and returns an `RpcPromise` that can be used to get the position of that entity if successful. + +--- + +#### `bool` world.loungeableOccupied(`EntityId` entityId) + +Checks whether the specified loungeable entity is currently occupied and returns `true` if it is occupied, `false` if it is unoccupied, or `nil` if it is not a loungeable entity. + +--- + +#### `bool` world.isMonster(`EntityId` entityId, [`bool` aggressive]) + +Returns `true` if the specified entity exists and is a monster and `false` otherwise. If aggressive is specified, will return `false` unless the monster's aggressive state matches the specified value. + +--- + +#### `String` world.monsterType(`EntityId` entityId) + +Returns the monster type of the specified monster, or `nil` if the entity is not a monster. + +--- + +#### `bool` world.isNpc(`EntityId` entityId, [`int` damageTeam]) + +Returns `true` if the specified entity exists and is an NPC and `false` otherwise. If damageTeam is specified, will return `false` unless the NPC's damage team number matches the specified value. + +--- + +#### `bool` world.isEntityInteractive(`EntityId` entityId) + +Returns `true` if an entity with the specified id is player interactive and `false` otherwise. + +--- + +#### `String` world.npcType(`EntityId` entityId) + +Returns the NPC type of the specified NPC, or `nil` if the entity is not an NPC. + +--- + +#### `String` world.stagehandType(`EntityId` entityId) + +Returns the stagehand type of the specified stagehand, or `nil` if the entity is not a stagehand. + +--- + +#### `void` world.debugPoint(`Vec2F` position, `Color` color) + +Displays a point visible in debug mode at the specified world position. + +--- + +#### `void` world.debugLine(`Vec2F` startPosition, `Vec2F` endPosition, `Color` color) + +Displayes a line visible in debug mode between the specified world positions. + +--- + +#### `void` world.debugPoly(`PolyF` poly, `Color` color) + +Displays a polygon consisting of the specified points that is visible in debug mode. + +--- + +#### `void` world.debugText(`String` formatString, [`LuaValue` formatValues ...], `Vec2F` position, `Color` color) + +Displays text visible in debug mode at the specified position using the specified format string and optional formatted values. + +--- + +The following additional world bindings are available only for scripts running on the server. + +--- + +#### `bool` world.breakObject(`EntityId` entityId, `bool` smash) + +Breaks the specified object and returns `true` if successful and `false` otherwise. If smash is `true` the object will not (by default) drop any items. + +--- + +#### `bool` world.isVisibleToPlayer(`RectF` region) + +Returns `true` if any part of the specified region overlaps any player's screen area and `false` otherwise. + +--- + +#### `bool` world.loadRegion(`RectF` region) + +Attempts to load all sectors overlapping the specified region and returns `true` if all sectors are fully loaded and `false` otherwise. + +--- + +#### `bool` world.regionActive(`RectF` region) + +Returns `true` if all sectors overlapping the specified region are fully loaded and `false` otherwise. + +--- + +#### `void` world.setTileProtection(`DungeonId` dungeonId, `bool` protected) + +Enables or disables tile protection for the specified dungeon id. + +--- + +#### `DungeonId` world.dungeonId(`Vec2F` position) + +Returns the dungeon id at the specified world position. + +--- + +#### `DungeonId` world.setDungeonId(`RectI` tileArea, `DungeonId` dungeonId) + +Sets the dungeonId of all tiles within the specified area. + +--- + +#### `Promise` world.enqueuePlacement(`List` distributionConfigs, [`DungeonId` id]) + +Enqueues a biome distribution config for placement through world generation. The returned promise is fulfilled with the position of the placement, once it has been placed. + +--- + +#### `bool` world.isPlayerModified(`RectI` region) + +Returns `true` if any tile within the specified region has been modified (placed or broken) by a player and `false` otherwise. + +--- + +#### `LiquidLevel` world.forceDestroyLiquid(`Vec2F` position) + +Identical to world.destroyLiquid but ignores tile protection. + +--- + +#### `EntityId` world.loadUniqueEntity(`String` uniqueId) + +Forces (synchronous) loading of the specified unique entity and returns its non-unique entity id or 0 if no such unique entity exists. + +--- + +#### `void` world.setUniqueId(`EntityId` entityId, [`String` uniqueId]) + +Sets the unique id of the specified entity to the specified unique id or clears it if no unique id is specified. + +--- + +#### `ItemDescriptor` world.takeItemDrop(`EntityId` targetEntityId, [`EntityId` sourceEntityId]) + +Takes the specified item drop and returns an `ItemDescriptor` of its contents or `nil` if the operation fails. If a source entity id is specified, the item drop will briefly animate toward that entity. + +--- + +#### `void` world.setPlayerStart(`Vec2F` position, [`bool` respawnInWorld]) + +Sets the world's default beam-down point to the specified position. If respawnInWorld is set to `true` then players who die in that world will respawn at the specified start position rather than being returned to their ships. + +--- + +#### `List` world.players() + +Returns a list of the entity ids of all players currently in the world. + +--- + +#### `String` world.fidelity() + +Returns the name of the fidelity level at which the world is currently running. See worldserver.config for fidelity configuration. + +--- + +#### `String` world.flyingType() + +Returns the current flight status of a ship world. + +--- + +#### `String` world.warpPhase() + +Returns the current warp phase of a ship world. + +--- + +#### `void` world.setUniverseFlag(`String` flagName) + +Sets the specified universe flag on the current universe. + +--- + +#### `List` world.universeFlags() + +Returns a list of all universe flags set on the current universe. + +--- + +#### `bool` world.universeFlagSet(`String` flagName) + +Returns `true` if the specified universe flag is set and `false` otherwise. + +--- + +#### `double` world.skyTime() + +Returns the current time for the world's sky. + +--- + +#### `void` world.setSkyTime(`double` time) + +Sets the current time for the world's sky to the specified value. + +--- + +#### `void` world.placeDungeon(`String` dungeonName, `Vec2I` position, [`DungeonId` dungeonId]) + +Generates the specified dungeon in the world at the specified position, ignoring normal dungeon anchoring rules. If a dungeon id is specified, it will be assigned to the dungeon. + +--- + +#### `void` world.placeDungeon(`String` dungeonName, `Vec2I` position, [`DungeonId` dungeonId]) + +Generates the specified dungeon in the world at the specified position. Does not ignore anchoring rules, will fail if the dungeon can't be placed. If a dungeon id is specified, it will be assigned to the dungeon. + +--- + +#### `void` world.addBiomeRegion(`Vec2I` position, `String` biomeName, `String` subBlockSelector, `int` width) + +Adds a biome region to the world, centered on `position`, `width` blocks wide. + +--- + +#### `void` world.expandBiomeRegion(`Vec2I` position, `int` width) + +Expands the biome region currently at `position` by `width` blocks. + +--- + +#### `void` world.pregenerateAddBiome(`Vec2I` position, `int` width) + +Signals a region for asynchronous generation. The region signaled is the region that needs to be generated to add a biome region of `width` tiles to `position`. + +--- + +#### `void` world.pregenerateExpandBiome(`Vec2I` position, `int` width) + +Signals a region for asynchronous generation. The region signaled is the region that needs to be generated to expand the biome at `position` by `width` blocks. + +--- + +#### `void` world.setLayerEnvironmentBiome(`Vec2I` position) + +Sets the environment biome for a layer to the biome at `position`. + +--- + +#### `void` world.setPlanetType(`String` planetType, `String`, primaryBiomeName) + +Sets the planet type of the current world to `planetType` with primary biome `primaryBiomeName`. + +--- + +#### `void` world.setDungeonGravity(`DungeonId` dungeonId, `Maybe` gravity) + +Sets the overriding gravity for the specified dungeon id, or returns it to the world default if unspecified. + +--- + +#### `void` world.setDungeonBreathable(`DungeonId` dungeonId, `Maybe` breathable) + +Sets the overriding breathability for the specified dungeon id, or returns it to the world default if unspecified. diff --git a/scripts/distclean.sh b/scripts/distclean.sh new file mode 100644 index 0000000..8e2856d --- /dev/null +++ b/scripts/distclean.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +cd "`dirname \"$0\"`/.." + +rm -rf build +rm -rf dist diff --git a/scripts/findskippedmatids.sh b/scripts/findskippedmatids.sh new file mode 100644 index 0000000..1e82d08 --- /dev/null +++ b/scripts/findskippedmatids.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +cd "`dirname \"$0\"`" + +cd ../assets/packed/tiles/; + +THISMAT=0; +for i in $(grep materialId */*.material | awk '{print $4}' | sort -n ); + do + THISMAT=$(($THISMAT+1)); + if [ ${i%?} -ne $THISMAT ]; + then + echo "Skipped $THISMAT to $((${i%?}-1))"; + THISMAT=${i%?}; + fi; +done; diff --git a/scripts/format-source.sh b/scripts/format-source.sh new file mode 100644 index 0000000..9b3bd49 --- /dev/null +++ b/scripts/format-source.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +cd "`dirname \"$0\"`/../source" + +: ${CLANG_FORMAT:=clang-format} + +for file in *; do + if [ $file != "extern" -a -d $file ]; then + find $file \( -name '*.cpp' -or -name '*.hpp' \) -exec $CLANG_FORMAT -fallback-style=none -i {} \; + fi +done diff --git a/scripts/gitlab-ci/assemble.sh b/scripts/gitlab-ci/assemble.sh new file mode 100644 index 0000000..95d18cd --- /dev/null +++ b/scripts/gitlab-ci/assemble.sh @@ -0,0 +1,98 @@ +#!/bin/sh -e + +mkdir client_distribution +mkdir client_distribution/assets +mkdir client_distribution/tiled + +./linux_binaries/asset_packer -c scripts/packing.config assets/packed client_distribution/assets/packed.pak +cp -r assets/user client_distribution/assets/ + +cp -r tiled/packed client_distribution/tiled/ + +cp -r doc client_distribution/doc + +mkdir client_distribution/mods +touch client_distribution/mods/mods_go_here + +mkdir client_distribution/win64 +cp -r \ + windows64_binaries/starbound.exe \ + windows64_binaries/starbound.pdb \ + windows64_binaries/starbound_server.exe \ + windows64_binaries/mod_uploader.exe \ + windows64_binaries/*.dll \ + windows64_binaries/iconengines \ + windows64_binaries/imageformats \ + windows64_binaries/platforms \ + windows64_binaries/translations \ + scripts/gitlab-ci/windows64/sbinit.config \ + client_distribution/win64/ + +mkdir client_distribution/win32 +cp \ + windows32_binaries/starbound.exe \ + windows32_binaries/starbound.pdb \ + windows32_binaries/starbound_server.exe \ + windows32_binaries/asset_packer.exe \ + windows32_binaries/asset_unpacker.exe \ + windows32_binaries/dump_versioned_json.exe \ + windows32_binaries/make_versioned_json.exe \ + windows32_binaries/planet_mapgen.exe \ + windows32_binaries/*.dll \ + scripts/gitlab-ci/windows32/sbinit.config \ + client_distribution/win32/ + +mkdir client_distribution/osx +cp -LR scripts/gitlab-ci/macos/Starbound.app client_distribution/osx/ +mkdir client_distribution/osx/Starbound.app/Contents/MacOS +cp macos_binaries/starbound client_distribution/osx/Starbound.app/Contents/MacOS/ +cp macos_binaries/*.dylib client_distribution/osx/Starbound.app/Contents/MacOS/ +cp \ + macos_binaries/starbound_server \ + macos_binaries/asset_packer \ + macos_binaries/asset_unpacker \ + macos_binaries/dump_versioned_json \ + macos_binaries/make_versioned_json \ + macos_binaries/planet_mapgen \ + scripts/gitlab-ci/macos/sbinit.config \ + scripts/gitlab-ci/macos/run-server.sh \ + client_distribution/osx/ + +mkdir client_distribution/linux +cp \ + linux_binaries/starbound \ + linux_binaries/starbound_server \ + linux_binaries/asset_packer \ + linux_binaries/asset_unpacker \ + linux_binaries/dump_versioned_json \ + linux_binaries/make_versioned_json \ + linux_binaries/planet_mapgen \ + linux_binaries/*.so \ + scripts/gitlab-ci/linux/sbinit.config \ + scripts/gitlab-ci/linux/run-client.sh \ + scripts/gitlab-ci/linux/run-server.sh \ + client_distribution/linux/ + +mkdir server_distribution +mkdir server_distribution/assets + +mkdir server_distribution/mods +touch server_distribution/mods/mods_go_here + +./linux_binaries/asset_packer -c scripts/packing.config -s assets/packed server_distribution/assets/packed.pak + +mkdir server_distribution/win64 +mkdir server_distribution/linux + +cp \ + linux_binaries/starbound_server \ + linux_binaries/*.so \ + scripts/gitlab-ci/linux/run-server.sh \ + scripts/gitlab-ci/linux/sbinit.config \ + server_distribution/linux/ + +cp \ + windows64_binaries/starbound_server.exe \ + windows64_binaries/*.dll \ + scripts/gitlab-ci/windows64/sbinit.config \ + server_distribution/win64/ diff --git a/scripts/gitlab-ci/linux/build.sh b/scripts/gitlab-ci/linux/build.sh new file mode 100644 index 0000000..b70f6de --- /dev/null +++ b/scripts/gitlab-ci/linux/build.sh @@ -0,0 +1,22 @@ +#!/bin/sh -e + +mkdir -p build + +cd build +rm -f CMakeCache.txt + +cmake \ + -DCMAKE_BUILD_TYPE=Release \ + -DSTAR_ENABLE_STATIC_LIBGCC_LIBSTDCXX=ON \ + -DSTAR_USE_JEMALLOC=ON \ + -DSTAR_ENABLE_STEAM_INTEGRATION=ON \ + -DCMAKE_INCLUDE_PATH=../lib/linux/include \ + -DCMAKE_LIBRARY_PATH=../lib/linux \ + ../source + +make -j2 + +cd .. + +mv dist linux_binaries +cp lib/linux/*.so linux_binaries/ diff --git a/scripts/gitlab-ci/linux/run-client.sh b/scripts/gitlab-ci/linux/run-client.sh new file mode 100644 index 0000000..b7cb8ba --- /dev/null +++ b/scripts/gitlab-ci/linux/run-client.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +cd "`dirname \"$0\"`" + +LD_LIBRARY_PATH="$LD_LIBRARY_PATH:./" ./starbound "$@" diff --git a/scripts/gitlab-ci/linux/run-server.sh b/scripts/gitlab-ci/linux/run-server.sh new file mode 100644 index 0000000..79b02fd --- /dev/null +++ b/scripts/gitlab-ci/linux/run-server.sh @@ -0,0 +1,22 @@ +#!/bin/sh + +cd "`dirname \"$0\"`" + +terms=" +x-terminal-emulator +konsole +gnome-terminal.wrapper +xfce4-terminal.wrapper +koi8rxterm +lxterm +uxterm +xterm" + +for term in $terms; do + $term -e ./starbound_server $@ + if [ $? -eq 0 ]; then + exit 0; + fi +done + +exit 1 diff --git a/scripts/gitlab-ci/linux/sbinit.config b/scripts/gitlab-ci/linux/sbinit.config new file mode 100644 index 0000000..0be7e7c --- /dev/null +++ b/scripts/gitlab-ci/linux/sbinit.config @@ -0,0 +1,8 @@ +{ + "assetDirectories" : [ + "../assets/", + "../mods/" + ], + + "storageDirectory" : "../storage/" +} diff --git a/scripts/gitlab-ci/linux/test.sh b/scripts/gitlab-ci/linux/test.sh new file mode 100644 index 0000000..0bc8e7c --- /dev/null +++ b/scripts/gitlab-ci/linux/test.sh @@ -0,0 +1,8 @@ +#!/bin/sh -e + +cd linux_binaries + +cp ../scripts/linux/sbinit.config . + +./core_tests +./game_tests diff --git a/scripts/gitlab-ci/macos/Starbound.app/Contents/Info.plist b/scripts/gitlab-ci/macos/Starbound.app/Contents/Info.plist new file mode 100644 index 0000000..44fcdca --- /dev/null +++ b/scripts/gitlab-ci/macos/Starbound.app/Contents/Info.plist @@ -0,0 +1,27 @@ + + + + + CFBundleGetInfoString + Starbound + CFBundleExecutable + starbound + CFBundleIdentifier + com.chucklefish + CFBundleName + starbound + CFBundleIconFile + starbound + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + APPL + LSEnvironment + + MinimumSystemVersion + 10.9.0 + + SDL_FILESYSTEM_BASE_DIR_TYPE + parent + + diff --git a/scripts/gitlab-ci/macos/Starbound.app/Contents/Resources/starbound.icns b/scripts/gitlab-ci/macos/Starbound.app/Contents/Resources/starbound.icns new file mode 100644 index 0000000000000000000000000000000000000000..d6c93ff85d2932c79cc33718a6563c2ca33076ea GIT binary patch literal 47576 zcmeFa2OyR0|37})g^)hu0D!Uqa|cci06>rSMxn{b zL$uJ8Wc*O*GKv}yEp!bP8DG>9Wi^XrEx)wFJYN*HmX=MCR_QzF7MNQ@6QYIHJU=(L zwYgtb$y*Cebw9s`hIxD=vio_g4+>99Q`HJF_i<^YPDTcb!qn1|GaiJjVJwXZ34yA* zri9ucbPauxg9FrLRKL^kIAuW%`F?God*4VRFU%wRcyMHFq>Fw19^Cg99@(yq#ePO^23)x%KrShFHmu9~YIE?Uz;cHbm3f&#$L%8CcrUknIl+ z%+Nr~{K@e2+xDV^ff*Vq>L4I<7!z&RuY*I;)t8h(Kln1lJ< z??ODVY%MJ5<{n(}xhDJgKyEG!JAQ05jEW>Qiphbz{W0DyJ`ZU1sY?O%@$ zWbh+&=0IO=`3yWdP*JG0-{_l4cn8QAJ8FjdSNd=Bf7YYNKi9XNMz){-m(~ujbN~PK z)6kXA)uT4Oj-#QSD=nP`fzl-uYF+YMf6);8xz3@PTkQuYcAh`TTp1-FEkFYz6ZV}P zjKY$Zkx@}q_tC=AR8>`wkqHDj18Debp(`H9vIH7h`lhB@TKdMu=H>=EI)Nx}Eu5&q zK`pI_8ie=Ok5pTKe=V)dsTsd5e-vB`cnsSDHvLf$bwCsBKnipi3pqHnxd|G7)zbH^ zsu5^v*@uRfme#d2mzE|51?@M3@1B-6IQVYu2^t*qUBj9fY?YCb{u#)38M2=fefh&U+H z9aT|L)6)7iBqO8v#}Gaj*NxA)hK6Rrg=%uBb-zL!4i3Qs96AWR1L29mmVFpsR8&k{ zoQuQ0x_WSM96EzO@(YfxsU;2NKsQNCgAD>;P9GGOjI^|-<^hN|)vFwnxz4&vT2 zfTFLK?;v_pO*QQI8VvdjDJF5GMqI zFf_Dsa`!I(k!tVfXK3h~lIB+qGRF{zfP*PS7cc;u4}j*NXJuqU_7NH2TQlFw+5`rM zxBdKbbITC*xw)ZUUi)p}yF^4F5QB5m2t;~BL;#2!*VcA__^`eG&D>0T`{RcXej$wk z)DR?%4+7ykht-9s_=Pn85aeca+|P+VHPH)U14*-=k&Vd?fq=~cNC*>18cj`YL&IM~ zGBV144B;~~3x3Y^>J<{)1bSXo>{p1x#B}`tha!MM5bi=e--q!$zk&}z_NMv4bwU=oDKkZt=gzJYAZ2>!| zJ(h)m0TP)HO+;AJ-p>0o`@eO3 z{E`8`_3yHzs$rL;H^$^e6p;frZuigW}uU!oT=m?*~Q{>Ki})$j)>d zywW~ba{G4mFVcEwD8twJMf)0GXlQ|-bN-mWzqOm2?H^fjZv>;DGJq<0epH0Y#Qy_1 zdLNgEC;{qVK2?YkR3>5{Q4H1khms1Q2GIh-A$j1ZEhG_m3aNosM4* zfGOXtKaJ=@^ubcWkah5kyA^^80_C9ZQmEQN-@JTVAx%Il6tw)Q{<%V6kW3&CQVQgQ zsW$|2(4Rm^8e|X{hpYh;V449*`)ANll<&TeWPuz=59A}T0@ktu`H0cm(}RG5r+(0= z0v@dLA%p&F7|6lF#-D*o5HTS4Am`ZVXe(6gptv-I0|5IMxCXp|a{M_2ss#G(H|3x* ze;tEx1DC&*h71FfzH1n7hKFD6cL?kr=NF?7=tj^4j`QUonfL&Hh$0{cmc9>Z1KmF6 zy99pzQVhJG^5p@{I&cCF=}gS zsssN(^_2lEeaC=mNFihZSoB?md;sR5H6bX!Euafh10KP)fR#Viy-^2IrUjn0l|M(> z59EU=Q-r7ix?nynh}!4aQVuozhmt0s12F`nA_e?3f!F}|APGPuBm(5CA;?<<}NgH0JOpSoFJZ% zBp@B~6zo+J-~w?z=#Muf0)hZKAakHE5g1Vs5#j$F42IgiqhvwG)IevzYTiL+FzRY* zo*u-MA)pBc+5-r}e-5JofhhSIs0I-M5)X2=x3<23ihyBd2*^U1KxZNX=YVb~)1O12 zs^DOMQw|KYzl=dx0rqdD!KJO+cMhYusi|bYLtyuqzZiW$KY}K3oKOD9bQ$0QIVKL4 z4uX_}{%`Yr`>mWIpy$g3+=fI1B7k3N`a|TGNW)N3Q&R;)%^c$9kHAPuOpJmueU3Fl ze+1wParzu;|3Cu+P2V>U@P*t4yHw>{fl*jckOOf9oWNKs2Ld5ZV62q`kD-64AHgvIzblF&-Bc75;ND7;*c~vIB}Ceb9NZ zngz%_Mt@&l3lzM^1WhiWPfH<4|3$Es)}LUG5EJm07R-k+Ix^A$H90730g(i^Vr1YV zc;7Ag=Mbn9=(yjMgWCRe3|uhezLf?ywsGG@@UwZpKVa{qzSw*~CxR9v(D(-yB|sTs z4;X=EGa#K{{t@5VucbVIJHC=YD5NOxOFe&xfDvU2F7u8MCtqX04-)N<#E1wD4TMU9 z1{+^ne-D2ocutc7L+vM;uPtb`1{Y2xuq%f0uV3_{aS}%#W5cKR^G4 zF2756L8IQUeOmgvOuqqs!-vEChyMAVLI0QdAm!)h_vSa#|0{I~+zHvgc_;K&#vdrZ zS+D-p`N=LGT%6za1O%+VsoVYi{c7L(|5^Hey#*nqZ~6D7->eT{hJQtVbI1KBX=k1Q z;FSYDPk_k)>ha)X9=J@)zgeqA!DRp?mnH?um`XXpcj0dItHds;YkPc5)Ipb z2R;J6nV$pGa(LQ*FZu{%7F+{JfaSo3!7;1>^I%#H%K_5iGHLs)Qv~C&3O1Mke#-zH zOFpPc|387tz(jx-2jiN9jRS>nk%LhQ!8m|2*kJk^Py*-p??d3q2kXOcdU>#FX#xN1 zC?6mJM%3p~fO9exwjV#^K+gV5z&QYsFV;{nwm{oHAJhI%*i=Bz@BmyOHzQ%W@WHTQ z0Qq(7T7X0t2M`QPOaJ1H{~!_%zBYhKgL|hYAQ{F51j3RN27v)sKadV%2RJ~hbQrk9 zCTM|Z3%n-nzeB$j1_hj84xmSoVPilatQB|-uL0M^Eua}z14AWl0W<%7dLZiaSxGk@ zd{Y1Qtn_cw4$exdFbzN-Y+n~ll|P@4G#?p)??Zn%H~nj6Wk3gJ2tZ&k@Y4up4%oxI z0e84lDfl&~Fhk%83<2UMFee}+y!?Lz^MeJ0eYS!n0-53Euri?h5jd7KL%=@cUm(F1 z0c~*ZOv79OTbLPe8xFPs?j--(Is3kTeZT-5EesX{s{&qvXT}~dP2E519?V|=c$P!} zE#S#&7)-O_5&tVVxN?ABLxkypYjPZ{31|mL@CHmD!Qz0ZgEJNQ785*06~GV)bKuDl z0X+D8ruyHcm=TH^E6pWwG zqX6$k-iGZ*%NY1v5S$6PB%t@j8VW`dXxnFB-v0@kGk6az2S|bQn*u9_Bf)1+Ltn@4 z0p2}I0{3A>>0i9@A4K90c4ydKuuoDzBJ5ED5j z4X_Eb1swMG3fOJXcimytfHzDLe7htC-rt*tSpf1d@E@ooS^)b03rHZ!@!*?Fg1`Ib z((hg`eg~xd`qkneYq!5E{5Ob)?f#qd(XIn=@J8b{2x%^k94*mbn6Yq10L;n4WNq zTUqz5vP1s;fNj5r4*BH7P%w(o?WfB)fY#>4i1?)NX&L;ruJ z%%2ku`S*o7KT5y)@P9P^%#GNt0KnC|;4kjRw~#-3_82Z47nDg%9o_6K>^RPXGVnQ- z15-0lJ!x)cY!CjI*PsagVB+BH=K%vny2mb(| z4qjk&MkqOy0s#K%@UPYFiwD=|>c6O%IA~~J|Due6a{!vKy@`RNtv%n_n+6W@CiZ7l zP3#@aZEeo7v+%O9v9O~Q0erAD8i0j~38100ffIs)5)$&?|3&ElBxpZG(SL|y{2mPh z_T2^W^Mg%9j(Q8CS7<~?iO_vKH&trhBSXtvwJwc&j=bH7WZWp4ft`2g*0=^Vl^T9J zFTV7I9`l0uxU(mwk*;Gd?b9VDft04A#5^praN&!3quy80+r!tMGU+8~I`oOx=My?+ z#@=m>PF{(q^ZS&5?)ad3p@bfyCt{X)l8c^5NVmb(?^UlxScmL=!q;iS@>|E3bZTnl z*PhIbdS5uG=@RGj9F1uTTRtVU}9(=;Wy4O zj!HQiuc;DT8J_#v{0K{6;Tmq6G`IhGS4~9pIzdF}y;vsY7_7u0FT^YF_L@CU9QF+g z2<4f%(D;?hT!O&Vl*f`TSTcj;{7S zlB;^k5F8I?>VOdPdBQA52I{l35es!IrF*&e6r-;YHN{{$ZLpr!>|5l6A&=HNl(|Mx znl6|Gc6z*h=j;!0XW8o5c3{eGy*Jq1UbE$ibNgG*O(9L9+boaDvp1TZvK3-P@_A!4 zsNEFuQ!iglZOp;j8M4da)22=zx+`0a}}s{)zRkB=tr7 zkHws%mh~uMJsCa?uqi`UirX+b6R-B-I=+`ZOLj{BLNmh@u zK9-7cjNLRLE5s;>He<+jIl`fuL-65~S+hubaf}Qnx<&`9%8a^J*xnm$NC4T6mEJ+u z6*ee=D`zA53kq#8>fWSHY859tHuP0t$l19U=7&PELvMvhkyz$(U?f39*B`0{F$ ziD^jJQajsI{LJDpUUe^Z+@*5enMxv)5^oo}7@Z2j;xLChz72x%?WJ|YxbnBDPa4-F z5`$i|`;kyPiL9Q4ILsl6>q#b z?%7YBzSjq|tW3=d8sx(PPgc`%ks) z*U*e9shi~K=AwMrNNKXrY6RV^a+C>KG4#l3=c%u~^q|0fL(03@XBJ$!NqsJS)C=ze zHm9^<`pN6Gi-a1y?w5H<>NETrBGDxFzj&n~Nlg z`)pM7(E&-n^&D9-l^dcQ5>$-tu` zPX4h~Il!x;)5Co2?SPfN_=v#c>|SVyZs8E+Ilg3nLnU1sw*GZ&%L+*Abg72BNp=xw zwNsFMp(h>UjD1jRhubseEPwxNXs(y##YeKccu8MEf2le*4lF*VI_z?IjiqK&NDV0nMCnr{2@#bu2hV|rD$)p=T z@@-vV8L^Gw;*;JfVyYF8qbC}P)bL7-&nFj(iHNXu2>8ERq2CmZBkpt^#- zo0eL%g5&7eY1vvbjv#`cO84G5A0&x?8};V1XU%ygskn$ zC^xj_dO7EWO*kBWlH{FX$)sa4qGR+tZf(5^ZLpTUpN#vxd#iBOS_*x=SL^kn4|vkW zP-JP05YL{$i%AMn)xH<+$LNF#4p5%uS8C+GeJ+~8WZTzhj?I4MW=q%OQ$fqq$!@a+ z_V)f|O0!Oy6Y3e0t{GDE!2!XWR+N^Q8TA+iQgye$`HId^zw}n=Jzo1#$-b zWozB9n$8DUbA|yQPxrL5(582~y}yz1PUr%T^?R9*RQ}R?=C-8{;`J$)3FvbYvK6iS zTZaLU3+2Z}lt|ecTe!!m2*-Yz^#MwT5Ddm?qX6Q;TKis$)A#NT1Z0goaeY33b&s;t zy5@xxbvhquP?x!!j|-bunj5xs`~ZyVLmiD*$$%xi)_T-Kg)}XMfIxO0-8mFJFgy8* zEd9~bYCbK?6Gi_1A7=gN(XVBt<(+0Od7CXppVxo3tuHCPsi*v<{Z*w8*;n=X;)e+1 z{j&Ov$Sk`PH)0E34XM8xQm9>1s9|U?p*W%uR*JebFiBL_l`n}sNJrOBZO9coc%f>K zCrlAu*EntT*5X-W+zGe;Y4c;k-y1>b|LQ)}7j<3HLN*pYa+_9Onv1W%vJS^Tp%Hz#@e^JO_`8U zdaY~ksyw76j5z1LcVO^`ks`bCapGHRq;K?}x@BvaaizW{-c zuljpeH9FPg#Kk<@y5qaxe+5deaKDdH{TP9srK`!$e){`T5tPkf?nm6{8l z+yioZizr)_o%844pRT@k$Gg7H`Qg*Zm6)B^#2FXp2A~@^Nz^W|Um|>nPtk#HvT>@6 z&pMQZO9myjJSi%H#;S9&MY=GZVdr>qPxH;GM3m-h+4EjEhVEUM=;a(Scq5JX$ewNV z4jMeYbY2wU(e1@_2{M`R&P$zv@+QB~#wZtNIo|7?iap2k^5wCYt1sP+v$Yg_4~v6f ztw<30N5&W`Mb>o~QdEjBo?PPsgxFe1UQ!X9WoWE=L{*-O!Fwyo0NY@xF}*WS_(XBv z<4Y*7^Ed)+<#ov-5Z}bgZQ+J_g|)XQF-@FKspYy+h9oLajZF+`g`_m)$x<$zs}Hrl zmy-KH*4*6VjVA75RX&^+Us?U7P}ffO3lV) zof6L)W^BpeS>C;LZZ{{cz()O}%WEAhYP7(n@GZ_OH$1S<7IB{^zlbv;^>o*>EtD$J z;3cl2<0;4G*sJQD3!0BVFb+Qt4}Uv$KD=09x)XYfqDJAIiq7duU;Ys1b6q62Q(~X7 zc6`dOoe+_`h89&(myv8Z%~ROiFrhm;*v@6K*cOfkN1aaK2_7eK-+J`$dO*o(5kWD& zHAE8Hk(%u`&oY8uqDYpD2_@+lgVg6J37&s!&WyZ18`y-YUeg^B5YaBqs(4&DuJM>n zg#IzjdUicE6n5Xl7-ZsZ^b29(w+0^3rsza0qS}nZyuSTqy~0H3Ztqp|SgXmGcox%G z@nlqq02@Zi(=Lfwc{Z%06kTUlJaP!nGdxd{vqgJ8b?!ME_sZ)}&j+fThs9_IqFqot zG>Sw3-Kf5xS_V)nw#Y|Jp}raM!bzap{hHDB%OcG4!C|(|YBkiya6Va=XR{Hybe9ym zpMW)7+e}j9Ni(-f?GQ@yIxSj&U|&mF41- zb0uCX$F{wN*L?-gn zDR zvE=oVsj-w&VI&$$$?LFLLlJa0>uio(Tl`_({F3#x9wU@fEvLs_`l%J$3;_~pi0_MA zvcpuO+_j7vH{ov=AR*1ClwhktXAMR>D2w#{s?~PPJbTYJR2DOwgX%HqnbGTa^|ovw zBVNkaMT{%OsBn!2yw6BT=psJDqA=dZmO~g3KMr}!5429$rE91}mZ?6zpr%<<#&hw> zl%&b=yQ{;Fa)r`odGkKrmCdBO*v!axQv_=%>@?~12VH7h)01B9^@c`^-C$e}e2q&1 zp4O{LNS1=a^f#k-g=e)DN+nLxD5#k|z*;yuVwIgud1@M~gKMKdHN_xKa#nsv{@iO9 z$BW8IK$2oJ3plg)*K}22U{6f^tt`hH_t9jyPw(}%2%3&g`ZLu>Kr-k@FMhTfn zvt7|>TMlFR4<^i9c%Wp+8cuwx}Jg>1(7VH^47TbwqrvjBLHPQin2%B^z2t(6KmbNpM29fh+DIS zN&e_~AqD(=5;W!rDuI1XX_TcBgXXmQM*hcCB8! z?za$DCx5TxYS~g@Q@(xYwA@CnWE$Is3cn^Zzx-lCkxwLkq_*2$w*1wM?2k$1qjQhsKVN)k%y!K-?bNvOc*3sk4IkQQ0Ok{FqE2B3Ff_q1N zkFi`Y`@$uKa^GVXfFqhMjxaktI$4}8tPCO)avcfMbAdh>= zSTO#yY1aiHUdXc5 zEuuyx^aQ_V%~J$XR$+I31f&`X`VF`2uw z%ONAvdd`e-gBe%q2;rksuxZrs*xPc(bH%Uix^<=|=S3u6TgAOQ5`Q%Nw1%~o5aw#+ zF*X0JduIAs=7FMfugsbr(`{~-zH3=}HT0LmfpcfqvWNLY^(pgKQ~`If3PF8ADeNS(lRh{a&u zJ@c!nH!1AQuSeG`MRQ{ohMnSctTCh#^mpr0anPkro5^6?QpUUp2u(6-Nb#xZVS0*( zFh=UOKifNQC8l0`H1~?j+ctXI{OzLPg1472r6@w5SDiAki^aq14^?P0=5=N{-eROC zqC|xrg2(bcDn$O#N3Y-w2!}Yc?!~>z*fJgZtzDizqZoo2-P#+vnNgu$nVTJV#yZoo zbB;by$}sXUAQxjvOT;O-(b$WleoLwLv~BPE#T2};f~zl9f-$$RYPmGJ+HpjaZqDE5 zPjS$68awX41xNOE7pFZtZ@DFH#RyR~;U~?cAi}h52eMYhm_=NKZ-e1_W?bl~Rwr+; zz95lw`UwU)?%ZQ7;nx8Bupzjk} zI~ipuZ1AEB!(QWcQEg$Jx#y8aoFlpsXE^6j@MndRkxwgM`JN7>8pOZz)_8-4kHCJW z$klNP?uw^hgTj$en9|E66U}TVVsR9Vw2o068p8MQ*ML2|fDyYzKD`oVXf&POhx1Gu zcZ?-Zm)PMJ1W#((PB7&4hvBygI?7t_xu_YR+AR&fiRU6|lH|#`GozB?WHG^bJzRGu z_7cKYL%htGH&CRb_w}8ay0|hdg;bg?JkArc7d6TW-QF)#CCkP!kT@!?(2;t-2|&_g zG_7*QuU|PYXno#9Xzq%;BaY-mjta{heO=i&MVcz!_Gt_rVMsnzFTYURc(U#bpJ0Q| zs`KwRqU}y};VyK_Z~&aQ@;0%#RIq8zC5Xvjy?n~O+#Wy1BTXy5^W?!!Ot?W_=2Bmg zkv{bpZ|#7#dc%_Hm1GivD9DbI?n;-mL3OrZ(EEg9{&(@8Zisa5=sH`-pHH?|x_D#U ztA6lCG=wxtG>&Z1gdoP_B%dWon060^N$$xIjF1@r!H!t_V=fI+TyCN#-kvx@l=BF2 z?k;YSsEC2y3N5DgGcSwS#WHrl`>4uI!&Fu)N}4&x4jD&-%>1g#RK{~o*DgZxuF>Ny z@KGxkb$GScy-4=MAuN98{iwE&Qx>E5vKl(Od-i-I7BLp(TfM8&ZK}RYH*FhsJf-QM z`y9Qcp03}1DKj8W!0?Tu;)9GURSpsQTUY3UQ`q^`g<~S<)$%I?n%DI&Qx$ZFUcw$X ziR-QM-fUP#U?Gu3^ah<|J>GB5)sN@|2wudjVXIQ446B}xHoCtlr?-1ar+4Bi&V?Oe zGThvH-V^Io7grv07HagtOQ-13F*JQ9AJ1ayu?SzSNHg)A2!Kr!n(k!fbu7l_*Cxc? zkb|C?v~U^bZ=Bf9$y$l*$#Ram2Bhmnl6mSFck9h}qp95xo0hGplzlFnJ72iI#9n;* z7{??05yJK8*V)Ns8@X;-=Tz>j^^nTWEBV+9sM*Zy93{ zaj!=W_QwH5k~4RL%!Y;u1FziC%3AA<3mR1K_ueeDq_!wYlRwYMoo}BoqlJcM>+8f{p0A{b2uKwxF?oEKG@#u z#+oLB*v36GO02nDXcUe$7?R>OHSvPmC-v_3Vw0^yd1gDstD#POj|wlsQXxT4E2;}D zA-N*2hpb=U1dH;GRZ(y0jmM01=+5;`4oLZv&U<_LzUCa?Eg2T_FufH$ zRupA&6WAR0aff7r4>N>hFBe`|&JB2y3aq@MDa9L}Izy^GHM6T=y_&rcZlSRh`#6_e z$MEc&CzULk4J=E#`p%wQHFQG4uI@N=-cIhk+nsI|`5N3tL>8qD%a$_i$$4r{k#jnW zY`LGV-U=1Ga3w_4duZ{guw}_DimtIjI(C6g(^}%mvlQ)R#Ql}T?pBim%(yBqWS>b# zLt0UyI@kF{^pT^%(?=HXxoPmMLkB`))p)1bZcGjEA=L8jkjK5_7GGqei}urFArojLitFp!sm-*m^$e@#Pq+a|6W1 z6cyQXR6{SlJ#TB88Ko?Cwk{7e@yCHLRmG2W~@!F4vI| zoiJU?_wTRjON$ZuF~{!k5->WB0A2xv0m?=t^9cGR*y!zy(5_ zk`{c*CT%5sd|!qSpPuWao~|k%!lzTO#rDmoWGOqZ>TyHkmI*vms4;hjTg^cugYG;> z{mZs!c0O^2n{w0WID^cI=G>m@OV-n(AwAa|W*i5r@Mh|4Rx5Acq&)ImXp(iLw-fn+ zc0h6HrJ1E7Ly;Tj&7@Oef$}e66r?=OEUKoB7Tzim#X2;0R%(k~oup;(qLvO~$D+A? zvCs+crtrT|yE)le774$n<$n*iYRdM9dE`htP?xUd}U9X6(`JSiQEknDgh z1-D)A{!`T3q4Auh`J_ZrdY2|2x-YSm>d>COar69odbvU~r^T}%Qq6n0OcD;w6Iu+T zeSF9TbH4Xu*&ht;qY>|~3~ zj|uT^(}<-quCpXe*Xd6C(OtYiid?A0M>$lPx+)e|uo)VPDB|}vaN4?6zM$)IUnQR? zixzcEnQ^>oL_4;gjIdd7er0?Gs@|{oO3vHnWPc9kJJJW29-2))@7x^AS{H~Eqy6w9 zR6`v}6NhaH+ls0XF!60Ta=&9$Te<&O^Y}BiG^XN|*JT`JE4=trM~v3?f}h)@)DYs% zv*_@|S31nfEvQ|Fokn7#@9CJILc`CGMBPoiF)Jwv3o2=4#&*W_dVlP;>e@LyFFY=_|?T zY?gEZpAznE6RWixd7rj{BDu`Dhks(PH25U zt>P%RSM}ME_;4oI+nU`9dvQi--M$G|U6zN~f{R+E{cZeL@87$)bn1zVMe_CZIx9-1 z)%8-wg+yUTTekUxv+>6ElOvgslw(`?Tm&1!SNilWk3ghE^)%3*E=*DP10JPm$-T~r zOS@Pbp=@We?{^97j**?y8y4y79+UMo??1wtWN@zUz6-_hS=^2tbE~q9EOYyeZtf&P zGIt**&i7g8XwVn$4Jp0mA#k$3_#{A8ASgpcS6hP(&bBHnkaZW5vUC*fM&forwUI|B zlsB|aB^h$%qkQ8X^}NgCAtR&bC9zMq@V8D)*HUL%C>%4vV_CZ5*J-(9zo%y;F-~G2 zq}JMRGL$FkTdAC!9kti#(ZiWAWF2NjqaEe??i4ff?Lt!X_>2Bf0lt??BNyW~pRv{n z^t{*<-}D|~rsPvxFqG)kesm`m9S;c%GxNpA~liCflh+#OToU>a>g7W&dM$+d==`pH> zUUhwUn@1tSRiwJjk}-+wbnN&}cPd|HkUYIAEluWj#OvBRY2H&`C{PySe=d}23vMlMw8_tCXHHgFW0{`eKjEp`ZOWO5xINu_ z?k?Ru+tu)P>UiOhmwN#H5v|H>(f9JT?r}Q?>hJONGJLNtzSnPU(lTK;t@CbWt(_rx zl>BO_!^)$=matT?#?$J)bB5AVVFg=+!^1EF6Xkf`My8#fvD}+FhCv)`HwDfpC#|wT z$vuN6tT6P$g_;quWb9?cSD&+>vp)XRnRse&n(loPvo(KkbaCuMQVe@q>t~m&sJz5) zJVGAQd?F=o`-9S@ja=~kh*RWo{0 zy?$0ZL*zsNc8o&|;TfCZkE<-plZD(Lg$-5(1L(bcf)^MrtcR!-G>-J#9>RL3lG|hJ z*GZ;=e}Zvk*jGF~R5;7lpduwlC+#}QR>K&$W#$35IX9j2d|Hv^xNTFl(}@>wO6ewv zLOX$@$4+v+PYs$0*cz1OjU0UV$fboLD+{7xh9=gR%=IeYyk1(|e8RQp^qjD@28mt} zjeFDa+$P(N#h9XpMMnoFGW^f4A9)*jn?KpKVZ>xzunzYoY0XB71dvhdZvHIo#>IB$ z^-3Dcg(u?nN@vnjI_PY6k9NWP-rSZS6{s;WN#~eem$>to&-}fgP*a!~%jF(_tJHvV zfnjlc(@zv$HnVqfTFfhQ*zwY+rlp?V!JEy1t=F;J?a}NJS!qkN+`MXjvxiyS z9m)(SBwfb6IJv?RD$eVL#TeS7PfSHfD1VAlfJs#Gm8dv(i5su$@Jnv|Pm*_ZvNF0= zi7Dh&Xk#0y@*A0~-Va?O4t9g3iHXyV0$29{o=J_Mc!UCa(%Zy3a+pTd6E=c zW8k-yCx3YG+)1q$epU5ipn68P)j$=FHr{b2UA((yJ)Ab(^top&q10Phn6nA?*=chq z+E47&6UIl{9L|I-hzBJyUbNzK=odCQ_EFwp(nfJF_Mu>&%qo)f!G>zxG*5y#VOdmZ zNyxPh;v{R~BMlLrxsXU7#I-XD_mx>bs$>TX^iQi}t3=r-+@AH9`Ov$4)RmVkZs)|p z@rrSu5p}U9=qMgalbbx1>vZvx042Zv#O!#w{>4nSt>Zn~XU4}g3(Ot_Nz9w?Df+!1 zDeqrzs|xo|Ucup_x=c`3A?`)FX-PB~;h;$RlK-tFaaH)1^QpD_848a_HIo=#9LZy< z%|y}WZeeR>FKGKpBK+~#DMwm7we09MMFuT#@|zI5&#o$j6RTVgG2%K8NVGc3;fvBT zrz1J;q7NQPT2C|Y8p3-|>q4z@Z!VI)K+P|th=pT5lA9mfdcadtrE8myaDDovC3}!u zEba*rhZ)z00hDax)&4bfJ9y#uDvt$uhT@ccYOrn$I*LKy8M9~Dw3;A2VEqmjn61|{ zX6$i~`jVi^hA3s#-r!L^(}ZTRv|a1H%?lsL#)+N@r=Lx5CUs5$6_@7Ohae)45Ws7rzbnBUoi10 zQ?<)>UpX?8^5ohj*61m}Qazs5!G209EfrFo0sI-Y*vvDQ4u~@~zQ^n<*737p3GR9S5TJGWcoP$}{vO){} zeY$kX?Waj4KKMRNpE*G=W=tKgMz(U~!uH6bB61a%&|#IXgC^0d2_ zhSynhqzbmPM|rvtBYt1oZr4A4?F`fvz7#X41T zx>n8XjRZD3;R<6gjiYpG@XliZ zo$SntHYvMjI9zb)XapbcI_Fb6%=_1DEY+9T#-BwIyCuec zBy#_hhfjB{j3}~tL_@NmR1wLfZ@9KMi<9gxIo$D7EtF!a(b_NP?RU*NsJ*s3}8 zHi*qYZsq3GQ95tTLT3GxDU62<(6+lbp>P(Iif@+2e3pvFiTw0uosscEkLBjBFy#eTWJ+Mp zbhpt&-NE2ysiD*u){M<{Ox`HCa2bC!GMT9-z3tWwM~G|(bWlB-1vfaw6RS9@qNBI~ znM%9rz+XQh6xM}SYdUzRPnY=B?yO1vOviPjM3w6i<-W7G7~V$54~!%8rW8B{p6~H~ znk%iuqVAR7nnfa&!S@um+RXDf?1vu9#n9t4&3ci0-EmfPt4^m?!B5o`r$gUOltSZ+ zvP45%yInX(M%sAl$rvvj(r}OY9{GhPowUG)opNVQor0ACno_z~m&X|@7_`qblRUV` z-oA)MlXdo5suKB~@w2*dWH-omIjY#jb*aw3ToVW$$?rNQZGHNqR)bXrH_nHO505I& zXK*dgw3sECWK6BaGre^?&Us1T=ySSbRUhqq!t6in89{c;Z>4XXnWeC^dHY7XnUd>c z9!{W!ojz(RRkcu; z;H*7rl|sg!Y9VC2<2&!DCml^fUMJu2qBUhmXN7{|g%&GG>y}*2N9S6HCKBk>=}|P& z+m#Bp3UEX^q0-N^4E4C9-oSd%S*m-AOW755A5osef{gNZlxrXv>9e;eq`h_Y7LFQc z)p{|Fp^2R7FX|~FATqzfuNNg%7w0SA>c5+SyivcrI?e2hO^g{}zZMWIzp_?Ibl)>q zG>RyGB?oyk;!KAz$Fn;+Mt=Mq6xBikE!o4%zM+B%g6zj}g(l>+@2b8+w-yrO@u8_p z5h|+;hQxk50D0cUZswv7Mu{}|b!ugiQ&?mQJ>WxM zuaz3|pxEgcVl;O_Ry4FQwU@H)3>u!JG6emqv1)Dt1Wufo2}szFFJz1*`_pD^a?XVjS~v3uW?z? zYAZb=Jrt%hZC9#4evOeQ5r#XHG~n{oYn6jB=sx=g&0ejO57B%H)=pOM(XJpauu3Rw z^jJtZ69pglXdt8@LfcVTUhJr(mDf_}(ODgyI#+LeV-g{wct+V7E?HxifWvyvj$CxS zG=<2-AguY_vZ4}h@a#2rih2Pk(2H0lGJH5t>k!YBv!Y65S@zB$hchw)D z&U)31!e-babsZ8tVrJeAel7mD;&nGoY#h9L?4HYyw1`)qH?SdiS--i>96BI2^F-W2 zb$Fe!+-snP_gku##EwEH~{ca`mNS*(1t=?udCLf*Ns#Zq$gUhEc7gSz5 z8-!iTgu-OOd=Z&Fw)pymiHt!ZbNy^g%fzRi(US1pQrpE;_HFHIYnN@^YD*i!9;dBP z0fqKCx`XAG&r`2Hc!lyIe=<>I@Z_*%^cFu>*|M}G2|baH1LfUWl#^8F)O}my{wVre zT=7L)jA5oG95|!w%=S;;_PI=T54O-e3aX-(USFo^eUd#6)z$kre=Q2 z-pdYtxN}mo!%tGHiuAoS`U1j_=n(tkPmJuwP}i@ugcS9T1*4q^NJ zYlZfgSSpGxk!94)7LAyX&zqYj*|#LHmQ9^Nqq=uDy1I(2luSd0EqCTa%;)v@Ve8YX zRtHF^bpnjPwfq^Erw(D?7M)Nbw+0xoPXr}~N&HH10)*K=1Ep;YNNDwtuwgtvU)sR5Z8>sW zl~|yF_;UU=zs))n(fT88+M{WA+(?||7-Rbq+9U-#heP?e7b`xg@oO)~?$oP3Q8Eh! zH1OrrM7#aGZQJ_qtA|b?QWAwut9C7@$}96+m!3L)iPgZ|p7Htn>I?^++s1FWBoq84Wl(H_5PYXcTqq2!w?#s*G6O|ZU0k)J2K~tD5&u2-n6ja zyN*9l?ZE%;i69zKd0sG}z`XfW5F=qh#8Li$8RP%2Y5V?%!tvt(erHo$$X3~z89AFf z^ANIi7cxR+o>2&AoN+47-VSH`I9r$Pl0+!6DegxdM22PZ)Xz@;;j<<_2N<(i&~eLc)T$eskkX)Q#|>trKFd za$Z(;As>}`zb%z-al8d=3ZC%3)IKgNLPtSdGwXXr3+W>1h9rFXtPm=X%g|@iK zFNWkhRsGpt(!+qtcF+>oy0S#l&foZ=r8f>8D1QS4=p~g$VT?!+g9{Ek6^?(a0}{!p*yN)@ z)oF37{gHBW7PWFM_UqLl;$s3cWuJCA6PFjONwWmFH-DZ-qidxJB!H$EYmC+uv}YOW zoeOfjl4qfI`4-Q-CY_gwH`+clQ4B_kUSFi+8swreuZoKK4?q_D_q3CA= zE5ZKHYE|wMMQJBe#Ll7ey1^&cB3BoYC$z*S<3TY+F2~iV&_m2-au0>CBLg-(CVUbpyABM?5Iw|O;J_2gOp0*fU@b~k7+XbbGgoH6L$L3-47`9ez}ua1Tfy2v3pzsMmL zd>N?Ea-XsH?)5mxInx;%%)oS;p<6x%sar#@b6JhAj;N-}2zHqzV#nBPn&#iXN?y>x zUr0cK15sAsz#lM6?BuoJW7LTF3k#jpz0JMr*`Cd zGsn901F~7_Iy_Q*R4}-I#2#XO5EnYX!{(RI1u^P#W^$D=me#x(24AqMZx0LlKndHy z9mwemaD8l6HXGK#D3MEkBa*NUh$`ss+BeZ<_jY_r4go?9fQ zyQiAC#9Cw=KTK!xWItv{)aomCXsw3%{Ycc@kD~)s5T?dINyw;#;A<15S=`u47UxYw z@HVxGGrEcUdrQ#W?S{Ui@jeGyVUz)_Z`X?SQ88Lw6wi9K@HI!C)c(5dYZzY+4KuFV z1kgo)@2$KS*FAzmVd;K@mLNH0i}%gTgBgkae5XEM5$=VZNZ?O3++9OR`Y5#m0;gqT-u z{9V_`=Z}n7ha}gckzPE%J6*>@DOdpha?G_aSO#RlO-p6>CYzfv|4u;4l>LVt^XT<> zsd-pa2a%P={z}t`Gko;^9Mprx$eLclq)fHaZ=gc?2Z5E;$|U}d{katF%BdyR#;XMsYxT?;?Siaz8S8ffKUh6`dW+Tl+ZU*Dr(ks@ELo-}7GO;m73E&z*1BDZ>J6f<3m7^(aH;;Kr}%t;(lCzHycuGiM9Zn3 zEhrWcsM==yJauonoPub?QE$gL^2oC-p*aJ|Mc4os2>z6Rt&1>v2+dI$5jLu9OToGg z@9hgh(jot~mT2PEH8HU9^eCQ7fAI^7Q$jgYztA;B1sL6M_W~7A4#6bb-~c5=KHXqg zTdk_y@fQ=Jzox(WczL)xVn;-G1^RHw$)0v#uj89v5cMarl!Ox<8=yc>byk{#hfySW zuo!+OthjqzuF$h#8DG9PPi+)y##(6kQ{0% zRy-~m*)*K$0k@PiGm;kRVq|TA?2VZvzVY1i4dpi^yOcPsS!PjNH=|q!*T)XyYsy}j z+PO?0tOolF+KFmtsR35)xNq2App?6<_MA!LoetP!Pim>=W*S=N135xsrlaEkN`3R; zJ#1#va?q^gHbYu$1Lef{09AyzPjQUZXD8vV)9TpgadCON-6abTA=NWMZ}S&6f?6n9dcX$4)0aY#&RUuerEl8PQ+%Yl zam_o$bSTI#W(B>A={=;=RlzJJplWqm(SPJP!86O~?(PWm1m8;aoBFn&bp z-f~Kg7iZNBA!W}aGJTbf_<}q4q_!uh>l)I_PmXDAjRL^1qbpueIB`4p;GXe9lijt! znrF)gIbq|t8LMMB^4I|K=*br6DLxQlidq*u)x?!6WhoL6eGI3?Cd_i?hN+^$24eFU z`iS~kbjebej5s-WU55DuojMw%0g9dmYk?orL%+oq)>~lMqz41Dx&)Zj4>gEdk8|if zv}P>N)?sSeqv3$=>?a`TOWpl3Jz1Xx$}5brSt|nAB&>5lJGDPOfrcIsca(8)>4KzA zH13+QvMO6%^LB|V{Vfl>Qvhu5BU`D&?~SZ`6Ggp%Nj1nr&$hsd+ta#hQ&C^~ z6e70D%1IUr-FZQE?>4s!wbv)vqGjn#Yz&A!(P3w!&-XU=-QLWP7!v!Ob%FeW?e5zZ znuT#53OtN4J&v_g2s0vHrt!nuvt`R}$VbKV&`;FC_`HD2&nn7Pw;XkN=d7Jay6J8v z_4rOf2mX>QgY|S|hraZEcYSp4sOiOObtZ+$JNwkeHu>8h70G!HZ3J>1z5&L&dCUq8 zKT)$ADIh^P#mTjsEf8CjFGxmH^g%b}1Kr0C)`=_^7wXe^;;X)NX5mn6{LqV7;tPAwN)HbZa# literal 0 HcmV?d00001 diff --git a/scripts/gitlab-ci/macos/build.sh b/scripts/gitlab-ci/macos/build.sh new file mode 100644 index 0000000..7517843 --- /dev/null +++ b/scripts/gitlab-ci/macos/build.sh @@ -0,0 +1,25 @@ +#!/bin/sh -e + +mkdir -p build +cd build + +rm -f CMakeCache.txt + +cmake \ + -DCMAKE_BUILD_TYPE=Release \ + -DSTAR_ENABLE_STATIC_LIBGCC_LIBSTDCXX=ON \ + -DSTAR_USE_JEMALLOC=OFF \ + -DSTAR_ENABLE_STEAM_INTEGRATION=ON \ + -DSTAR_ENABLE_DISCORD_INTEGRATION=ON \ + -DCMAKE_INCLUDE_PATH=../lib/osx/include \ + -DCMAKE_LIBRARY_PATH=../lib/osx \ + -DCMAKE_OSX_SYSROOT=/ \ + -DCMAKE_OSX_DEPLOYMENT_TARGET=10.9 \ + ../source + +make -j2 + +cd .. + +mv dist macos_binaries +cp lib/osx/*.dylib macos_binaries/ diff --git a/scripts/gitlab-ci/macos/run-server.sh b/scripts/gitlab-ci/macos/run-server.sh new file mode 100644 index 0000000..e043263 --- /dev/null +++ b/scripts/gitlab-ci/macos/run-server.sh @@ -0,0 +1,11 @@ +#!/bin/sh -e + +cd "`dirname \"$0\"`" + +osascript <[^\\.]+.hs):(?\\d+):(?\\d+)", + "targets": { + "build": { + "cmd": "make -Cbuild build" + }, + "clean": { + "cmd": "make -Cbuild clean" + } + } +} diff --git a/scripts/ide/clang_complete b/scripts/ide/clang_complete new file mode 100644 index 0000000..668155d --- /dev/null +++ b/scripts/ide/clang_complete @@ -0,0 +1,12 @@ +-Isource/application/ +-Isource/core/ +-Isource/extern/ +-Isource/frontend/ +-Isource/game/ +-Isource/game/interfaces/ +-Isource/game/items/ +-Isource/game/objects/ +-Isource/game/scripting/ +-Isource/game/terrain/ +-Isource/graphics/ +-Isource/windowing/ diff --git a/scripts/ide/linter-clang-flags b/scripts/ide/linter-clang-flags new file mode 100644 index 0000000..ff2eca9 --- /dev/null +++ b/scripts/ide/linter-clang-flags @@ -0,0 +1 @@ +-Wall -Wextra -Wuninitialized -Wno-parentheses-equality -Qunused-arguments -Wno-deprecated-declarations -Woverloaded-virtual -Wnon-virtual-dtor -Winit-self diff --git a/scripts/ide/linter-clang-includes b/scripts/ide/linter-clang-includes new file mode 100644 index 0000000..940befb --- /dev/null +++ b/scripts/ide/linter-clang-includes @@ -0,0 +1,12 @@ +source/application/ +source/core/ +source/extern/ +source/frontend/ +source/game/ +source/game/interfaces/ +source/game/items/ +source/game/objects/ +source/game/scripting/ +source/game/terrain/ +source/graphics/ +source/windowing/ diff --git a/scripts/join-multiline-source-strings.sh b/scripts/join-multiline-source-strings.sh new file mode 100644 index 0000000..2b47d55 --- /dev/null +++ b/scripts/join-multiline-source-strings.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +cd "`dirname \"$0\"`/../source" + +for file in *; do + if [ $file != "extern" -a -d $file ]; then + # This is not bulletproof, this will break if the last character on a line + # is an *escaped* quote. + find $file \( -name '*.cpp' -or -name '*.hpp' \) -exec perl -0777 -i -pe 's/\"\s*\n\s*\"//igs' {} \; + fi +done diff --git a/scripts/linux/autotag_objects.sh b/scripts/linux/autotag_objects.sh new file mode 100644 index 0000000..45e3b67 --- /dev/null +++ b/scripts/linux/autotag_objects.sh @@ -0,0 +1,21 @@ +#!/bin/sh -e + +cd "`dirname \"$0\"`/../.." + +for OBJECT in $(find assets/ -name *.object); do + + EXISTING_TAGS=$(./dist/json_tool --opt '/tags' "$OBJECT") + if test "x$EXISTING_TAGS" != "x"; then + echo "Skipping $OBJECT; it already has tags..." + continue + fi + + echo "Automatically tagging $OBJECT" + + RACE_TAGS=$(./dist/json_tool --opt '/race' "$OBJECT" --array) + CATEGORY_TAGS=$(./dist/json_tool --opt '/category' "$OBJECT" --array) + TYPE_TAGS=$(./dist/json_tool --opt '/objectType' "$OBJECT" --array) + + TAGS=$(./dist/json_tool -j "$RACE_TAGS" -j "$CATEGORY_TAGS" -j "$TYPE_TAGS" --get '/*' --array-unique) + ./dist/json_tool -i --set '/tags' "$TAGS" --after objectName "$OBJECT" +done diff --git a/scripts/linux/find_invalid_tiles.sh b/scripts/linux/find_invalid_tiles.sh new file mode 100644 index 0000000..9f2d939 --- /dev/null +++ b/scripts/linux/find_invalid_tiles.sh @@ -0,0 +1,6 @@ +#!/bin/sh -e + +cd "`dirname \"$0\"`/../../dist" + +./map_grep "invalid=true" ../assets/packed/dungeons/ +./map_grep "invalid=true" ../assets/devel/dungeons/ diff --git a/scripts/linux/object_tag_editor.sh b/scripts/linux/object_tag_editor.sh new file mode 100644 index 0000000..56735ef --- /dev/null +++ b/scripts/linux/object_tag_editor.sh @@ -0,0 +1,5 @@ +#!/bin/sh -e + +cd "`dirname \"$0\"`/../.." + +./dist/json_tool --find ./assets .object --edit '/tags' --input csv --after objectName --editor-image '/orientations/0/imageLayers/0/image' --editor-image '/orientations/0/dualImage' --editor-image '/orientations/0/image' --editor-image '/orientations/0/leftImage' diff --git a/scripts/linux/sbinit.config b/scripts/linux/sbinit.config new file mode 100644 index 0000000..66776ed --- /dev/null +++ b/scripts/linux/sbinit.config @@ -0,0 +1,20 @@ +{ + "assetDirectories" : [ + "../assets/", + "./mods/" + ], + + "storageDirectory" : "./", + + "assetsSettings" : { + "pathIgnore" : [], + "digestIgnore" : [ + ".*" + ] + }, + + "defaultConfiguration" : { + "allowAdminCommandsFromAnyone" : true, + "anonymousConnectionsAreAdmin" : true + } +} diff --git a/scripts/linux/setup.sh b/scripts/linux/setup.sh new file mode 100644 index 0000000..d40b92d --- /dev/null +++ b/scripts/linux/setup.sh @@ -0,0 +1,27 @@ +#!/bin/sh + +cd "`dirname \"$0\"`/../.." + +mkdir -p dist +cp scripts/linux/sbinit.config dist/ + +mkdir -p build +cd build + +if [ -d /usr/lib/ccache ]; then + export PATH=/usr/lib/ccache/:$PATH +fi + +LINUX_LIB_DIR=../lib/linux + +cmake \ + -DCMAKE_EXPORT_COMPILE_COMMANDS=1 \ + -DCMAKE_BUILD_TYPE=RelWithAsserts \ + -DSTAR_USE_JEMALLOC=ON \ + -DCMAKE_INCLUDE_PATH=$LINUX_LIB_DIR/include \ + -DCMAKE_LIBRARY_PATH=$LINUX_LIB_DIR/ \ + ../source + +if [ $# -ne 0 ]; then + make -j$* +fi diff --git a/scripts/linux/ycm_extra_conf.py b/scripts/linux/ycm_extra_conf.py new file mode 100644 index 0000000..6de5135 --- /dev/null +++ b/scripts/linux/ycm_extra_conf.py @@ -0,0 +1,149 @@ +# This file is NOT licensed under the GPLv3, which is the license for the rest +# of YouCompleteMe. +# +# Here's the license text for this file: +# +# This is free and unencumbered software released into the public domain. +# +# Anyone is free to copy, modify, publish, use, compile, sell, or +# distribute this software, either in source code form or as a compiled +# binary, for any purpose, commercial or non-commercial, and by any +# means. +# +# In jurisdictions that recognize copyright laws, the author or authors +# of this software dedicate any and all copyright interest in the +# software to the public domain. We make this dedication for the benefit +# of the public at large and to the detriment of our heirs and +# successors. We intend this dedication to be an overt act of +# relinquishment in perpetuity of all present and future rights to this +# software under copyright law. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. +# +# For more information, please refer to + +import os +import ycm_core + +# These are the compilation flags that will be used in case there's no +# compilation database set (by default, one is not set). +# CHANGE THIS LIST OF FLAGS. YES, THIS IS THE DROID YOU HAVE BEEN LOOKING FOR. +flags = [ +'-Wall', +'-Wextra', +'-Werror', +'-fexceptions', +'-DNDEBUG', +# THIS IS IMPORTANT! Without a "-std=" flag, clang won't know which +# language to use when compiling headers. So it will guess. Badly. So C++ +# headers will be compiled as C headers. You don't want that so ALWAYS specify +# a "-std=". +# For a C project, you would set this to something like 'c99' instead of +# 'c++11'. +'-std=c++11', +'-stdlib=libc++', +# ...and the same thing goes for the magic -x option which specifies the +# language that the files to be compiled are written in. This is mostly +# relevant for c++ headers. +# For a C project, you would set this to 'c' instead of 'c++'. +'-x', 'c++' +] + + +# Set this to the absolute path to the folder (NOT the file!) containing the +# compile_commands.json file to use that instead of 'flags'. See here for +# more details: http://clang.llvm.org/docs/JSONCompilationDatabase.html +# +# Most projects will NOT need to set this to anything; you can just change the +# 'flags' list of compilation flags. Notice that YCM itself uses that approach. +compilation_database_folder = '/home/catherine/starbound/build' + +if compilation_database_folder: + database = ycm_core.CompilationDatabase( compilation_database_folder ) +else: + database = None + +SOURCE_EXTENSIONS = [ '.cpp', '.cxx', '.cc', '.c', '.m', '.mm' ] + +def DirectoryOfThisScript(): + return os.path.dirname( os.path.abspath( __file__ ) ) + + +def MakeRelativePathsInFlagsAbsolute( flags, working_directory ): + if not working_directory: + return list( flags ) + new_flags = [] + make_next_absolute = False + path_flags = [ '-isystem', '-I', '-iquote', '--sysroot=' ] + for flag in flags: + new_flag = flag + + if make_next_absolute: + make_next_absolute = False + if not flag.startswith( '/' ): + new_flag = os.path.join( working_directory, flag ) + + for path_flag in path_flags: + if flag == path_flag: + make_next_absolute = True + break + + if flag.startswith( path_flag ): + path = flag[ len( path_flag ): ] + new_flag = path_flag + os.path.join( working_directory, path ) + break + + if new_flag: + new_flags.append( new_flag ) + return new_flags + + +def IsHeaderFile( filename ): + extension = os.path.splitext( filename )[ 1 ] + return extension in [ '.h', '.hxx', '.hpp', '.hh' ] + + +def GetCompilationInfoForFile( filename ): + # The compilation_commands.json file generated by CMake does not have entries + # for header files. So we do our best by asking the db for flags for a + # corresponding source file, if any. If one exists, the flags for that file + # should be good enough. + if IsHeaderFile( filename ): + basename = os.path.splitext( filename )[ 0 ] + for extension in SOURCE_EXTENSIONS: + replacement_file = basename + extension + if os.path.exists( replacement_file ): + compilation_info = database.GetCompilationInfoForFile( + replacement_file ) + if compilation_info.compiler_flags_: + return compilation_info + return None + return database.GetCompilationInfoForFile( filename ) + + +def FlagsForFile( filename, **kwargs ): + if database: + # Bear in mind that compilation_info.compiler_flags_ does NOT return a + # python list, but a "list-like" StringVec object + compilation_info = GetCompilationInfoForFile( filename ) + if not compilation_info: + return None + + final_flags = MakeRelativePathsInFlagsAbsolute( + compilation_info.compiler_flags_, + compilation_info.compiler_working_dir_ ) + + else: + relative_to = DirectoryOfThisScript() + final_flags = MakeRelativePathsInFlagsAbsolute( flags, relative_to ) + + return { + 'flags': final_flags, + 'do_cache': True + } diff --git a/scripts/osx/build.command b/scripts/osx/build.command new file mode 100644 index 0000000..bd124ff --- /dev/null +++ b/scripts/osx/build.command @@ -0,0 +1,6 @@ +#!/bin/sh + +cd "$(dirname $0)/../.." + +cd build +make -j3 diff --git a/scripts/osx/copy-to-steam.sh b/scripts/osx/copy-to-steam.sh new file mode 100644 index 0000000..165ee40 --- /dev/null +++ b/scripts/osx/copy-to-steam.sh @@ -0,0 +1,9 @@ +#!/bin/sh -e + +cd "`dirname \"$0\"`/../../dist" + +STEAM_INSTALL_DIR="$HOME/Library/Application Support/Steam/steamapps/common/Starbound - Unstable" + +./asset_packer -c ../scripts/packing.config ../assets/packed ./packed.pak +mv packed.pak "$STEAM_INSTALL_DIR/assets/packed.pak" +cp starbound "$STEAM_INSTALL_DIR/osx/Starbound.app/Contents/MacOS/" diff --git a/scripts/osx/launch-steam.sh b/scripts/osx/launch-steam.sh new file mode 100644 index 0000000..0c2d283 --- /dev/null +++ b/scripts/osx/launch-steam.sh @@ -0,0 +1,7 @@ +#!/bin/sh -e + +cd "`dirname \"$0\"`/../../dist" + +cp ../scripts/steam_appid.txt . + +DYLD_INSERT_LIBRARIES=~/Library/Application\ Support/Steam/Steam.AppBundle/Steam/Contents/MacOS/gameoverlayrenderer.dylib DYLD_LIBRARY_PATH=../lib/osx/ $@ diff --git a/scripts/osx/run.command b/scripts/osx/run.command new file mode 100644 index 0000000..88c14b8 --- /dev/null +++ b/scripts/osx/run.command @@ -0,0 +1,6 @@ +#!/bin/sh + +cd "$(dirname $0)/../.." + +cd dist +./starbound diff --git a/scripts/osx/sbinit.config b/scripts/osx/sbinit.config new file mode 100644 index 0000000..05a983b --- /dev/null +++ b/scripts/osx/sbinit.config @@ -0,0 +1,23 @@ +{ + "assetDirectories" : [ + "../assets/", + "./mods/" + ], + + "storageDirectory" : "./", + + "assetsSettings" : { + "pathIgnore" : [], + "digestIgnore" : [ + ".*" + ] + }, + + "defaultConfiguration" : { + "allowAdminCommandsFromAnyone" : true, + "anonymousConnectionsAreAdmin" : true, + "bindings" : { + "KeybindingClear" : [ { "type" : "key", "value" : "Del", "mods" : [] } ] + } + } +} diff --git a/scripts/osx/setup.command b/scripts/osx/setup.command new file mode 100644 index 0000000..2a1d17c --- /dev/null +++ b/scripts/osx/setup.command @@ -0,0 +1,17 @@ +#!/bin/sh + +cd "$(dirname $0)/../.." + +mkdir -p dist +cp scripts/osx/sbinit.config dist/ + +mkdir -p build +cd build + +CC=clang CXX=clang++ /Applications/CMake.app/Contents/bin/cmake \ + -DCMAKE_EXPORT_COMPILE_COMMANDS=true \ + -DCMAKE_BUILD_TYPE=RelWithAsserts \ + -DSTAR_USE_JEMALLOC=ON \ + -DCMAKE_INCLUDE_PATH=../lib/osx/include \ + -DCMAKE_LIBRARY_PATH=../lib/osx/ \ + ../source diff --git a/scripts/osx/setup.sh b/scripts/osx/setup.sh new file mode 100644 index 0000000..7144ac0 --- /dev/null +++ b/scripts/osx/setup.sh @@ -0,0 +1,29 @@ +#!/bin/sh + +cd "`dirname \"$0\"`/../.." + +mkdir -p dist +cp scripts/osx/sbinit.config dist/ + +mkdir -p build +cd build + +QT5_INSTALL_PATH=/usr/local/opt/qt5 +if [ -d $QT5_INSTALL_PATH ]; then + export PATH=$QT5_INSTALL_PATH/bin:$PATH + export LDFLAGS=-L$QT5_INSTALL_PATH/lib + export CPPFLAGS=-I$QT5_INSTALL_PATH/include + export CMAKE_PREFIX_PATH=$QT5_INSTALL_PATH + BUILD_QT_TOOLS=ON +else + BUILD_QT_TOOLS=OFF +fi + +CC=clang CXX=clang++ cmake \ + -DCMAKE_EXPORT_COMPILE_COMMANDS=true \ + -DCMAKE_BUILD_TYPE=RelWithAsserts \ + -DSTAR_BUILD_QT_TOOLS=$BUILD_QT_TOOLS \ + -DSTAR_USE_JEMALLOC=ON \ + -DCMAKE_INCLUDE_PATH=../lib/osx/include \ + -DCMAKE_LIBRARY_PATH=../lib/osx/ \ + ../source diff --git a/scripts/osx/ycm_extra_conf.py b/scripts/osx/ycm_extra_conf.py new file mode 100644 index 0000000..b9b969d --- /dev/null +++ b/scripts/osx/ycm_extra_conf.py @@ -0,0 +1,160 @@ +# This file is NOT licensed under the GPLv3, which is the license for the rest +# of YouCompleteMe. +# +# Here's the license text for this file: +# +# This is free and unencumbered software released into the public domain. +# +# Anyone is free to copy, modify, publish, use, compile, sell, or +# distribute this software, either in source code form or as a compiled +# binary, for any purpose, commercial or non-commercial, and by any +# means. +# +# In jurisdictions that recognize copyright laws, the author or authors +# of this software dedicate any and all copyright interest in the +# software to the public domain. We make this dedication for the benefit +# of the public at large and to the detriment of our heirs and +# successors. We intend this dedication to be an overt act of +# relinquishment in perpetuity of all present and future rights to this +# software under copyright law. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. +# +# For more information, please refer to + +import os +import ycm_core + +# These are the compilation flags that will be used in case there's no +# compilation database set (by default, one is not set). +# CHANGE THIS LIST OF FLAGS. YES, THIS IS THE DROID YOU HAVE BEEN LOOKING FOR. +flags = [ +'-Wall', +'-Wextra', +'-Werror', +'-fexceptions', +'-DNDEBUG', +# THIS IS IMPORTANT! Without a "-std=" flag, clang won't know which +# language to use when compiling headers. So it will guess. Badly. So C++ +# headers will be compiled as C headers. You don't want that so ALWAYS specify +# a "-std=". +# For a C project, you would set this to something like 'c99' instead of +# 'c++11'. +'-std=c++14', +'-stdlib=libc++', +# ...and the same thing goes for the magic -x option which specifies the +# language that the files to be compiled are written in. This is mostly +# relevant for c++ headers. +# For a C project, you would set this to 'c' instead of 'c++'. +'-x', 'c++' +] + + +# Set this to the absolute path to the folder (NOT the file!) containing the +# compile_commands.json file to use that instead of 'flags'. See here for +# more details: http://clang.llvm.org/docs/JSONCompilationDatabase.html +# +# You can get CMake to generate this file for you by adding: +# set( CMAKE_EXPORT_COMPILE_COMMANDS 1 ) +# to your CMakeLists.txt file. +# +# Most projects will NOT need to set this to anything; you can just change the +# 'flags' list of compilation flags. Notice that YCM itself uses that approach. +compilation_database_folder = '/Users/catherine/Documents/starbound/build' + +if os.path.exists( compilation_database_folder ): + database = ycm_core.CompilationDatabase( compilation_database_folder ) +else: + database = None + +SOURCE_EXTENSIONS = [ '.cpp', '.cxx', '.cc', '.c', '.m', '.mm' ] + +def DirectoryOfThisScript(): + return os.path.dirname( os.path.abspath( __file__ ) ) + + +def MakeRelativePathsInFlagsAbsolute( flags, working_directory ): + if not working_directory: + return list( flags ) + new_flags = [] + make_next_absolute = False + path_flags = [ '-isystem', '-I', '-iquote', '--sysroot=' ] + for flag in flags: + new_flag = flag + + if make_next_absolute: + make_next_absolute = False + if not flag.startswith( '/' ): + new_flag = os.path.join( working_directory, flag ) + + for path_flag in path_flags: + if flag == path_flag: + make_next_absolute = True + break + + if flag.startswith( path_flag ): + path = flag[ len( path_flag ): ] + new_flag = path_flag + os.path.join( working_directory, path ) + break + + if new_flag: + new_flags.append( new_flag ) + return new_flags + + +def IsHeaderFile( filename ): + extension = os.path.splitext( filename )[ 1 ] + return extension in [ '.h', '.hxx', '.hpp', '.hh' ] + + +def GetCompilationInfoForFile( filename ): + # The compilation_commands.json file generated by CMake does not have entries + # for header files. So we do our best by asking the db for flags for a + # corresponding source file, if any. If one exists, the flags for that file + # should be good enough. + if IsHeaderFile( filename ): + basename = os.path.splitext( filename )[ 0 ] + for extension in SOURCE_EXTENSIONS: + replacement_file = basename + extension + if os.path.exists( replacement_file ): + compilation_info = database.GetCompilationInfoForFile( + replacement_file ) + if compilation_info.compiler_flags_: + return compilation_info + return None + return database.GetCompilationInfoForFile( filename ) + + +def FlagsForFile( filename, **kwargs ): + if database: + # Bear in mind that compilation_info.compiler_flags_ does NOT return a + # python list, but a "list-like" StringVec object + compilation_info = GetCompilationInfoForFile( filename ) + if not compilation_info: + return None + + final_flags = MakeRelativePathsInFlagsAbsolute( + compilation_info.compiler_flags_, + compilation_info.compiler_working_dir_ ) + + else: + relative_to = DirectoryOfThisScript() + final_flags = MakeRelativePathsInFlagsAbsolute( flags, relative_to ) + + # Workaround for clang bug regarding default paths in compiling from + # libclang.so vs the clang binary + final_flags.extend([ + "-isystem/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1/", + "-isystem/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/7.0.2/include" + ]) + + return { + 'flags': final_flags, + 'do_cache': True + } diff --git a/scripts/packing.config b/scripts/packing.config new file mode 100644 index 0000000..faa51b5 --- /dev/null +++ b/scripts/packing.config @@ -0,0 +1,69 @@ +{ + "globalIgnore" : [ + "/\\.", + "/~", + "thumbs\\.db$", + "\\.bak$", + "\\.tmp$", + "\\.zip$", + "\\.orig$", + "\\.fail$", + "\\.psd$", + "\\.tmx$" + ], + + "serverIgnore" : [ + "\\.ogg$", + "\\.wav$" + ], + + "extensionOrdering" : [ + "", + "config", + "bush", + "grass", + "modularfoliage", + "modularstem", + "projectile", + "monstertype", + "monsterpart", + "npctype", + "npctype", + "vehicle", + "particle", + "animation", + "object", + "liquid", + "material", + "object", + "coin", + "miningtool", + "flashlight", + "wiretool", + "beamminingtool", + "harvestingtool", + "tillingtool", + "paintingbeamtool", + "headarmor", + "chestarmor", + "legsarmor", + "backarmor", + "consumable", + "blueprint", + "codex", + "techitem", + "instrument", + "grapplinghook", + "thrownitem", + "unlockitem", + "activeitem", + "augmentitem", + "material", + "matmod", + "lua", + "frames", + "png", + "wav", + "ogg" + ] +} diff --git a/scripts/steam_appid.txt b/scripts/steam_appid.txt new file mode 100644 index 0000000..ba0ecc1 --- /dev/null +++ b/scripts/steam_appid.txt @@ -0,0 +1 @@ +367540 diff --git a/scripts/windows/build.bat b/scripts/windows/build.bat new file mode 100644 index 0000000..8d84823 --- /dev/null +++ b/scripts/windows/build.bat @@ -0,0 +1,7 @@ +cd /d %~dp0 +cd ..\.. + +cd build +IF %ERRORLEVEL% NEQ 0 exit /b %ERRORLEVEL% + +"C:\Program Files (x86)\CMake\bin\cmake.exe" --build . --config %1 diff --git a/scripts/windows/copy-to-steam32.bat b/scripts/windows/copy-to-steam32.bat new file mode 100644 index 0000000..5e8c414 --- /dev/null +++ b/scripts/windows/copy-to-steam32.bat @@ -0,0 +1,8 @@ +cd /d %~dp0 +cd ..\..\dist + +set STEAM_STARBOUND_DIR=c:\Program Files (x86)\Steam\steamapps\common\Starbound - Unstable + +.\asset_packer.exe -c ..\assets\packing.config "custom assets" ..\assets\packed .\packed.pak +move packed.pak "%STEAM_STARBOUND_DIR%\assets\packed.pak" +copy starbound.exe "%STEAM_STARBOUND_DIR%\win32\" diff --git a/scripts/windows/copy-to-steam64.bat b/scripts/windows/copy-to-steam64.bat new file mode 100644 index 0000000..319ff33 --- /dev/null +++ b/scripts/windows/copy-to-steam64.bat @@ -0,0 +1,8 @@ +cd /d %~dp0 +cd ..\..\dist + +set STEAM_STARBOUND_DIR=c:\Program Files (x86)\Steam\steamapps\common\Starbound - Unstable + +.\asset_packer.exe -c ..\assets\packing.config "custom assets" ..\assets\packed .\packed.pak +move packed.pak "%STEAM_STARBOUND_DIR%\assets\packed.pak" +copy starbound.exe "%STEAM_STARBOUND_DIR%\win64\" diff --git a/scripts/windows/find_invalid_tiles.bat b/scripts/windows/find_invalid_tiles.bat new file mode 100644 index 0000000..20d0bb9 --- /dev/null +++ b/scripts/windows/find_invalid_tiles.bat @@ -0,0 +1,7 @@ +pushd %~dp0 +pushd ..\..\dist +map_grep "invalid=true" ..\assets\packed\dungeons\ +map_grep "invalid=true" ..\assets\devel\dungeons\ +pause +popd +popd diff --git a/scripts/windows/sbinit.config b/scripts/windows/sbinit.config new file mode 100644 index 0000000..03c55d6 --- /dev/null +++ b/scripts/windows/sbinit.config @@ -0,0 +1,24 @@ +{ + "assetDirectories" : [ + "..\\assets\\", + ".\\mods\\" + ], + + "storageDirectory" : ".\\", + + "assetsSettings" : { + "pathIgnore" : [], + "digestIgnore" : [ + ".*" + ] + }, + + "defaultConfiguration" : { + "allowAdminCommandsFromAnyone" : true, + "anonymousConnectionsAreAdmin" : true, + + "gameServerBind" : "*", + "queryServerBind" : "*", + "rconServerBind" : "*" + } +} diff --git a/scripts/windows/setup32.bat b/scripts/windows/setup32.bat new file mode 100644 index 0000000..2934e01 --- /dev/null +++ b/scripts/windows/setup32.bat @@ -0,0 +1,44 @@ +cd /d %~dp0 +cd ..\.. + +mkdir dist +del dist\*.dll +copy lib\windows32\*.dll dist\ +copy scripts\windows\sbinit.config dist\ + +mkdir build +cd build + +if exist "C:\Program Files (x86)\CMake\bin" ( + set CMAKE_EXE_PATH="C:\Program Files (x86)\CMake\bin" +) else ( + set CMAKE_EXE_PATH="C:\Program Files\CMake\bin" +) + +set QT_PREFIX_PATH=C:\Qt\5.6\msvc2015 + +if exist %QT_PREFIX_PATH% ( + +%CMAKE_EXE_PATH%\cmake.exe ^ + ..\source ^ + -G"Visual Studio 14" ^ + -T"v140_xp" ^ + -DSTAR_USE_JEMALLOC=OFF ^ + -DCMAKE_PREFIX_PATH=%QT_PREFIX_PATH% ^ + -DSTAR_BUILD_QT_TOOLS=ON ^ + -DCMAKE_INCLUDE_PATH="..\lib\windows32\include" ^ + -DCMAKE_LIBRARY_PATH="..\lib\windows32" + +) else ( + +%CMAKE_EXE_PATH%\cmake.exe ^ + ..\source ^ + -G "Visual Studio 14" ^ + -T"v140_xp" ^ + -DSTAR_USE_JEMALLOC=OFF ^ + -DCMAKE_INCLUDE_PATH="..\lib\windows32\include" ^ + -DCMAKE_LIBRARY_PATH="..\lib\windows32" + +) + +pause diff --git a/scripts/windows/setup64.bat b/scripts/windows/setup64.bat new file mode 100644 index 0000000..c7a3d7c --- /dev/null +++ b/scripts/windows/setup64.bat @@ -0,0 +1,42 @@ +cd /d %~dp0 +cd ..\.. + +mkdir dist +del dist\*.dll +copy lib\windows64\*.dll dist\ +copy scripts\windows\sbinit.config dist\ + +mkdir build +cd build + +if exist "C:\Program Files (x86)\CMake\bin" ( + set CMAKE_EXE_PATH="C:\Program Files (x86)\CMake\bin" +) else ( + set CMAKE_EXE_PATH="C:\Program Files\CMake\bin" +) + +set QT_PREFIX_PATH=C:\Qt\5.6\msvc2015_64 + +if exist %QT_PREFIX_PATH% ( + +%CMAKE_EXE_PATH%\cmake.exe ^ + ..\source ^ + -G"Visual Studio 15 Win64" ^ + -DSTAR_USE_JEMALLOC=ON ^ + -DCMAKE_PREFIX_PATH=%QT_PREFIX_PATH% ^ + -DSTAR_BUILD_QT_TOOLS=ON ^ + -DCMAKE_INCLUDE_PATH="..\lib\windows64\include" ^ + -DCMAKE_LIBRARY_PATH="..\lib\windows64" + +) else ( + +%CMAKE_EXE_PATH%\cmake.exe ^ + ..\source ^ + -G "Visual Studio 15 Win64" ^ + -DSTAR_USE_JEMALLOC=ON ^ + -DCMAKE_INCLUDE_PATH="..\lib\windows64\include" ^ + -DCMAKE_LIBRARY_PATH="..\lib\windows64" + +) + +pause diff --git a/scripts/windows/showlog.bat b/scripts/windows/showlog.bat new file mode 100644 index 0000000..407e588 --- /dev/null +++ b/scripts/windows/showlog.bat @@ -0,0 +1 @@ +powershell.exe -command "Get-Content -Path '%cd%\..\..\dist\starbound.log' -Wait" diff --git a/scripts/windows/update_tilesets.bat b/scripts/windows/update_tilesets.bat new file mode 100644 index 0000000..8155eb6 --- /dev/null +++ b/scripts/windows/update_tilesets.bat @@ -0,0 +1,6 @@ +pushd %~dp0 +pushd ..\..\dist +update_tilesets +pause +popd +popd diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt new file mode 100644 index 0000000..cf507c6 --- /dev/null +++ b/source/CMakeLists.txt @@ -0,0 +1,533 @@ +PROJECT (starbound) + +CMAKE_MINIMUM_REQUIRED (VERSION 3.0) +SET (CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/../cmake) + +SET (CMAKE_CONFIGURATION_TYPES Debug RelWithAsserts RelWithDebInfo Release) +SET (CMAKE_EXE_LINKER_FLAGS_RELWITHASSERTS "" CACHE STRING "" FORCE) +# Update the docstring on CMAKE_BUILD_TYPE to show what options we actually +# allow +SET (CMAKE_BUILD_TYPE "${CMAKE_BUILD_TYPE}" CACHE STRING "Choose the type of build, options are: Debug RelWithAsserts RelWithDebInfo Release" FORCE) + +# Discover all the relevant environment / system information and place the +# result in STAR_* cmake variables. + +# STAR_SOURCE_IDENTIFIER may be set to any string value +IF (NOT DEFINED STAR_SOURCE_IDENTIFIER) + INCLUDE (GetGitRevisionDescription) + GET_GIT_HEAD_REVISION (STAR_GIT_REFSPEC STAR_GIT_HASHVAR) + SET (STAR_SOURCE_IDENTIFIER "${STAR_GIT_HASHVAR}") +ENDIF () + +# Architecture identifier, like i386, x86_64 or ppc +IF (NOT DEFINED STAR_ARCHITECTURE) + INCLUDE (TargetArch) + TARGET_ARCHITECTURE (STAR_ARCHITECTURE) +ENDIF () + +# Either TRUE or FALSE +IF (NOT DEFINED STAR_LITTLE_ENDIAN) + INCLUDE (TestBigEndian) + TEST_BIG_ENDIAN (BIGENDIAN) + IF (NOT BIGENDIAN) + SET (STAR_LITTLE_ENDIAN TRUE) + ELSE () + SET (STAR_LITTLE_ENDIAN FALSE) + ENDIF () +ENDIF () + +# System name, like windows, macos, linux, freebsd, or (generic) unix +IF (NOT DEFINED STAR_SYSTEM) + IF (WIN32) + SET (STAR_SYSTEM "windows") + ELSEIF (APPLE AND ${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") + SET (STAR_SYSTEM "macos") + ELSEIF (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + SET (STAR_SYSTEM "linux") + ELSEIF (${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD") + SET (STAR_SYSTEM "freebsd") + ELSEIF (UNIX) + SET (STAR_SYSTEM "unix") + ELSE () + SET (STAR_SYSTEM "unknown") + ENDIF () +ENDIF () + +IF (NOT DEFINED STAR_SYSTEM_FAMILY) + IF (WIN32) + SET (STAR_SYSTEM_FAMILY "windows") + ELSEIF (UNIX) + SET (STAR_SYSTEM_FAMILY "unix") + ELSE () + SET (STAR_SYSTEM_FAMILY "unknown") + ENDIF () +ENDIF () + +# C/C++ compiler ID, like clang, gnu, or msvc +IF (NOT DEFINED STAR_COMPILER) + IF (NOT CMAKE_C_COMPILER_ID STREQUAL CMAKE_CXX_COMPILER_ID) + MESSAGE (FATAL_ERROR "C and C++ compiler id do not match, unsupported build configuration") + ENDIF () + + IF (CMAKE_C_COMPILER_ID STREQUAL "Clang") + SET (STAR_COMPILER "clang") + ELSEIF (CMAKE_COMPILER_IS_GNUC) + SET (STAR_COMPILER "gnu") + ELSEIF (MSVC) + SET (STAR_COMPILER "msvc") + ELSE () + STRING (TOLOWER "${CMAKE_C_COMPILER_ID}" STAR_COMPILER) + ENDIF () +ENDIF () + +# Enable OPTIONs based on the discovered system / environment... + +IF (STAR_COMPILER STREQUAL "gnu") + OPTION (STAR_ENABLE_STATIC_LIBGCC_LIBSTDCXX "Statically link libgcc and libstdc++" OFF) + OPTION (STAR_ENABLE_GCC_PROFILING "Enable gcc/g++ profiling via the -pg flag" OFF) + OPTION (STAR_ENABLE_GLIBCXX_DEBUG "Enable _GLIBCXX_DEBUG for g++" OFF) +ENDIF () + +IF (STAR_COMPILER STREQUAL "msvc") + OPTION (STAR_ENABLE_STATIC_MSVC_RUNTIME "Statically link with the CRT" OFF) +ENDIF () + +OPTION (STAR_BUILD_GUI "Build GUI utilities and Client" ON) + +IF (STAR_BUILD_GUI) + OPTION (STAR_BUILD_QT_TOOLS "Build GUI utilities that require Qt" OFF) + OPTION (STAR_ENABLE_STEAM_INTEGRATION "Use Steam platform services" OFF) + OPTION (STAR_ENABLE_DISCORD_INTEGRATION "Use Discord platform services" OFF) +ENDIF () + +OPTION (STAR_LUA_APICHECK "Use lua api checks" OFF) +OPTION (STAR_USE_JEMALLOC "Use jemalloc allocators" OFF) + +# Report all the discovered system / environment settings and all options. + +MESSAGE (STATUS "Source ID: ${STAR_SOURCE_IDENTIFIER}") +MESSAGE (STATUS "Architecture: ${STAR_ARCHITECTURE}") +MESSAGE (STATUS "Little Endian: ${STAR_LITTLE_ENDIAN}") +MESSAGE (STATUS "System: ${STAR_SYSTEM}") +MESSAGE (STATUS "System family: ${STAR_SYSTEM_FAMILY}") +MESSAGE (STATUS "C/C++ compiler: ${STAR_COMPILER}") + +IF (DEFINED STAR_ENABLE_STATIC_LIBGCC_LIBSTDCXX) + MESSAGE (STATUS "Statically linking to libgcc / libstdc++: ${STAR_ENABLE_STATIC_LIBGCC_LIBSTDCXX}") +ENDIF () + +IF (DEFINED STAR_ENABLE_STATIC_MSVC_RUNTIME) + MESSAGE (STATUS "Statically linking to CRT: ${STAR_ENABLE_STATIC_MSVC_RUNTIME}") +ENDIF () + +IF (DEFINED STAR_ENABLE_GLIBCXX_DEBUG) + MESSAGE (STATUS "Enabling _GLIBCXX_DEBUG: ${STAR_ENABLE_GLIBCXX_DEBUG}") +ENDIF () + +MESSAGE (STATUS "Building GUI: ${STAR_BUILD_GUI}") + +IF (DEFINED STAR_BUILD_QT_TOOLS) + MESSAGE (STATUS "Building Qt tools: ${STAR_BUILD_QT_TOOLS}") +ENDIF () + +IF (DEFINED STAR_ENABLE_STEAM_INTEGRATION) + MESSAGE (STATUS "Using Steam platform services: ${STAR_ENABLE_STEAM_INTEGRATION}") +ENDIF () + +IF (DEFINED STAR_ENABLE_DISCORD_INTEGRATION) + MESSAGE (STATUS "Using Discrod platform services: ${STAR_ENABLE_DISCORD_INTEGRATION}") +ENDIF () + +MESSAGE (STATUS "Using Lua API checks: ${STAR_LUA_APICHECK}") +MESSAGE (STATUS "Using jemalloc: ${STAR_USE_JEMALLOC}") + +# Set C defines and cmake variables based on the build settings we have now +# determined... + +# Set a cmake variable to true and define a corresponding C/C++ definition +FUNCTION (SET_FLAG flagValue) + SET (${flagValue} TRUE PARENT_SCOPE) + ADD_DEFINITIONS (-D${flagValue}) +ENDFUNCTION () + +IF (STAR_LITTLE_ENDIAN) + SET_FLAG (STAR_LITTLE_ENDIAN) +ELSEIF () + SET_FLAG (STAR_BIG_ENDIAN) +ENDIF () + +IF (STAR_ARCHITECTURE STREQUAL "i386") + SET_FLAG (STAR_ARCHITECTURE_I386) +ELSEIF (STAR_ARCHITECTURE STREQUAL "x86_64") + SET_FLAG (STAR_ARCHITECTURE_X86_64) +ENDIF () + +IF (STAR_SYSTEM STREQUAL "windows") + SET_FLAG (STAR_SYSTEM_WINDOWS) +ELSEIF (STAR_SYSTEM STREQUAL "macos") + SET_FLAG (STAR_SYSTEM_MACOS) +ELSEIF (STAR_SYSTEM STREQUAL "linux") + SET_FLAG (STAR_SYSTEM_LINUX) +ELSEIF (STAR_SYSTEM STREQUAL "freebsd") + SET_FLAG (STAR_SYSTEM_FREEBSD) +ENDIF () + +IF (STAR_SYSTEM_FAMILY STREQUAL "windows") + SET_FLAG (STAR_SYSTEM_FAMILY_WINDOWS) +ELSEIF (STAR_SYSTEM_FAMILY STREQUAL "unix") + SET_FLAG (STAR_SYSTEM_FAMILY_UNIX) +ENDIF () + +IF (STAR_COMPILER STREQUAL "gnu") + SET_FLAG (STAR_COMPILER_GNU) +ELSEIF (STAR_COMPILER STREQUAL "clang") + SET_FLAG (STAR_COMPILER_CLANG) +ELSEIF (STAR_COMPILER STREQUAL "msvc") + SET_FLAG (STAR_COMPILER_MSVC) +ENDIF () + +IF (STAR_LUA_APICHECK) + ADD_DEFINITIONS (-DLUA_USE_APICHECK) +ENDIF () + +IF (STAR_SYSTEM_WINDOWS) + # LUA_USE_WINDOWS is automatically defined in luaconf if _WIN32 is defined +ELSEIF (STAR_SYSTEM_MACOS) + ADD_DEFINITIONS(-DLUA_USE_MACOSX) +ELSEIF (STAR_SYSTEM_LINUX) + ADD_DEFINITIONS(-DLUA_USE_LINUX) +ELSEIF (STAR_SYSTEM_FAMILY_UNIX) + ADD_DEFINITIONS(-DLUA_USE_POSIX) +ENDIF () + +IF (STAR_ENABLE_STEAM_INTEGRATION) + ADD_DEFINITIONS (-DSTAR_ENABLE_STEAM_INTEGRATION) +ENDIF () + +IF (STAR_ENABLE_DISCORD_INTEGRATION) + ADD_DEFINITIONS (-DSTAR_ENABLE_DISCORD_INTEGRATION) +ENDIF () + +IF (STAR_USE_JEMALLOC) + ADD_DEFINITIONS (-DSTAR_USE_JEMALLOC) +ENDIF () + +# Set C/C++ compiler flags based on build environment... + +IF (STAR_COMPILER_GNU) + SET (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Wuninitialized -Wunreachable-code -Wformat -no-pie") + SET (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -Wall -Wextra -Wuninitialized -Wunreachable-code -Wformat -no-pie") + + IF (STAR_SYSTEM_FAMILY_WINDOWS) + SET (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mthreads") + SET (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mthreads") + ELSE () + SET (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pthread -D_REENTRANT") + SET (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread -D_REENTRANT") + ENDIF () + + IF (STAR_ENABLE_STATIC_LIBGCC_LIBSTDCXX) + SET (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -static-libgcc") + SET (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -static-libgcc -static-libstdc++") + ENDIF () + + IF (STAR_ENABLE_GCC_PROFILING) + SET (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pg") + SET (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pg") + ENDIF () + + SET (CMAKE_C_FLAGS_DEBUG "-g -Og") + SET (CMAKE_CXX_FLAGS_DEBUG "-g -Og") + + SET (CMAKE_C_FLAGS_RELWITHASSERTS "-g -Ofast") + SET (CMAKE_CXX_FLAGS_RELWITHASSERTS "-g -Ofast") + + SET (CMAKE_C_FLAGS_RELWITHDEBINFO "-g -DNDEBUG -Ofast") + SET (CMAKE_CXX_FLAGS_RELWITHDEBINFO "-g -DNDEBUG -Ofast") + + SET (CMAKE_C_FLAGS_RELEASE "-DNDEBUG -Ofast") + SET (CMAKE_CXX_FLAGS_RELEASE "-DNDEBUG -Ofast") + +ELSEIF (STAR_COMPILER_CLANG) + SET (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Wuninitialized -Wno-parentheses-equality -Wno-deprecated-declarations") + SET (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -Wall -Wextra -Wuninitialized -Wno-parentheses-equality -Wno-deprecated-declarations") + + IF (STAR_SYSTEM_MACOS) + SET (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++") + SET (CMAKE_XCODE_ATTRIBUTE_CLANG_CXX_LANGUAGE_STANDARD "c++14") + SET (CMAKE_XCODE_ATTRIBUTE_CLANG_CXX_LIBRARY "libc++") + ELSEIF () + SET (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pthread -D_REENTRANT") + SET (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread -D_REENTRANT") + ENDIF () + + SET (CMAKE_C_FLAGS_DEBUG "-g") + SET (CMAKE_CXX_FLAGS_DEBUG "-g") + + SET (CMAKE_C_FLAGS_RELWITHASSERTS "-g -Ofast") + SET (CMAKE_CXX_FLAGS_RELWITHASSERTS "-g -Ofast") + + SET (CMAKE_C_FLAGS_RELWITHDEBINFO "-g -DNDEBUG -Ofast") + SET (CMAKE_CXX_FLAGS_RELWITHDEBINFO "-g -DNDEBUG -Ofast") + + SET (CMAKE_C_FLAGS_RELEASE "-DNDEBUG -Ofast") + SET (CMAKE_CXX_FLAGS_RELEASE "-DNDEBUG -Ofast") + +ELSEIF (STAR_COMPILER_MSVC) + # /MP - Multi-processor building + # /EHsc - Enable normal C++ exception handling + # /bigobj - More sections in .obj files (Cannot build in Debug without it) + # /MT - Use multi-threaded statically linked C runtime library + # /GA - Optimize for windows application + # /Ox - Full optimization + # /fp:fast - Equivalent to -ffast-math + # /GS- - Disable buffers security check + # /Zi - Generates debugging information without Edit and Continue + # /Gy - Use function-level linking + # /wd4996 - Disable warnings about unsafe C functions + # /wd4351 - Disable warnings about new behavior of default initialization of + # arrays (which is the correct behavior anyway) + # /wd4800 - Disable warnings about using non-bool as true or false (useless + # performance warning) + # /wd4244 - Disable warnings about type conversion loss of data, it's a nice + # warning, but it triggers on lots and lots of harmless things that no + # other compiler warns about, like passing an int as a float parameter + # /wd4305 - Disable warnings about truncation from double to float + # /wd4267 - Disable warnings about 64 - 32 bit truncation + # /wd4456 - Disable warnings about hiding previous local declaration + # /wd4503 - Silence warnings about MSVC generating a name so long it has to + # truncate it + # /wd4250 - Silence "XX inherits YY via dominance" + # /wd4624 - Silence implicitly deleted destructor warnings that show up when + # using unions in interesting ways. + + SET (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /MP /EHsc /bigobj /wd4996 /wd4351 /wd4800 /wd4244 /wd4305 /wd4267 /wd4456 /wd4503 /wd4250 /wd4624") + SET (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP /EHsc /bigobj /wd4996 /wd4351 /wd4800 /wd4244 /wd4305 /wd4267 /wd4456 /wd4503 /wd4250 /wd4624") + + IF (STAR_ENABLE_STATIC_MSVC_RUNTIME) + SET (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /MT") + SET (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MT") + ENDIF () + + SET (CMAKE_C_FLAGS_DEBUG "/Zi /Od") + SET (CMAKE_CXX_FLAGS_DEBUG "/Zi /Od") + + SET (CMAKE_C_FLAGS_RELWITHASSERTS "/Ox /fp:fast /GA /GS- /Zi /Gy") + SET (CMAKE_CXX_FLAGS_RELWITHASSERTS "/Ox /fp:fast /GA /GS- /Zi /Gy") + + SET (CMAKE_C_FLAGS_RELWITHDEBINFO "/Ox /fp:fast /GA /GS- /Zi /Gy /DNDEBUG") + SET (CMAKE_CXX_FLAGS_RELWITHDEBINFO "/Ox /fp:fast /GA /GS- /Zi /Gy /DNDEBUG") + + SET (CMAKE_C_FLAGS_RELEASE "/Ox /fp:fast /GA /GS- /Gy /DNDEBUG") + SET (CMAKE_CXX_FLAGS_RELEASE "/Ox /fp:fast /GA /GS- /Gy /DNDEBUG") + + IF (STAR_ARCHITECTURE_I386) + # Assume all 32 bit target cpus support MMX, SSE, and SSE2 + + SET (CMAKE_C_FLAGS_RELWITHASSERTS "${CMAKE_C_FLAGS_RELWITHASSERTS} /arch:SSE2") + SET (CMAKE_CXX_FLAGS_RELWITHASSERTS "${CMAKE_CXX_FLAGS_RELWITHASSERTS} /arch:SSE2") + + SET (CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO} /arch:SSE2") + SET (CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} /arch:SSE2") + + SET (CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /arch:SSE2") + SET (CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /arch:SSE2") + ENDIF () + + ADD_DEFINITIONS (/DUNICODE) + ADD_DEFINITIONS (/D_UNICODE) + ADD_DEFINITIONS (/DNOMINMAX) + +ELSE () + SET (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -pthread -D_REENTRANT") + SET (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -Wall -pthread -D_REENTRANT") + + SET (CMAKE_C_FLAGS_DEBUG "-g") + SET (CMAKE_CXX_FLAGS_DEBUG "-g") + + SET (CMAKE_C_FLAGS_RELWITHASSERTS "-g -O2") + SET (CMAKE_CXX_FLAGS_RELWITHASSERTS "-g -O2") + + SET (CMAKE_C_FLAGS_RELWITHDEBINFO "-DNDEBUG -g -O2") + SET (CMAKE_CXX_FLAGS_RELWITHDEBINFO "-DNDEBUG -g -O2") + + SET (CMAKE_C_FLAGS_RELEASE "$-DNDEBUG -O2") + SET (CMAKE_CXX_FLAGS_RELEASE "-DNDEBUG -O2") + +ENDIF () + +# Set other global build settings based on environment... + +IF (STAR_SYSTEM_MACOS) + SET (CMAKE_MODULE_LINKER_FLAGS "-flat_namespace -undefined suppress") +ELSEIF (STAR_SYSTEM_WINDOWS) + SET (CMAKE_RC_COMPILER_INIT windres) + + ENABLE_LANGUAGE (RC) + SET (CMAKE_RC_COMPILE_OBJECT " -O coff -i -o ") +ENDIF () + +IF (STAR_COMPILER STREQUAL "msvc") + # /largeaddressaware - Make 32 bit build able to use 3GB addresses + # /OPT:REF - Eliminates functions and data that are never referenced + # /OPT:ICF - Performs identical COMDAT folding + # /PDBCompress - Hint to windows that it should compress the resulting PDB files + SET (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /largeaddressaware /OPT:REF /OPT:ICF /PDBCompress") + + # Make sure RelWithAsserts has debugging enabled + SET (CMAKE_EXE_LINKER_FLAGS_RELWITHASSERTS "${CMAKE_EXE_LINKER_FLAGS_RELWITHASSERTS} /DEBUG") +ENDIF () + +IF (STAR_SYSTEM_WINDOWS) + SET (CMAKE_C_STANDARD_LIBRARIES "${CMAKE_C_STANDARD_LIBRARIES} ws2_32.lib iphlpapi.lib shlwapi.lib dbghelp.lib") + SET (CMAKE_CXX_STANDARD_LIBRARIES "${CMAKE_CXX_STANDARD_LIBRARIES} ws2_32.lib iphlpapi.lib shlwapi.lib dbghelp.lib") + +ELSEIF (STAR_SYSTEM_LINUX) + SET (CMAKE_C_STANDARD_LIBRARIES "${CMAKE_C_STANDARD_LIBRARIES} -lpthread -ldl -lrt") + SET (CMAKE_CXX_STANDARD_LIBRARIES "${CMAKE_CXX_STANDARD_LIBRARIES} -lpthread -ldl -lrt") + +ELSEIF (STAR_SYSTEM_FREEBSD) + SET (CMAKE_C_STANDARD_LIBRARIES "${CMAKE_C_STANDARD_LIBRARIES} -lpthread -lrt") + SET (CMAKE_CXX_STANDARD_LIBRARIES "${CMAKE_CXX_STANDARD_LIBRARIES} -lpthread -lrt") + +ENDIF () + +# Find all required external libraries, based on build settings... + +IF (STAR_USE_JEMALLOC) + # Assumes jemalloc was configured with a "je_" function prefix + FIND_PACKAGE (JeMalloc REQUIRED) + + INCLUDE_DIRECTORIES (SYSTEM ${JEMALLOC_INCLUDE_DIR}) + SET (STAR_EXT_LIBS ${JEMALLOC_LIBRARY}) +ENDIF () + +FIND_PACKAGE (ZLIB REQUIRED) +FIND_PACKAGE (PNG REQUIRED) +FIND_PACKAGE (Freetype REQUIRED) +FIND_PACKAGE (OggVorbis REQUIRED) + +INCLUDE_DIRECTORIES (SYSTEM + ${ZLIB_INCLUDE_DIR} + ${PNG_INCLUDE_DIR} + ${FREETYPE_INCLUDE_DIRS} + ${OGGVORBIS_INCLUDE_DIR} + ) + +SET (STAR_EXT_LIBS ${STAR_EXT_LIBS} + ${VORBISFILE_LIBRARY} + ${VORBIS_LIBRARY} + ${OGG_LIBRARY} + ${FREETYPE_LIBRARY} + ${PNG_LIBRARY} + ${ZLIB_LIBRARY} + ) + +IF (STAR_BUILD_GUI) + FIND_PACKAGE (SDL2 REQUIRED) + INCLUDE_DIRECTORIES (SYSTEM ${SDL2_INCLUDE_DIR}) + SET (STAR_EXT_GUI_LIBS ${SDL2_LIBRARY}) + + FIND_PACKAGE (OpenGL REQUIRED) + FIND_PACKAGE (GLEW REQUIRED) + + INCLUDE_DIRECTORIES (SYSTEM ${GLEW_INCLUDE_DIR} ${SDL2_INCLUDE_DIR}) + SET (STAR_EXT_GUI_LIBS ${STAR_EXT_GUI_LIBS} ${OPENGL_LIBRARY} ${GLEW_LIBRARY}) + + IF (STAR_ENABLE_STEAM_INTEGRATION) + FIND_PACKAGE (SteamApi REQUIRED) + + INCLUDE_DIRECTORIES (SYSTEM ${STEAM_API_INCLUDE_DIR}) + SET (STAR_EXT_GUI_LIBS ${STAR_EXT_GUI_LIBS} ${STEAM_API_LIBRARY}) + ENDIF () + + IF (STAR_ENABLE_DISCORD_INTEGRATION) + FIND_PACKAGE (DiscordApi REQUIRED) + + INCLUDE_DIRECTORIES (SYSTEM ${DISCORD_API_INCLUDE_DIR}) + SET (STAR_EXT_GUI_LIBS ${STAR_EXT_GUI_LIBS} ${DISCORD_API_LIBRARY}) + ENDIF () +ENDIF () + +# Set basic build flags, include all the relevant source directories, based on +# build settings... + +SET (BUILD_SHARED_LIBS false) + +# First set output dir for the generic no-config case (e.g. macos / linux) +SET (CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/../dist) + +# Second, set output dir for multi-config builds (e.g. msvc) +FOREACH (OUTPUTCONFIG ${CMAKE_CONFIGURATION_TYPES}) + STRING (TOUPPER ${OUTPUTCONFIG} OUTPUTCONFIG) + SET (CMAKE_RUNTIME_OUTPUT_DIRECTORY_${OUTPUTCONFIG} ${PROJECT_SOURCE_DIR}/../dist) +ENDFOREACH (OUTPUTCONFIG CMAKE_CONFIGURATION_TYPES) + +# External code included with starbound source, which core depends on +SET (STAR_EXTERN_INCLUDES ${PROJECT_SOURCE_DIR}/extern) +ADD_SUBDIRECTORY (extern) + +# Core support code, not specific to starbound. +SET (STAR_CORE_INCLUDES ${PROJECT_SOURCE_DIR}/core) +ADD_SUBDIRECTORY (core) + +# Less general purpose code than core that is available to both the game and +# application modules. +SET (STAR_BASE_INCLUDES ${PROJECT_SOURCE_DIR}/base) +ADD_SUBDIRECTORY (base) + +# Platform APIs that are implemented by the application module +SET (STAR_PLATFORM_INCLUDES ${PROJECT_SOURCE_DIR}/platform) +ADD_SUBDIRECTORY (platform) + +# Core game logic used by both server and client. +SET (STAR_GAME_INCLUDES + ${PROJECT_SOURCE_DIR}/game + ${PROJECT_SOURCE_DIR}/game/interfaces + ${PROJECT_SOURCE_DIR}/game/items + ${PROJECT_SOURCE_DIR}/game/objects + ${PROJECT_SOURCE_DIR}/game/scripting + ${PROJECT_SOURCE_DIR}/game/terrain + ) +ADD_SUBDIRECTORY (game) + +# Googletest based tests +ENABLE_TESTING() +ADD_SUBDIRECTORY (test) + +# Starbound stand-alone server. +ADD_SUBDIRECTORY (server) + +# cmdline utilities +ADD_SUBDIRECTORY (utility) + +IF (STAR_BUILD_GUI) + # Handles creating windows, keyboard / mouse / joystick input, and the 2d + # rendering model. + SET (STAR_APPLICATION_INCLUDES ${PROJECT_SOURCE_DIR}/application) + ADD_SUBDIRECTORY (application) + + # Rendering code not dependent on widget system + SET (STAR_RENDERING_INCLUDES ${PROJECT_SOURCE_DIR}/rendering) + ADD_SUBDIRECTORY (rendering) + + # Panes and Widgets + SET (STAR_WINDOWING_INCLUDES ${PROJECT_SOURCE_DIR}/windowing) + ADD_SUBDIRECTORY (windowing) + + # Client interface code + SET (STAR_FRONTEND_INCLUDES ${PROJECT_SOURCE_DIR}/frontend) + ADD_SUBDIRECTORY (frontend) + + # Starbound game / client + ADD_SUBDIRECTORY (client) + + # Qt GUI tools + IF (STAR_BUILD_QT_TOOLS) + ADD_SUBDIRECTORY (json_tool) + + if (STAR_ENABLE_STEAM_INTEGRATION) + ADD_SUBDIRECTORY (mod_uploader) + ENDIF () + ENDIF () +ENDIF () diff --git a/source/application/CMakeLists.txt b/source/application/CMakeLists.txt new file mode 100644 index 0000000..db557aa --- /dev/null +++ b/source/application/CMakeLists.txt @@ -0,0 +1,65 @@ +INCLUDE_DIRECTORIES ( + ${STAR_EXTERN_INCLUDES} + ${STAR_CORE_INCLUDES} + ${STAR_PLATFORM_INCLUDES} + ${STAR_APPLICATION_INCLUDES} + ) + +SET (star_application_HEADERS + StarApplication.hpp + StarApplicationController.hpp + StarMainApplication.hpp + StarInputEvent.hpp + StarRenderer.hpp + ) + +SET (star_application_SOURCES + StarApplication.cpp + StarInputEvent.cpp + StarRenderer.cpp + ) + +SET (star_application_HEADERS ${star_application_HEADERS} + StarP2PNetworkingService_pc.hpp + StarPlatformServices_pc.hpp + StarRenderer_opengl20.hpp + ) + +SET (star_application_SOURCES ${star_application_SOURCES} + StarMainApplication_sdl.cpp + StarP2PNetworkingService_pc.cpp + StarPlatformServices_pc.cpp + StarRenderer_opengl20.cpp + ) + +IF (STAR_ENABLE_STEAM_INTEGRATION) + SET (star_application_HEADERS ${star_application_HEADERS} + StarDesktopService_pc_steam.hpp + StarStatisticsService_pc_steam.hpp + StarUserGeneratedContentService_pc_steam.hpp + ) + SET (star_application_SOURCES ${star_application_SOURCES} + StarDesktopService_pc_steam.cpp + StarStatisticsService_pc_steam.cpp + StarUserGeneratedContentService_pc_steam.cpp + ) +ENDIF () + +IF (STAR_ENABLE_DISCORD_INTEGRATION) + SET (star_application_SOURCES ${star_application_SOURCES} + discord/activity_manager.cpp + discord/application_manager.cpp + discord/core.cpp + discord/image_manager.cpp + discord/lobby_manager.cpp + discord/network_manager.cpp + discord/overlay_manager.cpp + discord/relationship_manager.cpp + discord/storage_manager.cpp + discord/store_manager.cpp + discord/types.cpp + discord/user_manager.cpp + ) +ENDIF () + +ADD_LIBRARY (star_application OBJECT ${star_application_SOURCES} ${star_application_HEADERS}) diff --git a/source/application/StarApplication.cpp b/source/application/StarApplication.cpp new file mode 100644 index 0000000..e894673 --- /dev/null +++ b/source/application/StarApplication.cpp @@ -0,0 +1,31 @@ +#include "StarApplication.hpp" +#include "StarTime.hpp" +#include "StarLogging.hpp" + +namespace Star { + +void Application::startup(StringList const&) {} + +void Application::applicationInit(ApplicationControllerPtr appController) { + m_appController = move(appController); +} + +void Application::renderInit(RendererPtr renderer) { + m_renderer = move(renderer); +} + +void Application::windowChanged(WindowMode, Vec2U) {} + +void Application::processInput(InputEvent const&) {} + +void Application::update() {} + +void Application::render() {} + +void Application::getAudioData(int16_t* samples, size_t sampleCount) { + for (size_t i = 0; i < sampleCount; ++i) + samples[i] = 0; +} + +void Application::shutdown() {} +} diff --git a/source/application/StarApplication.hpp b/source/application/StarApplication.hpp new file mode 100644 index 0000000..9e10f58 --- /dev/null +++ b/source/application/StarApplication.hpp @@ -0,0 +1,76 @@ +#ifndef STAR_APPLICATION_HPP +#define STAR_APPLICATION_HPP + +#include "StarInputEvent.hpp" + +namespace Star { + +STAR_CLASS(ApplicationController); +STAR_CLASS(Renderer); +STAR_CLASS(Application); + +STAR_EXCEPTION(ApplicationException, StarException); + +enum class WindowMode { + Normal, + Maximized, + Fullscreen, + Borderless +}; + +class Application { +public: + virtual ~Application() = default; + + // Called once on application startup, before any other methods. + virtual void startup(StringList const& cmdLineArgs); + + // Called on application initialization, before rendering initialization. If + // overriden, must call base class instance. + virtual void applicationInit(ApplicationControllerPtr appController); + + // Called immediately after application initialization on startup, and then + // also whenever the renderer invalidated and recreated. If overridden, must + // call base class instance. + virtual void renderInit(RendererPtr renderer); + + // Called when the window mode or size is changed. + virtual void windowChanged(WindowMode windowMode, Vec2U screenSize); + + // Called before update, once for every pending event. + virtual void processInput(InputEvent const& event); + + // Will be called at updateRate hz, or as close as possible. + virtual void update(); + + // Will be called at updateRate hz, or more or less depending on settings and + // performance. update() is always prioritized over render(). + virtual void render(); + + // Will be called *from a different thread* to retrieve audio data (if audio + // is playing). Default implementation simply fills the buffer with silence. + virtual void getAudioData(int16_t* sampleData, size_t frameCount); + + // Will be called once on application shutdown, including when shutting down + // due to an Application exception. + virtual void shutdown(); + + ApplicationControllerPtr const& appController() const; + RendererPtr const& renderer() const; + +private: + ApplicationControllerPtr m_appController; + RendererPtr m_renderer; +}; + +inline ApplicationControllerPtr const& Application::appController() const { + return m_appController; +} + +inline RendererPtr const& Application::renderer() const { + return m_renderer; +} + +} + +#endif diff --git a/source/application/StarApplicationController.hpp b/source/application/StarApplicationController.hpp new file mode 100644 index 0000000..d19ab4c --- /dev/null +++ b/source/application/StarApplicationController.hpp @@ -0,0 +1,67 @@ +#ifndef STAR_APPLICATION_CONTROLLER_HPP +#define STAR_APPLICATION_CONTROLLER_HPP + +#include "StarApplication.hpp" +#include "StarStatisticsService.hpp" +#include "StarP2PNetworkingService.hpp" +#include "StarUserGeneratedContentService.hpp" +#include "StarDesktopService.hpp" + +namespace Star { + +STAR_CLASS(ApplicationController); + +// Audio format is always 16 bit signed integer samples +struct AudioFormat { + unsigned sampleRate; + unsigned channels; +}; + +// Window size defaults to 800x600, target update rate to 60hz, maximized and +// fullscreen are false, vsync is on, the cursor is visible, and audio and text +// input are disabled. +class ApplicationController { +public: + virtual ~ApplicationController() = default; + + // Target hz at which update() will be called + virtual void setTargetUpdateRate(float targetUpdateRate) = 0; + // Window that controls how long the update rate will be increased or + // decreased to make up for rate errors in the past. + virtual void setUpdateTrackWindow(float updateTrackWindow) = 0; + // Maximum number of calls to update() that can occur before we force + // 'render()' to be called, even if we are still behind on our update rate. + virtual void setMaxFrameSkip(unsigned maxFrameSkip) = 0; + + virtual void setApplicationTitle(String title) = 0; + virtual void setFullscreenWindow(Vec2U fullScreenResolution) = 0; + virtual void setNormalWindow(Vec2U windowSize) = 0; + virtual void setMaximizedWindow() = 0; + virtual void setBorderlessWindow() = 0; + virtual void setVSyncEnabled(bool vSync) = 0; + virtual void setCursorVisible(bool cursorVisible) = 0; + virtual void setAcceptingTextInput(bool acceptingTextInput) = 0; + + virtual AudioFormat enableAudio() = 0; + virtual void disableAudio() = 0; + + virtual void setClipboard(String text) = 0; + virtual Maybe getClipboard() = 0; + + // Returns the latest actual measured update and render rate, which may be + // different than the target update rate. + virtual float updateRate() const = 0; + virtual float renderFps() const = 0; + + virtual StatisticsServicePtr statisticsService() const = 0; + virtual P2PNetworkingServicePtr p2pNetworkingService() const = 0; + virtual UserGeneratedContentServicePtr userGeneratedContentService() const = 0; + virtual DesktopServicePtr desktopService() const = 0; + + // Signals the application to quit + virtual void quit() = 0; +}; + +} + +#endif diff --git a/source/application/StarDesktopService_pc_steam.cpp b/source/application/StarDesktopService_pc_steam.cpp new file mode 100644 index 0000000..c00c0c6 --- /dev/null +++ b/source/application/StarDesktopService_pc_steam.cpp @@ -0,0 +1,11 @@ +#include "StarDesktopService_pc_steam.hpp" + +namespace Star { + +SteamDesktopService::SteamDesktopService(PcPlatformServicesStatePtr) {} + +void SteamDesktopService::openUrl(String const& url) { + SteamFriends()->ActivateGameOverlayToWebPage(url.utf8Ptr()); +} + +} diff --git a/source/application/StarDesktopService_pc_steam.hpp b/source/application/StarDesktopService_pc_steam.hpp new file mode 100644 index 0000000..da38089 --- /dev/null +++ b/source/application/StarDesktopService_pc_steam.hpp @@ -0,0 +1,19 @@ +#ifndef STAR_DESKTOP_SERVICE_PC_STEAM_HPP +#define STAR_DESKTOP_SERVICE_PC_STEAM_HPP + +#include "StarPlatformServices_pc.hpp" + +namespace Star { + +STAR_CLASS(SteamDesktopService); + +class SteamDesktopService : public DesktopService { +public: + SteamDesktopService(PcPlatformServicesStatePtr state); + + void openUrl(String const& url) override; +}; + +} + +#endif diff --git a/source/application/StarInputEvent.cpp b/source/application/StarInputEvent.cpp new file mode 100644 index 0000000..650344a --- /dev/null +++ b/source/application/StarInputEvent.cpp @@ -0,0 +1,166 @@ +#include "StarInputEvent.hpp" + +namespace Star { + +EnumMap const KeyNames{ + {Key::Backspace, "Backspace"}, + {Key::Tab, "Tab"}, + {Key::Clear, "Clear"}, + {Key::Return, "Return"}, + {Key::Escape, "Esc"}, + {Key::Space, "Space"}, + {Key::Exclaim, "!"}, + {Key::QuotedBL, "\""}, + {Key::Hash, "#"}, + {Key::Dollar, "$"}, + {Key::Ampersand, "&"}, + {Key::Quote, "\'"}, + {Key::LeftParen, "("}, + {Key::RightParen, ")"}, + {Key::Asterisk, "*"}, + {Key::Plus, "+"}, + {Key::Comma, ","}, + {Key::Minus, "-"}, + {Key::Period, "."}, + {Key::Slash, "/"}, + {Key::Zero, "0"}, + {Key::One, "1"}, + {Key::Two, "2"}, + {Key::Three, "3"}, + {Key::Four, "4"}, + {Key::Five, "5"}, + {Key::Six, "6"}, + {Key::Seven, "7"}, + {Key::Eight, "8"}, + {Key::Nine, "9"}, + {Key::Colon, ":"}, + {Key::Semicolon, ";"}, + {Key::Less, "<"}, + {Key::Equals, "="}, + {Key::Greater, ">"}, + {Key::Question, "?"}, + {Key::At, "@"}, + {Key::LeftBracket, "["}, + {Key::Backslash, "\\"}, + {Key::RightBracket, "]"}, + {Key::Caret, "^"}, + {Key::Underscore, "_"}, + {Key::Backquote, "`"}, + {Key::A, "A"}, + {Key::B, "B"}, + {Key::C, "C"}, + {Key::D, "D"}, + {Key::E, "E"}, + {Key::F, "F"}, + {Key::G, "G"}, + {Key::H, "H"}, + {Key::I, "I"}, + {Key::J, "J"}, + {Key::K, "K"}, + {Key::L, "L"}, + {Key::M, "M"}, + {Key::N, "N"}, + {Key::O, "O"}, + {Key::P, "P"}, + {Key::Q, "Q"}, + {Key::R, "R"}, + {Key::S, "S"}, + {Key::T, "T"}, + {Key::U, "U"}, + {Key::V, "V"}, + {Key::W, "W"}, + {Key::X, "X"}, + {Key::Y, "Y"}, + {Key::Z, "Z"}, + {Key::Delete, "Del"}, + {Key::Kp0, "Kp0"}, + {Key::Kp1, "Kp1"}, + {Key::Kp2, "Kp2"}, + {Key::Kp3, "Kp3"}, + {Key::Kp4, "Kp4"}, + {Key::Kp5, "Kp5"}, + {Key::Kp6, "Kp6"}, + {Key::Kp7, "Kp7"}, + {Key::Kp8, "Kp8"}, + {Key::Kp9, "Kp9"}, + {Key::Kp_period, "Kp_period"}, + {Key::Kp_divide, "Kp_divide"}, + {Key::Kp_multiply, "Kp_multiply"}, + {Key::Kp_minus, "Kp_minus"}, + {Key::Kp_plus, "Kp_plus"}, + {Key::Kp_enter, "Kp_enter"}, + {Key::Kp_equals, "Kp_equals"}, + {Key::Up, "Up"}, + {Key::Down, "Down"}, + {Key::Right, "Right"}, + {Key::Left, "Left"}, + {Key::Insert, "Ins"}, + {Key::Home, "Home"}, + {Key::End, "End"}, + {Key::PageUp, "PageUp"}, + {Key::PageDown, "PageDown"}, + {Key::F1, "F1"}, + {Key::F2, "F2"}, + {Key::F3, "F3"}, + {Key::F4, "F4"}, + {Key::F5, "F5"}, + {Key::F6, "F6"}, + {Key::F7, "F7"}, + {Key::F8, "F8"}, + {Key::F9, "F9"}, + {Key::F10, "F10"}, + {Key::F11, "F11"}, + {Key::F12, "F12"}, + {Key::F13, "F13"}, + {Key::F14, "F14"}, + {Key::F15, "F15"}, + {Key::NumLock, "NumLock"}, + {Key::CapsLock, "CapsLock"}, + {Key::ScrollLock, "ScrollLock"}, + {Key::RShift, "RShift"}, + {Key::LShift, "LShift"}, + {Key::RCtrl, "RCtrl"}, + {Key::LCtrl, "LCtrl"}, + {Key::RAlt, "RAlt"}, + {Key::LAlt, "LAlt"}, + {Key::RGui, "RGui"}, + {Key::LGui, "LGui"}, + {Key::AltGr, "AltGr"}, + {Key::Compose, "Compose"}, + {Key::Help, "Help"}, + {Key::PrintScreen, "PrintScreen"}, + {Key::SysReq, "SysReq"}, + {Key::Pause, "Pause"}, + {Key::Menu, "Menu"}, + {Key::Power, "Power"} +}; + +EnumMap const KeyModNames{ + {KeyMod::NoMod, "NoMod"}, + {KeyMod::LShift, "LShift"}, + {KeyMod::RShift, "RShift"}, + {KeyMod::LCtrl, "LCtrl"}, + {KeyMod::RCtrl, "RCtrl"}, + {KeyMod::LAlt, "LAlt"}, + {KeyMod::RAlt, "RAlt"}, + {KeyMod::LGui, "LMeta"}, + {KeyMod::RGui, "RMeta"}, + {KeyMod::Num, "Num"}, + {KeyMod::Caps, "Caps"}, + {KeyMod::AltGr, "AltGr"} +}; + +EnumMap const MouseButtonNames{ + {MouseButton::Left, "MouseLeft"}, + {MouseButton::Middle, "MouseMiddle"}, + {MouseButton::Right, "MouseRight"}, + {MouseButton::FourthButton, "MouseFourth"}, + {MouseButton::FifthButton, "MouseFifth"} +}; + +EnumMap const MouseWheelNames{ + {MouseWheel::Up, "MouseWheelUp"}, + {MouseWheel::Down, "MouseWheelDown"} +}; + +} diff --git a/source/application/StarInputEvent.hpp b/source/application/StarInputEvent.hpp new file mode 100644 index 0000000..bbf46b5 --- /dev/null +++ b/source/application/StarInputEvent.hpp @@ -0,0 +1,274 @@ +#ifndef STAR_INPUT_EVENT_HPP +#define STAR_INPUT_EVENT_HPP + +#include "StarString.hpp" +#include "StarBiMap.hpp" +#include "StarVariant.hpp" +#include "StarVector.hpp" + +namespace Star { + +enum class Key : uint16_t { + Backspace, + Tab, + Clear, + Return, + Escape, + Space, + Exclaim, + QuotedBL, + Hash, + Dollar, + Ampersand, + Quote, + LeftParen, + RightParen, + Asterisk, + Plus, + Comma, + Minus, + Period, + Slash, + Zero, + One, + Two, + Three, + Four, + Five, + Six, + Seven, + Eight, + Nine, + Colon, + Semicolon, + Less, + Equals, + Greater, + Question, + At, + LeftBracket, + Backslash, + RightBracket, + Caret, + Underscore, + Backquote, + A, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L, + M, + N, + O, + P, + Q, + R, + S, + T, + U, + V, + W, + X, + Y, + Z, + Delete, + Kp0, + Kp1, + Kp2, + Kp3, + Kp4, + Kp5, + Kp6, + Kp7, + Kp8, + Kp9, + Kp_period, + Kp_divide, + Kp_multiply, + Kp_minus, + Kp_plus, + Kp_enter, + Kp_equals, + Up, + Down, + Right, + Left, + Insert, + Home, + End, + PageUp, + PageDown, + F1, + F2, + F3, + F4, + F5, + F6, + F7, + F8, + F9, + F10, + F11, + F12, + F13, + F14, + F15, + NumLock, + CapsLock, + ScrollLock, + RShift, + LShift, + RCtrl, + LCtrl, + RAlt, + LAlt, + RGui, + LGui, + AltGr, + Compose, + Help, + PrintScreen, + SysReq, + Pause, + Menu, + Power +}; +extern EnumMap const KeyNames; + +enum class KeyMod : uint16_t { + NoMod = 0x0000, + LShift = 0x0001, + RShift = 0x0002, + LCtrl = 0x0040, + RCtrl = 0x0080, + LAlt = 0x0100, + RAlt = 0x0200, + LGui = 0x0400, + RGui = 0x0800, + Num = 0x1000, + Caps = 0x2000, + AltGr = 0x4000 +}; +extern EnumMap const KeyModNames; + +KeyMod operator|(KeyMod a, KeyMod b); +KeyMod operator&(KeyMod a, KeyMod b); +KeyMod operator~(KeyMod a); +KeyMod& operator|=(KeyMod& a, KeyMod b); +KeyMod& operator&=(KeyMod& a, KeyMod b); + +enum class MouseButton : uint8_t { + Left, + Middle, + Right, + FourthButton, + FifthButton +}; +extern EnumMap const MouseButtonNames; + +enum class MouseWheel : uint8_t { + Up, + Down +}; +extern EnumMap const MouseWheelNames; + +typedef uint32_t ControllerId; + +struct KeyDownEvent { + Key key; + KeyMod mods; +}; + +struct KeyUpEvent { + Key key; +}; + +struct TextInputEvent { + String text; +}; + +struct MouseMoveEvent { + Vec2I mouseMove; + Vec2I mousePosition; +}; + +struct MouseButtonDownEvent { + MouseButton mouseButton; + Vec2I mousePosition; +}; + +struct MouseButtonUpEvent { + MouseButton mouseButton; + Vec2I mousePosition; +}; + +struct MouseWheelEvent { + MouseWheel mouseWheel; + Vec2I mousePosition; +}; + +struct ControllerAxisEvent { + ControllerId controller; + unsigned controllerAxis; + float controllerAxisValue; +}; + +struct ControllerButtonDownEvent { + ControllerId controller; + unsigned controllerButton; +}; + +struct ControllerButtonUpEvent { + ControllerId controller; + unsigned controllerButton; +}; + +typedef Variant< + KeyDownEvent, + KeyUpEvent, + TextInputEvent, + MouseMoveEvent, + MouseButtonDownEvent, + MouseButtonUpEvent, + MouseWheelEvent, + ControllerAxisEvent, + ControllerButtonDownEvent, + ControllerButtonUpEvent> + InputEvent; + +inline KeyMod operator|(KeyMod a, KeyMod b) { + return (KeyMod)((uint16_t)a | (uint16_t)b); +} + +inline KeyMod operator&(KeyMod a, KeyMod b) { + return (KeyMod)((uint16_t)a & (uint16_t)b); +} + +inline KeyMod operator~(KeyMod a) { + return (KeyMod) ~(uint16_t)a; +} + +inline KeyMod& operator|=(KeyMod& a, KeyMod b) { + uint16_t a_cast = (uint16_t)a; + a_cast |= (uint16_t)b; + a = (KeyMod)a_cast; + return a; +} + +inline KeyMod& operator&=(KeyMod& a, KeyMod b) { + uint16_t a_cast = (uint16_t)a; + a_cast &= (uint16_t)b; + a = (KeyMod)a_cast; + return a; +} + +} + +#endif diff --git a/source/application/StarMainApplication.hpp b/source/application/StarMainApplication.hpp new file mode 100644 index 0000000..515dfb9 --- /dev/null +++ b/source/application/StarMainApplication.hpp @@ -0,0 +1,34 @@ +#ifndef STAR_MAIN_APPLICATION_HPP +#define STAR_MAIN_APPLICATION_HPP + +#include "StarApplication.hpp" +#include "StarApplicationController.hpp" +#include "StarRenderer.hpp" + +namespace Star { + int runMainApplication(ApplicationUPtr application, StringList cmdLineArgs); +} + +#if defined STAR_SYSTEM_WINDOWS + +#include + +#define STAR_MAIN_APPLICATION(ApplicationClass) \ + int __stdcall WinMain(HINSTANCE, HINSTANCE, LPSTR, int) { \ + int nArgs; \ + LPWSTR* argsList = CommandLineToArgvW(GetCommandLineW(), &nArgs); \ + Star::StringList args; \ + for (int i = 0; i < nArgs; ++i) args.append(Star::String(argsList[i])); \ + return Star::runMainApplication(Star::make_unique(), args); \ + } + +#else + +#define STAR_MAIN_APPLICATION(ApplicationClass) \ + int main(int argc, char** argv) { \ + return Star::runMainApplication(Star::make_unique(), Star::StringList(argc, argv)); \ + } + +#endif + +#endif diff --git a/source/application/StarMainApplication_sdl.cpp b/source/application/StarMainApplication_sdl.cpp new file mode 100644 index 0000000..503a8c4 --- /dev/null +++ b/source/application/StarMainApplication_sdl.cpp @@ -0,0 +1,692 @@ +#include "StarMainApplication.hpp" +#include "StarLogging.hpp" +#include "StarSignalHandler.hpp" +#include "StarTickRateMonitor.hpp" +#include "StarRenderer_opengl20.hpp" + +#include "SDL.h" +#include "StarPlatformServices_pc.hpp" + +namespace Star { + +Maybe keyFromSdlKeyCode(SDL_Keycode sym) { + static HashMap KeyCodeMap{ + {SDLK_BACKSPACE, Key::Backspace}, + {SDLK_TAB, Key::Tab}, + {SDLK_CLEAR, Key::Clear}, + {SDLK_RETURN, Key::Return}, + {SDLK_PAUSE, Key::Pause}, + {SDLK_ESCAPE, Key::Escape}, + {SDLK_SPACE, Key::Space}, + {SDLK_EXCLAIM, Key::Exclaim}, + {SDLK_QUOTEDBL, Key::QuotedBL}, + {SDLK_HASH, Key::Hash}, + {SDLK_DOLLAR, Key::Dollar}, + {SDLK_AMPERSAND, Key::Ampersand}, + {SDLK_QUOTE, Key::Quote}, + {SDLK_LEFTPAREN, Key::LeftParen}, + {SDLK_RIGHTPAREN, Key::RightParen}, + {SDLK_ASTERISK, Key::Asterisk}, + {SDLK_PLUS, Key::Plus}, + {SDLK_COMMA, Key::Comma}, + {SDLK_MINUS, Key::Minus}, + {SDLK_PERIOD, Key::Period}, + {SDLK_SLASH, Key::Slash}, + {SDLK_0, Key::Zero}, + {SDLK_1, Key::One}, + {SDLK_2, Key::Two}, + {SDLK_3, Key::Three}, + {SDLK_4, Key::Four}, + {SDLK_5, Key::Five}, + {SDLK_6, Key::Six}, + {SDLK_7, Key::Seven}, + {SDLK_8, Key::Eight}, + {SDLK_9, Key::Nine}, + {SDLK_COLON, Key::Colon}, + {SDLK_SEMICOLON, Key::Semicolon}, + {SDLK_LESS, Key::Less}, + {SDLK_EQUALS, Key::Equals}, + {SDLK_GREATER, Key::Greater}, + {SDLK_QUESTION, Key::Question}, + {SDLK_AT, Key::At}, + {SDLK_LEFTBRACKET, Key::LeftBracket}, + {SDLK_BACKSLASH, Key::Backslash}, + {SDLK_RIGHTBRACKET, Key::RightBracket}, + {SDLK_CARET, Key::Caret}, + {SDLK_UNDERSCORE, Key::Underscore}, + {SDLK_BACKQUOTE, Key::Backquote}, + {SDLK_a, Key::A}, + {SDLK_b, Key::B}, + {SDLK_c, Key::C}, + {SDLK_d, Key::D}, + {SDLK_e, Key::E}, + {SDLK_f, Key::F}, + {SDLK_g, Key::G}, + {SDLK_h, Key::H}, + {SDLK_i, Key::I}, + {SDLK_j, Key::J}, + {SDLK_k, Key::K}, + {SDLK_l, Key::L}, + {SDLK_m, Key::M}, + {SDLK_n, Key::N}, + {SDLK_o, Key::O}, + {SDLK_p, Key::P}, + {SDLK_q, Key::Q}, + {SDLK_r, Key::R}, + {SDLK_s, Key::S}, + {SDLK_t, Key::T}, + {SDLK_u, Key::U}, + {SDLK_v, Key::V}, + {SDLK_w, Key::W}, + {SDLK_x, Key::X}, + {SDLK_y, Key::Y}, + {SDLK_z, Key::Z}, + {SDLK_DELETE, Key::Delete}, + {SDLK_KP_0, Key::Kp0}, + {SDLK_KP_1, Key::Kp1}, + {SDLK_KP_2, Key::Kp2}, + {SDLK_KP_3, Key::Kp3}, + {SDLK_KP_4, Key::Kp4}, + {SDLK_KP_5, Key::Kp5}, + {SDLK_KP_6, Key::Kp6}, + {SDLK_KP_7, Key::Kp7}, + {SDLK_KP_8, Key::Kp8}, + {SDLK_KP_9, Key::Kp9}, + {SDLK_KP_PERIOD, Key::Kp_period}, + {SDLK_KP_DIVIDE, Key::Kp_divide}, + {SDLK_KP_MULTIPLY, Key::Kp_multiply}, + {SDLK_KP_MINUS, Key::Kp_minus}, + {SDLK_KP_PLUS, Key::Kp_plus}, + {SDLK_KP_ENTER, Key::Kp_enter}, + {SDLK_KP_EQUALS, Key::Kp_equals}, + {SDLK_UP, Key::Up}, + {SDLK_DOWN, Key::Down}, + {SDLK_RIGHT, Key::Right}, + {SDLK_LEFT, Key::Left}, + {SDLK_INSERT, Key::Insert}, + {SDLK_HOME, Key::Home}, + {SDLK_END, Key::End}, + {SDLK_PAGEUP, Key::PageUp}, + {SDLK_PAGEDOWN, Key::PageDown}, + {SDLK_F1, Key::F1}, + {SDLK_F2, Key::F2}, + {SDLK_F3, Key::F3}, + {SDLK_F4, Key::F4}, + {SDLK_F5, Key::F5}, + {SDLK_F6, Key::F6}, + {SDLK_F7, Key::F7}, + {SDLK_F8, Key::F8}, + {SDLK_F9, Key::F9}, + {SDLK_F10, Key::F10}, + {SDLK_F11, Key::F11}, + {SDLK_F12, Key::F12}, + {SDLK_F13, Key::F13}, + {SDLK_F14, Key::F14}, + {SDLK_F15, Key::F15}, + {SDLK_NUMLOCKCLEAR, Key::NumLock}, + {SDLK_CAPSLOCK, Key::CapsLock}, + {SDLK_SCROLLLOCK, Key::ScrollLock}, + {SDLK_RSHIFT, Key::RShift}, + {SDLK_LSHIFT, Key::LShift}, + {SDLK_RCTRL, Key::RCtrl}, + {SDLK_LCTRL, Key::LCtrl}, + {SDLK_RALT, Key::RAlt}, + {SDLK_LALT, Key::LAlt}, + {SDLK_RGUI, Key::RGui}, + {SDLK_LGUI, Key::LGui}, + {SDLK_MODE, Key::AltGr}, + {SDLK_APPLICATION, Key::Compose}, + {SDLK_HELP, Key::Help}, + {SDLK_PRINTSCREEN, Key::PrintScreen}, + {SDLK_SYSREQ, Key::SysReq}, + {SDLK_PAUSE, Key::Pause}, + {SDLK_MENU, Key::Menu}, + {SDLK_POWER, Key::Power} + }; + + return KeyCodeMap.maybe(sym); +} + +KeyMod keyModsFromSdlKeyMods(uint16_t mod) { + KeyMod keyMod = KeyMod::NoMod; + + if (mod & KMOD_LSHIFT) + keyMod |= KeyMod::LShift; + if (mod & KMOD_RSHIFT) + keyMod |= KeyMod::RShift; + if (mod & KMOD_LCTRL) + keyMod |= KeyMod::LCtrl; + if (mod & KMOD_RCTRL) + keyMod |= KeyMod::RCtrl; + if (mod & KMOD_LALT) + keyMod |= KeyMod::LAlt; + if (mod & KMOD_RALT) + keyMod |= KeyMod::RAlt; + if (mod & KMOD_LGUI) + keyMod |= KeyMod::LGui; + if (mod & KMOD_RGUI) + keyMod |= KeyMod::RGui; + if (mod & KMOD_NUM) + keyMod |= KeyMod::Num; + if (mod & KMOD_CAPS) + keyMod |= KeyMod::Caps; + if (mod & KMOD_MODE) + keyMod |= KeyMod::AltGr; + + return keyMod; +} + +MouseButton mouseButtonFromSdlMouseButton(uint8_t button) { + if (button == SDL_BUTTON_LEFT) + return MouseButton::Left; + else if (button == SDL_BUTTON_MIDDLE) + return MouseButton::Middle; + else if (button == SDL_BUTTON_RIGHT) + return MouseButton::Right; + else if (button == SDL_BUTTON_X1) + return MouseButton::FourthButton; + else + return MouseButton::FifthButton; +} + +class SdlPlatform { +public: + SdlPlatform(ApplicationUPtr application, StringList cmdLineArgs) { + m_application = move(application); + + // extract application path from command line args + String applicationPath = cmdLineArgs.first(); + cmdLineArgs = cmdLineArgs.slice(1); + + StringList platformArguments; + eraseWhere(cmdLineArgs, [&platformArguments](String& argument) { + if (argument.beginsWith("+platform")) { + platformArguments.append(move(argument)); + return true; + } + return false; + }); + + Logger::info("Application: Initializing SDL"); + if (SDL_Init(0)) + throw ApplicationException(strf("Couldn't initialize SDL: %s", SDL_GetError())); + + if (char* basePath = SDL_GetBasePath()) { + File::changeDirectory(basePath); + SDL_free(basePath); + } + + m_signalHandler.setHandleInterrupt(true); + m_signalHandler.setHandleFatal(true); + + try { + Logger::info("Application: startup..."); + m_application->startup(cmdLineArgs); + } catch (std::exception const& e) { + throw ApplicationException("Application threw exception during startup", e); + } + + Logger::info("Application: Initializing SDL Video"); + if (SDL_InitSubSystem(SDL_INIT_VIDEO)) + throw ApplicationException(strf("Couldn't initialize SDL Video: %s", SDL_GetError())); + + Logger::info("Application: Initializing SDL Joystick"); + if (SDL_InitSubSystem(SDL_INIT_JOYSTICK)) + throw ApplicationException(strf("Couldn't initialize SDL Joystick: %s", SDL_GetError())); + + Logger::info("Application: Initializing SDL Sound"); + if (SDL_InitSubSystem(SDL_INIT_AUDIO)) + throw ApplicationException(strf("Couldn't initialize SDL Sound: %s", SDL_GetError())); + + SDL_JoystickEventState(SDL_ENABLE); + + m_platformServices = PcPlatformServices::create(applicationPath, platformArguments); + if (!m_platformServices) + Logger::info("Application: No platform services available"); + + Logger::info("Application: Creating SDL Window"); + m_sdlWindow = SDL_CreateWindow(m_windowTitle.utf8Ptr(), SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, + m_windowSize[0], m_windowSize[1], SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE); + if (!m_sdlWindow) + throw ApplicationException::format("Application: Could not create SDL window: %s", SDL_GetError()); + + SDL_ShowWindow(m_sdlWindow); + SDL_RaiseWindow(m_sdlWindow); + + int width; + int height; + SDL_GetWindowSize(m_sdlWindow, &width, &height); + m_windowSize = Vec2U(width, height); + + m_sdlGlContext = SDL_GL_CreateContext(m_sdlWindow); + if (!m_sdlGlContext) + throw ApplicationException::format("Application: Could not create OpenGL context: %s", SDL_GetError()); + + setVSyncEnabled(m_windowVSync); + + SDL_StopTextInput(); + + SDL_AudioSpec desired = {}; + desired.freq = 44100; + desired.format = AUDIO_S16SYS; + desired.samples = 2048; + desired.channels = 2; + desired.userdata = this; + desired.callback = [](void* userdata, Uint8* stream, int len) { + ((SdlPlatform*)(userdata))->getAudioData(stream, len); + }; + + SDL_AudioSpec obtained = {}; + if (SDL_OpenAudio(&desired, &obtained) < 0) { + Logger::error("Application: Could not open audio device, no sound available!"); + } else if (obtained.freq != desired.freq || obtained.channels != desired.channels || obtained.format != desired.format) { + SDL_CloseAudio(); + Logger::error("Application: Could not open 44.1khz / 16 bit stereo audio device, no sound available!"); + } else { + Logger::info("Application: Opened default audio device with 44.1khz / 16 bit stereo audio, %s sample size buffer", obtained.samples); + } + + m_renderer = make_shared(); + m_renderer->setScreenSize(m_windowSize); + } + + ~SdlPlatform() { + SDL_CloseAudio(); + + m_renderer.reset(); + + Logger::info("Application: Destroying SDL Window"); + SDL_DestroyWindow(m_sdlWindow); + + SDL_Quit(); + } + + void run() { + try { + Logger::info("Application: initialization..."); + m_application->applicationInit(make_shared(this)); + + Logger::info("Application: renderer initialization..."); + m_application->renderInit(m_renderer); + + Logger::info("Application: main update loop..."); + + m_updateTicker.reset(); + m_renderTicker.reset(); + + bool quit = false; + while (true) { + for (auto const& event : processEvents()) + m_application->processInput(event); + + if (m_platformServices) + m_platformServices->update(); + + if (m_platformServices->overlayActive()) + SDL_ShowCursor(1); + else + SDL_ShowCursor(m_cursorVisible ? 1 : 0); + + int updatesBehind = max(round(m_updateTicker.ticksBehind()), 1); + updatesBehind = min(updatesBehind, m_maxFrameSkip + 1); + for (int i = 0; i < updatesBehind; ++i) { + m_application->update(); + m_updateRate = m_updateTicker.tick(); + } + + m_renderer->startFrame(); + m_application->render(); + m_renderer->finishFrame(); + SDL_GL_SwapWindow(m_sdlWindow); + m_renderRate = m_renderTicker.tick(); + + if (m_quitRequested) { + Logger::info("Application: quit requested"); + quit = true; + } + + if (m_signalHandler.interruptCaught()) { + Logger::info("Application: Interrupt caught"); + quit = true; + } + + if (quit) { + Logger::info("Application: quitting..."); + break; + } + + int64_t spareMilliseconds = round(m_updateTicker.spareTime() * 1000); + if (spareMilliseconds > 0) + Thread::sleepPrecise(spareMilliseconds); + } + } catch (std::exception const& e) { + Logger::error("Application: exception thrown, shutting down: %s", outputException(e, true)); + } + + try { + Logger::info("Application: shutdown..."); + m_application->shutdown(); + } catch (std::exception const& e) { + Logger::error("Application: threw exception during shutdown: %s", outputException(e, true)); + } + + SDL_CloseAudio(); + m_application.reset(); + } + +private: + struct Controller : public ApplicationController { + Controller(SdlPlatform* parent) + : parent(parent) {} + + Maybe getClipboard() override { + if (SDL_HasClipboardText()) + return String(SDL_GetClipboardText()); + return {}; + } + + void setClipboard(String text) override { + SDL_SetClipboardText(text.utf8Ptr()); + } + + void setTargetUpdateRate(float targetUpdateRate) override { + parent->m_updateTicker.setTargetTickRate(targetUpdateRate); + } + + void setUpdateTrackWindow(float updateTrackWindow) override { + parent->m_updateTicker.setWindow(updateTrackWindow); + } + + void setApplicationTitle(String title) override { + parent->m_windowTitle = move(title); + if (parent->m_sdlWindow) + SDL_SetWindowTitle(parent->m_sdlWindow, parent->m_windowTitle.utf8Ptr()); + } + + void setFullscreenWindow(Vec2U fullScreenResolution) override { + if (parent->m_windowMode != WindowMode::Fullscreen || parent->m_windowSize != fullScreenResolution) { + SDL_DisplayMode requestedDisplayMode = {SDL_PIXELFORMAT_RGB888, (int)fullScreenResolution[0], (int)fullScreenResolution[1], 0, 0}; + int currentDisplayIndex = SDL_GetWindowDisplayIndex(parent->m_sdlWindow); + + SDL_DisplayMode targetDisplayMode; + if (SDL_GetClosestDisplayMode(currentDisplayIndex, &requestedDisplayMode, &targetDisplayMode) != NULL) { + if (SDL_SetWindowDisplayMode(parent->m_sdlWindow, &requestedDisplayMode) == 0) { + if (parent->m_windowMode == WindowMode::Fullscreen) + SDL_SetWindowFullscreen(parent->m_sdlWindow, 0); + else + parent->m_windowMode = WindowMode::Fullscreen; + SDL_SetWindowFullscreen(parent->m_sdlWindow, SDL_WINDOW_FULLSCREEN); + } else { + Logger::warn("Failed to set resolution %s, %s", (unsigned)requestedDisplayMode.w, (unsigned)requestedDisplayMode.h); + } + } else { + Logger::warn("Unable to set requested display resolution %s, %s", (int)fullScreenResolution[0], (int)fullScreenResolution[1]); + } + + SDL_DisplayMode actualDisplayMode; + if (SDL_GetWindowDisplayMode(parent->m_sdlWindow, &actualDisplayMode) == 0) { + parent->m_windowSize = {(unsigned)actualDisplayMode.w, (unsigned)actualDisplayMode.h}; + + // call these manually since no SDL_WindowEvent is triggered when changing between fullscreen resolutions for some reason + parent->m_renderer->setScreenSize(parent->m_windowSize); + parent->m_application->windowChanged(parent->m_windowMode, parent->m_windowSize); + } else { + Logger::error("Couldn't get window display mode!"); + } + } + } + + void setNormalWindow(Vec2U windowSize) override { + if (parent->m_windowMode != WindowMode::Normal || parent->m_windowSize != windowSize) { + if (parent->m_windowMode == WindowMode::Fullscreen || parent->m_windowMode == WindowMode::Borderless) + SDL_SetWindowFullscreen(parent->m_sdlWindow, 0); + else if (parent->m_windowMode == WindowMode::Maximized) + SDL_RestoreWindow(parent->m_sdlWindow); + + SDL_SetWindowSize(parent->m_sdlWindow, windowSize[0], windowSize[1]); + SDL_SetWindowPosition(parent->m_sdlWindow, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED); + + parent->m_windowMode = WindowMode::Normal; + parent->m_windowSize = windowSize; + } + } + + void setMaximizedWindow() override { + if (parent->m_windowMode != WindowMode::Maximized) { + if (parent->m_windowMode == WindowMode::Fullscreen || parent->m_windowMode == WindowMode::Borderless) + SDL_SetWindowFullscreen(parent->m_sdlWindow, 0); + + SDL_MaximizeWindow(parent->m_sdlWindow); + parent->m_windowMode = WindowMode::Maximized; + } + } + + void setBorderlessWindow() override { + if (parent->m_windowMode != WindowMode::Borderless) { + SDL_SetWindowFullscreen(parent->m_sdlWindow, SDL_WINDOW_FULLSCREEN_DESKTOP); + parent->m_windowMode = WindowMode::Borderless; + + SDL_DisplayMode actualDisplayMode; + if (SDL_GetWindowDisplayMode(parent->m_sdlWindow, &actualDisplayMode) == 0) { + parent->m_windowSize = {(unsigned)actualDisplayMode.w, (unsigned)actualDisplayMode.h}; + + parent->m_renderer->setScreenSize(parent->m_windowSize); + parent->m_application->windowChanged(parent->m_windowMode, parent->m_windowSize); + } else { + Logger::error("Couldn't get window display mode!"); + } + } + } + + void setVSyncEnabled(bool vSync) override { + if (parent->m_windowVSync != vSync) { + parent->setVSyncEnabled(vSync); + parent->m_windowVSync = vSync; + } + } + + void setMaxFrameSkip(unsigned maxFrameSkip) override { + parent->m_maxFrameSkip = maxFrameSkip; + } + + void setCursorVisible(bool cursorVisible) override { + parent->m_cursorVisible = cursorVisible; + } + + void setAcceptingTextInput(bool acceptingTextInput) override { + if (acceptingTextInput != parent->m_acceptingTextInput) { + if (acceptingTextInput) + SDL_StartTextInput(); + else + SDL_StopTextInput(); + + parent->m_acceptingTextInput = acceptingTextInput; + } + } + + AudioFormat enableAudio() override { + parent->m_audioEnabled = true; + SDL_PauseAudio(false); + return AudioFormat{44100, 2}; + } + + void disableAudio() override { + parent->m_audioEnabled = false; + SDL_PauseAudio(true); + } + + float updateRate() const override { + return parent->m_updateRate; + } + + float renderFps() const override { + return parent->m_renderRate; + } + + StatisticsServicePtr statisticsService() const override { + if (parent->m_platformServices) + return parent->m_platformServices->statisticsService(); + return {}; + } + + P2PNetworkingServicePtr p2pNetworkingService() const override { + if (parent->m_platformServices) + return parent->m_platformServices->p2pNetworkingService(); + return {}; + } + + UserGeneratedContentServicePtr userGeneratedContentService() const override { + if (parent->m_platformServices) + return parent->m_platformServices->userGeneratedContentService(); + return {}; + } + + DesktopServicePtr desktopService() const override { + if (parent->m_platformServices) + return parent->m_platformServices->desktopService(); + return {}; + } + + void quit() override { + parent->m_quitRequested = true; + } + + SdlPlatform* parent; + }; + + List processEvents() { + List inputEvents; + + SDL_Event event; + while (SDL_PollEvent(&event)) { + Maybe starEvent; + if (event.type == SDL_WINDOWEVENT) { + if (event.window.event == SDL_WINDOWEVENT_MAXIMIZED || event.window.event == SDL_WINDOWEVENT_RESTORED) { + auto windowFlags = SDL_GetWindowFlags(m_sdlWindow); + + if (windowFlags & SDL_WINDOW_MAXIMIZED) { + m_windowMode = WindowMode::Maximized; + } else if (windowFlags & SDL_WINDOW_FULLSCREEN || windowFlags & SDL_WINDOW_FULLSCREEN_DESKTOP) { + if (m_windowMode != WindowMode::Fullscreen && m_windowMode != WindowMode::Borderless) + m_windowMode = WindowMode::Fullscreen; + } else { + m_windowMode = WindowMode::Normal; + } + + m_application->windowChanged(m_windowMode, m_windowSize); + + } else if (event.window.event == SDL_WINDOWEVENT_RESIZED || event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) { + m_windowSize = Vec2U(event.window.data1, event.window.data2); + m_renderer->setScreenSize(m_windowSize); + m_application->windowChanged(m_windowMode, m_windowSize); + } + + } else if (event.type == SDL_KEYDOWN) { + if (!event.key.repeat) { + if (auto key = keyFromSdlKeyCode(event.key.keysym.sym)) + starEvent.set(KeyDownEvent{*key, keyModsFromSdlKeyMods(event.key.keysym.mod)}); + } + + } else if (event.type == SDL_KEYUP) { + if (auto key = keyFromSdlKeyCode(event.key.keysym.sym)) + starEvent.set(KeyUpEvent{*key}); + + } else if (event.type == SDL_TEXTINPUT) { + starEvent.set(TextInputEvent{String(event.text.text)}); + + } else if (event.type == SDL_MOUSEMOTION) { + starEvent.set(MouseMoveEvent{ + {event.motion.xrel, -event.motion.yrel}, {event.motion.x, (int)m_windowSize[1] - event.motion.y}}); + + } else if (event.type == SDL_MOUSEBUTTONDOWN) { + starEvent.set(MouseButtonDownEvent{mouseButtonFromSdlMouseButton(event.button.button), + {event.button.x, (int)m_windowSize[1] - event.button.y}}); + + } else if (event.type == SDL_MOUSEBUTTONUP) { + starEvent.set(MouseButtonUpEvent{mouseButtonFromSdlMouseButton(event.button.button), + {event.button.x, (int)m_windowSize[1] - event.button.y}}); + + } else if (event.type == SDL_MOUSEWHEEL) { + int x, y; + SDL_GetMouseState(&x, &y); + starEvent.set(MouseWheelEvent{event.wheel.y < 0 ? MouseWheel::Down : MouseWheel::Up, {x, (int)m_windowSize[1] - y}}); + + } else if (event.type == SDL_QUIT) { + m_quitRequested = true; + starEvent.reset(); + } + + if (starEvent) + inputEvents.append(starEvent.take()); + } + + return inputEvents; + } + + void getAudioData(Uint8* stream, int len) { + if (m_audioEnabled) { + m_application->getAudioData((int16_t*)stream, len / 4); + } else { + for (int i = 0; i < len; ++i) + stream[i] = 0; + } + } + + void setVSyncEnabled(bool vsyncEnabled) { + if (vsyncEnabled) { + // If VSync is requested, try for late swap tearing first, then fall back + // to regular VSync + Logger::info("Application: Enabling VSync with late swap tearing"); + if (SDL_GL_SetSwapInterval(-1) < 0) { + Logger::info("Application: Enabling VSync late swap tearing failed, falling back to full VSync"); + SDL_GL_SetSwapInterval(1); + } + } else { + Logger::info("Application: Disabling VSync"); + SDL_GL_SetSwapInterval(0); + } + } + + SignalHandler m_signalHandler; + + TickRateApproacher m_updateTicker = TickRateApproacher(60.0f, 1.0f); + float m_updateRate = 0.0f; + TickRateMonitor m_renderTicker = TickRateMonitor(1.0f); + float m_renderRate = 0.0f; + + SDL_Window* m_sdlWindow = nullptr; + SDL_GLContext m_sdlGlContext = nullptr; + + Vec2U m_windowSize = {800, 600}; + WindowMode m_windowMode = WindowMode::Normal; + + String m_windowTitle = ""; + bool m_windowVSync = true; + unsigned m_maxFrameSkip = 5; + bool m_cursorVisible = true; + bool m_acceptingTextInput = false; + bool m_audioEnabled = false; + bool m_quitRequested = false; + + OpenGl20RendererPtr m_renderer; + ApplicationUPtr m_application; + PcPlatformServicesUPtr m_platformServices; +}; + +int runMainApplication(ApplicationUPtr application, StringList cmdLineArgs) { + try { + { + SdlPlatform platform(move(application), move(cmdLineArgs)); + platform.run(); + } + Logger::info("Application: stopped gracefully"); + return 0; + } catch (std::exception const& e) { + fatalException(e, true); + } catch (...) { + fatalError("Unknown Exception", true); + } + return 1; +} + +} diff --git a/source/application/StarP2PNetworkingService_pc.cpp b/source/application/StarP2PNetworkingService_pc.cpp new file mode 100644 index 0000000..2a0b876 --- /dev/null +++ b/source/application/StarP2PNetworkingService_pc.cpp @@ -0,0 +1,616 @@ +#include "StarP2PNetworkingService_pc.hpp" +#include "StarLexicalCast.hpp" +#include "StarEither.hpp" +#include "StarLogging.hpp" +#include "StarRandom.hpp" +#include "StarEncode.hpp" +#include "StarUuid.hpp" + +namespace Star { + +#ifdef STAR_ENABLE_DISCORD_INTEGRATION + +discord::NetworkChannelId const DiscordMainNetworkChannel = 0; + +#endif + +PcP2PNetworkingService::PcP2PNetworkingService(PcPlatformServicesStatePtr state) +#ifdef STAR_ENABLE_STEAM_INTEGRATION + : m_callbackConnectionFailure(this, &PcP2PNetworkingService::steamOnConnectionFailure), + m_callbackJoinRequested(this, &PcP2PNetworkingService::steamOnJoinRequested), + m_callbackSessionRequest(this, &PcP2PNetworkingService::steamOnSessionRequest), + m_state(move(state)) { +#else + : m_state(move(state)) { +#endif + +#ifdef STAR_ENABLE_DISCORD_INTEGRATION + if (m_state->discordAvailable) { + MutexLocker discordLocker(m_state->discordMutex); + + m_discordPartySize = {}; + + m_discordOnActivityJoinToken = m_state->discordCore->ActivityManager().OnActivityJoin.Connect([this](char const* peerId) { + MutexLocker serviceLocker(m_mutex); + Logger::info("Joining discord peer at '%s'", peerId); + addPendingJoin(strf("+platform:%s", peerId)); + }); + m_discordOnActivityRequestToken = m_state->discordCore->ActivityManager().OnActivityJoinRequest.Connect([this](discord::User const& user) { + MutexLocker serviceLocker(m_mutex); + String userName = String(user.GetUsername()); + Logger::info("Received join request from user '%s'", userName); + m_discordJoinRequests.emplace_back(make_pair(user.GetId(), userName)); + }); + m_discordOnReceiveMessage = m_state->discordCore->LobbyManager().OnNetworkMessage.Connect(bind(&PcP2PNetworkingService::discordOnReceiveMessage, this, _1, _2, _3, _4, _5)); + m_discordOnLobbyMemberConnect = m_state->discordCore->LobbyManager().OnMemberConnect.Connect(bind(&PcP2PNetworkingService::discordOnLobbyMemberConnect, this, _1, _2)); + m_discordOnLobbyMemberUpdate = m_state->discordCore->LobbyManager().OnMemberUpdate.Connect(bind(&PcP2PNetworkingService::discordOnLobbyMemberUpdate, this, _1, _2)); + m_discordOnLobbyMemberDisconnect = m_state->discordCore->LobbyManager().OnMemberDisconnect.Connect(bind(&PcP2PNetworkingService::discordOnLobbyMemberDisconnect, this, _1, _2)); + } +#endif +} + +PcP2PNetworkingService::~PcP2PNetworkingService() { +#ifdef STAR_ENABLE_DISCORD_INTEGRATION + if (m_state->discordAvailable) { + MutexLocker discordLocker(m_state->discordMutex); + if (m_discordServerLobby) { + Logger::info("Deleting discord server lobby %s", m_discordServerLobby->first); + m_state->discordCore->LobbyManager().DeleteLobby(m_discordServerLobby->first, [](discord::Result res) { + Logger::error("Could not connect delete server lobby (err %s)", (int)res); + }); + } + + m_state->discordCore->ActivityManager().OnActivityJoin.Disconnect(m_discordOnActivityJoinToken); + m_state->discordCore->LobbyManager().OnNetworkMessage.Disconnect(m_discordOnReceiveMessage); + m_state->discordCore->LobbyManager().OnMemberConnect.Disconnect(m_discordOnLobbyMemberConnect); + m_state->discordCore->LobbyManager().OnMemberUpdate.Disconnect(m_discordOnLobbyMemberUpdate); + m_state->discordCore->LobbyManager().OnMemberDisconnect.Disconnect(m_discordOnLobbyMemberDisconnect); + } +#endif +} + +void PcP2PNetworkingService::setJoinUnavailable() { + setJoinLocation(JoinUnavailable()); +} + +void PcP2PNetworkingService::setJoinLocal(uint32_t capacity) { + setJoinLocation(JoinLocal{capacity}); +} + +void PcP2PNetworkingService::setJoinRemote(HostAddressWithPort location) { + setJoinLocation(JoinRemote(location)); +} + +void Star::PcP2PNetworkingService::setActivityData(String const& title, Maybe> party) { +#ifdef STAR_ENABLE_DISCORD_INTEGRATION + MutexLocker discordLocker(m_state->discordMutex); +#endif + MutexLocker serviceLocker(m_mutex); + +#ifdef STAR_ENABLE_DISCORD_INTEGRATION + if (m_state->discordAvailable && m_state->discordCurrentUser) { + if (m_discordUpdatingActivity) + return; + + if (title != m_discordActivityTitle || party != m_discordPartySize || m_discordForceUpdateActivity) { + m_discordForceUpdateActivity = false; + m_discordPartySize = party; + m_discordActivityTitle = title; + + discord::Activity activity = {}; + activity.SetType(discord::ActivityType::Playing); + activity.SetName("Starbound"); + activity.SetState(title.utf8Ptr()); + + if (auto p = party) { + activity.GetParty().GetSize().SetCurrentSize(p->first); + activity.GetParty().GetSize().SetMaxSize(p->second); + } + + if (auto lobby = m_discordServerLobby) + activity.GetParty().SetId(strf("%s", lobby->first).c_str()); + + if (m_joinLocation.is()) { + if (auto lobby = m_discordServerLobby) { + String joinSecret = strf("connect:discord_%s_%s_%s", m_state->discordCurrentUser->GetId(), lobby->first, lobby->second); + Logger::info("Setting discord join secret as %s", joinSecret); + activity.GetSecrets().SetJoin(joinSecret.utf8Ptr()); + } + } else if (m_joinLocation.is()) { + String address = strf("%s", (HostAddressWithPort)m_joinLocation.get()); + String joinSecret = strf("connect:address_%s", address); + Logger::info("Setting discord join secret as %s", joinSecret); + activity.GetSecrets().SetJoin(joinSecret.utf8Ptr()); + + activity.GetParty().SetId(address.utf8Ptr()); + } + + m_discordUpdatingActivity = true; + m_state->discordCore->ActivityManager().UpdateActivity(activity, [this](discord::Result res) { + if (res != discord::Result::Ok) + Logger::error("failed to set discord activity (err %s)", (int)res); + + MutexLocker serviceLocker(m_mutex); + m_discordUpdatingActivity = false; + }); + } + } +#endif +} + +MVariant PcP2PNetworkingService::pullPendingJoin() { + MutexLocker serviceLocker(m_mutex); + return take(m_pendingJoin); +} + +Maybe>> Star::PcP2PNetworkingService::pullJoinRequest() { + MutexLocker serviceLocker(m_mutex); + +#ifdef STAR_ENABLE_DISCORD_INTEGRATION + if (auto request = m_discordJoinRequests.maybeTakeLast()) { + auto promisePair = RpcPromise::createPair(); + m_pendingDiscordJoinRequests.push_back(make_pair(request->first, promisePair.first)); + return make_pair(request->second, promisePair.second); + } +#endif + + return {}; +} + +void PcP2PNetworkingService::setAcceptingP2PConnections(bool acceptingP2PConnections) { + MutexLocker serviceLocker(m_mutex); + m_acceptingP2PConnections = acceptingP2PConnections; + if (!m_acceptingP2PConnections) + m_pendingIncomingConnections.clear(); +} + +List PcP2PNetworkingService::acceptP2PConnections() { + MutexLocker serviceLocker(m_mutex); + return take(m_pendingIncomingConnections); +} + +void Star::PcP2PNetworkingService::update() { +#ifdef STAR_ENABLE_DISCORD_INTEGRATION + MutexLocker discordLocker(m_state->discordMutex); +#endif + MutexLocker serviceLocker(m_mutex); + +#ifdef STAR_ENABLE_DISCORD_INTEGRATION + for (auto& p : m_pendingDiscordJoinRequests) { + if (auto res = p.second.result()) { + discord::ActivityJoinRequestReply reply; + switch (*res) { + case P2PJoinRequestReply::Yes: + reply = discord::ActivityJoinRequestReply::Yes; + break; + case P2PJoinRequestReply::No: + reply = discord::ActivityJoinRequestReply::No; + break; + case P2PJoinRequestReply::Ignore: + reply = discord::ActivityJoinRequestReply::Ignore; + break; + } + + m_state->discordCore->ActivityManager().SendRequestReply(p.first, reply, [](discord::Result res) { + if (res != discord::Result::Ok) + Logger::error("Could not send discord activity join response (err %s)", (int)res); + }); + } + } + m_pendingDiscordJoinRequests = m_pendingDiscordJoinRequests.filtered([this](pair>& p) { + return !p.second.finished(); + }); +#endif +} + +Either PcP2PNetworkingService::connectToPeer(P2PNetworkingPeerId peerId) { +#ifdef STAR_ENABLE_DISCORD_INTEGRATION + MutexLocker discordLocker(m_state->discordMutex); +#endif + MutexLocker serviceLocker(m_mutex); + String type = peerId.extract("_"); + +#ifdef STAR_ENABLE_STEAM_INTEGRATION + if (m_state->steamAvailable) { + if (type == "steamid") { + CSteamID steamId(lexicalCast(peerId)); + return makeRight(createSteamP2PSocket(steamId)); + } + } +#endif + +#ifdef STAR_ENABLE_DISCORD_INTEGRATION + if (m_state->discordAvailable) { + if (type == "discord") { + auto remoteUserId = lexicalCast(peerId.extract("_")); + auto lobbyId = lexicalCast(peerId.extract("_")); + String lobbySecret = move(peerId); + return makeRight(discordConnectRemote(remoteUserId, lobbyId, lobbySecret)); + } + } +#endif + + return makeLeft(strf("Unsupported peer type '%s'", type)); +} + +void PcP2PNetworkingService::addPendingJoin(String connectionString) { + MutexLocker serviceLocker(m_mutex); + + if (connectionString.extract(":") != "+platform") + throw ApplicationException::format("malformed connection string '%s'", connectionString); + + if (connectionString.extract(":") != "connect") + throw ApplicationException::format("malformed connection string '%s'", connectionString); + + String target = move(connectionString); + String targetType = target.extract("_"); + + if (targetType == "address") + m_pendingJoin = HostAddressWithPort(target); + else + m_pendingJoin = P2PNetworkingPeerId(strf("%s_%s", targetType, target)); +} + +#ifdef STAR_ENABLE_STEAM_INTEGRATION + +PcP2PNetworkingService::SteamP2PSocket::~SteamP2PSocket() { + MutexLocker serviceLocker(parent->m_mutex); + MutexLocker socketLocker(mutex); + parent->steamCloseSocket(this); +} + +bool PcP2PNetworkingService::SteamP2PSocket::isOpen() { + MutexLocker socketLocker(mutex); + return connected; +} + +bool PcP2PNetworkingService::SteamP2PSocket::sendMessage(ByteArray const& message) { + MutexLocker socketLocker(mutex); + if (!connected) + return false; + + if (!SteamNetworking()->SendP2PPacket(steamId, message.ptr(), message.size(), k_EP2PSendReliable)) + throw ApplicationException("SteamNetworking::SendP2PPacket unexpectedly returned false"); + return true; +} + +Maybe PcP2PNetworkingService::SteamP2PSocket::receiveMessage() { + MutexLocker socketLocker(mutex); + if (!incoming.empty()) + return incoming.takeFirst(); + + if (connected) { + socketLocker.unlock(); + { + MutexLocker serviceLocker(parent->m_mutex); + parent->steamReceiveAll(); + } + + socketLocker.lock(); + if (!incoming.empty()) + return incoming.takeFirst(); + } + + return {}; +} + +auto PcP2PNetworkingService::createSteamP2PSocket(CSteamID steamId) -> unique_ptr { + if (auto oldSocket = m_steamOpenSockets.value(steamId.ConvertToUint64())) { + MutexLocker socketLocker(oldSocket->mutex); + steamCloseSocket(oldSocket); + } + + unique_ptr socket(new SteamP2PSocket); + socket->parent = this; + socket->steamId = steamId; + socket->connected = true; + + m_steamOpenSockets[steamId.ConvertToUint64()] = socket.get(); + + return socket; +} + +void PcP2PNetworkingService::steamOnConnectionFailure(P2PSessionConnectFail_t* callback) { + MutexLocker serviceLocker(m_mutex); + Logger::warn("Connection with steam user %s failed", callback->m_steamIDRemote.ConvertToUint64()); + if (auto socket = m_steamOpenSockets.value(callback->m_steamIDRemote.ConvertToUint64())) { + MutexLocker socketLocker(socket->mutex); + steamCloseSocket(socket); + } +} + +void PcP2PNetworkingService::steamOnJoinRequested(GameRichPresenceJoinRequested_t* callback) { + Logger::info("Queueing join request with steam friend id %s to address %s", callback->m_steamIDFriend.ConvertToUint64(), callback->m_rgchConnect); + addPendingJoin(callback->m_rgchConnect); +} + +void PcP2PNetworkingService::steamOnSessionRequest(P2PSessionRequest_t* callback) { + MutexLocker serviceLocker(m_mutex); + // Not sure whether this HasFriend call is actually necessary, or whether + // non-friends can even initiate P2P sessions. + if (m_acceptingP2PConnections && SteamFriends()->HasFriend(callback->m_steamIDRemote, k_EFriendFlagImmediate)) { + if (SteamNetworking()->AcceptP2PSessionWithUser(callback->m_steamIDRemote)) { + Logger::info("Accepted steam p2p connection with user %s", callback->m_steamIDRemote.ConvertToUint64()); + m_pendingIncomingConnections.append(createSteamP2PSocket(callback->m_steamIDRemote)); + } else { + Logger::error("Accepting steam p2p connection from user %s failed!", callback->m_steamIDRemote.ConvertToUint64()); + } + } else { + Logger::error("Ignoring steam p2p connection from user %s", callback->m_steamIDRemote.ConvertToUint64()); + } +} + +void PcP2PNetworkingService::steamCloseSocket(SteamP2PSocket* socket) { + if (socket->connected) { + Logger::info("Closing p2p connection with steam user %s", socket->steamId.ConvertToUint64()); + m_steamOpenSockets.remove(socket->steamId.ConvertToUint64()); + socket->connected = false; + } + SteamNetworking()->CloseP2PSessionWithUser(socket->steamId); +} + +void PcP2PNetworkingService::steamReceiveAll() { + uint32_t messageSize; + CSteamID messageRemoteUser; + while (SteamNetworking()->IsP2PPacketAvailable(&messageSize)) { + ByteArray data(messageSize, 0); + SteamNetworking()->ReadP2PPacket(data.ptr(), messageSize, &messageSize, &messageRemoteUser); + if (auto openSocket = m_steamOpenSockets.value(messageRemoteUser.ConvertToUint64())) { + MutexLocker socketLocker(openSocket->mutex); + openSocket->incoming.append(move(data)); + } + } +} + +#endif + +#ifdef STAR_ENABLE_DISCORD_INTEGRATION + +PcP2PNetworkingService::DiscordP2PSocket::~DiscordP2PSocket() { + MutexLocker discordLocker(parent->m_state->discordMutex); + MutexLocker serviceLocker(parent->m_mutex); + MutexLocker socketLocker(mutex); + parent->discordCloseSocket(this); +} + +bool PcP2PNetworkingService::DiscordP2PSocket::isOpen() { + MutexLocker socketLocker(mutex); + return mode != DiscordSocketMode::Disconnected; +} + +bool PcP2PNetworkingService::DiscordP2PSocket::sendMessage(ByteArray const& message) { + MutexLocker discordLocker(parent->m_state->discordMutex); + MutexLocker socketLocker(mutex); + if (mode != DiscordSocketMode::Connected) + return false; + + discord::Result res = parent->m_state->discordCore->LobbyManager().SendNetworkMessage(lobbyId, remoteUserId, DiscordMainNetworkChannel, (uint8_t*)message.ptr(), message.size()); + if (res != discord::Result::Ok) + throw ApplicationException::format("discord::Network::Send returned error (err %s)", (int)res); + + return true; +} + +Maybe PcP2PNetworkingService::DiscordP2PSocket::receiveMessage() { + MutexLocker socketLocker(mutex); + if (!incoming.empty()) + return incoming.takeFirst(); + else + return {}; +} + +void PcP2PNetworkingService::discordCloseSocket(DiscordP2PSocket* socket) { + if (socket->mode != DiscordSocketMode::Disconnected) { + m_discordOpenSockets.remove(socket->remoteUserId); + + if (socket->mode == DiscordSocketMode::Connected) { + if (!m_joinLocation.is() && m_discordOpenSockets.empty()) { + auto res = m_state->discordCore->LobbyManager().DisconnectNetwork(socket->lobbyId); + if (res != discord::Result::Ok) + Logger::error("failed to leave network for lobby %s (err %s)", socket->lobbyId, (int)res); + + m_state->discordCore->LobbyManager().DisconnectLobby(socket->lobbyId, [this, lobbyId = socket->lobbyId](discord::Result res) { + if (res != discord::Result::Ok) + Logger::error("failed to leave discord lobby %s", lobbyId); + + Logger::info("Left discord lobby %s", lobbyId); + MutexLocker serviceLocker(m_mutex); + m_discordServerLobby = {}; + m_discordForceUpdateActivity = true; + }); + } + } + + socket->mode = DiscordSocketMode::Disconnected; + } +} + +P2PSocketUPtr PcP2PNetworkingService::discordConnectRemote(discord::UserId remoteUserId, discord::LobbyId lobbyId, String const& lobbySecret) { + if (auto oldSocket = m_discordOpenSockets.value(remoteUserId)) { + MutexLocker socketLocker(oldSocket->mutex); + discordCloseSocket(oldSocket); + } + + unique_ptr socket(new DiscordP2PSocket); + socket->parent = this; + socket->mode = DiscordSocketMode::Startup; + socket->remoteUserId = remoteUserId; + socket->lobbyId = lobbyId; + m_discordOpenSockets[remoteUserId] = socket.get(); + + Logger::info("Connect to discord lobby %s", lobbyId); + m_state->discordCore->LobbyManager().ConnectLobby(lobbyId, lobbySecret.utf8Ptr(), [this, remoteUserId, lobbyId](discord::Result res, discord::Lobby const& lobby) { + MutexLocker serviceLocker(m_mutex); + if (res == discord::Result::Ok) { + if (auto socket = m_discordOpenSockets.value(remoteUserId)) { + MutexLocker socketLocker(socket->mutex); + + res = m_state->discordCore->LobbyManager().ConnectNetwork(lobbyId); + if (res != discord::Result::Ok) { + discordCloseSocket(socket); + return Logger::error("Could not connect to discord lobby network (err %s)", (int)res); + } + + res = m_state->discordCore->LobbyManager().OpenNetworkChannel(lobbyId, DiscordMainNetworkChannel, true); + if (res != discord::Result::Ok) { + discordCloseSocket(socket); + return Logger::error("Could not open discord main network channel (err %s)", (int)res); + } + + socket->mode = DiscordSocketMode::Connected; + Logger::info("Discord p2p connection opened to remote user %s via lobby %s", remoteUserId, lobbyId); + + m_discordServerLobby = make_pair(lobbyId, String()); + m_discordForceUpdateActivity = true; + } else { + Logger::error("discord::Lobbies::Connect callback no matching remoteUserId %s found", remoteUserId); + } + } else { + Logger::error("failed to connect to remote lobby (err %s)", (int)res); + if (auto socket = m_discordOpenSockets.value(remoteUserId)) { + MutexLocker socketLocker(socket->mutex); + discordCloseSocket(socket); + } + } + }); + + return unique_ptr(move(socket)); +} + +void PcP2PNetworkingService::discordOnReceiveMessage(discord::LobbyId lobbyId, discord::UserId userId, discord::NetworkChannelId channel, uint8_t* data, uint32_t size) { + MutexLocker serviceLocker(m_mutex); + + if (lobbyId != m_discordServerLobby->first) + return Logger::error("Received message from unexpected lobby %s", lobbyId); + + if (auto socket = m_discordOpenSockets.value(userId)) { + if (channel == DiscordMainNetworkChannel) { + MutexLocker socketLocker(socket->mutex); + socket->incoming.append(ByteArray((char const*)data, size)); + } else { + Logger::error("Received discord message on unexpected channel %s, ignoring", channel); + } + } else { + Logger::error("Could not find associated discord socket for user id %s", userId); + } + } + +void PcP2PNetworkingService::discordOnLobbyMemberConnect(discord::LobbyId lobbyId, discord::UserId userId) { + MutexLocker serviceLocker(m_mutex); + + if (m_discordServerLobby && m_discordServerLobby->first == lobbyId && userId != m_state->discordCurrentUser->GetId()) { + if (!m_discordOpenSockets.contains(userId)) { + unique_ptr socket(new DiscordP2PSocket); + socket->parent = this; + socket->lobbyId = lobbyId; + socket->remoteUserId = userId; + socket->mode = DiscordSocketMode::Connected; + + m_discordOpenSockets[userId] = socket.get(); + m_pendingIncomingConnections.append(move(socket)); + Logger::info("Accepted new discord connection from remote user %s", userId); + } + } +} + +void PcP2PNetworkingService::discordOnLobbyMemberUpdate(discord::LobbyId lobbyId, discord::UserId userId) { + discordOnLobbyMemberConnect(lobbyId, userId); +} + +void PcP2PNetworkingService::discordOnLobbyMemberDisconnect(discord::LobbyId lobbyId, discord::UserId userId) { + MutexLocker serviceLocker(m_mutex); + + if (m_discordServerLobby && m_discordServerLobby->first == lobbyId && userId != m_state->discordCurrentUser->GetId()) { + if (auto socket = m_discordOpenSockets.value(userId)) { + MutexLocker socketLocker(socket->mutex); + discordCloseSocket(socket); + } + } +} + +#endif + +void PcP2PNetworkingService::setJoinLocation(JoinLocation location) { +#ifdef STAR_ENABLE_DISCORD_INTEGRATION + MutexLocker discordLocker(m_state->discordMutex); +#endif + MutexLocker serviceLocker(m_mutex); + + if (location == m_joinLocation) + return; + m_joinLocation = location; + +#ifdef STAR_ENABLE_STEAM_INTEGRATION + if (m_state->steamAvailable) { + if (m_joinLocation.is()) { + Logger::info("Clearing steam rich presence connection"); + SteamFriends()->SetRichPresence("connect", ""); + + } else if (m_joinLocation.is()) { + auto steamId = SteamUser()->GetSteamID().ConvertToUint64(); + Logger::info("Setting steam rich presence connection as steamid_%s", steamId); + SteamFriends()->SetRichPresence("connect", strf("+platform:connect:steamid_%s", steamId).c_str()); + + } else if (m_joinLocation.is()) { + auto address = (HostAddressWithPort)location.get(); + Logger::info("Setting steam rich presence connection as address_%s", address); + SteamFriends()->SetRichPresence("connect", strf("+platform:connect:address_%s", address).c_str()); + } + } +#endif + +#ifdef STAR_ENABLE_DISCORD_INTEGRATION + if (m_state->discordAvailable && m_state->discordCurrentUser) { + if (m_discordServerLobby) { + Logger::info("Deleting discord server lobby %s", m_discordServerLobby->first); + m_state->discordCore->LobbyManager().DeleteLobby(m_discordServerLobby->first, [](discord::Result res) { + Logger::error("Could not connect delete server lobby (err %s)", (int)res); + }); + } + + if (auto joinLocal = m_joinLocation.maybe()) { + discord::LobbyTransaction createLobby{}; + if (m_state->discordCore->LobbyManager().GetLobbyCreateTransaction(&createLobby) != discord::Result::Ok) + throw ApplicationException::format("discord::Lobbies::CreateLobbyTransaction failed"); + + createLobby.SetCapacity(joinLocal->capacity); + createLobby.SetType(discord::LobbyType::Private); + m_state->discordCore->LobbyManager().CreateLobby(createLobby, [this](discord::Result res, discord::Lobby const& lobby) { + if (res == discord::Result::Ok) { + MutexLocker serviceLocker(m_mutex); + + discord::LobbyId lobbyId = lobby.GetId(); + + res = m_state->discordCore->LobbyManager().ConnectNetwork(lobbyId); + if (res == discord::Result::Ok) { + res = m_state->discordCore->LobbyManager().OpenNetworkChannel(lobbyId, DiscordMainNetworkChannel, true); + if (res == discord::Result::Ok) { + m_discordServerLobby = make_pair(lobbyId, String(lobby.GetSecret())); + m_discordForceUpdateActivity = true; + + // successfully joined lobby network + return; + } else { + Logger::error("Failed to open discord main network channel (err %s)", (int)res); + } + } else { + Logger::error("Failed to join discord lobby network (err %s)", (int)res); + } + + // Created lobby but failed to join the lobby network, delete lobby + Logger::error("Failed to join discord lobby network (err %s)", (int)res); + + Logger::info("Deleting discord lobby %s", lobbyId); + m_state->discordCore->LobbyManager().DeleteLobby(lobbyId, [lobbyId](discord::Result res) { + Logger::error("failed to delete lobby %s (err %s)", lobbyId, (int)res); + }); + } else { + Logger::error("failed to create discord lobby (err %s)", (int)res); + } + }); + } + } +#endif +} + +} diff --git a/source/application/StarP2PNetworkingService_pc.hpp b/source/application/StarP2PNetworkingService_pc.hpp new file mode 100644 index 0000000..47cebb6 --- /dev/null +++ b/source/application/StarP2PNetworkingService_pc.hpp @@ -0,0 +1,146 @@ +#ifndef STAR_P2P_NETWORKING_SERVICE_PC_HPP +#define STAR_P2P_NETWORKING_SERVICE_PC_HPP + +#include "StarPlatformServices_pc.hpp" +#include "StarAlgorithm.hpp" +#include "StarThread.hpp" +#include "StarStrongTypedef.hpp" +#include "StarRpcPromise.hpp" + +namespace Star { + +STAR_CLASS(PcP2PNetworkingService); + +class PcP2PNetworkingService : public P2PNetworkingService { +public: + PcP2PNetworkingService(PcPlatformServicesStatePtr state); + ~PcP2PNetworkingService(); + + void setJoinUnavailable() override; + void setJoinLocal(uint32_t capacity) override; + void setJoinRemote(HostAddressWithPort location) override; + void setActivityData(String const& title, Maybe>) override; + + MVariant pullPendingJoin() override; + Maybe>> pullJoinRequest() override; + + void setAcceptingP2PConnections(bool acceptingP2PConnections) override; + List acceptP2PConnections() override; + void update() override; + Either connectToPeer(P2PNetworkingPeerId peerId) override; + + void addPendingJoin(String connectionString); + +private: + strong_typedef(Empty, JoinUnavailable); + struct JoinLocal { + bool operator==(JoinLocal const& rhs) const { return capacity == rhs.capacity; }; + uint32_t capacity; + }; + strong_typedef(HostAddressWithPort, JoinRemote); + typedef Variant JoinLocation; + +#ifdef STAR_ENABLE_STEAM_INTEGRATION + + struct SteamP2PSocket : P2PSocket { + SteamP2PSocket() = default; + ~SteamP2PSocket(); + + bool isOpen() override; + bool sendMessage(ByteArray const& message) override; + Maybe receiveMessage() override; + + Mutex mutex; + PcP2PNetworkingService* parent = nullptr; + CSteamID steamId = CSteamID(); + Deque incoming; + bool connected = false; + }; + + unique_ptr createSteamP2PSocket(CSteamID steamId); + + STEAM_CALLBACK(PcP2PNetworkingService, steamOnConnectionFailure, P2PSessionConnectFail_t, m_callbackConnectionFailure); + STEAM_CALLBACK(PcP2PNetworkingService, steamOnJoinRequested, GameRichPresenceJoinRequested_t, m_callbackJoinRequested); + STEAM_CALLBACK(PcP2PNetworkingService, steamOnSessionRequest, P2PSessionRequest_t, m_callbackSessionRequest); + + void steamCloseSocket(SteamP2PSocket* socket); + void steamReceiveAll(); + +#endif + +#ifdef STAR_ENABLE_DISCORD_INTEGRATION + + enum class DiscordSocketMode { + Startup, + Connected, + Disconnected + }; + + struct DiscordP2PSocket : P2PSocket { + DiscordP2PSocket() = default; + ~DiscordP2PSocket(); + + bool isOpen() override; + bool sendMessage(ByteArray const& message) override; + Maybe receiveMessage() override; + + Mutex mutex; + PcP2PNetworkingService* parent = nullptr; + DiscordSocketMode mode = DiscordSocketMode::Disconnected; + discord::LobbyId lobbyId = {}; + discord::UserId remoteUserId; + Deque incoming; + }; + + P2PSocketUPtr discordConnectRemote(discord::UserId remoteUserId, discord::LobbyId lobbyId, String const& lobbySecret); + void discordCloseSocket(DiscordP2PSocket* socket); + + void discordOnReceiveMessage(discord::LobbyId lobbyId, discord::UserId userId, discord::NetworkChannelId channel, uint8_t* data, uint32_t size); + void discordOnLobbyMemberConnect(discord::LobbyId lobbyId, discord::UserId userId); + void discordOnLobbyMemberUpdate(discord::LobbyId lobbyId, discord::UserId userId); + void discordOnLobbyMemberDisconnect(discord::LobbyId lobbyId, discord::UserId userId); + +#endif + + void setJoinLocation(JoinLocation joinLocation); + + PcPlatformServicesStatePtr m_state; + + Mutex m_mutex; + JoinLocation m_joinLocation; + bool m_acceptingP2PConnections = false; + List m_pendingIncomingConnections; + MVariant m_pendingJoin; + +#ifdef STAR_ENABLE_STEAM_INTEGRATION + + HashMap m_steamOpenSockets; + +#endif + +#ifdef STAR_ENABLE_DISCORD_INTEGRATION + + + List> m_discordJoinRequests; + List>> m_pendingDiscordJoinRequests; + + HashMap m_discordOpenSockets; + String m_discordActivityTitle; + Maybe> m_discordPartySize; + bool m_discordForceUpdateActivity = false; + bool m_discordUpdatingActivity = false; + Maybe> m_discordServerLobby = {}; + + int m_discordOnActivityJoinToken = 0; + int m_discordOnActivityRequestToken = 0; + int m_discordOnReceiveMessage = 0; + int m_discordOnLobbyMemberConnect = 0; + int m_discordOnLobbyMemberUpdate = 0; + int m_discordOnLobbyMemberDisconnect = 0; + +#endif +}; + +} + +#endif diff --git a/source/application/StarPlatformServices_pc.cpp b/source/application/StarPlatformServices_pc.cpp new file mode 100644 index 0000000..7e528f0 --- /dev/null +++ b/source/application/StarPlatformServices_pc.cpp @@ -0,0 +1,175 @@ +#include "StarLogging.hpp" +#include "StarPlatformServices_pc.hpp" +#include "StarP2PNetworkingService_pc.hpp" + +#ifdef STAR_ENABLE_STEAM_INTEGRATION +#include "StarStatisticsService_pc_steam.hpp" +#include "StarUserGeneratedContentService_pc_steam.hpp" +#include "StarDesktopService_pc_steam.hpp" +#endif + +namespace Star { + +#ifdef STAR_ENABLE_DISCORD_INTEGRATION +uint64_t const DiscordClientId = 467102538278109224; +#endif + +PcPlatformServicesState::PcPlatformServicesState() +#ifdef STAR_ENABLE_STEAM_INTEGRATION + : callbackGameOverlayActivated(this, &PcPlatformServicesState::onGameOverlayActivated) { +#else + { +#endif + +#ifdef STAR_ENABLE_STEAM_INTEGRATION + if (SteamAPI_Init()) { + steamAvailable = true; + Logger::info("Initialized Steam platform services"); + } else { + Logger::info("Failed to initialize Steam platform services"); + } +#endif + +#ifdef STAR_ENABLE_DISCORD_INTEGRATION + static int64_t const DiscordEventSleep = 3; + + discord::Core* discordCorePtr = nullptr; + discord::Result res = discord::Core::Create(DiscordClientId, DiscordCreateFlags_NoRequireDiscord, &discordCorePtr); + if (res == discord::Result::Ok && discordCorePtr) { + discordCore.reset(discordCorePtr); + discordAvailable = true; + + discordCore->UserManager().OnCurrentUserUpdate.Connect([this](){ + discord::User user; + auto res = discordCore->UserManager().GetCurrentUser(&user); + if (res != discord::Result::Ok) + Logger::error("Could not get current discord user. (err %s)", (int)res); + else + discordCurrentUser = user; + }); + + } else { + Logger::error("Failed to instantiate discord core (err %s)", (int)res); + } + + if (discordAvailable) { + MutexLocker locker(discordMutex); + discordCore->SetLogHook(discord::LogLevel::Info, [](discord::LogLevel level, char const* msg) { + if (level == discord::LogLevel::Debug) + Logger::debug("[DISCORD]: %s", msg); + else if (level == discord::LogLevel::Error) + Logger::debug("[DISCORD]: %s", msg); + else if (level == discord::LogLevel::Info) + Logger::info("[DISCORD]: %s", msg); + else if (level == discord::LogLevel::Warn) + Logger::warn("[DISCORD]: %s", msg); + }); + discordEventShutdown = false; + discordEventThread = Thread::invoke("PcPlatformServices::discordEventThread", [this]() { + while (!discordEventShutdown) { + { + MutexLocker locker(discordMutex); + discordCore->RunCallbacks(); + discordCore->LobbyManager().FlushNetwork(); + } + Thread::sleep(DiscordEventSleep); + } + }); + + Logger::info("Initialized Discord platform services"); + } else { + Logger::info("Was not able to authenticate with Discord and create all components, Discord services will be unavailable"); + } +#endif +} + +PcPlatformServicesState::~PcPlatformServicesState() { +#ifdef STAR_ENABLE_DISCORD_INTEGRATION + if (discordAvailable) { + discordEventShutdown = true; + discordEventThread.finish(); + } +#endif +} + +#ifdef STAR_ENABLE_STEAM_INTEGRATION +void PcPlatformServicesState::onGameOverlayActivated(GameOverlayActivated_t* callback) { + overlayActive = callback->m_bActive; +} +#endif + +PcPlatformServicesUPtr PcPlatformServices::create(String const& path, StringList platformArguments) { + auto services = unique_ptr(new PcPlatformServices); + + services->m_state = make_shared(); + + bool provideP2PNetworking = false; + +#ifdef STAR_ENABLE_STEAM_INTEGRATION + provideP2PNetworking |= services->m_state->steamAvailable; +#endif + +#ifdef STAR_ENABLE_DISCORD_INTEGRATION + provideP2PNetworking |= services->m_state->discordAvailable; +#endif + + if (provideP2PNetworking) { + auto p2pNetworkingService = make_shared(services->m_state); + for (auto& argument : platformArguments) { + if (argument.beginsWith("+platform:connect:")) { + Logger::info("PC platform services joining from command line argument '%s'", argument); + p2pNetworkingService->addPendingJoin(move(argument)); + } else { + throw ApplicationException::format("Unrecognized PC platform services command line argument '%s'", argument); + } + } + + services->m_p2pNetworkingService = p2pNetworkingService; + } + +#ifdef STAR_ENABLE_STEAM_INTEGRATION + if (services->m_state->steamAvailable) { + services->m_statisticsService = make_shared(services->m_state); + services->m_userGeneratedContentService = make_shared(services->m_state); + services->m_desktopService = make_shared(services->m_state); + } +#endif + +#ifdef STAR_ENABLE_DISCORD_INTEGRATION + MutexLocker discordLocker(services->m_state->discordMutex); + if (services->m_state->discordAvailable) { + Logger::debug("Registering starbound to discord at path: %s", path); + services->m_state->discordCore->ActivityManager().RegisterCommand(path.utf8Ptr()); + } +#endif + + return services; +} + +StatisticsServicePtr PcPlatformServices::statisticsService() const { + return m_statisticsService; +} + +P2PNetworkingServicePtr PcPlatformServices::p2pNetworkingService() const { + return m_p2pNetworkingService; +} + +UserGeneratedContentServicePtr PcPlatformServices::userGeneratedContentService() const { + return m_userGeneratedContentService; +} + +DesktopServicePtr PcPlatformServices::desktopService() const { + return m_desktopService; +} + +bool PcPlatformServices::overlayActive() const { + return m_state->overlayActive; +} + +void PcPlatformServices::update() { +#ifdef STAR_ENABLE_STEAM_INTEGRATION + SteamAPI_RunCallbacks(); +#endif +} + +} diff --git a/source/application/StarPlatformServices_pc.hpp b/source/application/StarPlatformServices_pc.hpp new file mode 100644 index 0000000..4240515 --- /dev/null +++ b/source/application/StarPlatformServices_pc.hpp @@ -0,0 +1,83 @@ +#ifndef STAR_PLATFORM_SERVICES_PC_HPP +#define STAR_PLATFORM_SERVICES_PC_HPP + +#include "StarThread.hpp" +#include "StarApplication.hpp" +#include "StarStatisticsService.hpp" +#include "StarP2PNetworkingService.hpp" +#include "StarUserGeneratedContentService.hpp" +#include "StarDesktopService.hpp" + +#ifdef STAR_ENABLE_STEAM_INTEGRATION +#include "steam/steam_api.h" +#endif + +#ifdef STAR_ENABLE_DISCORD_INTEGRATION +#include "discord/discord.h" +#endif + +namespace Star { + +STAR_CLASS(PcPlatformServices); +STAR_STRUCT(PcPlatformServicesState); + +struct PcPlatformServicesState { + PcPlatformServicesState(); + ~PcPlatformServicesState(); + +#ifdef STAR_ENABLE_STEAM_INTEGRATION + STEAM_CALLBACK(PcPlatformServicesState, onGameOverlayActivated, GameOverlayActivated_t, callbackGameOverlayActivated); + + bool steamAvailable = false; +#endif + +#ifdef STAR_ENABLE_DISCORD_INTEGRATION + bool discordAvailable = false; + + // Must lock discordMutex before accessing any of the managers when not inside + // a discord callback. + Mutex discordMutex; + + unique_ptr discordCore; + + Maybe discordCurrentUser; + ThreadFunction discordEventThread; + atomic discordEventShutdown; +#endif + + bool overlayActive = false; +}; + + +class PcPlatformServices { +public: + // Any command line arguments that start with '+platform' will be stripped + // out and passed here + static PcPlatformServicesUPtr create(String const& path, StringList platformArguments); + + StatisticsServicePtr statisticsService() const; + P2PNetworkingServicePtr p2pNetworkingService() const; + UserGeneratedContentServicePtr userGeneratedContentService() const; + DesktopServicePtr desktopService() const; + + // Will return true if there is an in-game overlay active. This is important + // because the cursor must be visible when such an overlay is active, + // regardless of the ApplicationController setting. + bool overlayActive() const; + + void update(); + +private: + PcPlatformServices() = default; + + PcPlatformServicesStatePtr m_state; + + StatisticsServicePtr m_statisticsService; + P2PNetworkingServicePtr m_p2pNetworkingService; + UserGeneratedContentServicePtr m_userGeneratedContentService; + DesktopServicePtr m_desktopService; +}; + +} + +#endif diff --git a/source/application/StarRenderer.cpp b/source/application/StarRenderer.cpp new file mode 100644 index 0000000..51b11c7 --- /dev/null +++ b/source/application/StarRenderer.cpp @@ -0,0 +1,60 @@ +#include "StarRenderer.hpp" + +namespace Star { + +EnumMap const TextureAddressingNames{ + {TextureAddressing::Clamp, "Clamp"}, + {TextureAddressing::Wrap, "Wrap"} +}; + +EnumMap const TextureFilteringNames{ + {TextureFiltering::Nearest, "Nearest"}, + {TextureFiltering::Linear, "Linear"} +}; + +RenderQuad renderTexturedRect(TexturePtr texture, Vec2F minPosition, float textureScale, Vec4B color, float param1) { + if (!texture) + throw RendererException("renderTexturedRect called with null texture"); + + auto textureSize = Vec2F(texture->size()); + return { + move(texture), + RenderVertex{minPosition, Vec2F(0, 0), color, param1}, + RenderVertex{minPosition + Vec2F(textureSize[0], 0) * textureScale, Vec2F(textureSize[0], 0), color, param1}, + RenderVertex{minPosition + Vec2F(textureSize[0], textureSize[1]) * textureScale, Vec2F(textureSize[0], textureSize[1]), color, param1}, + RenderVertex{minPosition + Vec2F(0, textureSize[1]) * textureScale, Vec2F(0, textureSize[1]), color, param1} + }; +} + +RenderQuad renderTexturedRect(TexturePtr texture, RectF const& screenCoords, Vec4B color, float param1) { + if (!texture) + throw RendererException("renderTexturedRect called with null texture"); + + auto textureSize = Vec2F(texture->size()); + return { + move(texture), + RenderVertex{{screenCoords.xMin(), screenCoords.yMin()}, Vec2F(0, 0), color, param1}, + RenderVertex{{screenCoords.xMax(), screenCoords.yMin()}, Vec2F(textureSize[0], 0), color, param1}, + RenderVertex{{screenCoords.xMax(), screenCoords.yMax()}, Vec2F(textureSize[0], textureSize[1]), color, param1}, + RenderVertex{{screenCoords.xMin(), screenCoords.yMax()}, Vec2F(0, textureSize[1]), color, param1} + }; +} + +RenderQuad renderFlatRect(RectF const& rect, Vec4B color, float param1) { + return { + {}, + RenderVertex{{rect.xMin(), rect.yMin()}, {}, color, param1}, + RenderVertex{{rect.xMax(), rect.yMin()}, {}, color, param1}, + RenderVertex{{rect.xMax(), rect.yMax()}, {}, color, param1}, + RenderVertex{{rect.xMin(), rect.yMax()}, {}, color, param1} + }; +} + +RenderPoly renderFlatPoly(PolyF const& poly, Vec4B color, float param1) { + RenderPoly renderPoly; + for (auto const& v : poly) + renderPoly.vertexes.append({v, {}, color, param1}); + return renderPoly; +} + +} diff --git a/source/application/StarRenderer.hpp b/source/application/StarRenderer.hpp new file mode 100644 index 0000000..1cd3223 --- /dev/null +++ b/source/application/StarRenderer.hpp @@ -0,0 +1,147 @@ +#ifndef STAR_RENDERER_HPP +#define STAR_RENDERER_HPP + +#include "StarVariant.hpp" +#include "StarImage.hpp" +#include "StarPoly.hpp" +#include "StarJson.hpp" +#include "StarBiMap.hpp" +#include "StarRefPtr.hpp" + +namespace Star { + +STAR_EXCEPTION(RendererException, StarException); + +class Texture; +typedef RefPtr TexturePtr; + +STAR_CLASS(TextureGroup); +STAR_CLASS(RenderBuffer); +STAR_CLASS(Renderer); + +enum class TextureAddressing { + Clamp, + Wrap +}; +extern EnumMap const TextureAddressingNames; + +enum class TextureFiltering { + Nearest, + Linear +}; +extern EnumMap const TextureFilteringNames; + +// Medium is the maximum guaranteed texture group size +// Where a Medium sized texture group is expected to fill a single page Large can be used, +// but is not guaranteed to be supported by all systems. +// Where Large sized textures are not supported, a Medium one is used +enum class TextureGroupSize { + Small, + Medium, + Large +}; + +// Both screen coordinates and texture coordinates are in pixels from the +// bottom left to top right. +struct RenderVertex { + Vec2F screenCoordinate; + Vec2F textureCoordinate; + Vec4B color; + float param1; +}; + +struct RenderTriangle { + TexturePtr texture; + RenderVertex a, b, c; +}; + +struct RenderQuad { + TexturePtr texture; + RenderVertex a, b, c, d; +}; + +struct RenderPoly { + TexturePtr texture; + List vertexes; +}; + +RenderQuad renderTexturedRect(TexturePtr texture, Vec2F minScreen, float textureScale = 1.0f, Vec4B color = Vec4B::filled(255), float param1 = 0.0f); +RenderQuad renderTexturedRect(TexturePtr texture, RectF const& screenCoords, Vec4B color = Vec4B::filled(255), float param1 = 0.0f); +RenderQuad renderFlatRect(RectF const& rect, Vec4B color, float param1 = 0.0f); +RenderPoly renderFlatPoly(PolyF const& poly, Vec4B color, float param1 = 0.0f); + +typedef Variant RenderPrimitive; + +class Texture : public RefCounter { +public: + virtual ~Texture() = default; + + virtual Vec2U size() const = 0; + virtual TextureFiltering filtering() const = 0; + virtual TextureAddressing addressing() const = 0; +}; + +// Textures may be created individually, or in a texture group. Textures in +// a texture group will be faster to render when rendered together, and will +// use less texture memory when many small textures are in a common group. +// Texture groups must all have the same texture parameters, and will always +// use clamped texture addressing. +class TextureGroup { +public: + virtual ~TextureGroup() = default; + + virtual TextureFiltering filtering() const = 0; + virtual TexturePtr create(Image const& texture) = 0; +}; + +class RenderBuffer { +public: + virtual ~RenderBuffer() = default; + + // Transforms the given primitives into a form suitable for the underlying + // graphics system and stores it for fast replaying. + virtual void set(List primitives) = 0; +}; + +typedef Variant RenderEffectParameter; + +class Renderer { +public: + virtual ~Renderer() = default; + + virtual String rendererId() const = 0; + virtual Vec2U screenSize() const = 0; + + // The actual shaders used by this renderer will be in a default no effects + // state when constructed, but can be overridden here. This config will be + // specific to each type of renderer, so it will be necessary to key the + // configuration off of the renderId string. This should not be called every + // frame, because it will result in a recompile of the underlying shader set. + virtual void setEffectConfig(Json const& effectConfig) = 0; + + // The effect config will specify named parameters and textures which can be + // set here. + virtual void setEffectParameter(String const& parameterName, RenderEffectParameter const& parameter) = 0; + virtual void setEffectTexture(String const& textureName, Image const& image) = 0; + + // Any further rendering will be scissored based on this rect, specified in + // pixels + virtual void setScissorRect(Maybe const& scissorRect) = 0; + + virtual TexturePtr createTexture(Image const& texture, + TextureAddressing addressing = TextureAddressing::Clamp, + TextureFiltering filtering = TextureFiltering::Nearest) = 0; + virtual void setSizeLimitEnabled(bool enabled) = 0; + virtual void setMultiTexturingEnabled(bool enabled) = 0; + virtual TextureGroupPtr createTextureGroup(TextureGroupSize size = TextureGroupSize::Medium, TextureFiltering filtering = TextureFiltering::Nearest) = 0; + virtual RenderBufferPtr createRenderBuffer() = 0; + + virtual void render(RenderPrimitive primitive) = 0; + virtual void renderBuffer(RenderBufferPtr const& renderBuffer, Mat3F const& transformation = Mat3F::identity()) = 0; + + virtual void flush() = 0; +}; + +} + +#endif diff --git a/source/application/StarRenderer_opengl20.cpp b/source/application/StarRenderer_opengl20.cpp new file mode 100644 index 0000000..82f9854 --- /dev/null +++ b/source/application/StarRenderer_opengl20.cpp @@ -0,0 +1,828 @@ +#include "StarRenderer_opengl20.hpp" +#include "StarJsonExtra.hpp" +#include "StarCasting.hpp" +#include "StarLogging.hpp" + +namespace Star { + +size_t const MultiTextureCount = 4; + +char const* DefaultEffectConfig = R"JSON( + { + "vertexShader" : " + #version 110 + + uniform vec2 textureSize0; + uniform vec2 textureSize1; + uniform vec2 textureSize2; + uniform vec2 textureSize3; + uniform vec2 screenSize; + uniform mat3 vertexTransform; + + attribute vec2 vertexPosition; + attribute vec2 vertexTextureCoordinate; + attribute float vertexTextureIndex; + attribute vec4 vertexColor; + attribute float vertexParam1; + + varying vec2 fragmentTextureCoordinate; + varying float fragmentTextureIndex; + varying vec4 fragmentColor; + + void main() { + vec2 screenPosition = (vertexTransform * vec3(vertexPosition, 1.0)).xy; + gl_Position = vec4(screenPosition / screenSize * 2.0 - 1.0, 0.0, 1.0); + if (vertexTextureIndex > 2.9) { + fragmentTextureCoordinate = vertexTextureCoordinate / textureSize3; + } else if (vertexTextureIndex > 1.9) { + fragmentTextureCoordinate = vertexTextureCoordinate / textureSize2; + } else if (vertexTextureIndex > 0.9) { + fragmentTextureCoordinate = vertexTextureCoordinate / textureSize1; + } else { + fragmentTextureCoordinate = vertexTextureCoordinate / textureSize0; + } + fragmentTextureIndex = vertexTextureIndex; + fragmentColor = vertexColor; + } + ", + + "fragmentShader" : " + #version 110 + + uniform sampler2D texture0; + uniform sampler2D texture1; + uniform sampler2D texture2; + uniform sampler2D texture3; + + varying vec2 fragmentTextureCoordinate; + varying float fragmentTextureIndex; + varying vec4 fragmentColor; + + void main() { + if (fragmentTextureIndex > 2.9) { + gl_FragColor = texture2D(texture3, fragmentTextureCoordinate) * fragmentColor; + } else if (fragmentTextureIndex > 1.9) { + gl_FragColor = texture2D(texture2, fragmentTextureCoordinate) * fragmentColor; + } else if (fragmentTextureIndex > 0.9) { + gl_FragColor = texture2D(texture1, fragmentTextureCoordinate) * fragmentColor; + } else { + gl_FragColor = texture2D(texture0, fragmentTextureCoordinate) * fragmentColor; + } + } + " + } + )JSON"; + +OpenGl20Renderer::OpenGl20Renderer() { + if (glewInit() != GLEW_OK) + throw RendererException("Could not initialize GLEW"); + + if (!GLEW_VERSION_2_0) + throw RendererException("OpenGL 2.0 not available!"); + + Logger::info("OpenGL version: '%s' vendor: '%s' renderer: '%s' shader: '%s'", + glGetString(GL_VERSION), + glGetString(GL_VENDOR), + glGetString(GL_RENDERER), + glGetString(GL_SHADING_LANGUAGE_VERSION)); + + glClearColor(0.0, 0.0, 0.0, 1.0); + glEnable(GL_TEXTURE_2D); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDisable(GL_DEPTH_TEST); + + m_whiteTexture = createGlTexture(Image::filled({1, 1}, Vec4B(255, 255, 255, 255), PixelFormat::RGBA32), + TextureAddressing::Clamp, + TextureFiltering::Nearest); + m_immediateRenderBuffer = createGlRenderBuffer(); + + setEffectConfig(Json::parse(DefaultEffectConfig)); + + m_limitTextureGroupSize = false; + m_useMultiTexturing = true; + + logGlErrorSummary("OpenGL errors during renderer initialization"); +} + +OpenGl20Renderer::~OpenGl20Renderer() { + glDeleteProgram(m_program); + + logGlErrorSummary("OpenGL errors during shutdown"); +} + +String OpenGl20Renderer::rendererId() const { + return "OpenGL20"; +} + +Vec2U OpenGl20Renderer::screenSize() const { + return m_screenSize; +} + +void OpenGl20Renderer::setEffectConfig(Json const& effectConfig) { + flushImmediatePrimitives(); + + GLint status = 0; + char logBuffer[1024]; + + GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER); + String vertexSource = effectConfig.getString("vertexShader"); + char const* vertexSourcePtr = vertexSource.utf8Ptr(); + glShaderSource(vertexShader, 1, &vertexSourcePtr, NULL); + glCompileShader(vertexShader); + + glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &status); + if (!status) { + glGetShaderInfoLog(vertexShader, sizeof(logBuffer), NULL, logBuffer); + throw RendererException(strf("Failed to compile vertex shader: %s\n", logBuffer)); + } + + GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); + String fragmentSource = effectConfig.getString("fragmentShader"); + char const* fragmentSourcePtr = fragmentSource.utf8Ptr(); + glShaderSource(fragmentShader, 1, &fragmentSourcePtr, NULL); + glCompileShader(fragmentShader); + + glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &status); + if (!status) { + glGetShaderInfoLog(fragmentShader, sizeof(logBuffer), NULL, logBuffer); + throw RendererException(strf("Failed to compile fragment shader: %s\n", logBuffer)); + } + + GLuint program = glCreateProgram(); + + glAttachShader(program, vertexShader); + glAttachShader(program, fragmentShader); + glLinkProgram(program); + + glDeleteShader(vertexShader); + glDeleteShader(fragmentShader); + + glGetProgramiv(program, GL_LINK_STATUS, &status); + if (!status) { + glGetProgramInfoLog(program, sizeof(logBuffer), NULL, logBuffer); + glDeleteProgram(program); + throw RendererException(strf("Failed to link program: %s\n", logBuffer)); + } + + if (m_program != 0) + glDeleteProgram(m_program); + m_program = program; + glUseProgram(m_program); + + m_positionAttribute = glGetAttribLocation(m_program, "vertexPosition"); + m_texCoordAttribute = glGetAttribLocation(m_program, "vertexTextureCoordinate"); + m_texIndexAttribute = glGetAttribLocation(m_program, "vertexTextureIndex"); + m_colorAttribute = glGetAttribLocation(m_program, "vertexColor"); + m_param1Attribute = glGetAttribLocation(m_program, "vertexParam1"); + + m_textureUniforms.clear(); + m_textureSizeUniforms.clear(); + for (size_t i = 0; i < MultiTextureCount; ++i) { + m_textureUniforms.append(glGetUniformLocation(m_program, strf("texture%s", i).c_str())); + m_textureSizeUniforms.append(glGetUniformLocation(m_program, strf("textureSize%s", i).c_str())); + } + m_screenSizeUniform = glGetUniformLocation(m_program, "screenSize"); + m_vertexTransformUniform = glGetUniformLocation(m_program, "vertexTransform"); + + for (size_t i = 0; i < MultiTextureCount; ++i) { + glUniform1i(m_textureUniforms[i], i); + } + glUniform2f(m_screenSizeUniform, m_screenSize[0], m_screenSize[1]); + + m_effectParameters.clear(); + + for (auto const& p : effectConfig.getObject("effectParameters", {})) { + EffectParameter effectParameter; + + effectParameter.parameterUniform = glGetUniformLocation(m_program, p.second.getString("uniform").utf8Ptr()); + if (effectParameter.parameterUniform == -1) { + Logger::warn("OpenGL20 effect parameter '%s' has no associated uniform, skipping", p.first); + } else { + String type = p.second.getString("type"); + if (type == "bool") { + effectParameter.parameterType = RenderEffectParameter::typeIndexOf(); + } else if (type == "int") { + effectParameter.parameterType = RenderEffectParameter::typeIndexOf(); + } else if (type == "float") { + effectParameter.parameterType = RenderEffectParameter::typeIndexOf(); + } else if (type == "vec2") { + effectParameter.parameterType = RenderEffectParameter::typeIndexOf(); + } else if (type == "vec3") { + effectParameter.parameterType = RenderEffectParameter::typeIndexOf(); + } else if (type == "vec4") { + effectParameter.parameterType = RenderEffectParameter::typeIndexOf(); + } else { + throw RendererException::format("Unrecognized effect parameter type '%s'", type); + } + + m_effectParameters[p.first] = effectParameter; + + if (Json def = p.second.get("default", {})) { + if (type == "bool") { + setEffectParameter(p.first, def.toBool()); + } else if (type == "int") { + setEffectParameter(p.first, (int)def.toInt()); + } else if (type == "float") { + setEffectParameter(p.first, def.toFloat()); + } else if (type == "vec2") { + setEffectParameter(p.first, jsonToVec2F(def)); + } else if (type == "vec3") { + setEffectParameter(p.first, jsonToVec3F(def)); + } else if (type == "vec4") { + setEffectParameter(p.first, jsonToVec4F(def)); + } + } + } + } + + m_effectTextures.clear(); + + // Assign each texture parameter a texture unit starting with MultiTextureCount, the first + // few texture units are used by the primary textures being drawn. Currently, + // maximum texture units are not checked. + unsigned parameterTextureUnit = MultiTextureCount; + + for (auto const& p : effectConfig.getObject("effectTextures", {})) { + EffectTexture effectTexture; + effectTexture.textureUniform = glGetUniformLocation(m_program, p.second.getString("textureUniform").utf8Ptr()); + if (effectTexture.textureUniform == -1) { + Logger::warn("OpenGL20 effect parameter '%s' has no associated uniform, skipping", p.first); + } else { + effectTexture.textureUnit = parameterTextureUnit++; + glUniform1i(effectTexture.textureUniform, effectTexture.textureUnit); + + effectTexture.textureAddressing = TextureAddressingNames.getLeft(p.second.getString("textureAddressing", "clamp")); + effectTexture.textureFiltering = TextureFilteringNames.getLeft(p.second.getString("textureFiltering", "nearest")); + if (auto tsu = p.second.optString("textureSizeUniform")) { + effectTexture.textureSizeUniform = glGetUniformLocation(m_program, tsu->utf8Ptr()); + if (effectTexture.textureSizeUniform == -1) + Logger::warn("OpenGL20 effect parameter '%s' has textureSizeUniform '%s' with no associated uniform", p.first, *tsu); + } + + m_effectTextures[p.first] = effectTexture; + } + } + + if (DebugEnabled) + logGlErrorSummary("OpenGL errors setting effect config"); +} + +void OpenGl20Renderer::setEffectParameter(String const& parameterName, RenderEffectParameter const& value) { + auto ptr = m_effectParameters.ptr(parameterName); + if (!ptr || (ptr->parameterValue && *ptr->parameterValue == value)) + return; + + if (ptr->parameterType != value.typeIndex()) + throw RendererException::format("OpenGL20Renderer::setEffectParameter '%s' parameter type mismatch", parameterName); + + flushImmediatePrimitives(); + + if (auto v = value.ptr()) + glUniform1i(ptr->parameterUniform, *v); + else if (auto v = value.ptr()) + glUniform1i(ptr->parameterUniform, *v); + else if (auto v = value.ptr()) + glUniform1f(ptr->parameterUniform, *v); + else if (auto v = value.ptr()) + glUniform2f(ptr->parameterUniform, (*v)[0], (*v)[1]); + else if (auto v = value.ptr()) + glUniform3f(ptr->parameterUniform, (*v)[0], (*v)[1], (*v)[2]); + else if (auto v = value.ptr()) + glUniform4f(ptr->parameterUniform, (*v)[0], (*v)[1], (*v)[2], (*v)[3]); + + ptr->parameterValue = value; +} + +void OpenGl20Renderer::setEffectTexture(String const& textureName, Image const& image) { + auto ptr = m_effectTextures.ptr(textureName); + if (!ptr) + return; + + flushImmediatePrimitives(); + + if (!ptr->textureValue || ptr->textureValue->textureId == 0) { + ptr->textureValue = createGlTexture(image, ptr->textureAddressing, ptr->textureFiltering); + } else { + glBindTexture(GL_TEXTURE_2D, ptr->textureValue->textureId); + ptr->textureValue->textureSize = image.size(); + uploadTextureImage(image.pixelFormat(), image.size(), image.data()); + } + + if (ptr->textureSizeUniform != -1) { + auto textureSize = ptr->textureValue->glTextureSize(); + glUniform2f(ptr->textureSizeUniform, textureSize[0], textureSize[1]); + } +} + +void OpenGl20Renderer::setScissorRect(Maybe const& scissorRect) { + if (scissorRect == m_scissorRect) + return; + + flushImmediatePrimitives(); + + m_scissorRect = scissorRect; + if (m_scissorRect) { + glEnable(GL_SCISSOR_TEST); + glScissor(m_scissorRect->xMin(), m_scissorRect->yMin(), m_scissorRect->width(), m_scissorRect->height()); + } else { + glDisable(GL_SCISSOR_TEST); + } +} + +TexturePtr OpenGl20Renderer::createTexture(Image const& texture, TextureAddressing addressing, TextureFiltering filtering) { + return createGlTexture(texture, addressing, filtering); +} + +void OpenGl20Renderer::setSizeLimitEnabled(bool enabled) { + m_limitTextureGroupSize = enabled; +} + +void OpenGl20Renderer::setMultiTexturingEnabled(bool enabled) { + m_useMultiTexturing = enabled; +} + +TextureGroupPtr OpenGl20Renderer::createTextureGroup(TextureGroupSize textureSize, TextureFiltering filtering) { + int maxTextureSize; + glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize); + + // Large texture sizes are not always supported + if (textureSize == TextureGroupSize::Large && (m_limitTextureGroupSize || maxTextureSize < 4096)) + textureSize = TextureGroupSize::Medium; + + unsigned atlasNumCells; + if (textureSize == TextureGroupSize::Large) + atlasNumCells = 256; + else if (textureSize == TextureGroupSize::Medium) + atlasNumCells = 128; + else // TextureGroupSize::Small + atlasNumCells = 64; + + Logger::info("detected supported OpenGL texture size %s, using atlasNumCells %s", maxTextureSize, atlasNumCells); + + auto glTextureGroup = make_shared(atlasNumCells); + glTextureGroup->textureAtlasSet.textureFiltering = filtering; + m_liveTextureGroups.append(glTextureGroup); + return glTextureGroup; +} + +RenderBufferPtr OpenGl20Renderer::createRenderBuffer() { + return createGlRenderBuffer(); +} + +void OpenGl20Renderer::render(RenderPrimitive primitive) { + m_immediatePrimitives.append(move(primitive)); +} + +void OpenGl20Renderer::renderBuffer(RenderBufferPtr const& renderBuffer, Mat3F const& transformation) { + flushImmediatePrimitives(); + renderGlBuffer(*convert(renderBuffer.get()), transformation); +} + +void OpenGl20Renderer::flush() { + flushImmediatePrimitives(); +} + +void OpenGl20Renderer::setScreenSize(Vec2U screenSize) { + m_screenSize = screenSize; + glViewport(0, 0, m_screenSize[0], m_screenSize[1]); + glUniform2f(m_screenSizeUniform, m_screenSize[0], m_screenSize[1]); +} + +void OpenGl20Renderer::startFrame() { + if (m_scissorRect) + glDisable(GL_SCISSOR_TEST); + + glClear(GL_COLOR_BUFFER_BIT); + + if (m_scissorRect) + glEnable(GL_SCISSOR_TEST); +} + +void OpenGl20Renderer::finishFrame() { + flushImmediatePrimitives(); + // Make sure that the immediate render buffer doesn't needlessly lock texutres + // from being compressed. + m_immediateRenderBuffer->set({}); + + filter(m_liveTextureGroups, [](auto const& p) { + unsigned const CompressionsPerFrame = 1; + + if (!p.unique() || p->textureAtlasSet.totalTextures() > 0) { + p->textureAtlasSet.compressionPass(CompressionsPerFrame); + return true; + } + + return false; + }); + + if (DebugEnabled) + logGlErrorSummary("OpenGL errors this frame"); +} + +OpenGl20Renderer::GlTextureAtlasSet::GlTextureAtlasSet(unsigned atlasNumCells) + : TextureAtlasSet(16, atlasNumCells) {} + +GLuint OpenGl20Renderer::GlTextureAtlasSet::createAtlasTexture(Vec2U const& size, PixelFormat pixelFormat) { + GLuint glTextureId; + glGenTextures(1, &glTextureId); + if (glTextureId == 0) + throw RendererException("Could not generate texture in OpenGL20Renderer::TextureGroup::createAtlasTexture()"); + + glBindTexture(GL_TEXTURE_2D, glTextureId); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + if (textureFiltering == TextureFiltering::Nearest) { + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + } else { + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + } + + uploadTextureImage(pixelFormat, size, nullptr); + return glTextureId; +} + +void OpenGl20Renderer::GlTextureAtlasSet::destroyAtlasTexture(GLuint const& glTexture) { + glDeleteTextures(1, &glTexture); +} + +void OpenGl20Renderer::GlTextureAtlasSet::copyAtlasPixels( + GLuint const& glTexture, Vec2U const& bottomLeft, Image const& image) { + glBindTexture(GL_TEXTURE_2D, glTexture); + + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + if (image.pixelFormat() == PixelFormat::RGB24) { + glTexSubImage2D(GL_TEXTURE_2D, 0, bottomLeft[0], bottomLeft[1], image.width(), image.height(), GL_RGB, GL_UNSIGNED_BYTE, image.data()); + } else if (image.pixelFormat() == PixelFormat::RGBA32) { + glTexSubImage2D(GL_TEXTURE_2D, 0, bottomLeft[0], bottomLeft[1], image.width(), image.height(), GL_RGBA, GL_UNSIGNED_BYTE, image.data()); + } else if (image.pixelFormat() == PixelFormat::BGR24) { + glTexSubImage2D(GL_TEXTURE_2D, 0, bottomLeft[0], bottomLeft[1], image.width(), image.height(), GL_BGR, GL_UNSIGNED_BYTE, image.data()); + } else if (image.pixelFormat() == PixelFormat::BGRA32) { + glTexSubImage2D(GL_TEXTURE_2D, 0, bottomLeft[0], bottomLeft[1], image.width(), image.height(), GL_BGRA, GL_UNSIGNED_BYTE, image.data()); + } else { + throw RendererException("Unsupported texture format in OpenGL20Renderer::TextureGroup::copyAtlasPixels"); + } +} + +OpenGl20Renderer::GlTextureGroup::GlTextureGroup(unsigned atlasNumCells) + : textureAtlasSet(atlasNumCells) {} + +OpenGl20Renderer::GlTextureGroup::~GlTextureGroup() { + textureAtlasSet.reset(); +} + +TextureFiltering OpenGl20Renderer::GlTextureGroup::filtering() const { + return textureAtlasSet.textureFiltering; +} + +TexturePtr OpenGl20Renderer::GlTextureGroup::create(Image const& texture) { + // If the image is empty, or would not fit in the texture atlas with border + // pixels, just create a regular texture + Vec2U atlasTextureSize = textureAtlasSet.atlasTextureSize(); + if (texture.empty() || texture.width() + 2 > atlasTextureSize[0] || texture.height() + 2 > atlasTextureSize[1]) + return createGlTexture(texture, TextureAddressing::Clamp, textureAtlasSet.textureFiltering); + + auto glGroupedTexture = make_ref(); + glGroupedTexture->parentGroup = shared_from_this(); + glGroupedTexture->parentAtlasTexture = textureAtlasSet.addTexture(texture); + + return glGroupedTexture; +} + +OpenGl20Renderer::GlGroupedTexture::~GlGroupedTexture() { + if (parentAtlasTexture) + parentGroup->textureAtlasSet.freeTexture(parentAtlasTexture); +} + +Vec2U OpenGl20Renderer::GlGroupedTexture::size() const { + return parentAtlasTexture->imageSize(); +} + +TextureFiltering OpenGl20Renderer::GlGroupedTexture::filtering() const { + return parentGroup->filtering(); +} + +TextureAddressing OpenGl20Renderer::GlGroupedTexture::addressing() const { + return TextureAddressing::Clamp; +} + +GLuint OpenGl20Renderer::GlGroupedTexture::glTextureId() const { + return parentAtlasTexture->atlasTexture(); +} + +Vec2U OpenGl20Renderer::GlGroupedTexture::glTextureSize() const { + return parentGroup->textureAtlasSet.atlasTextureSize(); +} + +Vec2U OpenGl20Renderer::GlGroupedTexture::glTextureCoordinateOffset() const { + return parentAtlasTexture->atlasTextureCoordinates().min(); +} + +void OpenGl20Renderer::GlGroupedTexture::incrementBufferUseCount() { + if (bufferUseCount == 0) + parentAtlasTexture->setLocked(true); + ++bufferUseCount; +} + +void OpenGl20Renderer::GlGroupedTexture::decrementBufferUseCount() { + starAssert(bufferUseCount != 0); + if (bufferUseCount == 1) + parentAtlasTexture->setLocked(false); + --bufferUseCount; +} + +OpenGl20Renderer::GlLoneTexture::~GlLoneTexture() { + if (textureId != 0) + glDeleteTextures(1, &textureId); +} + +Vec2U OpenGl20Renderer::GlLoneTexture::size() const { + return textureSize; +} + +TextureFiltering OpenGl20Renderer::GlLoneTexture::filtering() const { + return textureFiltering; +} + +TextureAddressing OpenGl20Renderer::GlLoneTexture::addressing() const { + return textureAddressing; +} + +GLuint OpenGl20Renderer::GlLoneTexture::glTextureId() const { + return textureId; +} + +Vec2U OpenGl20Renderer::GlLoneTexture::glTextureSize() const { + return textureSize; +} + +Vec2U OpenGl20Renderer::GlLoneTexture::glTextureCoordinateOffset() const { + return Vec2U(); +} + +OpenGl20Renderer::GlRenderBuffer::~GlRenderBuffer() { + for (auto const& texture : usedTextures) { + if (auto gt = as(texture.get())) + gt->decrementBufferUseCount(); + } + for (auto const& vb : vertexBuffers) + glDeleteBuffers(1, &vb.vertexBuffer); +} + +void OpenGl20Renderer::GlRenderBuffer::set(List primitives) { + for (auto const& texture : usedTextures) { + if (auto gt = as(texture.get())) + gt->decrementBufferUseCount(); + } + usedTextures.clear(); + + auto oldVertexBuffers = take(vertexBuffers); + + List currentTextures; + List currentTextureSizes; + size_t currentVertexCount = 0; + + auto finishCurrentBuffer = [&]() { + if (currentVertexCount > 0) { + GlVertexBuffer vb; + for (size_t i = 0; i < currentTextures.size(); ++i) { + vb.textures.append(GlVertexBufferTexture{currentTextures[i], currentTextureSizes[i]}); + } + vb.vertexCount = currentVertexCount; + if (!oldVertexBuffers.empty()) { + auto oldVb = oldVertexBuffers.takeLast(); + vb.vertexBuffer = oldVb.vertexBuffer; + glBindBuffer(GL_ARRAY_BUFFER, vb.vertexBuffer); + if (oldVb.vertexCount >= vb.vertexCount) + glBufferSubData(GL_ARRAY_BUFFER, 0, accumulationBuffer.size(), accumulationBuffer.ptr()); + else + glBufferData(GL_ARRAY_BUFFER, accumulationBuffer.size(), accumulationBuffer.ptr(), GL_STREAM_DRAW); + } else { + glGenBuffers(1, &vb.vertexBuffer); + glBindBuffer(GL_ARRAY_BUFFER, vb.vertexBuffer); + glBufferData(GL_ARRAY_BUFFER, accumulationBuffer.size(), accumulationBuffer.ptr(), GL_STREAM_DRAW); + } + + vertexBuffers.append(vb); + + currentTextures.clear(); + currentTextureSizes.clear(); + accumulationBuffer.clear(); + currentVertexCount = 0; + } + }; + + auto textureCount = useMultiTexturing ? MultiTextureCount : 1; + auto addCurrentTexture = [&](TexturePtr texture) -> pair { + if (!texture) + texture = whiteTexture; + + auto glTexture = as(texture.get()); + GLuint glTextureId = glTexture->glTextureId(); + + auto textureIndex = currentTextures.indexOf(glTextureId); + if (textureIndex == NPos) { + if (currentTextures.size() >= textureCount) + finishCurrentBuffer(); + + textureIndex = currentTextures.size(); + currentTextures.append(glTextureId); + currentTextureSizes.append(glTexture->glTextureSize()); + } + + if (auto gt = as(texture.get())) + gt->incrementBufferUseCount(); + usedTextures.add(move(texture)); + + return {float(textureIndex), Vec2F(glTexture->glTextureCoordinateOffset())}; + }; + + auto appendBufferVertex = [&](RenderVertex v, float textureIndex, Vec2F textureCoordinateOffset) { + GlRenderVertex glv { + v.screenCoordinate, + v.textureCoordinate + textureCoordinateOffset, + textureIndex, + v.color, + v.param1 + }; + accumulationBuffer.append((char const*)&glv, sizeof(GlRenderVertex)); + ++currentVertexCount; + }; + + for (auto& primitive : primitives) { + float textureIndex; + Vec2F textureOffset; + if (auto tri = primitive.ptr()) { + tie(textureIndex, textureOffset) = addCurrentTexture(move(tri->texture)); + + appendBufferVertex(tri->a, textureIndex, textureOffset); + appendBufferVertex(tri->b, textureIndex, textureOffset); + appendBufferVertex(tri->c, textureIndex, textureOffset); + + } else if (auto quad = primitive.ptr()) { + tie(textureIndex, textureOffset) = addCurrentTexture(move(quad->texture)); + + appendBufferVertex(quad->a, textureIndex, textureOffset); + appendBufferVertex(quad->b, textureIndex, textureOffset); + appendBufferVertex(quad->c, textureIndex, textureOffset); + + appendBufferVertex(quad->a, textureIndex, textureOffset); + appendBufferVertex(quad->c, textureIndex, textureOffset); + appendBufferVertex(quad->d, textureIndex, textureOffset); + + } else if (auto poly = primitive.ptr()) { + if (poly->vertexes.size() > 2) { + tie(textureIndex, textureOffset) = addCurrentTexture(move(poly->texture)); + for (size_t i = 1; i < poly->vertexes.size() - 1; ++i) { + appendBufferVertex(poly->vertexes[0], textureIndex, textureOffset); + appendBufferVertex(poly->vertexes[i], textureIndex, textureOffset); + appendBufferVertex(poly->vertexes[i + 1], textureIndex, textureOffset); + } + } + } + } + + finishCurrentBuffer(); + + for (auto const& vb : oldVertexBuffers) + glDeleteBuffers(1, &vb.vertexBuffer); +} + +void OpenGl20Renderer::logGlErrorSummary(String prefix) { + List errors; + while (GLenum error = glGetError()) + errors.append(error); + + if (!errors.empty()) { + String errorMessage = move(prefix); + errorMessage.append(": "); + for (auto const& error : errors) { + if (error == GL_INVALID_ENUM) { + errorMessage += " GL_INVALID_ENUM"; + } else if (error == GL_INVALID_VALUE) { + errorMessage += " GL_INVALID_VALUE"; + } else if (error == GL_INVALID_OPERATION) { + errorMessage += " GL_INVALID_OPERATION"; + } else if (error == GL_INVALID_FRAMEBUFFER_OPERATION) { + errorMessage += " GL_INVALID_FRAMEBUFFER_OPERATION"; + } else if (error == GL_OUT_OF_MEMORY) { + errorMessage += " GL_OUT_OF_MEMORY"; + } else if (error == GL_STACK_UNDERFLOW) { + errorMessage += " GL_STACK_UNDERFLOW"; + } else if (error == GL_STACK_OVERFLOW) { + errorMessage += " GL_STACK_OVERFLOW"; + } else { + errorMessage += " "; + } + } + Logger::error(errorMessage.utf8Ptr()); + } +} + +void OpenGl20Renderer::uploadTextureImage(PixelFormat pixelFormat, Vec2U size, uint8_t const* data) { + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + if (pixelFormat == PixelFormat::RGB24) { + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, size[0], size[1], 0, GL_RGB, GL_UNSIGNED_BYTE, data); + } else if (pixelFormat == PixelFormat::RGBA32) { + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, size[0], size[1], 0, GL_RGBA, GL_UNSIGNED_BYTE, data); + } else if (pixelFormat == PixelFormat::BGR24) { + glTexImage2D(GL_TEXTURE_2D, 0, GL_BGR, size[0], size[1], 0, GL_BGR, GL_UNSIGNED_BYTE, data); + } else if (pixelFormat == PixelFormat::BGRA32) { + glTexImage2D(GL_TEXTURE_2D, 0, GL_BGRA, size[0], size[1], 0, GL_RGBA, GL_UNSIGNED_BYTE, data); + } else { + starAssert(false); + } +} + +void OpenGl20Renderer::flushImmediatePrimitives() { + if (m_immediatePrimitives.empty()) + return; + + m_immediateRenderBuffer->set(take(m_immediatePrimitives)); + renderGlBuffer(*m_immediateRenderBuffer, Mat3F::identity()); +} + +auto OpenGl20Renderer::createGlTexture(Image const& image, TextureAddressing addressing, TextureFiltering filtering) + -> RefPtr { + auto glLoneTexture = make_ref(); + glLoneTexture->textureFiltering = filtering; + glLoneTexture->textureAddressing = addressing; + glLoneTexture->textureSize = image.size(); + + if (image.empty()) + return glLoneTexture; + + glGenTextures(1, &glLoneTexture->textureId); + if (glLoneTexture->textureId == 0) + throw RendererException("Could not generate texture in OpenGL20Renderer::createGlTexture"); + + glBindTexture(GL_TEXTURE_2D, glLoneTexture->textureId); + + if (addressing == TextureAddressing::Clamp) { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + } else { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + } + + if (filtering == TextureFiltering::Nearest) { + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + } else { + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + } + + uploadTextureImage(image.pixelFormat(), image.size(), image.data()); + + return glLoneTexture; +} + +auto OpenGl20Renderer::createGlRenderBuffer() -> shared_ptr { + auto glrb = make_shared(); + glrb->whiteTexture = m_whiteTexture; + glrb->useMultiTexturing = m_useMultiTexturing; + return glrb; +} + +void OpenGl20Renderer::renderGlBuffer(GlRenderBuffer const& renderBuffer, Mat3F const& transformation) { + for (auto const& vb : renderBuffer.vertexBuffers) { + glUniformMatrix3fv(m_vertexTransformUniform, 1, GL_TRUE, transformation.ptr()); + + for (size_t i = 0; i < vb.textures.size(); ++i) { + glUniform2f(m_textureSizeUniforms[i], vb.textures[i].size[0], vb.textures[i].size[1]); + glActiveTexture(GL_TEXTURE0 + i); + glBindTexture(GL_TEXTURE_2D, vb.textures[i].texture); + } + + for (auto const& p : m_effectTextures) { + if (p.second.textureValue) { + glActiveTexture(GL_TEXTURE0 + p.second.textureUnit); + glBindTexture(GL_TEXTURE_2D, p.second.textureValue->textureId); + } + } + + glBindBuffer(GL_ARRAY_BUFFER, vb.vertexBuffer); + + glEnableVertexAttribArray(m_positionAttribute); + glEnableVertexAttribArray(m_texCoordAttribute); + glEnableVertexAttribArray(m_texIndexAttribute); + glEnableVertexAttribArray(m_colorAttribute); + glEnableVertexAttribArray(m_param1Attribute); + + glVertexAttribPointer(m_positionAttribute, 2, GL_FLOAT, GL_FALSE, sizeof(GlRenderVertex), (GLvoid*)offsetof(GlRenderVertex, screenCoordinate)); + glVertexAttribPointer(m_texCoordAttribute, 2, GL_FLOAT, GL_FALSE, sizeof(GlRenderVertex), (GLvoid*)offsetof(GlRenderVertex, textureCoordinate)); + glVertexAttribPointer(m_texIndexAttribute, 1, GL_FLOAT, GL_FALSE, sizeof(GlRenderVertex), (GLvoid*)offsetof(GlRenderVertex, textureIndex)); + glVertexAttribPointer(m_colorAttribute, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(GlRenderVertex), (GLvoid*)offsetof(GlRenderVertex, color)); + glVertexAttribPointer(m_param1Attribute, 1, GL_FLOAT, GL_FALSE, sizeof(GlRenderVertex), (GLvoid*)offsetof(GlRenderVertex, param1)); + + glDrawArrays(GL_TRIANGLES, 0, vb.vertexCount); + } +} + +} diff --git a/source/application/StarRenderer_opengl20.hpp b/source/application/StarRenderer_opengl20.hpp new file mode 100644 index 0000000..0bdb73a --- /dev/null +++ b/source/application/StarRenderer_opengl20.hpp @@ -0,0 +1,199 @@ +#ifndef STAR_RENDERER_OPENGL_HPP +#define STAR_RENDERER_OPENGL_HPP + +#include "StarTextureAtlas.hpp" +#include "StarRenderer.hpp" + +#include "GL/glew.h" + +namespace Star { + +STAR_CLASS(OpenGl20Renderer); + +// OpenGL 2.0 implementation of Renderer. OpenGL context must be created and +// active during construction, destruction, and all method calls. +class OpenGl20Renderer : public Renderer { +public: + OpenGl20Renderer(); + ~OpenGl20Renderer(); + + String rendererId() const override; + Vec2U screenSize() const override; + + void setEffectConfig(Json const& effectConfig) override; + void setEffectParameter(String const& parameterName, RenderEffectParameter const& parameter) override; + void setEffectTexture(String const& textureName, Image const& image) override; + + void setScissorRect(Maybe const& scissorRect) override; + + TexturePtr createTexture(Image const& texture, TextureAddressing addressing, TextureFiltering filtering) override; + void setSizeLimitEnabled(bool enabled) override; + void setMultiTexturingEnabled(bool enabled) override; + TextureGroupPtr createTextureGroup(TextureGroupSize size, TextureFiltering filtering) override; + RenderBufferPtr createRenderBuffer() override; + + void render(RenderPrimitive primitive) override; + void renderBuffer(RenderBufferPtr const& renderBuffer, Mat3F const& transformation) override; + + void flush() override; + + void setScreenSize(Vec2U screenSize); + + void startFrame(); + void finishFrame(); + +private: + struct GlTextureAtlasSet : public TextureAtlasSet { + public: + GlTextureAtlasSet(unsigned atlasNumCells); + + GLuint createAtlasTexture(Vec2U const& size, PixelFormat pixelFormat) override; + void destroyAtlasTexture(GLuint const& glTexture) override; + void copyAtlasPixels(GLuint const& glTexture, Vec2U const& bottomLeft, Image const& image) override; + + TextureFiltering textureFiltering; + }; + + struct GlTextureGroup : enable_shared_from_this, public TextureGroup { + GlTextureGroup(unsigned atlasNumCells); + ~GlTextureGroup(); + + TextureFiltering filtering() const override; + TexturePtr create(Image const& texture) override; + + GlTextureAtlasSet textureAtlasSet; + }; + + struct GlTexture : public Texture { + virtual GLuint glTextureId() const = 0; + virtual Vec2U glTextureSize() const = 0; + virtual Vec2U glTextureCoordinateOffset() const = 0; + }; + + struct GlGroupedTexture : public GlTexture { + ~GlGroupedTexture(); + + Vec2U size() const override; + TextureFiltering filtering() const override; + TextureAddressing addressing() const override; + + GLuint glTextureId() const override; + Vec2U glTextureSize() const override; + Vec2U glTextureCoordinateOffset() const override; + + void incrementBufferUseCount(); + void decrementBufferUseCount(); + + unsigned bufferUseCount = 0; + shared_ptr parentGroup; + GlTextureAtlasSet::TextureHandle parentAtlasTexture = nullptr; + }; + + struct GlLoneTexture : public GlTexture { + ~GlLoneTexture(); + + Vec2U size() const override; + TextureFiltering filtering() const override; + TextureAddressing addressing() const override; + + GLuint glTextureId() const override; + Vec2U glTextureSize() const override; + Vec2U glTextureCoordinateOffset() const override; + + GLuint textureId = 0; + Vec2U textureSize; + TextureAddressing textureAddressing = TextureAddressing::Clamp; + TextureFiltering textureFiltering = TextureFiltering::Nearest; + }; + + struct GlRenderVertex { + Vec2F screenCoordinate; + Vec2F textureCoordinate; + float textureIndex; + Vec4B color; + float param1; + }; + + struct GlRenderBuffer : public RenderBuffer { + struct GlVertexBufferTexture { + GLuint texture; + Vec2U size; + }; + + struct GlVertexBuffer { + List textures; + GLuint vertexBuffer = 0; + size_t vertexCount = 0; + }; + + ~GlRenderBuffer(); + + void set(List primitives) override; + + RefPtr whiteTexture; + ByteArray accumulationBuffer; + + HashSet usedTextures; + List vertexBuffers; + + bool useMultiTexturing; + }; + + struct EffectParameter { + GLint parameterUniform = -1; + VariantTypeIndex parameterType; + Maybe parameterValue; + }; + + struct EffectTexture { + GLint textureUniform = -1; + unsigned textureUnit; + TextureAddressing textureAddressing = TextureAddressing::Clamp; + TextureFiltering textureFiltering = TextureFiltering::Linear; + GLint textureSizeUniform = -1; + RefPtr textureValue; + }; + + static void logGlErrorSummary(String prefix); + static void uploadTextureImage(PixelFormat pixelFormat, Vec2U size, uint8_t const* data); + + static RefPtr createGlTexture(Image const& texture, TextureAddressing addressing, TextureFiltering filtering); + + shared_ptr createGlRenderBuffer(); + + void flushImmediatePrimitives(); + + void renderGlBuffer(GlRenderBuffer const& renderBuffer, Mat3F const& transformation); + + Vec2U m_screenSize; + + GLuint m_program = 0; + + GLint m_positionAttribute = -1; + GLint m_texCoordAttribute = -1; + GLint m_texIndexAttribute = -1; + GLint m_colorAttribute = -1; + GLint m_param1Attribute = -1; + + List m_textureUniforms = {}; + List m_textureSizeUniforms = {}; + GLint m_screenSizeUniform = -1; + GLint m_vertexTransformUniform = -1; + + RefPtr m_whiteTexture; + + StringMap m_effectParameters; + StringMap m_effectTextures; + Maybe m_scissorRect; + + bool m_limitTextureGroupSize; + bool m_useMultiTexturing; + List> m_liveTextureGroups; + + List m_immediatePrimitives; + shared_ptr m_immediateRenderBuffer; +}; + +} + +#endif diff --git a/source/application/StarStatisticsService_pc_steam.cpp b/source/application/StarStatisticsService_pc_steam.cpp new file mode 100644 index 0000000..00b37ba --- /dev/null +++ b/source/application/StarStatisticsService_pc_steam.cpp @@ -0,0 +1,130 @@ +#include "StarStatisticsService_pc_steam.hpp" +#include "StarLogging.hpp" + +namespace Star { + +SteamStatisticsService::SteamStatisticsService(PcPlatformServicesStatePtr) +: m_callbackUserStatsReceived(this, &SteamStatisticsService::onUserStatsReceived), + m_callbackUserStatsStored(this, &SteamStatisticsService::onUserStatsStored), + m_callbackAchievementStored(this, &SteamStatisticsService::onAchievementStored) { + m_appId = SteamUtils()->GetAppID(); + refresh(); +} + +bool SteamStatisticsService::initialized() const { + return m_initialized; +} + +Maybe SteamStatisticsService::error() const { + return m_error; +} + +bool SteamStatisticsService::setStat(String const& name, String const& type, Json const& value) { + if (type == "int") + return SteamUserStats()->SetStat(name.utf8Ptr(), (int32_t)value.toInt()); + + if (type == "float") + return SteamUserStats()->SetStat(name.utf8Ptr(), value.toFloat()); + + return false; +} + +Json SteamStatisticsService::getStat(String const& name, String const& type, Json def) const { + if (type == "int") { + int32_t intValue = 0; + if (SteamUserStats()->GetStat(name.utf8Ptr(), &intValue)) + return Json(intValue); + } + + if (type == "float") { + float floatValue = 0.0f; + if (SteamUserStats()->GetStat(name.utf8Ptr(), &floatValue)) + return Json(floatValue); + } + + return def; +} + +bool SteamStatisticsService::reportEvent(String const&, Json const&) { + // Steam doesn't support events + return false; +} + +bool SteamStatisticsService::unlockAchievement(String const& name) { + if (!SteamUserStats()->SetAchievement(name.utf8Ptr())) { + Logger::error("Cannot set Steam achievement %s", name); + return false; + } + return true; +} + +StringSet SteamStatisticsService::achievementsUnlocked() const { + StringSet achievements; + for (uint32_t i = 0; i < SteamUserStats()->GetNumAchievements(); ++i) { + String achievement = SteamUserStats()->GetAchievementName(i); + + bool unlocked = false; + if (SteamUserStats()->GetAchievement(achievement.utf8Ptr(), &unlocked) && unlocked) { + achievements.add(achievement); + } + } + return {}; +} + +void SteamStatisticsService::refresh() { + if (!SteamUser()->BLoggedOn()) { + m_error = {"Not logged in"}; + return; + } + + SteamUserStats()->RequestCurrentStats(); +} + +void SteamStatisticsService::flush() { + SteamUserStats()->StoreStats(); +} + +bool SteamStatisticsService::reset() { + SteamUserStats()->ResetAllStats(true); + return true; +} + +void SteamStatisticsService::onUserStatsReceived(UserStatsReceived_t* callback) { + if (callback->m_nGameID != m_appId) + return; + + if (callback->m_eResult != k_EResultOK) { + m_error = {strf("Steam RequestCurrentStats failed with code %d", callback->m_eResult)}; + return; + } + + Logger::debug("Steam RequestCurrentStats successful"); + m_initialized = true; +} + +void SteamStatisticsService::onUserStatsStored(UserStatsStored_t* callback) { + if (callback->m_nGameID != m_appId) + return; + + if (callback->m_eResult == k_EResultOK) { + Logger::debug("Steam StoreStats successful"); + return; + } + + if (callback->m_eResult == k_EResultInvalidParam) { + // A stat we set broke a constraint and was reverted on the service. + Logger::info("Steam StoreStats: Some stats failed validation"); + return; + } + + m_error = {strf("Steam StoreStats failed with code %d", callback->m_eResult)}; +} + +void SteamStatisticsService::onAchievementStored(UserAchievementStored_t* callback) { + if (callback->m_nGameID != m_appId) + return; + + Logger::debug("Steam achievement %s stored successfully", callback->m_rgchAchievementName); +} + +} diff --git a/source/application/StarStatisticsService_pc_steam.hpp b/source/application/StarStatisticsService_pc_steam.hpp new file mode 100644 index 0000000..f58d7c1 --- /dev/null +++ b/source/application/StarStatisticsService_pc_steam.hpp @@ -0,0 +1,41 @@ +#ifndef STAR_STATISTICS_SERVICE_PC_STEAM_HPP +#define STAR_STATISTICS_SERVICE_PC_STEAM_HPP + +#include "StarPlatformServices_pc.hpp" + +namespace Star { + +STAR_CLASS(SteamStatisticsService); + +class SteamStatisticsService : public StatisticsService { +public: + SteamStatisticsService(PcPlatformServicesStatePtr state); + + bool initialized() const override; + Maybe error() const override; + + bool setStat(String const& name, String const& type, Json const& value) override; + Json getStat(String const& name, String const& type, Json def = {}) const override; + + bool reportEvent(String const& name, Json const& fields) override; + + bool unlockAchievement(String const& name) override; + StringSet achievementsUnlocked() const override; + + void refresh() override; + void flush() override; + bool reset() override; + +private: + STEAM_CALLBACK(SteamStatisticsService, onUserStatsReceived, UserStatsReceived_t, m_callbackUserStatsReceived); + STEAM_CALLBACK(SteamStatisticsService, onUserStatsStored, UserStatsStored_t, m_callbackUserStatsStored); + STEAM_CALLBACK(SteamStatisticsService, onAchievementStored, UserAchievementStored_t, m_callbackAchievementStored); + + uint64_t m_appId; + bool m_initialized; + Maybe m_error; +}; + +} + +#endif diff --git a/source/application/StarTextureAtlas.hpp b/source/application/StarTextureAtlas.hpp new file mode 100644 index 0000000..db5a163 --- /dev/null +++ b/source/application/StarTextureAtlas.hpp @@ -0,0 +1,417 @@ +#ifndef STAR_TEXTURE_ATLAS_HPP +#define STAR_TEXTURE_ATLAS_HPP + +#include "StarRect.hpp" +#include "StarImage.hpp" +#include "StarCasting.hpp" + +namespace Star { + +STAR_EXCEPTION(TextureAtlasException, StarException); + +// Implements a set of "texture atlases" or, sets of smaller textures grouped +// as a larger texture. +template +class TextureAtlasSet { +public: + struct Texture { + virtual Vec2U imageSize() const = 0; + + virtual AtlasTextureHandle const& atlasTexture() const = 0; + virtual RectU atlasTextureCoordinates() const = 0; + + // A locked texture will never be moved during compression, so its + // atlasTexture and textureCoordinates will not change. + virtual void setLocked(bool locked) = 0; + + // Returns true if this texture has been freed or the parent + // TextureAtlasSet has been destructed. + virtual bool expired() const = 0; + }; + + typedef shared_ptr TextureHandle; + + TextureAtlasSet(unsigned cellSize, unsigned atlasNumCells); + + // The constant square size of all atlas textures + Vec2U atlasTextureSize() const; + + // Removes all existing textures and destroys all texture atlases. + void reset(); + + // Adds texture to some TextureAtlas. Texture must fit in a single atlas + // texture, otherwise an exception is thrown. Returns a pointer to the new + // texture. If borderPixels is true, then fills a 1px border around the + // given image in the atlas with the nearest color value, to prevent + // bleeding. + TextureHandle addTexture(Image const& image, bool borderPixels = true); + + // Removes the given texture from the TextureAtlasSet and invalidates the + // pointer. + void freeTexture(TextureHandle const& texture); + + unsigned totalAtlases() const; + unsigned totalTextures() const; + float averageFillLevel() const; + + // Takes images from sparsely filled atlases and moves them to less sparsely + // filled atlases in an effort to free up room. This method tages the atlas + // with the lowest fill level and picks a texture from it, removes it, and + // re-adds it to the AtlasSet. It does this up to textureCount textures, + // until it finds a texture where re-adding it to the texture atlas simply + // moves the texture into the same atlas, at which point it stops. + void compressionPass(size_t textureCount = NPos); + + // The number of atlases that the AtlasSet will attempt to fit a texture in + // before giving up and creating a new atlas. Tries in order of least full + // to most full. Defaults to 3. + unsigned textureFitTries() const; + void setTextureFitTries(unsigned textureFitTries); + +protected: + virtual AtlasTextureHandle createAtlasTexture(Vec2U const& size, PixelFormat pixelFormat) = 0; + virtual void destroyAtlasTexture(AtlasTextureHandle const& atlasTexture) = 0; + virtual void copyAtlasPixels(AtlasTextureHandle const& atlasTexture, Vec2U const& bottomLeft, Image const& image) = 0; + +private: + struct TextureAtlas { + AtlasTextureHandle atlasTexture; + unique_ptr usedCells; + unsigned usedCellCount; + }; + + struct AtlasPlacement { + TextureAtlas* atlas; + bool borderPixels = false; + RectU occupiedCells; + RectU textureCoords; + }; + + struct TextureEntry : Texture { + Vec2U imageSize() const override; + + AtlasTextureHandle const& atlasTexture() const override; + RectU atlasTextureCoordinates() const override; + + // A locked texture will never be moved during compression, so its + // atlasTexture and textureCoordinates will not change. + void setLocked(bool locked) override; + + bool expired() const override; + + Image textureImage; + AtlasPlacement atlasPlacement; + bool placementLocked = false; + bool textureExpired = false; + }; + + void setAtlasRegionUsed(TextureAtlas* extureAtlas, RectU const& region, bool used) const; + + Maybe addTextureToAtlas(TextureAtlas* atlas, Image const& image, bool borderPixels); + void sortAtlases(); + + unsigned m_atlasCellSize; + unsigned m_atlasNumCells; + unsigned m_textureFitTries; + + List> m_atlases; + HashSet> m_textures; +}; + +template +TextureAtlasSet::TextureAtlasSet(unsigned cellSize, unsigned atlasNumCells) + : m_atlasCellSize(cellSize), m_atlasNumCells(atlasNumCells), m_textureFitTries(3) {} + +template +Vec2U TextureAtlasSet::atlasTextureSize() const { + return Vec2U::filled(m_atlasCellSize * m_atlasNumCells); +} + +template +void TextureAtlasSet::reset() { + for (auto const& texture : m_textures) + texture->textureExpired = true; + + for (auto const& atlas : m_atlases) + destroyAtlasTexture(atlas->atlasTexture); + + m_atlases.clear(); + m_textures.clear(); +} + +template +auto TextureAtlasSet::addTexture(Image const& image, bool borderPixels) -> TextureHandle { + if (image.empty()) + throw TextureAtlasException("Empty image given in TextureAtlasSet::addTexture"); + + Image finalImage; + if (borderPixels) { + Vec2U imageSize = image.size(); + Vec2U finalImageSize = imageSize + Vec2U(2, 2); + finalImage = Image(finalImageSize, PixelFormat::RGBA32); + + // Fill 1px border on all sides of the image + for (unsigned y = 0; y < finalImageSize[1]; ++y) { + for (unsigned x = 0; x < finalImageSize[0]; ++x) { + unsigned xind = clamp(x, 1, imageSize[0]) - 1; + unsigned yind = clamp(y, 1, imageSize[1]) - 1; + finalImage.set32(x, y, image.getrgb(xind, yind)); + } + } + } else { + finalImage = image; + } + + auto tryAtlas = [&](TextureAtlas* atlas) -> TextureHandle { + auto placement = addTextureToAtlas(atlas, finalImage, borderPixels); + if (!placement) + return nullptr; + + auto textureEntry = make_shared(); + textureEntry->textureImage = move(finalImage); + textureEntry->atlasPlacement = *placement; + + m_textures.add(textureEntry); + sortAtlases(); + return textureEntry; + }; + + // Try the first 'm_textureFitTries' atlases to see if we can fit a given + // texture in an existing atlas. Do this from the most full to the least + // full atlas to maximize compression. + size_t startAtlas = m_atlases.size() - min(m_atlases.size(), m_textureFitTries); + for (size_t i = startAtlas; i < m_atlases.size(); ++i) { + if (auto texturePtr = tryAtlas(m_atlases[i].get())) + return texturePtr; + } + + // If we have not found an existing atlas to put the texture, need to create + // a new atlas + m_atlases.append(make_shared(TextureAtlas{ + createAtlasTexture(Vec2U::filled(m_atlasCellSize * m_atlasNumCells), PixelFormat::RGBA32), + unique_ptr(new bool[m_atlasNumCells * m_atlasNumCells]()), 0 + })); + + if (auto texturePtr = tryAtlas(m_atlases.last().get())) + return texturePtr; + + // If it can't fit in a brand new empty atlas, it will not fit in any atlas + destroyAtlasTexture(m_atlases.last()->atlasTexture); + m_atlases.removeLast(); + throw TextureAtlasException("Could not add texture to new atlas in TextureAtlasSet::addTexture, too large"); +} + +template +void TextureAtlasSet::freeTexture(TextureHandle const& texture) { + auto textureEntry = convert(texture); + + setAtlasRegionUsed(textureEntry->atlasPlacement.atlas, textureEntry->atlasPlacement.occupiedCells, false); + sortAtlases(); + + textureEntry->textureExpired = true; + m_textures.remove(textureEntry); +} + +template +unsigned TextureAtlasSet::totalAtlases() const { + return m_atlases.size(); +} + +template +unsigned TextureAtlasSet::totalTextures() const { + return m_textures.size(); +} + +template +float TextureAtlasSet::averageFillLevel() const { + if (m_atlases.empty()) + return 0.0f; + + float atlasFillLevelSum = 0.0f; + for (auto const& atlas : m_atlases) + atlasFillLevelSum += atlas->usedCellCount / (float)square(m_atlasNumCells); + return atlasFillLevelSum / m_atlases.size(); +} + +template +void TextureAtlasSet::compressionPass(size_t textureCount) { + while (m_atlases.size() > 1 && textureCount > 0) { + // Find the least full atlas, If it is empty, remove it and start at the + // next atlas. + auto const& smallestAtlas = m_atlases.last(); + if (smallestAtlas->usedCellCount == 0) { + destroyAtlasTexture(smallestAtlas->atlasTexture); + m_atlases.removeLast(); + continue; + } + + // Loop over the currently loaded textures to find the smallest texture in + // the smallest atlas that is not locked. + TextureEntry* smallestTexture = nullptr; + for (auto const& texture : m_textures) { + if (texture->atlasPlacement.atlas == m_atlases.last().get()) { + if (!texture->placementLocked) { + if (!smallestTexture || texture->atlasPlacement.occupiedCells.volume() < smallestTexture->atlasPlacement.occupiedCells.volume()) + smallestTexture = texture.get(); + } + } + } + + // If we were not able to find a smallest texture because the texture is + // locked, then simply stop. TODO: This could be done better, this will + // prevent compressing textures that are not from the smallest atlas if the + // smallest atlas has only locked textures. + if (!smallestTexture) + break; + + // Try to add the texture to any atlas that isn't the last (most empty) one + size_t startAtlas = m_atlases.size() - 1 - min(m_atlases.size() - 1, m_textureFitTries); + for (size_t i = startAtlas; i < m_atlases.size() - 1; ++i) { + if (auto placement = addTextureToAtlas(m_atlases[i].get(), smallestTexture->textureImage, smallestTexture->atlasPlacement.borderPixels)) { + setAtlasRegionUsed(smallestTexture->atlasPlacement.atlas, smallestTexture->atlasPlacement.occupiedCells, false); + smallestTexture->atlasPlacement = *placement; + smallestTexture = nullptr; + sortAtlases(); + break; + } + } + + // If we have not managed to move the smallest texture into any other + // atlas, assume the atlas set is compressed enough and quit. + if (smallestTexture) + break; + + --textureCount; + } +} + +template +unsigned TextureAtlasSet::textureFitTries() const { + return m_textureFitTries; +} + +template +void TextureAtlasSet::setTextureFitTries(unsigned textureFitTries) { + m_textureFitTries = textureFitTries; +} + +template +Vec2U TextureAtlasSet::TextureEntry::imageSize() const { + if (atlasPlacement.borderPixels) + return textureImage.size() - Vec2U(2, 2); + else + return textureImage.size(); +} + +template +AtlasTextureHandle const& TextureAtlasSet::TextureEntry::atlasTexture() const { + return atlasPlacement.atlas->atlasTexture; +} + +template +RectU TextureAtlasSet::TextureEntry::atlasTextureCoordinates() const { + return atlasPlacement.textureCoords; +} + +template +void TextureAtlasSet::TextureEntry::setLocked(bool locked) { + placementLocked = locked; +} + +template +bool TextureAtlasSet::TextureEntry::expired() const { + return textureExpired; +} + +template +void TextureAtlasSet::setAtlasRegionUsed(TextureAtlas* textureAtlas, RectU const& region, bool used) const { + for (unsigned y = region.yMin(); y < region.yMax(); ++y) { + for (unsigned x = region.xMin(); x < region.xMax(); ++x) { + auto& val = textureAtlas->usedCells[y * m_atlasNumCells + x]; + bool oldVal = val; + val = used; + if (oldVal && !val) { + starAssert(textureAtlas->usedCellCount != 0); + textureAtlas->usedCellCount -= 1; + } else if (!oldVal && used) { + textureAtlas->usedCellCount += 1; + starAssert(textureAtlas->usedCellCount <= square(m_atlasNumCells)); + } + } + } +} + +template +void TextureAtlasSet::sortAtlases() { + sort(m_atlases, [](auto const& a1, auto const& a2) { + return a1->usedCellCount > a2->usedCellCount; + }); +} + +template +auto TextureAtlasSet::addTextureToAtlas(TextureAtlas* atlas, Image const& image, bool borderPixels) -> Maybe { + bool found = false; + // Minimum cell indexes where this texture fits in this atlas. + unsigned fitCellX = 0; + unsigned fitCellY = 0; + + Vec2U imageSize = image.size(); + + // Number of cells this image will take. + size_t numCellsX = (imageSize[0] + m_atlasCellSize - 1) / m_atlasCellSize; + size_t numCellsY = (imageSize[1] + m_atlasCellSize - 1) / m_atlasCellSize; + + if (numCellsX > m_atlasNumCells || numCellsY > m_atlasNumCells) + return {}; + + for (size_t cellY = 0; cellY <= m_atlasNumCells - numCellsY; ++cellY) { + for (size_t cellX = 0; cellX <= m_atlasNumCells - numCellsX; ++cellX) { + // Check this box of numCellsX x numCellsY for fit. + found = true; + size_t fx; + size_t fy; + for (fy = cellY; fy < cellY + numCellsY; ++fy) { + for (fx = cellX; fx < cellX + numCellsX; ++fx) { + if (atlas->usedCells[fy * m_atlasNumCells + fx]) { + found = false; + break; + } + } + if (!found) + break; + } + if (!found) { + // If it does not fit, then we can skip to the block past the first + // horizontal used block; + cellX = fx; + } else { + fitCellX = cellX; + fitCellY = cellY; + break; + } + } + if (found) + break; + } + + if (!found) + return {}; + + setAtlasRegionUsed(atlas, RectU::withSize({fitCellX, fitCellY}, {(unsigned)numCellsX, (unsigned)numCellsY}), true); + + copyAtlasPixels(atlas->atlasTexture, Vec2U(fitCellX * m_atlasCellSize, fitCellY * m_atlasCellSize), image); + + AtlasPlacement atlasPlacement; + atlasPlacement.atlas = atlas; + atlasPlacement.borderPixels = borderPixels; + atlasPlacement.occupiedCells = RectU::withSize(Vec2U(fitCellX, fitCellY), Vec2U(numCellsX, numCellsY)); + if (borderPixels) + atlasPlacement.textureCoords = RectU::withSize(Vec2U(fitCellX * m_atlasCellSize + 1, fitCellY * m_atlasCellSize + 1), imageSize - Vec2U(2, 2)); + else + atlasPlacement.textureCoords = RectU::withSize(Vec2U(fitCellX * m_atlasCellSize, fitCellY * m_atlasCellSize), imageSize); + return atlasPlacement; +} + +} + +#endif diff --git a/source/application/StarUserGeneratedContentService_pc_steam.cpp b/source/application/StarUserGeneratedContentService_pc_steam.cpp new file mode 100644 index 0000000..57a0c17 --- /dev/null +++ b/source/application/StarUserGeneratedContentService_pc_steam.cpp @@ -0,0 +1,60 @@ +#include "StarUserGeneratedContentService_pc_steam.hpp" +#include "StarLogging.hpp" +#include "StarLexicalCast.hpp" + +namespace Star { + +SteamUserGeneratedContentService::SteamUserGeneratedContentService(PcPlatformServicesStatePtr) + : m_callbackDownloadResult(this, &SteamUserGeneratedContentService::onDownloadResult) {}; + +StringList SteamUserGeneratedContentService::subscribedContentIds() const { + List contentIds(SteamUGC()->GetNumSubscribedItems(), {}); + SteamUGC()->GetSubscribedItems(contentIds.ptr(), contentIds.size()); + return contentIds.transformed([](PublishedFileId_t id) { + return String(toString(id)); + }); +} + +Maybe SteamUserGeneratedContentService::contentDownloadDirectory(String const& contentId) const { + PublishedFileId_t id = lexicalCast(contentId); + uint32 itemState = SteamUGC()->GetItemState(id); + if (itemState & k_EItemStateInstalled) { + char path[4096]; + if (SteamUGC()->GetItemInstallInfo(id, nullptr, path, sizeof(path), nullptr)) + return String(path); + } + return {}; +} + +bool SteamUserGeneratedContentService::triggerContentDownload() { + List contentIds(SteamUGC()->GetNumSubscribedItems(), {}); + SteamUGC()->GetSubscribedItems(contentIds.ptr(), contentIds.size()); + + for (uint64 contentId : contentIds) { + if (!m_currentDownloadState.contains(contentId)) { + uint32 itemState = SteamUGC()->GetItemState(contentId); + if (!(itemState & k_EItemStateInstalled) || itemState & k_EItemStateNeedsUpdate) { + SteamUGC()->DownloadItem(contentId, true); + itemState = SteamUGC()->GetItemState(contentId); + m_currentDownloadState[contentId] = !(itemState & k_EItemStateDownloading); + } else { + m_currentDownloadState[contentId] = true; + } + } + } + + bool allDownloaded = true; + for (auto const& p : m_currentDownloadState) { + if (!p.second) + allDownloaded = false; + } + + return allDownloaded; +} + +void SteamUserGeneratedContentService::onDownloadResult(DownloadItemResult_t* result) { + m_currentDownloadState[result->m_nPublishedFileId] = true; +} + + +} diff --git a/source/application/StarUserGeneratedContentService_pc_steam.hpp b/source/application/StarUserGeneratedContentService_pc_steam.hpp new file mode 100644 index 0000000..8bd84d3 --- /dev/null +++ b/source/application/StarUserGeneratedContentService_pc_steam.hpp @@ -0,0 +1,26 @@ +#ifndef STAR_USER_GENERATED_CONTENT_SERVICE_PC_STEAM_HPP +#define STAR_USER_GENERATED_CONTENT_SERVICE_PC_STEAM_HPP + +#include "StarPlatformServices_pc.hpp" + +namespace Star { + +STAR_CLASS(SteamUserGeneratedContentService); + +class SteamUserGeneratedContentService : public UserGeneratedContentService { +public: + SteamUserGeneratedContentService(PcPlatformServicesStatePtr state); + + StringList subscribedContentIds() const override; + Maybe contentDownloadDirectory(String const& contentId) const override; + bool triggerContentDownload() override; + +private: + STEAM_CALLBACK(SteamUserGeneratedContentService, onDownloadResult, DownloadItemResult_t, m_callbackDownloadResult); + + HashMap m_currentDownloadState; +}; + +} + +#endif diff --git a/source/application/discord/activity_manager.cpp b/source/application/discord/activity_manager.cpp new file mode 100644 index 0000000..b9b6760 --- /dev/null +++ b/source/application/discord/activity_manager.cpp @@ -0,0 +1,177 @@ +#if !defined(_CRT_SECURE_NO_WARNINGS) +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include "activity_manager.h" + +#include "core.h" + +#include +#include + +namespace discord { + +class ActivityEvents final { +public: + static void OnActivityJoin(void* callbackData, char const* secret) + { + auto* core = reinterpret_cast(callbackData); + if (!core) { + return; + } + + auto& module = core->ActivityManager(); + module.OnActivityJoin(static_cast(secret)); + } + + static void OnActivitySpectate(void* callbackData, char const* secret) + { + auto* core = reinterpret_cast(callbackData); + if (!core) { + return; + } + + auto& module = core->ActivityManager(); + module.OnActivitySpectate(static_cast(secret)); + } + + static void OnActivityJoinRequest(void* callbackData, DiscordUser* user) + { + auto* core = reinterpret_cast(callbackData); + if (!core) { + return; + } + + auto& module = core->ActivityManager(); + module.OnActivityJoinRequest(*reinterpret_cast(user)); + } + + static void OnActivityInvite(void* callbackData, + EDiscordActivityActionType type, + DiscordUser* user, + DiscordActivity* activity) + { + auto* core = reinterpret_cast(callbackData); + if (!core) { + return; + } + + auto& module = core->ActivityManager(); + module.OnActivityInvite(static_cast(type), + *reinterpret_cast(user), + *reinterpret_cast(activity)); + } +}; + +IDiscordActivityEvents ActivityManager::events_{ + &ActivityEvents::OnActivityJoin, + &ActivityEvents::OnActivitySpectate, + &ActivityEvents::OnActivityJoinRequest, + &ActivityEvents::OnActivityInvite, +}; + +Result ActivityManager::RegisterCommand(char const* command) +{ + auto result = internal_->register_command(internal_, const_cast(command)); + return static_cast(result); +} + +Result ActivityManager::RegisterSteam(std::uint32_t steamId) +{ + auto result = internal_->register_steam(internal_, steamId); + return static_cast(result); +} + +void ActivityManager::UpdateActivity(Activity const& activity, std::function callback) +{ + static auto wrapper = [](void* callbackData, EDiscordResult result) -> void { + std::unique_ptr> cb( + reinterpret_cast*>(callbackData)); + if (!cb || !(*cb)) { + return; + } + (*cb)(static_cast(result)); + }; + std::unique_ptr> cb{}; + cb.reset(new std::function(std::move(callback))); + internal_->update_activity(internal_, + reinterpret_cast(const_cast(&activity)), + cb.release(), + wrapper); +} + +void ActivityManager::ClearActivity(std::function callback) +{ + static auto wrapper = [](void* callbackData, EDiscordResult result) -> void { + std::unique_ptr> cb( + reinterpret_cast*>(callbackData)); + if (!cb || !(*cb)) { + return; + } + (*cb)(static_cast(result)); + }; + std::unique_ptr> cb{}; + cb.reset(new std::function(std::move(callback))); + internal_->clear_activity(internal_, cb.release(), wrapper); +} + +void ActivityManager::SendRequestReply(UserId userId, + ActivityJoinRequestReply reply, + std::function callback) +{ + static auto wrapper = [](void* callbackData, EDiscordResult result) -> void { + std::unique_ptr> cb( + reinterpret_cast*>(callbackData)); + if (!cb || !(*cb)) { + return; + } + (*cb)(static_cast(result)); + }; + std::unique_ptr> cb{}; + cb.reset(new std::function(std::move(callback))); + internal_->send_request_reply(internal_, + userId, + static_cast(reply), + cb.release(), + wrapper); +} + +void ActivityManager::SendInvite(UserId userId, + ActivityActionType type, + char const* content, + std::function callback) +{ + static auto wrapper = [](void* callbackData, EDiscordResult result) -> void { + std::unique_ptr> cb( + reinterpret_cast*>(callbackData)); + if (!cb || !(*cb)) { + return; + } + (*cb)(static_cast(result)); + }; + std::unique_ptr> cb{}; + cb.reset(new std::function(std::move(callback))); + internal_->send_invite(internal_, + userId, + static_cast(type), + const_cast(content), + cb.release(), + wrapper); +} + +void ActivityManager::AcceptInvite(UserId userId, std::function callback) +{ + static auto wrapper = [](void* callbackData, EDiscordResult result) -> void { + std::unique_ptr> cb( + reinterpret_cast*>(callbackData)); + if (!cb || !(*cb)) { + return; + } + (*cb)(static_cast(result)); + }; + std::unique_ptr> cb{}; + cb.reset(new std::function(std::move(callback))); + internal_->accept_invite(internal_, userId, cb.release(), wrapper); +} + +} // namespace discord diff --git a/source/application/discord/activity_manager.h b/source/application/discord/activity_manager.h new file mode 100644 index 0000000..c107be2 --- /dev/null +++ b/source/application/discord/activity_manager.h @@ -0,0 +1,42 @@ +#pragma once + +#include "types.h" + +namespace discord { + +class ActivityManager final { +public: + ~ActivityManager() = default; + + Result RegisterCommand(char const* command); + Result RegisterSteam(std::uint32_t steamId); + void UpdateActivity(Activity const& activity, std::function callback); + void ClearActivity(std::function callback); + void SendRequestReply(UserId userId, + ActivityJoinRequestReply reply, + std::function callback); + void SendInvite(UserId userId, + ActivityActionType type, + char const* content, + std::function callback); + void AcceptInvite(UserId userId, std::function callback); + + Event OnActivityJoin; + Event OnActivitySpectate; + Event OnActivityJoinRequest; + Event OnActivityInvite; + +private: + friend class Core; + + ActivityManager() = default; + ActivityManager(ActivityManager const& rhs) = delete; + ActivityManager& operator=(ActivityManager const& rhs) = delete; + ActivityManager(ActivityManager&& rhs) = delete; + ActivityManager& operator=(ActivityManager&& rhs) = delete; + + IDiscordActivityManager* internal_; + static IDiscordActivityEvents events_; +}; + +} // namespace discord diff --git a/source/application/discord/application_manager.cpp b/source/application/discord/application_manager.cpp new file mode 100644 index 0000000..f5a06d3 --- /dev/null +++ b/source/application/discord/application_manager.cpp @@ -0,0 +1,63 @@ +#if !defined(_CRT_SECURE_NO_WARNINGS) +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include "application_manager.h" + +#include "core.h" + +#include +#include + +namespace discord { + +void ApplicationManager::ValidateOrExit(std::function callback) +{ + static auto wrapper = [](void* callbackData, EDiscordResult result) -> void { + std::unique_ptr> cb( + reinterpret_cast*>(callbackData)); + if (!cb || !(*cb)) { + return; + } + (*cb)(static_cast(result)); + }; + std::unique_ptr> cb{}; + cb.reset(new std::function(std::move(callback))); + internal_->validate_or_exit(internal_, cb.release(), wrapper); +} + +void ApplicationManager::GetCurrentLocale(char locale[128]) +{ + if (!locale) { + return; + } + + internal_->get_current_locale(internal_, reinterpret_cast(locale)); +} + +void ApplicationManager::GetCurrentBranch(char branch[4096]) +{ + if (!branch) { + return; + } + + internal_->get_current_branch(internal_, reinterpret_cast(branch)); +} + +void ApplicationManager::GetOAuth2Token(std::function callback) +{ + static auto wrapper = + [](void* callbackData, EDiscordResult result, DiscordOAuth2Token* oauth2Token) -> void { + std::unique_ptr> cb( + reinterpret_cast*>(callbackData)); + if (!cb || !(*cb)) { + return; + } + (*cb)(static_cast(result), *reinterpret_cast(oauth2Token)); + }; + std::unique_ptr> cb{}; + cb.reset(new std::function(std::move(callback))); + internal_->get_oauth2_token(internal_, cb.release(), wrapper); +} + +} // namespace discord diff --git a/source/application/discord/application_manager.h b/source/application/discord/application_manager.h new file mode 100644 index 0000000..03b3a8d --- /dev/null +++ b/source/application/discord/application_manager.h @@ -0,0 +1,29 @@ +#pragma once + +#include "types.h" + +namespace discord { + +class ApplicationManager final { +public: + ~ApplicationManager() = default; + + void ValidateOrExit(std::function callback); + void GetCurrentLocale(char locale[128]); + void GetCurrentBranch(char branch[4096]); + void GetOAuth2Token(std::function callback); + +private: + friend class Core; + + ApplicationManager() = default; + ApplicationManager(ApplicationManager const& rhs) = delete; + ApplicationManager& operator=(ApplicationManager const& rhs) = delete; + ApplicationManager(ApplicationManager&& rhs) = delete; + ApplicationManager& operator=(ApplicationManager&& rhs) = delete; + + IDiscordApplicationManager* internal_; + static IDiscordApplicationEvents events_; +}; + +} // namespace discord diff --git a/source/application/discord/core.cpp b/source/application/discord/core.cpp new file mode 100644 index 0000000..69a10a1 --- /dev/null +++ b/source/application/discord/core.cpp @@ -0,0 +1,162 @@ +#if !defined(_CRT_SECURE_NO_WARNINGS) +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include "core.h" + +#include +#include + +namespace discord { + +Result Core::Create(ClientId clientId, std::uint64_t flags, Core** instance) +{ + if (!instance) { + return Result::InternalError; + } + + (*instance) = new Core(); + DiscordCreateParams params{}; + DiscordCreateParamsSetDefault(¶ms); + params.client_id = clientId; + params.flags = flags; + params.events = nullptr; + params.event_data = *instance; + params.user_events = &UserManager::events_; + params.activity_events = &ActivityManager::events_; + params.relationship_events = &RelationshipManager::events_; + params.lobby_events = &LobbyManager::events_; + params.network_events = &NetworkManager::events_; + params.overlay_events = &OverlayManager::events_; + params.store_events = &StoreManager::events_; + auto result = DiscordCreate(DISCORD_VERSION, ¶ms, &((*instance)->internal_)); + if (result != DiscordResult_Ok || !(*instance)->internal_) { + delete (*instance); + (*instance) = nullptr; + } + + return static_cast(result); +} + +Core::~Core() +{ + if (internal_) { + internal_->destroy(internal_); + internal_ = nullptr; + } +} + +Result Core::RunCallbacks() +{ + auto result = internal_->run_callbacks(internal_); + return static_cast(result); +} + +void Core::SetLogHook(LogLevel minLevel, std::function hook) +{ + setLogHook_.DisconnectAll(); + setLogHook_.Connect(std::move(hook)); + static auto wrapper = + [](void* callbackData, EDiscordLogLevel level, char const* message) -> void { + auto cb(reinterpret_cast(callbackData)); + if (!cb) { + return; + } + (*cb)(static_cast(level), static_cast(message)); + }; + + internal_->set_log_hook( + internal_, static_cast(minLevel), &setLogHook_, wrapper); +} + +discord::ApplicationManager& Core::ApplicationManager() +{ + if (!applicationManager_.internal_) { + applicationManager_.internal_ = internal_->get_application_manager(internal_); + } + + return applicationManager_; +} + +discord::UserManager& Core::UserManager() +{ + if (!userManager_.internal_) { + userManager_.internal_ = internal_->get_user_manager(internal_); + } + + return userManager_; +} + +discord::ImageManager& Core::ImageManager() +{ + if (!imageManager_.internal_) { + imageManager_.internal_ = internal_->get_image_manager(internal_); + } + + return imageManager_; +} + +discord::ActivityManager& Core::ActivityManager() +{ + if (!activityManager_.internal_) { + activityManager_.internal_ = internal_->get_activity_manager(internal_); + } + + return activityManager_; +} + +discord::RelationshipManager& Core::RelationshipManager() +{ + if (!relationshipManager_.internal_) { + relationshipManager_.internal_ = internal_->get_relationship_manager(internal_); + } + + return relationshipManager_; +} + +discord::LobbyManager& Core::LobbyManager() +{ + if (!lobbyManager_.internal_) { + lobbyManager_.internal_ = internal_->get_lobby_manager(internal_); + } + + return lobbyManager_; +} + +discord::NetworkManager& Core::NetworkManager() +{ + if (!networkManager_.internal_) { + networkManager_.internal_ = internal_->get_network_manager(internal_); + } + + return networkManager_; +} + +discord::OverlayManager& Core::OverlayManager() +{ + if (!overlayManager_.internal_) { + overlayManager_.internal_ = internal_->get_overlay_manager(internal_); + } + + return overlayManager_; +} + +discord::StorageManager& Core::StorageManager() +{ + if (!storageManager_.internal_) { + storageManager_.internal_ = internal_->get_storage_manager(internal_); + } + + return storageManager_; +} + +discord::StoreManager& Core::StoreManager() +{ + if (!storeManager_.internal_) { + storeManager_.internal_ = internal_->get_store_manager(internal_); + } + + return storeManager_; +} + +} // namespace discord diff --git a/source/application/discord/core.h b/source/application/discord/core.h new file mode 100644 index 0000000..411e064 --- /dev/null +++ b/source/application/discord/core.h @@ -0,0 +1,58 @@ +#pragma once + +#include "types.h" +#include "application_manager.h" +#include "user_manager.h" +#include "image_manager.h" +#include "activity_manager.h" +#include "relationship_manager.h" +#include "lobby_manager.h" +#include "network_manager.h" +#include "overlay_manager.h" +#include "storage_manager.h" +#include "store_manager.h" + +namespace discord { + +class Core final { +public: + static Result Create(ClientId clientId, std::uint64_t flags, Core** instance); + + ~Core(); + + Result RunCallbacks(); + void SetLogHook(LogLevel minLevel, std::function hook); + + discord::ApplicationManager& ApplicationManager(); + discord::UserManager& UserManager(); + discord::ImageManager& ImageManager(); + discord::ActivityManager& ActivityManager(); + discord::RelationshipManager& RelationshipManager(); + discord::LobbyManager& LobbyManager(); + discord::NetworkManager& NetworkManager(); + discord::OverlayManager& OverlayManager(); + discord::StorageManager& StorageManager(); + discord::StoreManager& StoreManager(); + +private: + Core() = default; + Core(Core const& rhs) = delete; + Core& operator=(Core const& rhs) = delete; + Core(Core&& rhs) = delete; + Core& operator=(Core&& rhs) = delete; + + IDiscordCore* internal_; + Event setLogHook_; + discord::ApplicationManager applicationManager_; + discord::UserManager userManager_; + discord::ImageManager imageManager_; + discord::ActivityManager activityManager_; + discord::RelationshipManager relationshipManager_; + discord::LobbyManager lobbyManager_; + discord::NetworkManager networkManager_; + discord::OverlayManager overlayManager_; + discord::StorageManager storageManager_; + discord::StoreManager storeManager_; +}; + +} // namespace discord diff --git a/source/application/discord/discord.h b/source/application/discord/discord.h new file mode 100644 index 0000000..d604362 --- /dev/null +++ b/source/application/discord/discord.h @@ -0,0 +1,14 @@ +#pragma once + +#include "types.h" +#include "core.h" +#include "application_manager.h" +#include "user_manager.h" +#include "image_manager.h" +#include "activity_manager.h" +#include "relationship_manager.h" +#include "lobby_manager.h" +#include "network_manager.h" +#include "overlay_manager.h" +#include "storage_manager.h" +#include "store_manager.h" diff --git a/source/application/discord/event.h b/source/application/discord/event.h new file mode 100644 index 0000000..610887d --- /dev/null +++ b/source/application/discord/event.h @@ -0,0 +1,59 @@ +#pragma once + +#include +#include + +namespace discord { + +template +class Event final { +public: + using Token = int; + + Event() { slots_.reserve(4); } + + Event(Event const&) = default; + Event(Event&&) = default; + ~Event() = default; + + Event& operator=(Event const&) = default; + Event& operator=(Event&&) = default; + + template + Token Connect(EventHandler slot) + { + slots_.emplace_back(Slot{nextToken_, std::move(slot)}); + return nextToken_++; + } + + void Disconnect(Token token) + { + for (auto& slot : slots_) { + if (slot.token == token) { + slot = slots_.back(); + slots_.pop_back(); + break; + } + } + } + + void DisconnectAll() { slots_ = {}; } + + void operator()(Args... args) + { + for (auto const& slot : slots_) { + slot.fn(std::forward(args)...); + } + } + +private: + struct Slot { + Token token; + std::function fn; + }; + + Token nextToken_{}; + std::vector slots_{}; +}; + +} // namespace discord diff --git a/source/application/discord/ffi.h b/source/application/discord/ffi.h new file mode 100644 index 0000000..d9835f1 --- /dev/null +++ b/source/application/discord/ffi.h @@ -0,0 +1,790 @@ +#ifndef _DISCORD_GAME_SDK_H_ +#define _DISCORD_GAME_SDK_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#ifndef __cplusplus +#include +#endif + +#define DISCORD_VERSION 2 +#define DISCORD_APPLICATION_MANAGER_VERSION 1 +#define DISCORD_USER_MANAGER_VERSION 1 +#define DISCORD_IMAGE_MANAGER_VERSION 1 +#define DISCORD_ACTIVITY_MANAGER_VERSION 1 +#define DISCORD_RELATIONSHIP_MANAGER_VERSION 1 +#define DISCORD_LOBBY_MANAGER_VERSION 1 +#define DISCORD_NETWORK_MANAGER_VERSION 1 +#define DISCORD_OVERLAY_MANAGER_VERSION 1 +#define DISCORD_STORAGE_MANAGER_VERSION 1 +#define DISCORD_STORE_MANAGER_VERSION 1 + +enum EDiscordResult { + DiscordResult_Ok, + DiscordResult_ServiceUnavailable, + DiscordResult_InvalidVersion, + DiscordResult_LockFailed, + DiscordResult_InternalError, + DiscordResult_InvalidPayload, + DiscordResult_InvalidCommand, + DiscordResult_InvalidPermissions, + DiscordResult_NotFetched, + DiscordResult_NotFound, + DiscordResult_Conflict, + DiscordResult_InvalidSecret, + DiscordResult_InvalidJoinSecret, + DiscordResult_NoEligibleActivity, + DiscordResult_InvalidInvite, + DiscordResult_NotAuthenticated, + DiscordResult_InvalidAccessToken, + DiscordResult_ApplicationMismatch, + DiscordResult_InvalidDataUrl, + DiscordResult_InvalidBase64, + DiscordResult_NotFiltered, + DiscordResult_LobbyFull, + DiscordResult_InvalidLobbySecret, + DiscordResult_InvalidFilename, + DiscordResult_InvalidFileSize, + DiscordResult_InvalidEntitlement, + DiscordResult_NotInstalled, + DiscordResult_NotRunning, + DiscordResult_InsufficientBuffer, + DiscordResult_PurchaseCanceled, +}; + +enum EDiscordCreateFlags { + DiscordCreateFlags_Default = 0, + DiscordCreateFlags_NoRequireDiscord = 1, +}; + +enum EDiscordLogLevel { + DiscordLogLevel_Error = 1, + DiscordLogLevel_Warn, + DiscordLogLevel_Info, + DiscordLogLevel_Debug, +}; + +enum EDiscordImageType { + DiscordImageType_User, +}; + +enum EDiscordActivityType { + DiscordActivityType_Playing, + DiscordActivityType_Streaming, + DiscordActivityType_Listening, + DiscordActivityType_Watching, +}; + +enum EDiscordActivityActionType { + DiscordActivityActionType_Join = 1, + DiscordActivityActionType_Spectate, +}; + +enum EDiscordActivityJoinRequestReply { + DiscordActivityJoinRequestReply_No, + DiscordActivityJoinRequestReply_Yes, + DiscordActivityJoinRequestReply_Ignore, +}; + +enum EDiscordStatus { + DiscordStatus_Offline = 0, + DiscordStatus_Online = 1, + DiscordStatus_Idle = 2, + DiscordStatus_DoNotDisturb = 3, +}; + +enum EDiscordRelationshipType { + DiscordRelationshipType_None, + DiscordRelationshipType_Friend, + DiscordRelationshipType_Blocked, + DiscordRelationshipType_PendingIncoming, + DiscordRelationshipType_PendingOutgoing, + DiscordRelationshipType_Implicit, +}; + +enum EDiscordLobbyType { + DiscordLobbyType_Private = 1, + DiscordLobbyType_Public, +}; + +enum EDiscordLobbySearchComparison { + DiscordLobbySearchComparison_LessThanOrEqual = -2, + DiscordLobbySearchComparison_LessThan, + DiscordLobbySearchComparison_Equal, + DiscordLobbySearchComparison_GreaterThan, + DiscordLobbySearchComparison_GreaterThanOrEqual, + DiscordLobbySearchComparison_NotEqual, +}; + +enum EDiscordLobbySearchCast { + DiscordLobbySearchCast_String = 1, + DiscordLobbySearchCast_Number, +}; + +enum EDiscordLobbySearchDistance { + DiscordLobbySearchDistance_Local, + DiscordLobbySearchDistance_Default, + DiscordLobbySearchDistance_Extended, + DiscordLobbySearchDistance_Global, +}; + +enum EDiscordEntitlementType { + DiscordEntitlementType_Purchase = 1, + DiscordEntitlementType_PremiumSubscription, + DiscordEntitlementType_DeveloperGift, +}; + +enum EDiscordSkuType { + DiscordSkuType_Application = 1, + DiscordSkuType_DLC, + DiscordSkuType_Consumable, + DiscordSkuType_Bundle, +}; + +typedef int64_t DiscordClientId; +typedef int32_t DiscordVersion; +typedef int64_t DiscordSnowflake; +typedef int64_t DiscordTimestamp; +typedef DiscordSnowflake DiscordUserId; +typedef char DiscordLocale[128]; +typedef char DiscordBranch[4096]; +typedef DiscordSnowflake DiscordLobbyId; +typedef char DiscordLobbySecret[128]; +typedef char DiscordMetadataKey[256]; +typedef char DiscordMetadataValue[4096]; +typedef uint64_t DiscordNetworkPeerId; +typedef uint8_t DiscordNetworkChannelId; + +struct DiscordUser { + DiscordUserId id; + char username[256]; + char discriminator[8]; + char avatar[128]; + bool bot; +}; + +struct DiscordOAuth2Token { + char access_token[128]; + char scopes[1024]; + DiscordTimestamp expires; +}; + +struct DiscordImageHandle { + enum EDiscordImageType type; + int64_t id; + uint32_t size; +}; + +struct DiscordImageDimensions { + uint32_t width; + uint32_t height; +}; + +struct DiscordActivityTimestamps { + DiscordTimestamp start; + DiscordTimestamp end; +}; + +struct DiscordActivityAssets { + char large_image[128]; + char large_text[128]; + char small_image[128]; + char small_text[128]; +}; + +struct DiscordPartySize { + int32_t current_size; + int32_t max_size; +}; + +struct DiscordActivityParty { + char id[128]; + struct DiscordPartySize size; +}; + +struct DiscordActivitySecrets { + char match[128]; + char join[128]; + char spectate[128]; +}; + +struct DiscordActivity { + enum EDiscordActivityType type; + int64_t application_id; + char name[128]; + char state[128]; + char details[128]; + struct DiscordActivityTimestamps timestamps; + struct DiscordActivityAssets assets; + struct DiscordActivityParty party; + struct DiscordActivitySecrets secrets; + bool instance; +}; + +struct DiscordPresence { + enum EDiscordStatus status; + struct DiscordActivity activity; +}; + +struct DiscordRelationship { + enum EDiscordRelationshipType type; + struct DiscordUser user; + struct DiscordPresence presence; +}; + +struct DiscordLobby { + DiscordLobbyId id; + enum EDiscordLobbyType type; + DiscordUserId owner_id; + DiscordLobbySecret secret; + uint32_t capacity; + bool locked; +}; + +struct DiscordFileStat { + char filename[260]; + uint64_t size; + uint64_t last_modified; +}; + +struct DiscordEntitlement { + DiscordSnowflake id; + enum EDiscordEntitlementType type; + DiscordSnowflake sku_id; +}; + +struct DiscordSkuPrice { + uint32_t amount; + char currency[16]; +}; + +struct DiscordSku { + DiscordSnowflake id; + enum EDiscordSkuType type; + char name[256]; + struct DiscordSkuPrice price; +}; + +struct IDiscordLobbyTransaction { + enum EDiscordResult (*set_type)(struct IDiscordLobbyTransaction* lobby_transaction, + enum EDiscordLobbyType type); + enum EDiscordResult (*set_owner)(struct IDiscordLobbyTransaction* lobby_transaction, + DiscordUserId owner_id); + enum EDiscordResult (*set_capacity)(struct IDiscordLobbyTransaction* lobby_transaction, + uint32_t capacity); + enum EDiscordResult (*set_metadata)(struct IDiscordLobbyTransaction* lobby_transaction, + DiscordMetadataKey key, + DiscordMetadataValue value); + enum EDiscordResult (*delete_metadata)(struct IDiscordLobbyTransaction* lobby_transaction, + DiscordMetadataKey key); + enum EDiscordResult (*set_locked)(struct IDiscordLobbyTransaction* lobby_transaction, + bool locked); +}; + +struct IDiscordLobbyMemberTransaction { + enum EDiscordResult (*set_metadata)( + struct IDiscordLobbyMemberTransaction* lobby_member_transaction, + DiscordMetadataKey key, + DiscordMetadataValue value); + enum EDiscordResult (*delete_metadata)( + struct IDiscordLobbyMemberTransaction* lobby_member_transaction, + DiscordMetadataKey key); +}; + +struct IDiscordLobbySearchQuery { + enum EDiscordResult (*filter)(struct IDiscordLobbySearchQuery* lobby_search_query, + DiscordMetadataKey key, + enum EDiscordLobbySearchComparison comparison, + enum EDiscordLobbySearchCast cast, + DiscordMetadataValue value); + enum EDiscordResult (*sort)(struct IDiscordLobbySearchQuery* lobby_search_query, + DiscordMetadataKey key, + enum EDiscordLobbySearchCast cast, + DiscordMetadataValue value); + enum EDiscordResult (*limit)(struct IDiscordLobbySearchQuery* lobby_search_query, + uint32_t limit); + enum EDiscordResult (*distance)(struct IDiscordLobbySearchQuery* lobby_search_query, + enum EDiscordLobbySearchDistance distance); +}; + +typedef void* IDiscordApplicationEvents; + +struct IDiscordApplicationManager { + void (*validate_or_exit)(struct IDiscordApplicationManager* manager, + void* callback_data, + void (*callback)(void* callback_data, enum EDiscordResult result)); + void (*get_current_locale)(struct IDiscordApplicationManager* manager, DiscordLocale* locale); + void (*get_current_branch)(struct IDiscordApplicationManager* manager, DiscordBranch* branch); + void (*get_oauth2_token)(struct IDiscordApplicationManager* manager, + void* callback_data, + void (*callback)(void* callback_data, + enum EDiscordResult result, + struct DiscordOAuth2Token* oauth2_token)); +}; + +struct IDiscordUserEvents { + void (*on_current_user_update)(void* event_data); +}; + +struct IDiscordUserManager { + enum EDiscordResult (*get_current_user)(struct IDiscordUserManager* manager, + struct DiscordUser* current_user); + void (*get_user)(struct IDiscordUserManager* manager, + DiscordUserId user_id, + void* callback_data, + void (*callback)(void* callback_data, + enum EDiscordResult result, + struct DiscordUser* user)); +}; + +typedef void* IDiscordImageEvents; + +struct IDiscordImageManager { + void (*fetch)(struct IDiscordImageManager* manager, + struct DiscordImageHandle handle, + bool refresh, + void* callback_data, + void (*callback)(void* callback_data, + enum EDiscordResult result, + struct DiscordImageHandle handle_result)); + enum EDiscordResult (*get_dimensions)(struct IDiscordImageManager* manager, + struct DiscordImageHandle handle, + struct DiscordImageDimensions* dimensions); + enum EDiscordResult (*get_data)(struct IDiscordImageManager* manager, + struct DiscordImageHandle handle, + uint8_t* data, + uint32_t data_length); +}; + +struct IDiscordActivityEvents { + void (*on_activity_join)(void* event_data, const char* secret); + void (*on_activity_spectate)(void* event_data, const char* secret); + void (*on_activity_join_request)(void* event_data, struct DiscordUser* user); + void (*on_activity_invite)(void* event_data, + enum EDiscordActivityActionType type, + struct DiscordUser* user, + struct DiscordActivity* activity); +}; + +struct IDiscordActivityManager { + enum EDiscordResult (*register_command)(struct IDiscordActivityManager* manager, + const char* command); + enum EDiscordResult (*register_steam)(struct IDiscordActivityManager* manager, + uint32_t steam_id); + void (*update_activity)(struct IDiscordActivityManager* manager, + struct DiscordActivity* activity, + void* callback_data, + void (*callback)(void* callback_data, enum EDiscordResult result)); + void (*clear_activity)(struct IDiscordActivityManager* manager, + void* callback_data, + void (*callback)(void* callback_data, enum EDiscordResult result)); + void (*send_request_reply)(struct IDiscordActivityManager* manager, + DiscordUserId user_id, + enum EDiscordActivityJoinRequestReply reply, + void* callback_data, + void (*callback)(void* callback_data, enum EDiscordResult result)); + void (*send_invite)(struct IDiscordActivityManager* manager, + DiscordUserId user_id, + enum EDiscordActivityActionType type, + const char* content, + void* callback_data, + void (*callback)(void* callback_data, enum EDiscordResult result)); + void (*accept_invite)(struct IDiscordActivityManager* manager, + DiscordUserId user_id, + void* callback_data, + void (*callback)(void* callback_data, enum EDiscordResult result)); +}; + +struct IDiscordRelationshipEvents { + void (*on_refresh)(void* event_data); + void (*on_relationship_update)(void* event_data, struct DiscordRelationship* relationship); +}; + +struct IDiscordRelationshipManager { + void (*filter)(struct IDiscordRelationshipManager* manager, + void* filter_data, + bool (*filter)(void* filter_data, struct DiscordRelationship* relationship)); + enum EDiscordResult (*count)(struct IDiscordRelationshipManager* manager, int32_t* count); + enum EDiscordResult (*get)(struct IDiscordRelationshipManager* manager, + DiscordUserId user_id, + struct DiscordRelationship* relationship); + enum EDiscordResult (*get_at)(struct IDiscordRelationshipManager* manager, + uint32_t index, + struct DiscordRelationship* relationship); +}; + +struct IDiscordLobbyEvents { + void (*on_lobby_update)(void* event_data, int64_t lobby_id); + void (*on_lobby_delete)(void* event_data, int64_t lobby_id, uint32_t reason); + void (*on_member_connect)(void* event_data, int64_t lobby_id, int64_t user_id); + void (*on_member_update)(void* event_data, int64_t lobby_id, int64_t user_id); + void (*on_member_disconnect)(void* event_data, int64_t lobby_id, int64_t user_id); + void (*on_lobby_message)(void* event_data, + int64_t lobby_id, + int64_t user_id, + uint8_t* data, + uint32_t data_length); + void (*on_speaking)(void* event_data, int64_t lobby_id, int64_t user_id, bool speaking); + void (*on_network_message)(void* event_data, + int64_t lobby_id, + int64_t user_id, + uint8_t channel_id, + uint8_t* data, + uint32_t data_length); +}; + +struct IDiscordLobbyManager { + enum EDiscordResult (*get_lobby_create_transaction)( + struct IDiscordLobbyManager* manager, + struct IDiscordLobbyTransaction** transaction); + enum EDiscordResult (*get_lobby_update_transaction)( + struct IDiscordLobbyManager* manager, + DiscordLobbyId lobby_id, + struct IDiscordLobbyTransaction** transaction); + enum EDiscordResult (*get_member_update_transaction)( + struct IDiscordLobbyManager* manager, + DiscordLobbyId lobby_id, + DiscordUserId user_id, + struct IDiscordLobbyMemberTransaction** transaction); + void (*create_lobby)(struct IDiscordLobbyManager* manager, + struct IDiscordLobbyTransaction* transaction, + void* callback_data, + void (*callback)(void* callback_data, + enum EDiscordResult result, + struct DiscordLobby* lobby)); + void (*update_lobby)(struct IDiscordLobbyManager* manager, + DiscordLobbyId lobby_id, + struct IDiscordLobbyTransaction* transaction, + void* callback_data, + void (*callback)(void* callback_data, enum EDiscordResult result)); + void (*delete_lobby)(struct IDiscordLobbyManager* manager, + DiscordLobbyId lobby_id, + void* callback_data, + void (*callback)(void* callback_data, enum EDiscordResult result)); + void (*connect_lobby)(struct IDiscordLobbyManager* manager, + DiscordLobbyId lobby_id, + DiscordLobbySecret secret, + void* callback_data, + void (*callback)(void* callback_data, + enum EDiscordResult result, + struct DiscordLobby* lobby)); + void (*connect_lobby_with_activity_secret)(struct IDiscordLobbyManager* manager, + DiscordLobbySecret activity_secret, + void* callback_data, + void (*callback)(void* callback_data, + enum EDiscordResult result, + struct DiscordLobby* lobby)); + void (*disconnect_lobby)(struct IDiscordLobbyManager* manager, + DiscordLobbyId lobby_id, + void* callback_data, + void (*callback)(void* callback_data, enum EDiscordResult result)); + enum EDiscordResult (*get_lobby)(struct IDiscordLobbyManager* manager, + DiscordLobbyId lobby_id, + struct DiscordLobby* lobby); + enum EDiscordResult (*get_lobby_activity_secret)(struct IDiscordLobbyManager* manager, + DiscordLobbyId lobby_id, + DiscordLobbySecret* secret); + enum EDiscordResult (*get_lobby_metadata_value)(struct IDiscordLobbyManager* manager, + DiscordLobbyId lobby_id, + DiscordMetadataKey key, + DiscordMetadataValue* value); + enum EDiscordResult (*get_lobby_metadata_key)(struct IDiscordLobbyManager* manager, + DiscordLobbyId lobby_id, + int32_t index, + DiscordMetadataKey* key); + enum EDiscordResult (*lobby_metadata_count)(struct IDiscordLobbyManager* manager, + DiscordLobbyId lobby_id, + int32_t* count); + enum EDiscordResult (*member_count)(struct IDiscordLobbyManager* manager, + DiscordLobbyId lobby_id, + int32_t* count); + enum EDiscordResult (*get_member_user_id)(struct IDiscordLobbyManager* manager, + DiscordLobbyId lobby_id, + int32_t index, + DiscordUserId* user_id); + enum EDiscordResult (*get_member_user)(struct IDiscordLobbyManager* manager, + DiscordLobbyId lobby_id, + DiscordUserId user_id, + struct DiscordUser* user); + enum EDiscordResult (*get_member_metadata_value)(struct IDiscordLobbyManager* manager, + DiscordLobbyId lobby_id, + DiscordUserId user_id, + DiscordMetadataKey key, + DiscordMetadataValue* value); + enum EDiscordResult (*get_member_metadata_key)(struct IDiscordLobbyManager* manager, + DiscordLobbyId lobby_id, + DiscordUserId user_id, + int32_t index, + DiscordMetadataKey* key); + enum EDiscordResult (*member_metadata_count)(struct IDiscordLobbyManager* manager, + DiscordLobbyId lobby_id, + DiscordUserId user_id, + int32_t* count); + void (*update_member)(struct IDiscordLobbyManager* manager, + DiscordLobbyId lobby_id, + DiscordUserId user_id, + struct IDiscordLobbyMemberTransaction* transaction, + void* callback_data, + void (*callback)(void* callback_data, enum EDiscordResult result)); + void (*send_lobby_message)(struct IDiscordLobbyManager* manager, + DiscordLobbyId lobby_id, + uint8_t* data, + uint32_t data_length, + void* callback_data, + void (*callback)(void* callback_data, enum EDiscordResult result)); + enum EDiscordResult (*get_search_query)(struct IDiscordLobbyManager* manager, + struct IDiscordLobbySearchQuery** query); + void (*search)(struct IDiscordLobbyManager* manager, + struct IDiscordLobbySearchQuery* query, + void* callback_data, + void (*callback)(void* callback_data, enum EDiscordResult result)); + void (*lobby_count)(struct IDiscordLobbyManager* manager, int32_t* count); + enum EDiscordResult (*get_lobby_id)(struct IDiscordLobbyManager* manager, + int32_t index, + DiscordLobbyId* lobby_id); + void (*connect_voice)(struct IDiscordLobbyManager* manager, + DiscordLobbyId lobby_id, + void* callback_data, + void (*callback)(void* callback_data, enum EDiscordResult result)); + void (*disconnect_voice)(struct IDiscordLobbyManager* manager, + DiscordLobbyId lobby_id, + void* callback_data, + void (*callback)(void* callback_data, enum EDiscordResult result)); + enum EDiscordResult (*connect_network)(struct IDiscordLobbyManager* manager, + DiscordLobbyId lobby_id); + enum EDiscordResult (*disconnect_network)(struct IDiscordLobbyManager* manager, + DiscordLobbyId lobby_id); + enum EDiscordResult (*flush_network)(struct IDiscordLobbyManager* manager); + enum EDiscordResult (*open_network_channel)(struct IDiscordLobbyManager* manager, + DiscordLobbyId lobby_id, + uint8_t channel_id, + bool reliable); + enum EDiscordResult (*send_network_message)(struct IDiscordLobbyManager* manager, + DiscordLobbyId lobby_id, + DiscordUserId user_id, + uint8_t channel_id, + uint8_t* data, + uint32_t data_length); +}; + +struct IDiscordNetworkEvents { + void (*on_message)(void* event_data, + DiscordNetworkPeerId peer_id, + DiscordNetworkChannelId channel_id, + uint8_t* data, + uint32_t data_length); + void (*on_route_update)(void* event_data, const char* route_data); +}; + +struct IDiscordNetworkManager { + void (*get_peer_id)(struct IDiscordNetworkManager* manager, DiscordNetworkPeerId* peer_id); + enum EDiscordResult (*flush)(struct IDiscordNetworkManager* manager); + enum EDiscordResult (*open_peer)(struct IDiscordNetworkManager* manager, + DiscordNetworkPeerId peer_id, + const char* route_data); + enum EDiscordResult (*update_peer)(struct IDiscordNetworkManager* manager, + DiscordNetworkPeerId peer_id, + const char* route_data); + enum EDiscordResult (*close_peer)(struct IDiscordNetworkManager* manager, + DiscordNetworkPeerId peer_id); + enum EDiscordResult (*open_channel)(struct IDiscordNetworkManager* manager, + DiscordNetworkPeerId peer_id, + DiscordNetworkChannelId channel_id, + bool reliable); + enum EDiscordResult (*close_channel)(struct IDiscordNetworkManager* manager, + DiscordNetworkPeerId peer_id, + DiscordNetworkChannelId channel_id); + enum EDiscordResult (*send_message)(struct IDiscordNetworkManager* manager, + DiscordNetworkPeerId peer_id, + DiscordNetworkChannelId channel_id, + uint8_t* data, + uint32_t data_length); +}; + +struct IDiscordOverlayEvents { + void (*on_toggle)(void* event_data, bool locked); +}; + +struct IDiscordOverlayManager { + void (*is_enabled)(struct IDiscordOverlayManager* manager, bool* enabled); + void (*is_locked)(struct IDiscordOverlayManager* manager, bool* locked); + void (*set_locked)(struct IDiscordOverlayManager* manager, + bool locked, + void* callback_data, + void (*callback)(void* callback_data, enum EDiscordResult result)); + void (*open_activity_invite)(struct IDiscordOverlayManager* manager, + enum EDiscordActivityActionType type, + void* callback_data, + void (*callback)(void* callback_data, enum EDiscordResult result)); + void (*open_guild_invite)(struct IDiscordOverlayManager* manager, + const char* code, + void* callback_data, + void (*callback)(void* callback_data, enum EDiscordResult result)); +}; + +typedef void* IDiscordStorageEvents; + +struct IDiscordStorageManager { + enum EDiscordResult (*read)(struct IDiscordStorageManager* manager, + const char* name, + uint8_t* data, + uint32_t data_length, + uint32_t* read); + void (*read_async)(struct IDiscordStorageManager* manager, + const char* name, + void* callback_data, + void (*callback)(void* callback_data, + enum EDiscordResult result, + uint8_t* data, + uint32_t data_length)); + void (*read_async_partial)(struct IDiscordStorageManager* manager, + const char* name, + uint64_t offset, + uint64_t length, + void* callback_data, + void (*callback)(void* callback_data, + enum EDiscordResult result, + uint8_t* data, + uint32_t data_length)); + enum EDiscordResult (*write)(struct IDiscordStorageManager* manager, + const char* name, + uint8_t* data, + uint32_t data_length); + void (*write_async)(struct IDiscordStorageManager* manager, + const char* name, + uint8_t* data, + uint32_t data_length, + void* callback_data, + void (*callback)(void* callback_data, enum EDiscordResult result)); + enum EDiscordResult (*delete_)(struct IDiscordStorageManager* manager, const char* name); + enum EDiscordResult (*exists)(struct IDiscordStorageManager* manager, + const char* name, + bool* exists); + void (*count)(struct IDiscordStorageManager* manager, int32_t* count); + enum EDiscordResult (*stat)(struct IDiscordStorageManager* manager, + const char* name, + struct DiscordFileStat* stat); + enum EDiscordResult (*stat_at)(struct IDiscordStorageManager* manager, + int32_t index, + struct DiscordFileStat* stat); +}; + +struct IDiscordStoreEvents { + void (*on_entitlement_create)(void* event_data, struct DiscordEntitlement* entitlement); + void (*on_entitlement_delete)(void* event_data, struct DiscordEntitlement* entitlement); +}; + +struct IDiscordStoreManager { + void (*fetch_skus)(struct IDiscordStoreManager* manager, + void* callback_data, + void (*callback)(void* callback_data, enum EDiscordResult result)); + void (*count_skus)(struct IDiscordStoreManager* manager, int32_t* count); + enum EDiscordResult (*get_sku)(struct IDiscordStoreManager* manager, + DiscordSnowflake sku_id, + struct DiscordSku* sku); + enum EDiscordResult (*get_sku_at)(struct IDiscordStoreManager* manager, + int32_t index, + struct DiscordSku* sku); + void (*fetch_entitlements)(struct IDiscordStoreManager* manager, + void* callback_data, + void (*callback)(void* callback_data, enum EDiscordResult result)); + void (*count_entitlements)(struct IDiscordStoreManager* manager, int32_t* count); + enum EDiscordResult (*get_entitlement)(struct IDiscordStoreManager* manager, + DiscordSnowflake entitlement_id, + struct DiscordEntitlement* entitlement); + enum EDiscordResult (*get_entitlement_at)(struct IDiscordStoreManager* manager, + int32_t index, + struct DiscordEntitlement* entitlement); + enum EDiscordResult (*has_sku_entitlement)(struct IDiscordStoreManager* manager, + DiscordSnowflake sku_id, + bool* has_entitlement); + void (*start_purchase)(struct IDiscordStoreManager* manager, + DiscordSnowflake sku_id, + void* callback_data, + void (*callback)(void* callback_data, enum EDiscordResult result)); +}; + +typedef void* IDiscordCoreEvents; + +struct IDiscordCore { + void (*destroy)(struct IDiscordCore* core); + enum EDiscordResult (*run_callbacks)(struct IDiscordCore* core); + void (*set_log_hook)(struct IDiscordCore* core, + enum EDiscordLogLevel min_level, + void* hook_data, + void (*hook)(void* hook_data, + enum EDiscordLogLevel level, + const char* message)); + struct IDiscordApplicationManager* (*get_application_manager)(struct IDiscordCore* core); + struct IDiscordUserManager* (*get_user_manager)(struct IDiscordCore* core); + struct IDiscordImageManager* (*get_image_manager)(struct IDiscordCore* core); + struct IDiscordActivityManager* (*get_activity_manager)(struct IDiscordCore* core); + struct IDiscordRelationshipManager* (*get_relationship_manager)(struct IDiscordCore* core); + struct IDiscordLobbyManager* (*get_lobby_manager)(struct IDiscordCore* core); + struct IDiscordNetworkManager* (*get_network_manager)(struct IDiscordCore* core); + struct IDiscordOverlayManager* (*get_overlay_manager)(struct IDiscordCore* core); + struct IDiscordStorageManager* (*get_storage_manager)(struct IDiscordCore* core); + struct IDiscordStoreManager* (*get_store_manager)(struct IDiscordCore* core); +}; + +struct DiscordCreateParams { + DiscordClientId client_id; + uint64_t flags; + IDiscordCoreEvents* events; + void* event_data; + IDiscordApplicationEvents* application_events; + DiscordVersion application_version; + struct IDiscordUserEvents* user_events; + DiscordVersion user_version; + IDiscordImageEvents* image_events; + DiscordVersion image_version; + struct IDiscordActivityEvents* activity_events; + DiscordVersion activity_version; + struct IDiscordRelationshipEvents* relationship_events; + DiscordVersion relationship_version; + struct IDiscordLobbyEvents* lobby_events; + DiscordVersion lobby_version; + struct IDiscordNetworkEvents* network_events; + DiscordVersion network_version; + struct IDiscordOverlayEvents* overlay_events; + DiscordVersion overlay_version; + IDiscordStorageEvents* storage_events; + DiscordVersion storage_version; + struct IDiscordStoreEvents* store_events; + DiscordVersion store_version; +}; + +#ifdef __cplusplus +inline +#else +static +#endif + void + DiscordCreateParamsSetDefault(struct DiscordCreateParams* params) +{ + memset(params, 0, sizeof(struct DiscordCreateParams)); + params->application_version = DISCORD_APPLICATION_MANAGER_VERSION; + params->user_version = DISCORD_USER_MANAGER_VERSION; + params->image_version = DISCORD_IMAGE_MANAGER_VERSION; + params->activity_version = DISCORD_ACTIVITY_MANAGER_VERSION; + params->relationship_version = DISCORD_RELATIONSHIP_MANAGER_VERSION; + params->lobby_version = DISCORD_LOBBY_MANAGER_VERSION; + params->network_version = DISCORD_NETWORK_MANAGER_VERSION; + params->overlay_version = DISCORD_OVERLAY_MANAGER_VERSION; + params->storage_version = DISCORD_STORAGE_MANAGER_VERSION; + params->store_version = DISCORD_STORE_MANAGER_VERSION; +} + +enum EDiscordResult DiscordCreate(DiscordVersion version, + struct DiscordCreateParams* params, + struct IDiscordCore** result); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/source/application/discord/image_manager.cpp b/source/application/discord/image_manager.cpp new file mode 100644 index 0000000..03b1db4 --- /dev/null +++ b/source/application/discord/image_manager.cpp @@ -0,0 +1,57 @@ +#if !defined(_CRT_SECURE_NO_WARNINGS) +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include "image_manager.h" + +#include "core.h" + +#include +#include + +namespace discord { + +void ImageManager::Fetch(ImageHandle handle, + bool refresh, + std::function callback) +{ + static auto wrapper = + [](void* callbackData, EDiscordResult result, DiscordImageHandle handleResult) -> void { + std::unique_ptr> cb( + reinterpret_cast*>(callbackData)); + if (!cb || !(*cb)) { + return; + } + (*cb)(static_cast(result), *reinterpret_cast(&handleResult)); + }; + std::unique_ptr> cb{}; + cb.reset(new std::function(std::move(callback))); + internal_->fetch(internal_, + *reinterpret_cast(&handle), + (refresh ? 1 : 0), + cb.release(), + wrapper); +} + +Result ImageManager::GetDimensions(ImageHandle handle, ImageDimensions* dimensions) +{ + if (!dimensions) { + return Result::InternalError; + } + + auto result = internal_->get_dimensions(internal_, + *reinterpret_cast(&handle), + reinterpret_cast(dimensions)); + return static_cast(result); +} + +Result ImageManager::GetData(ImageHandle handle, std::uint8_t* data, std::uint32_t dataLength) +{ + auto result = internal_->get_data(internal_, + *reinterpret_cast(&handle), + reinterpret_cast(data), + dataLength); + return static_cast(result); +} + +} // namespace discord diff --git a/source/application/discord/image_manager.h b/source/application/discord/image_manager.h new file mode 100644 index 0000000..b096b17 --- /dev/null +++ b/source/application/discord/image_manager.h @@ -0,0 +1,28 @@ +#pragma once + +#include "types.h" + +namespace discord { + +class ImageManager final { +public: + ~ImageManager() = default; + + void Fetch(ImageHandle handle, bool refresh, std::function callback); + Result GetDimensions(ImageHandle handle, ImageDimensions* dimensions); + Result GetData(ImageHandle handle, std::uint8_t* data, std::uint32_t dataLength); + +private: + friend class Core; + + ImageManager() = default; + ImageManager(ImageManager const& rhs) = delete; + ImageManager& operator=(ImageManager const& rhs) = delete; + ImageManager(ImageManager&& rhs) = delete; + ImageManager& operator=(ImageManager&& rhs) = delete; + + IDiscordImageManager* internal_; + static IDiscordImageEvents events_; +}; + +} // namespace discord diff --git a/source/application/discord/lobby_manager.cpp b/source/application/discord/lobby_manager.cpp new file mode 100644 index 0000000..6bf1a1a --- /dev/null +++ b/source/application/discord/lobby_manager.cpp @@ -0,0 +1,547 @@ +#if !defined(_CRT_SECURE_NO_WARNINGS) +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include "lobby_manager.h" + +#include "core.h" + +#include +#include + +namespace discord { + +class LobbyEvents final { +public: + static void OnLobbyUpdate(void* callbackData, int64_t lobbyId) + { + auto* core = reinterpret_cast(callbackData); + if (!core) { + return; + } + + auto& module = core->LobbyManager(); + module.OnLobbyUpdate(lobbyId); + } + + static void OnLobbyDelete(void* callbackData, int64_t lobbyId, uint32_t reason) + { + auto* core = reinterpret_cast(callbackData); + if (!core) { + return; + } + + auto& module = core->LobbyManager(); + module.OnLobbyDelete(lobbyId, reason); + } + + static void OnMemberConnect(void* callbackData, int64_t lobbyId, int64_t userId) + { + auto* core = reinterpret_cast(callbackData); + if (!core) { + return; + } + + auto& module = core->LobbyManager(); + module.OnMemberConnect(lobbyId, userId); + } + + static void OnMemberUpdate(void* callbackData, int64_t lobbyId, int64_t userId) + { + auto* core = reinterpret_cast(callbackData); + if (!core) { + return; + } + + auto& module = core->LobbyManager(); + module.OnMemberUpdate(lobbyId, userId); + } + + static void OnMemberDisconnect(void* callbackData, int64_t lobbyId, int64_t userId) + { + auto* core = reinterpret_cast(callbackData); + if (!core) { + return; + } + + auto& module = core->LobbyManager(); + module.OnMemberDisconnect(lobbyId, userId); + } + + static void OnLobbyMessage(void* callbackData, + int64_t lobbyId, + int64_t userId, + uint8_t* data, + uint32_t dataLength) + { + auto* core = reinterpret_cast(callbackData); + if (!core) { + return; + } + + auto& module = core->LobbyManager(); + module.OnLobbyMessage(lobbyId, userId, data, dataLength); + } + + static void OnSpeaking(void* callbackData, int64_t lobbyId, int64_t userId, bool speaking) + { + auto* core = reinterpret_cast(callbackData); + if (!core) { + return; + } + + auto& module = core->LobbyManager(); + module.OnSpeaking(lobbyId, userId, (speaking != 0)); + } + + static void OnNetworkMessage(void* callbackData, + int64_t lobbyId, + int64_t userId, + uint8_t channelId, + uint8_t* data, + uint32_t dataLength) + { + auto* core = reinterpret_cast(callbackData); + if (!core) { + return; + } + + auto& module = core->LobbyManager(); + module.OnNetworkMessage(lobbyId, userId, channelId, data, dataLength); + } +}; + +IDiscordLobbyEvents LobbyManager::events_{ + &LobbyEvents::OnLobbyUpdate, + &LobbyEvents::OnLobbyDelete, + &LobbyEvents::OnMemberConnect, + &LobbyEvents::OnMemberUpdate, + &LobbyEvents::OnMemberDisconnect, + &LobbyEvents::OnLobbyMessage, + &LobbyEvents::OnSpeaking, + &LobbyEvents::OnNetworkMessage, +}; + +Result LobbyManager::GetLobbyCreateTransaction(LobbyTransaction* transaction) +{ + if (!transaction) { + return Result::InternalError; + } + + auto result = internal_->get_lobby_create_transaction(internal_, transaction->Receive()); + return static_cast(result); +} + +Result LobbyManager::GetLobbyUpdateTransaction(LobbyId lobbyId, LobbyTransaction* transaction) +{ + if (!transaction) { + return Result::InternalError; + } + + auto result = + internal_->get_lobby_update_transaction(internal_, lobbyId, transaction->Receive()); + return static_cast(result); +} + +Result LobbyManager::GetMemberUpdateTransaction(LobbyId lobbyId, + UserId userId, + LobbyMemberTransaction* transaction) +{ + if (!transaction) { + return Result::InternalError; + } + + auto result = + internal_->get_member_update_transaction(internal_, lobbyId, userId, transaction->Receive()); + return static_cast(result); +} + +void LobbyManager::CreateLobby(LobbyTransaction const& transaction, + std::function callback) +{ + static auto wrapper = + [](void* callbackData, EDiscordResult result, DiscordLobby* lobby) -> void { + std::unique_ptr> cb( + reinterpret_cast*>(callbackData)); + if (!cb || !(*cb)) { + return; + } + (*cb)(static_cast(result), *reinterpret_cast(lobby)); + }; + std::unique_ptr> cb{}; + cb.reset(new std::function(std::move(callback))); + internal_->create_lobby( + internal_, const_cast(transaction).Internal(), cb.release(), wrapper); +} + +void LobbyManager::UpdateLobby(LobbyId lobbyId, + LobbyTransaction const& transaction, + std::function callback) +{ + static auto wrapper = [](void* callbackData, EDiscordResult result) -> void { + std::unique_ptr> cb( + reinterpret_cast*>(callbackData)); + if (!cb || !(*cb)) { + return; + } + (*cb)(static_cast(result)); + }; + std::unique_ptr> cb{}; + cb.reset(new std::function(std::move(callback))); + internal_->update_lobby(internal_, + lobbyId, + const_cast(transaction).Internal(), + cb.release(), + wrapper); +} + +void LobbyManager::DeleteLobby(LobbyId lobbyId, std::function callback) +{ + static auto wrapper = [](void* callbackData, EDiscordResult result) -> void { + std::unique_ptr> cb( + reinterpret_cast*>(callbackData)); + if (!cb || !(*cb)) { + return; + } + (*cb)(static_cast(result)); + }; + std::unique_ptr> cb{}; + cb.reset(new std::function(std::move(callback))); + internal_->delete_lobby(internal_, lobbyId, cb.release(), wrapper); +} + +void LobbyManager::ConnectLobby(LobbyId lobbyId, + LobbySecret secret, + std::function callback) +{ + static auto wrapper = + [](void* callbackData, EDiscordResult result, DiscordLobby* lobby) -> void { + std::unique_ptr> cb( + reinterpret_cast*>(callbackData)); + if (!cb || !(*cb)) { + return; + } + (*cb)(static_cast(result), *reinterpret_cast(lobby)); + }; + std::unique_ptr> cb{}; + cb.reset(new std::function(std::move(callback))); + internal_->connect_lobby(internal_, lobbyId, const_cast(secret), cb.release(), wrapper); +} + +void LobbyManager::ConnectLobbyWithActivitySecret( + LobbySecret activitySecret, + std::function callback) +{ + static auto wrapper = + [](void* callbackData, EDiscordResult result, DiscordLobby* lobby) -> void { + std::unique_ptr> cb( + reinterpret_cast*>(callbackData)); + if (!cb || !(*cb)) { + return; + } + (*cb)(static_cast(result), *reinterpret_cast(lobby)); + }; + std::unique_ptr> cb{}; + cb.reset(new std::function(std::move(callback))); + internal_->connect_lobby_with_activity_secret( + internal_, const_cast(activitySecret), cb.release(), wrapper); +} + +void LobbyManager::DisconnectLobby(LobbyId lobbyId, std::function callback) +{ + static auto wrapper = [](void* callbackData, EDiscordResult result) -> void { + std::unique_ptr> cb( + reinterpret_cast*>(callbackData)); + if (!cb || !(*cb)) { + return; + } + (*cb)(static_cast(result)); + }; + std::unique_ptr> cb{}; + cb.reset(new std::function(std::move(callback))); + internal_->disconnect_lobby(internal_, lobbyId, cb.release(), wrapper); +} + +Result LobbyManager::GetLobby(LobbyId lobbyId, Lobby* lobby) +{ + if (!lobby) { + return Result::InternalError; + } + + auto result = internal_->get_lobby(internal_, lobbyId, reinterpret_cast(lobby)); + return static_cast(result); +} + +Result LobbyManager::GetLobbyActivitySecret(LobbyId lobbyId, char secret[128]) +{ + if (!secret) { + return Result::InternalError; + } + + auto result = internal_->get_lobby_activity_secret( + internal_, lobbyId, reinterpret_cast(secret)); + return static_cast(result); +} + +Result LobbyManager::GetLobbyMetadataValue(LobbyId lobbyId, MetadataKey key, char value[4096]) +{ + if (!value) { + return Result::InternalError; + } + + auto result = internal_->get_lobby_metadata_value( + internal_, lobbyId, const_cast(key), reinterpret_cast(value)); + return static_cast(result); +} + +Result LobbyManager::GetLobbyMetadataKey(LobbyId lobbyId, std::int32_t index, char key[256]) +{ + if (!key) { + return Result::InternalError; + } + + auto result = internal_->get_lobby_metadata_key( + internal_, lobbyId, index, reinterpret_cast(key)); + return static_cast(result); +} + +Result LobbyManager::LobbyMetadataCount(LobbyId lobbyId, std::int32_t* count) +{ + if (!count) { + return Result::InternalError; + } + + auto result = + internal_->lobby_metadata_count(internal_, lobbyId, reinterpret_cast(count)); + return static_cast(result); +} + +Result LobbyManager::MemberCount(LobbyId lobbyId, std::int32_t* count) +{ + if (!count) { + return Result::InternalError; + } + + auto result = internal_->member_count(internal_, lobbyId, reinterpret_cast(count)); + return static_cast(result); +} + +Result LobbyManager::GetMemberUserId(LobbyId lobbyId, std::int32_t index, UserId* userId) +{ + if (!userId) { + return Result::InternalError; + } + + auto result = + internal_->get_member_user_id(internal_, lobbyId, index, reinterpret_cast(userId)); + return static_cast(result); +} + +Result LobbyManager::GetMemberUser(LobbyId lobbyId, UserId userId, User* user) +{ + if (!user) { + return Result::InternalError; + } + + auto result = + internal_->get_member_user(internal_, lobbyId, userId, reinterpret_cast(user)); + return static_cast(result); +} + +Result LobbyManager::GetMemberMetadataValue(LobbyId lobbyId, + UserId userId, + MetadataKey key, + char value[4096]) +{ + if (!value) { + return Result::InternalError; + } + + auto result = + internal_->get_member_metadata_value(internal_, + lobbyId, + userId, + const_cast(key), + reinterpret_cast(value)); + return static_cast(result); +} + +Result LobbyManager::GetMemberMetadataKey(LobbyId lobbyId, + UserId userId, + std::int32_t index, + char key[256]) +{ + if (!key) { + return Result::InternalError; + } + + auto result = internal_->get_member_metadata_key( + internal_, lobbyId, userId, index, reinterpret_cast(key)); + return static_cast(result); +} + +Result LobbyManager::MemberMetadataCount(LobbyId lobbyId, UserId userId, std::int32_t* count) +{ + if (!count) { + return Result::InternalError; + } + + auto result = internal_->member_metadata_count( + internal_, lobbyId, userId, reinterpret_cast(count)); + return static_cast(result); +} + +void LobbyManager::UpdateMember(LobbyId lobbyId, + UserId userId, + LobbyMemberTransaction const& transaction, + std::function callback) +{ + static auto wrapper = [](void* callbackData, EDiscordResult result) -> void { + std::unique_ptr> cb( + reinterpret_cast*>(callbackData)); + if (!cb || !(*cb)) { + return; + } + (*cb)(static_cast(result)); + }; + std::unique_ptr> cb{}; + cb.reset(new std::function(std::move(callback))); + internal_->update_member(internal_, + lobbyId, + userId, + const_cast(transaction).Internal(), + cb.release(), + wrapper); +} + +void LobbyManager::SendLobbyMessage(LobbyId lobbyId, + std::uint8_t* data, + std::uint32_t dataLength, + std::function callback) +{ + static auto wrapper = [](void* callbackData, EDiscordResult result) -> void { + std::unique_ptr> cb( + reinterpret_cast*>(callbackData)); + if (!cb || !(*cb)) { + return; + } + (*cb)(static_cast(result)); + }; + std::unique_ptr> cb{}; + cb.reset(new std::function(std::move(callback))); + internal_->send_lobby_message( + internal_, lobbyId, reinterpret_cast(data), dataLength, cb.release(), wrapper); +} + +Result LobbyManager::GetSearchQuery(LobbySearchQuery* query) +{ + if (!query) { + return Result::InternalError; + } + + auto result = internal_->get_search_query(internal_, query->Receive()); + return static_cast(result); +} + +void LobbyManager::Search(LobbySearchQuery const& query, std::function callback) +{ + static auto wrapper = [](void* callbackData, EDiscordResult result) -> void { + std::unique_ptr> cb( + reinterpret_cast*>(callbackData)); + if (!cb || !(*cb)) { + return; + } + (*cb)(static_cast(result)); + }; + std::unique_ptr> cb{}; + cb.reset(new std::function(std::move(callback))); + internal_->search( + internal_, const_cast(query).Internal(), cb.release(), wrapper); +} + +void LobbyManager::LobbyCount(std::int32_t* count) +{ + if (!count) { + return; + } + + internal_->lobby_count(internal_, reinterpret_cast(count)); +} + +Result LobbyManager::GetLobbyId(std::int32_t index, LobbyId* lobbyId) +{ + if (!lobbyId) { + return Result::InternalError; + } + + auto result = internal_->get_lobby_id(internal_, index, reinterpret_cast(lobbyId)); + return static_cast(result); +} + +void LobbyManager::ConnectVoice(LobbyId lobbyId, std::function callback) +{ + static auto wrapper = [](void* callbackData, EDiscordResult result) -> void { + std::unique_ptr> cb( + reinterpret_cast*>(callbackData)); + if (!cb || !(*cb)) { + return; + } + (*cb)(static_cast(result)); + }; + std::unique_ptr> cb{}; + cb.reset(new std::function(std::move(callback))); + internal_->connect_voice(internal_, lobbyId, cb.release(), wrapper); +} + +void LobbyManager::DisconnectVoice(LobbyId lobbyId, std::function callback) +{ + static auto wrapper = [](void* callbackData, EDiscordResult result) -> void { + std::unique_ptr> cb( + reinterpret_cast*>(callbackData)); + if (!cb || !(*cb)) { + return; + } + (*cb)(static_cast(result)); + }; + std::unique_ptr> cb{}; + cb.reset(new std::function(std::move(callback))); + internal_->disconnect_voice(internal_, lobbyId, cb.release(), wrapper); +} + +Result LobbyManager::ConnectNetwork(LobbyId lobbyId) +{ + auto result = internal_->connect_network(internal_, lobbyId); + return static_cast(result); +} + +Result LobbyManager::DisconnectNetwork(LobbyId lobbyId) +{ + auto result = internal_->disconnect_network(internal_, lobbyId); + return static_cast(result); +} + +Result LobbyManager::FlushNetwork() +{ + auto result = internal_->flush_network(internal_); + return static_cast(result); +} + +Result LobbyManager::OpenNetworkChannel(LobbyId lobbyId, std::uint8_t channelId, bool reliable) +{ + auto result = + internal_->open_network_channel(internal_, lobbyId, channelId, (reliable ? 1 : 0)); + return static_cast(result); +} + +Result LobbyManager::SendNetworkMessage(LobbyId lobbyId, + UserId userId, + std::uint8_t channelId, + std::uint8_t* data, + std::uint32_t dataLength) +{ + auto result = internal_->send_network_message( + internal_, lobbyId, userId, channelId, reinterpret_cast(data), dataLength); + return static_cast(result); +} + +} // namespace discord diff --git a/source/application/discord/lobby_manager.h b/source/application/discord/lobby_manager.h new file mode 100644 index 0000000..96380cb --- /dev/null +++ b/source/application/discord/lobby_manager.h @@ -0,0 +1,88 @@ +#pragma once + +#include "types.h" + +namespace discord { + +class LobbyManager final { +public: + ~LobbyManager() = default; + + Result GetLobbyCreateTransaction(LobbyTransaction* transaction); + Result GetLobbyUpdateTransaction(LobbyId lobbyId, LobbyTransaction* transaction); + Result GetMemberUpdateTransaction(LobbyId lobbyId, + UserId userId, + LobbyMemberTransaction* transaction); + void CreateLobby(LobbyTransaction const& transaction, + std::function callback); + void UpdateLobby(LobbyId lobbyId, + LobbyTransaction const& transaction, + std::function callback); + void DeleteLobby(LobbyId lobbyId, std::function callback); + void ConnectLobby(LobbyId lobbyId, + LobbySecret secret, + std::function callback); + void ConnectLobbyWithActivitySecret(LobbySecret activitySecret, + std::function callback); + void DisconnectLobby(LobbyId lobbyId, std::function callback); + Result GetLobby(LobbyId lobbyId, Lobby* lobby); + Result GetLobbyActivitySecret(LobbyId lobbyId, char secret[128]); + Result GetLobbyMetadataValue(LobbyId lobbyId, MetadataKey key, char value[4096]); + Result GetLobbyMetadataKey(LobbyId lobbyId, std::int32_t index, char key[256]); + Result LobbyMetadataCount(LobbyId lobbyId, std::int32_t* count); + Result MemberCount(LobbyId lobbyId, std::int32_t* count); + Result GetMemberUserId(LobbyId lobbyId, std::int32_t index, UserId* userId); + Result GetMemberUser(LobbyId lobbyId, UserId userId, User* user); + Result GetMemberMetadataValue(LobbyId lobbyId, + UserId userId, + MetadataKey key, + char value[4096]); + Result GetMemberMetadataKey(LobbyId lobbyId, UserId userId, std::int32_t index, char key[256]); + Result MemberMetadataCount(LobbyId lobbyId, UserId userId, std::int32_t* count); + void UpdateMember(LobbyId lobbyId, + UserId userId, + LobbyMemberTransaction const& transaction, + std::function callback); + void SendLobbyMessage(LobbyId lobbyId, + std::uint8_t* data, + std::uint32_t dataLength, + std::function callback); + Result GetSearchQuery(LobbySearchQuery* query); + void Search(LobbySearchQuery const& query, std::function callback); + void LobbyCount(std::int32_t* count); + Result GetLobbyId(std::int32_t index, LobbyId* lobbyId); + void ConnectVoice(LobbyId lobbyId, std::function callback); + void DisconnectVoice(LobbyId lobbyId, std::function callback); + Result ConnectNetwork(LobbyId lobbyId); + Result DisconnectNetwork(LobbyId lobbyId); + Result FlushNetwork(); + Result OpenNetworkChannel(LobbyId lobbyId, std::uint8_t channelId, bool reliable); + Result SendNetworkMessage(LobbyId lobbyId, + UserId userId, + std::uint8_t channelId, + std::uint8_t* data, + std::uint32_t dataLength); + + Event OnLobbyUpdate; + Event OnLobbyDelete; + Event OnMemberConnect; + Event OnMemberUpdate; + Event OnMemberDisconnect; + Event OnLobbyMessage; + Event OnSpeaking; + Event OnNetworkMessage; + +private: + friend class Core; + + LobbyManager() = default; + LobbyManager(LobbyManager const& rhs) = delete; + LobbyManager& operator=(LobbyManager const& rhs) = delete; + LobbyManager(LobbyManager&& rhs) = delete; + LobbyManager& operator=(LobbyManager&& rhs) = delete; + + IDiscordLobbyManager* internal_; + static IDiscordLobbyEvents events_; +}; + +} // namespace discord diff --git a/source/application/discord/network_manager.cpp b/source/application/discord/network_manager.cpp new file mode 100644 index 0000000..97c219e --- /dev/null +++ b/source/application/discord/network_manager.cpp @@ -0,0 +1,103 @@ +#if !defined(_CRT_SECURE_NO_WARNINGS) +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include "network_manager.h" + +#include "core.h" + +#include +#include + +namespace discord { + +class NetworkEvents final { +public: + static void OnMessage(void* callbackData, + DiscordNetworkPeerId peerId, + DiscordNetworkChannelId channelId, + uint8_t* data, + uint32_t dataLength) + { + auto* core = reinterpret_cast(callbackData); + if (!core) { + return; + } + + auto& module = core->NetworkManager(); + module.OnMessage(peerId, channelId, data, dataLength); + } + + static void OnRouteUpdate(void* callbackData, char const* routeData) + { + auto* core = reinterpret_cast(callbackData); + if (!core) { + return; + } + + auto& module = core->NetworkManager(); + module.OnRouteUpdate(static_cast(routeData)); + } +}; + +IDiscordNetworkEvents NetworkManager::events_{ + &NetworkEvents::OnMessage, + &NetworkEvents::OnRouteUpdate, +}; + +void NetworkManager::GetPeerId(NetworkPeerId* peerId) +{ + if (!peerId) { + return; + } + + internal_->get_peer_id(internal_, reinterpret_cast(peerId)); +} + +Result NetworkManager::Flush() +{ + auto result = internal_->flush(internal_); + return static_cast(result); +} + +Result NetworkManager::OpenPeer(NetworkPeerId peerId, char const* routeData) +{ + auto result = internal_->open_peer(internal_, peerId, const_cast(routeData)); + return static_cast(result); +} + +Result NetworkManager::UpdatePeer(NetworkPeerId peerId, char const* routeData) +{ + auto result = internal_->update_peer(internal_, peerId, const_cast(routeData)); + return static_cast(result); +} + +Result NetworkManager::ClosePeer(NetworkPeerId peerId) +{ + auto result = internal_->close_peer(internal_, peerId); + return static_cast(result); +} + +Result NetworkManager::OpenChannel(NetworkPeerId peerId, NetworkChannelId channelId, bool reliable) +{ + auto result = internal_->open_channel(internal_, peerId, channelId, (reliable ? 1 : 0)); + return static_cast(result); +} + +Result NetworkManager::CloseChannel(NetworkPeerId peerId, NetworkChannelId channelId) +{ + auto result = internal_->close_channel(internal_, peerId, channelId); + return static_cast(result); +} + +Result NetworkManager::SendMessage(NetworkPeerId peerId, + NetworkChannelId channelId, + std::uint8_t* data, + std::uint32_t dataLength) +{ + auto result = internal_->send_message( + internal_, peerId, channelId, reinterpret_cast(data), dataLength); + return static_cast(result); +} + +} // namespace discord diff --git a/source/application/discord/network_manager.h b/source/application/discord/network_manager.h new file mode 100644 index 0000000..09c9735 --- /dev/null +++ b/source/application/discord/network_manager.h @@ -0,0 +1,39 @@ +#pragma once + +#include "types.h" + +namespace discord { + +class NetworkManager final { +public: + ~NetworkManager() = default; + + void GetPeerId(NetworkPeerId* peerId); + Result Flush(); + Result OpenPeer(NetworkPeerId peerId, char const* routeData); + Result UpdatePeer(NetworkPeerId peerId, char const* routeData); + Result ClosePeer(NetworkPeerId peerId); + Result OpenChannel(NetworkPeerId peerId, NetworkChannelId channelId, bool reliable); + Result CloseChannel(NetworkPeerId peerId, NetworkChannelId channelId); + Result SendMessage(NetworkPeerId peerId, + NetworkChannelId channelId, + std::uint8_t* data, + std::uint32_t dataLength); + + Event OnMessage; + Event OnRouteUpdate; + +private: + friend class Core; + + NetworkManager() = default; + NetworkManager(NetworkManager const& rhs) = delete; + NetworkManager& operator=(NetworkManager const& rhs) = delete; + NetworkManager(NetworkManager&& rhs) = delete; + NetworkManager& operator=(NetworkManager&& rhs) = delete; + + IDiscordNetworkManager* internal_; + static IDiscordNetworkEvents events_; +}; + +} // namespace discord diff --git a/source/application/discord/overlay_manager.cpp b/source/application/discord/overlay_manager.cpp new file mode 100644 index 0000000..51ee827 --- /dev/null +++ b/source/application/discord/overlay_manager.cpp @@ -0,0 +1,97 @@ +#if !defined(_CRT_SECURE_NO_WARNINGS) +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include "overlay_manager.h" + +#include "core.h" + +#include +#include + +namespace discord { + +class OverlayEvents final { +public: + static void OnToggle(void* callbackData, bool locked) + { + auto* core = reinterpret_cast(callbackData); + if (!core) { + return; + } + + auto& module = core->OverlayManager(); + module.OnToggle((locked != 0)); + } +}; + +IDiscordOverlayEvents OverlayManager::events_{ + &OverlayEvents::OnToggle, +}; + +void OverlayManager::IsEnabled(bool* enabled) +{ + if (!enabled) { + return; + } + + internal_->is_enabled(internal_, reinterpret_cast(enabled)); +} + +void OverlayManager::IsLocked(bool* locked) +{ + if (!locked) { + return; + } + + internal_->is_locked(internal_, reinterpret_cast(locked)); +} + +void OverlayManager::SetLocked(bool locked, std::function callback) +{ + static auto wrapper = [](void* callbackData, EDiscordResult result) -> void { + std::unique_ptr> cb( + reinterpret_cast*>(callbackData)); + if (!cb || !(*cb)) { + return; + } + (*cb)(static_cast(result)); + }; + std::unique_ptr> cb{}; + cb.reset(new std::function(std::move(callback))); + internal_->set_locked(internal_, (locked ? 1 : 0), cb.release(), wrapper); +} + +void OverlayManager::OpenActivityInvite(ActivityActionType type, + std::function callback) +{ + static auto wrapper = [](void* callbackData, EDiscordResult result) -> void { + std::unique_ptr> cb( + reinterpret_cast*>(callbackData)); + if (!cb || !(*cb)) { + return; + } + (*cb)(static_cast(result)); + }; + std::unique_ptr> cb{}; + cb.reset(new std::function(std::move(callback))); + internal_->open_activity_invite( + internal_, static_cast(type), cb.release(), wrapper); +} + +void OverlayManager::OpenGuildInvite(char const* code, std::function callback) +{ + static auto wrapper = [](void* callbackData, EDiscordResult result) -> void { + std::unique_ptr> cb( + reinterpret_cast*>(callbackData)); + if (!cb || !(*cb)) { + return; + } + (*cb)(static_cast(result)); + }; + std::unique_ptr> cb{}; + cb.reset(new std::function(std::move(callback))); + internal_->open_guild_invite(internal_, const_cast(code), cb.release(), wrapper); +} + +} // namespace discord diff --git a/source/application/discord/overlay_manager.h b/source/application/discord/overlay_manager.h new file mode 100644 index 0000000..4afba0b --- /dev/null +++ b/source/application/discord/overlay_manager.h @@ -0,0 +1,32 @@ +#pragma once + +#include "types.h" + +namespace discord { + +class OverlayManager final { +public: + ~OverlayManager() = default; + + void IsEnabled(bool* enabled); + void IsLocked(bool* locked); + void SetLocked(bool locked, std::function callback); + void OpenActivityInvite(ActivityActionType type, std::function callback); + void OpenGuildInvite(char const* code, std::function callback); + + Event OnToggle; + +private: + friend class Core; + + OverlayManager() = default; + OverlayManager(OverlayManager const& rhs) = delete; + OverlayManager& operator=(OverlayManager const& rhs) = delete; + OverlayManager(OverlayManager&& rhs) = delete; + OverlayManager& operator=(OverlayManager&& rhs) = delete; + + IDiscordOverlayManager* internal_; + static IDiscordOverlayEvents events_; +}; + +} // namespace discord diff --git a/source/application/discord/relationship_manager.cpp b/source/application/discord/relationship_manager.cpp new file mode 100644 index 0000000..005f2b0 --- /dev/null +++ b/source/application/discord/relationship_manager.cpp @@ -0,0 +1,90 @@ +#if !defined(_CRT_SECURE_NO_WARNINGS) +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include "relationship_manager.h" + +#include "core.h" + +#include +#include + +namespace discord { + +class RelationshipEvents final { +public: + static void OnRefresh(void* callbackData) + { + auto* core = reinterpret_cast(callbackData); + if (!core) { + return; + } + + auto& module = core->RelationshipManager(); + module.OnRefresh(); + } + + static void OnRelationshipUpdate(void* callbackData, DiscordRelationship* relationship) + { + auto* core = reinterpret_cast(callbackData); + if (!core) { + return; + } + + auto& module = core->RelationshipManager(); + module.OnRelationshipUpdate(*reinterpret_cast(relationship)); + } +}; + +IDiscordRelationshipEvents RelationshipManager::events_{ + &RelationshipEvents::OnRefresh, + &RelationshipEvents::OnRelationshipUpdate, +}; + +void RelationshipManager::Filter(std::function filter) +{ + static auto wrapper = [](void* callbackData, DiscordRelationship* relationship) -> bool { + auto cb(reinterpret_cast*>(callbackData)); + if (!cb || !(*cb)) { + return {}; + } + return (*cb)(*reinterpret_cast(relationship)); + }; + std::unique_ptr> cb{}; + cb.reset(new std::function(std::move(filter))); + internal_->filter(internal_, cb.get(), wrapper); +} + +Result RelationshipManager::Count(std::int32_t* count) +{ + if (!count) { + return Result::InternalError; + } + + auto result = internal_->count(internal_, reinterpret_cast(count)); + return static_cast(result); +} + +Result RelationshipManager::Get(UserId userId, Relationship* relationship) +{ + if (!relationship) { + return Result::InternalError; + } + + auto result = + internal_->get(internal_, userId, reinterpret_cast(relationship)); + return static_cast(result); +} + +Result RelationshipManager::GetAt(std::uint32_t index, Relationship* relationship) +{ + if (!relationship) { + return Result::InternalError; + } + + auto result = + internal_->get_at(internal_, index, reinterpret_cast(relationship)); + return static_cast(result); +} + +} // namespace discord diff --git a/source/application/discord/relationship_manager.h b/source/application/discord/relationship_manager.h new file mode 100644 index 0000000..e9cd016 --- /dev/null +++ b/source/application/discord/relationship_manager.h @@ -0,0 +1,32 @@ +#pragma once + +#include "types.h" + +namespace discord { + +class RelationshipManager final { +public: + ~RelationshipManager() = default; + + void Filter(std::function filter); + Result Count(std::int32_t* count); + Result Get(UserId userId, Relationship* relationship); + Result GetAt(std::uint32_t index, Relationship* relationship); + + Event<> OnRefresh; + Event OnRelationshipUpdate; + +private: + friend class Core; + + RelationshipManager() = default; + RelationshipManager(RelationshipManager const& rhs) = delete; + RelationshipManager& operator=(RelationshipManager const& rhs) = delete; + RelationshipManager(RelationshipManager&& rhs) = delete; + RelationshipManager& operator=(RelationshipManager&& rhs) = delete; + + IDiscordRelationshipManager* internal_; + static IDiscordRelationshipEvents events_; +}; + +} // namespace discord diff --git a/source/application/discord/storage_manager.cpp b/source/application/discord/storage_manager.cpp new file mode 100644 index 0000000..51fb4c7 --- /dev/null +++ b/source/application/discord/storage_manager.cpp @@ -0,0 +1,148 @@ +#if !defined(_CRT_SECURE_NO_WARNINGS) +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include "storage_manager.h" + +#include "core.h" + +#include +#include + +namespace discord { + +Result StorageManager::Read(char const* name, + std::uint8_t* data, + std::uint32_t dataLength, + std::uint32_t* read) +{ + if (!read) { + return Result::InternalError; + } + + auto result = internal_->read(internal_, + const_cast(name), + reinterpret_cast(data), + dataLength, + reinterpret_cast(read)); + return static_cast(result); +} + +void StorageManager::ReadAsync(char const* name, + std::function callback) +{ + static auto wrapper = + [](void* callbackData, EDiscordResult result, uint8_t* data, uint32_t dataLength) -> void { + std::unique_ptr> cb( + reinterpret_cast*>( + callbackData)); + if (!cb || !(*cb)) { + return; + } + (*cb)(static_cast(result), data, dataLength); + }; + std::unique_ptr> cb{}; + cb.reset(new std::function(std::move(callback))); + internal_->read_async(internal_, const_cast(name), cb.release(), wrapper); +} + +void StorageManager::ReadAsyncPartial( + char const* name, + std::uint64_t offset, + std::uint64_t length, + std::function callback) +{ + static auto wrapper = + [](void* callbackData, EDiscordResult result, uint8_t* data, uint32_t dataLength) -> void { + std::unique_ptr> cb( + reinterpret_cast*>( + callbackData)); + if (!cb || !(*cb)) { + return; + } + (*cb)(static_cast(result), data, dataLength); + }; + std::unique_ptr> cb{}; + cb.reset(new std::function(std::move(callback))); + internal_->read_async_partial( + internal_, const_cast(name), offset, length, cb.release(), wrapper); +} + +Result StorageManager::Write(char const* name, std::uint8_t* data, std::uint32_t dataLength) +{ + auto result = internal_->write( + internal_, const_cast(name), reinterpret_cast(data), dataLength); + return static_cast(result); +} + +void StorageManager::WriteAsync(char const* name, + std::uint8_t* data, + std::uint32_t dataLength, + std::function callback) +{ + static auto wrapper = [](void* callbackData, EDiscordResult result) -> void { + std::unique_ptr> cb( + reinterpret_cast*>(callbackData)); + if (!cb || !(*cb)) { + return; + } + (*cb)(static_cast(result)); + }; + std::unique_ptr> cb{}; + cb.reset(new std::function(std::move(callback))); + internal_->write_async(internal_, + const_cast(name), + reinterpret_cast(data), + dataLength, + cb.release(), + wrapper); +} + +Result StorageManager::Delete(char const* name) +{ + auto result = internal_->delete_(internal_, const_cast(name)); + return static_cast(result); +} + +Result StorageManager::Exists(char const* name, bool* exists) +{ + if (!exists) { + return Result::InternalError; + } + + auto result = + internal_->exists(internal_, const_cast(name), reinterpret_cast(exists)); + return static_cast(result); +} + +void StorageManager::Count(std::int32_t* count) +{ + if (!count) { + return; + } + + internal_->count(internal_, reinterpret_cast(count)); +} + +Result StorageManager::Stat(char const* name, FileStat* stat) +{ + if (!stat) { + return Result::InternalError; + } + + auto result = + internal_->stat(internal_, const_cast(name), reinterpret_cast(stat)); + return static_cast(result); +} + +Result StorageManager::StatAt(std::int32_t index, FileStat* stat) +{ + if (!stat) { + return Result::InternalError; + } + + auto result = internal_->stat_at(internal_, index, reinterpret_cast(stat)); + return static_cast(result); +} + +} // namespace discord diff --git a/source/application/discord/storage_manager.h b/source/application/discord/storage_manager.h new file mode 100644 index 0000000..cab7868 --- /dev/null +++ b/source/application/discord/storage_manager.h @@ -0,0 +1,45 @@ +#pragma once + +#include "types.h" + +namespace discord { + +class StorageManager final { +public: + ~StorageManager() = default; + + Result Read(char const* name, + std::uint8_t* data, + std::uint32_t dataLength, + std::uint32_t* read); + void ReadAsync(char const* name, + std::function callback); + void ReadAsyncPartial(char const* name, + std::uint64_t offset, + std::uint64_t length, + std::function callback); + Result Write(char const* name, std::uint8_t* data, std::uint32_t dataLength); + void WriteAsync(char const* name, + std::uint8_t* data, + std::uint32_t dataLength, + std::function callback); + Result Delete(char const* name); + Result Exists(char const* name, bool* exists); + void Count(std::int32_t* count); + Result Stat(char const* name, FileStat* stat); + Result StatAt(std::int32_t index, FileStat* stat); + +private: + friend class Core; + + StorageManager() = default; + StorageManager(StorageManager const& rhs) = delete; + StorageManager& operator=(StorageManager const& rhs) = delete; + StorageManager(StorageManager&& rhs) = delete; + StorageManager& operator=(StorageManager&& rhs) = delete; + + IDiscordStorageManager* internal_; + static IDiscordStorageEvents events_; +}; + +} // namespace discord diff --git a/source/application/discord/store_manager.cpp b/source/application/discord/store_manager.cpp new file mode 100644 index 0000000..d6bdad1 --- /dev/null +++ b/source/application/discord/store_manager.cpp @@ -0,0 +1,160 @@ +#if !defined(_CRT_SECURE_NO_WARNINGS) +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include "store_manager.h" + +#include "core.h" + +#include +#include + +namespace discord { + +class StoreEvents final { +public: + static void OnEntitlementCreate(void* callbackData, DiscordEntitlement* entitlement) + { + auto* core = reinterpret_cast(callbackData); + if (!core) { + return; + } + + auto& module = core->StoreManager(); + module.OnEntitlementCreate(*reinterpret_cast(entitlement)); + } + + static void OnEntitlementDelete(void* callbackData, DiscordEntitlement* entitlement) + { + auto* core = reinterpret_cast(callbackData); + if (!core) { + return; + } + + auto& module = core->StoreManager(); + module.OnEntitlementDelete(*reinterpret_cast(entitlement)); + } +}; + +IDiscordStoreEvents StoreManager::events_{ + &StoreEvents::OnEntitlementCreate, + &StoreEvents::OnEntitlementDelete, +}; + +void StoreManager::FetchSkus(std::function callback) +{ + static auto wrapper = [](void* callbackData, EDiscordResult result) -> void { + std::unique_ptr> cb( + reinterpret_cast*>(callbackData)); + if (!cb || !(*cb)) { + return; + } + (*cb)(static_cast(result)); + }; + std::unique_ptr> cb{}; + cb.reset(new std::function(std::move(callback))); + internal_->fetch_skus(internal_, cb.release(), wrapper); +} + +void StoreManager::CountSkus(std::int32_t* count) +{ + if (!count) { + return; + } + + internal_->count_skus(internal_, reinterpret_cast(count)); +} + +Result StoreManager::GetSku(Snowflake skuId, Sku* sku) +{ + if (!sku) { + return Result::InternalError; + } + + auto result = internal_->get_sku(internal_, skuId, reinterpret_cast(sku)); + return static_cast(result); +} + +Result StoreManager::GetSkuAt(std::int32_t index, Sku* sku) +{ + if (!sku) { + return Result::InternalError; + } + + auto result = internal_->get_sku_at(internal_, index, reinterpret_cast(sku)); + return static_cast(result); +} + +void StoreManager::FetchEntitlements(std::function callback) +{ + static auto wrapper = [](void* callbackData, EDiscordResult result) -> void { + std::unique_ptr> cb( + reinterpret_cast*>(callbackData)); + if (!cb || !(*cb)) { + return; + } + (*cb)(static_cast(result)); + }; + std::unique_ptr> cb{}; + cb.reset(new std::function(std::move(callback))); + internal_->fetch_entitlements(internal_, cb.release(), wrapper); +} + +void StoreManager::CountEntitlements(std::int32_t* count) +{ + if (!count) { + return; + } + + internal_->count_entitlements(internal_, reinterpret_cast(count)); +} + +Result StoreManager::GetEntitlement(Snowflake entitlementId, Entitlement* entitlement) +{ + if (!entitlement) { + return Result::InternalError; + } + + auto result = internal_->get_entitlement( + internal_, entitlementId, reinterpret_cast(entitlement)); + return static_cast(result); +} + +Result StoreManager::GetEntitlementAt(std::int32_t index, Entitlement* entitlement) +{ + if (!entitlement) { + return Result::InternalError; + } + + auto result = internal_->get_entitlement_at( + internal_, index, reinterpret_cast(entitlement)); + return static_cast(result); +} + +Result StoreManager::HasSkuEntitlement(Snowflake skuId, bool* hasEntitlement) +{ + if (!hasEntitlement) { + return Result::InternalError; + } + + auto result = + internal_->has_sku_entitlement(internal_, skuId, reinterpret_cast(hasEntitlement)); + return static_cast(result); +} + +void StoreManager::StartPurchase(Snowflake skuId, std::function callback) +{ + static auto wrapper = [](void* callbackData, EDiscordResult result) -> void { + std::unique_ptr> cb( + reinterpret_cast*>(callbackData)); + if (!cb || !(*cb)) { + return; + } + (*cb)(static_cast(result)); + }; + std::unique_ptr> cb{}; + cb.reset(new std::function(std::move(callback))); + internal_->start_purchase(internal_, skuId, cb.release(), wrapper); +} + +} // namespace discord diff --git a/source/application/discord/store_manager.h b/source/application/discord/store_manager.h new file mode 100644 index 0000000..8e0df8a --- /dev/null +++ b/source/application/discord/store_manager.h @@ -0,0 +1,38 @@ +#pragma once + +#include "types.h" + +namespace discord { + +class StoreManager final { +public: + ~StoreManager() = default; + + void FetchSkus(std::function callback); + void CountSkus(std::int32_t* count); + Result GetSku(Snowflake skuId, Sku* sku); + Result GetSkuAt(std::int32_t index, Sku* sku); + void FetchEntitlements(std::function callback); + void CountEntitlements(std::int32_t* count); + Result GetEntitlement(Snowflake entitlementId, Entitlement* entitlement); + Result GetEntitlementAt(std::int32_t index, Entitlement* entitlement); + Result HasSkuEntitlement(Snowflake skuId, bool* hasEntitlement); + void StartPurchase(Snowflake skuId, std::function callback); + + Event OnEntitlementCreate; + Event OnEntitlementDelete; + +private: + friend class Core; + + StoreManager() = default; + StoreManager(StoreManager const& rhs) = delete; + StoreManager& operator=(StoreManager const& rhs) = delete; + StoreManager(StoreManager&& rhs) = delete; + StoreManager& operator=(StoreManager&& rhs) = delete; + + IDiscordStoreManager* internal_; + static IDiscordStoreEvents events_; +}; + +} // namespace discord diff --git a/source/application/discord/types.cpp b/source/application/discord/types.cpp new file mode 100644 index 0000000..7e58ad1 --- /dev/null +++ b/source/application/discord/types.cpp @@ -0,0 +1,707 @@ +#if !defined(_CRT_SECURE_NO_WARNINGS) +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include "types.h" + +#include +#include + +namespace discord { + +void User::SetId(UserId id) +{ + internal_.id = id; +} + +UserId User::GetId() const +{ + return internal_.id; +} + +void User::SetUsername(char const* username) +{ + strncpy(internal_.username, username, 256); + internal_.username[256 - 1] = '\0'; +} + +char const* User::GetUsername() const +{ + return internal_.username; +} + +void User::SetDiscriminator(char const* discriminator) +{ + strncpy(internal_.discriminator, discriminator, 8); + internal_.discriminator[8 - 1] = '\0'; +} + +char const* User::GetDiscriminator() const +{ + return internal_.discriminator; +} + +void User::SetAvatar(char const* avatar) +{ + strncpy(internal_.avatar, avatar, 128); + internal_.avatar[128 - 1] = '\0'; +} + +char const* User::GetAvatar() const +{ + return internal_.avatar; +} + +void User::SetBot(bool bot) +{ + internal_.bot = bot; +} + +bool User::GetBot() const +{ + return internal_.bot != 0; +} + +void OAuth2Token::SetAccessToken(char const* accessToken) +{ + strncpy(internal_.access_token, accessToken, 128); + internal_.access_token[128 - 1] = '\0'; +} + +char const* OAuth2Token::GetAccessToken() const +{ + return internal_.access_token; +} + +void OAuth2Token::SetScopes(char const* scopes) +{ + strncpy(internal_.scopes, scopes, 1024); + internal_.scopes[1024 - 1] = '\0'; +} + +char const* OAuth2Token::GetScopes() const +{ + return internal_.scopes; +} + +void OAuth2Token::SetExpires(Timestamp expires) +{ + internal_.expires = expires; +} + +Timestamp OAuth2Token::GetExpires() const +{ + return internal_.expires; +} + +void ImageHandle::SetType(ImageType type) +{ + internal_.type = static_cast(type); +} + +ImageType ImageHandle::GetType() const +{ + return static_cast(internal_.type); +} + +void ImageHandle::SetId(std::int64_t id) +{ + internal_.id = id; +} + +std::int64_t ImageHandle::GetId() const +{ + return internal_.id; +} + +void ImageHandle::SetSize(std::uint32_t size) +{ + internal_.size = size; +} + +std::uint32_t ImageHandle::GetSize() const +{ + return internal_.size; +} + +void ImageDimensions::SetWidth(std::uint32_t width) +{ + internal_.width = width; +} + +std::uint32_t ImageDimensions::GetWidth() const +{ + return internal_.width; +} + +void ImageDimensions::SetHeight(std::uint32_t height) +{ + internal_.height = height; +} + +std::uint32_t ImageDimensions::GetHeight() const +{ + return internal_.height; +} + +void ActivityTimestamps::SetStart(Timestamp start) +{ + internal_.start = start; +} + +Timestamp ActivityTimestamps::GetStart() const +{ + return internal_.start; +} + +void ActivityTimestamps::SetEnd(Timestamp end) +{ + internal_.end = end; +} + +Timestamp ActivityTimestamps::GetEnd() const +{ + return internal_.end; +} + +void ActivityAssets::SetLargeImage(char const* largeImage) +{ + strncpy(internal_.large_image, largeImage, 128); + internal_.large_image[128 - 1] = '\0'; +} + +char const* ActivityAssets::GetLargeImage() const +{ + return internal_.large_image; +} + +void ActivityAssets::SetLargeText(char const* largeText) +{ + strncpy(internal_.large_text, largeText, 128); + internal_.large_text[128 - 1] = '\0'; +} + +char const* ActivityAssets::GetLargeText() const +{ + return internal_.large_text; +} + +void ActivityAssets::SetSmallImage(char const* smallImage) +{ + strncpy(internal_.small_image, smallImage, 128); + internal_.small_image[128 - 1] = '\0'; +} + +char const* ActivityAssets::GetSmallImage() const +{ + return internal_.small_image; +} + +void ActivityAssets::SetSmallText(char const* smallText) +{ + strncpy(internal_.small_text, smallText, 128); + internal_.small_text[128 - 1] = '\0'; +} + +char const* ActivityAssets::GetSmallText() const +{ + return internal_.small_text; +} + +void PartySize::SetCurrentSize(std::int32_t currentSize) +{ + internal_.current_size = currentSize; +} + +std::int32_t PartySize::GetCurrentSize() const +{ + return internal_.current_size; +} + +void PartySize::SetMaxSize(std::int32_t maxSize) +{ + internal_.max_size = maxSize; +} + +std::int32_t PartySize::GetMaxSize() const +{ + return internal_.max_size; +} + +void ActivityParty::SetId(char const* id) +{ + strncpy(internal_.id, id, 128); + internal_.id[128 - 1] = '\0'; +} + +char const* ActivityParty::GetId() const +{ + return internal_.id; +} + +PartySize& ActivityParty::GetSize() +{ + return reinterpret_cast(internal_.size); +} + +PartySize const& ActivityParty::GetSize() const +{ + return reinterpret_cast(internal_.size); +} + +void ActivitySecrets::SetMatch(char const* match) +{ + strncpy(internal_.match, match, 128); + internal_.match[128 - 1] = '\0'; +} + +char const* ActivitySecrets::GetMatch() const +{ + return internal_.match; +} + +void ActivitySecrets::SetJoin(char const* join) +{ + strncpy(internal_.join, join, 128); + internal_.join[128 - 1] = '\0'; +} + +char const* ActivitySecrets::GetJoin() const +{ + return internal_.join; +} + +void ActivitySecrets::SetSpectate(char const* spectate) +{ + strncpy(internal_.spectate, spectate, 128); + internal_.spectate[128 - 1] = '\0'; +} + +char const* ActivitySecrets::GetSpectate() const +{ + return internal_.spectate; +} + +void Activity::SetType(ActivityType type) +{ + internal_.type = static_cast(type); +} + +ActivityType Activity::GetType() const +{ + return static_cast(internal_.type); +} + +void Activity::SetApplicationId(std::int64_t applicationId) +{ + internal_.application_id = applicationId; +} + +std::int64_t Activity::GetApplicationId() const +{ + return internal_.application_id; +} + +void Activity::SetName(char const* name) +{ + strncpy(internal_.name, name, 128); + internal_.name[128 - 1] = '\0'; +} + +char const* Activity::GetName() const +{ + return internal_.name; +} + +void Activity::SetState(char const* state) +{ + strncpy(internal_.state, state, 128); + internal_.state[128 - 1] = '\0'; +} + +char const* Activity::GetState() const +{ + return internal_.state; +} + +void Activity::SetDetails(char const* details) +{ + strncpy(internal_.details, details, 128); + internal_.details[128 - 1] = '\0'; +} + +char const* Activity::GetDetails() const +{ + return internal_.details; +} + +ActivityTimestamps& Activity::GetTimestamps() +{ + return reinterpret_cast(internal_.timestamps); +} + +ActivityTimestamps const& Activity::GetTimestamps() const +{ + return reinterpret_cast(internal_.timestamps); +} + +ActivityAssets& Activity::GetAssets() +{ + return reinterpret_cast(internal_.assets); +} + +ActivityAssets const& Activity::GetAssets() const +{ + return reinterpret_cast(internal_.assets); +} + +ActivityParty& Activity::GetParty() +{ + return reinterpret_cast(internal_.party); +} + +ActivityParty const& Activity::GetParty() const +{ + return reinterpret_cast(internal_.party); +} + +ActivitySecrets& Activity::GetSecrets() +{ + return reinterpret_cast(internal_.secrets); +} + +ActivitySecrets const& Activity::GetSecrets() const +{ + return reinterpret_cast(internal_.secrets); +} + +void Activity::SetInstance(bool instance) +{ + internal_.instance = instance; +} + +bool Activity::GetInstance() const +{ + return internal_.instance != 0; +} + +void Presence::SetStatus(Status status) +{ + internal_.status = static_cast(status); +} + +Status Presence::GetStatus() const +{ + return static_cast(internal_.status); +} + +Activity& Presence::GetActivity() +{ + return reinterpret_cast(internal_.activity); +} + +Activity const& Presence::GetActivity() const +{ + return reinterpret_cast(internal_.activity); +} + +void Relationship::SetType(RelationshipType type) +{ + internal_.type = static_cast(type); +} + +RelationshipType Relationship::GetType() const +{ + return static_cast(internal_.type); +} + +User& Relationship::GetUser() +{ + return reinterpret_cast(internal_.user); +} + +User const& Relationship::GetUser() const +{ + return reinterpret_cast(internal_.user); +} + +Presence& Relationship::GetPresence() +{ + return reinterpret_cast(internal_.presence); +} + +Presence const& Relationship::GetPresence() const +{ + return reinterpret_cast(internal_.presence); +} + +void Lobby::SetId(LobbyId id) +{ + internal_.id = id; +} + +LobbyId Lobby::GetId() const +{ + return internal_.id; +} + +void Lobby::SetType(LobbyType type) +{ + internal_.type = static_cast(type); +} + +LobbyType Lobby::GetType() const +{ + return static_cast(internal_.type); +} + +void Lobby::SetOwnerId(UserId ownerId) +{ + internal_.owner_id = ownerId; +} + +UserId Lobby::GetOwnerId() const +{ + return internal_.owner_id; +} + +void Lobby::SetSecret(LobbySecret secret) +{ + strncpy(internal_.secret, secret, 128); + internal_.secret[128 - 1] = '\0'; +} + +LobbySecret Lobby::GetSecret() const +{ + return internal_.secret; +} + +void Lobby::SetCapacity(std::uint32_t capacity) +{ + internal_.capacity = capacity; +} + +std::uint32_t Lobby::GetCapacity() const +{ + return internal_.capacity; +} + +void Lobby::SetLocked(bool locked) +{ + internal_.locked = locked; +} + +bool Lobby::GetLocked() const +{ + return internal_.locked != 0; +} + +void FileStat::SetFilename(char const* filename) +{ + strncpy(internal_.filename, filename, 260); + internal_.filename[260 - 1] = '\0'; +} + +char const* FileStat::GetFilename() const +{ + return internal_.filename; +} + +void FileStat::SetSize(std::uint64_t size) +{ + internal_.size = size; +} + +std::uint64_t FileStat::GetSize() const +{ + return internal_.size; +} + +void FileStat::SetLastModified(std::uint64_t lastModified) +{ + internal_.last_modified = lastModified; +} + +std::uint64_t FileStat::GetLastModified() const +{ + return internal_.last_modified; +} + +void Entitlement::SetId(Snowflake id) +{ + internal_.id = id; +} + +Snowflake Entitlement::GetId() const +{ + return internal_.id; +} + +void Entitlement::SetType(EntitlementType type) +{ + internal_.type = static_cast(type); +} + +EntitlementType Entitlement::GetType() const +{ + return static_cast(internal_.type); +} + +void Entitlement::SetSkuId(Snowflake skuId) +{ + internal_.sku_id = skuId; +} + +Snowflake Entitlement::GetSkuId() const +{ + return internal_.sku_id; +} + +void SkuPrice::SetAmount(std::uint32_t amount) +{ + internal_.amount = amount; +} + +std::uint32_t SkuPrice::GetAmount() const +{ + return internal_.amount; +} + +void SkuPrice::SetCurrency(char const* currency) +{ + strncpy(internal_.currency, currency, 16); + internal_.currency[16 - 1] = '\0'; +} + +char const* SkuPrice::GetCurrency() const +{ + return internal_.currency; +} + +void Sku::SetId(Snowflake id) +{ + internal_.id = id; +} + +Snowflake Sku::GetId() const +{ + return internal_.id; +} + +void Sku::SetType(SkuType type) +{ + internal_.type = static_cast(type); +} + +SkuType Sku::GetType() const +{ + return static_cast(internal_.type); +} + +void Sku::SetName(char const* name) +{ + strncpy(internal_.name, name, 256); + internal_.name[256 - 1] = '\0'; +} + +char const* Sku::GetName() const +{ + return internal_.name; +} + +SkuPrice& Sku::GetPrice() +{ + return reinterpret_cast(internal_.price); +} + +SkuPrice const& Sku::GetPrice() const +{ + return reinterpret_cast(internal_.price); +} + +Result LobbyTransaction::SetType(LobbyType type) +{ + auto result = internal_->set_type(internal_, static_cast(type)); + return static_cast(result); +} + +Result LobbyTransaction::SetOwner(UserId ownerId) +{ + auto result = internal_->set_owner(internal_, ownerId); + return static_cast(result); +} + +Result LobbyTransaction::SetCapacity(std::uint32_t capacity) +{ + auto result = internal_->set_capacity(internal_, capacity); + return static_cast(result); +} + +Result LobbyTransaction::SetMetadata(MetadataKey key, MetadataValue value) +{ + auto result = + internal_->set_metadata(internal_, const_cast(key), const_cast(value)); + return static_cast(result); +} + +Result LobbyTransaction::DeleteMetadata(MetadataKey key) +{ + auto result = internal_->delete_metadata(internal_, const_cast(key)); + return static_cast(result); +} + +Result LobbyTransaction::SetLocked(bool locked) +{ + auto result = internal_->set_locked(internal_, (locked ? 1 : 0)); + return static_cast(result); +} + +Result LobbyMemberTransaction::SetMetadata(MetadataKey key, MetadataValue value) +{ + auto result = + internal_->set_metadata(internal_, const_cast(key), const_cast(value)); + return static_cast(result); +} + +Result LobbyMemberTransaction::DeleteMetadata(MetadataKey key) +{ + auto result = internal_->delete_metadata(internal_, const_cast(key)); + return static_cast(result); +} + +Result LobbySearchQuery::Filter(MetadataKey key, + LobbySearchComparison comparison, + LobbySearchCast cast, + MetadataValue value) +{ + auto result = internal_->filter(internal_, + const_cast(key), + static_cast(comparison), + static_cast(cast), + const_cast(value)); + return static_cast(result); +} + +Result LobbySearchQuery::Sort(MetadataKey key, LobbySearchCast cast, MetadataValue value) +{ + auto result = internal_->sort(internal_, + const_cast(key), + static_cast(cast), + const_cast(value)); + return static_cast(result); +} + +Result LobbySearchQuery::Limit(std::uint32_t limit) +{ + auto result = internal_->limit(internal_, limit); + return static_cast(result); +} + +Result LobbySearchQuery::Distance(LobbySearchDistance distance) +{ + auto result = + internal_->distance(internal_, static_cast(distance)); + return static_cast(result); +} + +} // namespace discord diff --git a/source/application/discord/types.h b/source/application/discord/types.h new file mode 100644 index 0000000..5032592 --- /dev/null +++ b/source/application/discord/types.h @@ -0,0 +1,426 @@ +#pragma once + +#include "ffi.h" +#include "event.h" + +namespace discord { + +enum class Result { + Ok, + ServiceUnavailable, + InvalidVersion, + LockFailed, + InternalError, + InvalidPayload, + InvalidCommand, + InvalidPermissions, + NotFetched, + NotFound, + Conflict, + InvalidSecret, + InvalidJoinSecret, + NoEligibleActivity, + InvalidInvite, + NotAuthenticated, + InvalidAccessToken, + ApplicationMismatch, + InvalidDataUrl, + InvalidBase64, + NotFiltered, + LobbyFull, + InvalidLobbySecret, + InvalidFilename, + InvalidFileSize, + InvalidEntitlement, + NotInstalled, + NotRunning, + InsufficientBuffer, + PurchaseCanceled, +}; + +enum class CreateFlags { + Default = 0, + NoRequireDiscord = 1, +}; + +enum class LogLevel { + Error = 1, + Warn, + Info, + Debug, +}; + +enum class ImageType { + User, +}; + +enum class ActivityType { + Playing, + Streaming, + Listening, + Watching, +}; + +enum class ActivityActionType { + Join = 1, + Spectate, +}; + +enum class ActivityJoinRequestReply { + No, + Yes, + Ignore, +}; + +enum class Status { + Offline = 0, + Online = 1, + Idle = 2, + DoNotDisturb = 3, +}; + +enum class RelationshipType { + None, + Friend, + Blocked, + PendingIncoming, + PendingOutgoing, + Implicit, +}; + +enum class LobbyType { + Private = 1, + Public, +}; + +enum class LobbySearchComparison { + LessThanOrEqual = -2, + LessThan, + Equal, + GreaterThan, + GreaterThanOrEqual, + NotEqual, +}; + +enum class LobbySearchCast { + String = 1, + Number, +}; + +enum class LobbySearchDistance { + Local, + Default, + Extended, + Global, +}; + +enum class EntitlementType { + Purchase = 1, + PremiumSubscription, + DeveloperGift, +}; + +enum class SkuType { + Application = 1, + DLC, + Consumable, + Bundle, +}; + +using ClientId = std::int64_t; +using Version = std::int32_t; +using Snowflake = std::int64_t; +using Timestamp = std::int64_t; +using UserId = Snowflake; +using Locale = char const*; +using Branch = char const*; +using LobbyId = Snowflake; +using LobbySecret = char const*; +using MetadataKey = char const*; +using MetadataValue = char const*; +using NetworkPeerId = std::uint64_t; +using NetworkChannelId = std::uint8_t; + +class User final { +public: + void SetId(UserId id); + UserId GetId() const; + void SetUsername(char const* username); + char const* GetUsername() const; + void SetDiscriminator(char const* discriminator); + char const* GetDiscriminator() const; + void SetAvatar(char const* avatar); + char const* GetAvatar() const; + void SetBot(bool bot); + bool GetBot() const; + +private: + DiscordUser internal_; +}; + +class OAuth2Token final { +public: + void SetAccessToken(char const* accessToken); + char const* GetAccessToken() const; + void SetScopes(char const* scopes); + char const* GetScopes() const; + void SetExpires(Timestamp expires); + Timestamp GetExpires() const; + +private: + DiscordOAuth2Token internal_; +}; + +class ImageHandle final { +public: + void SetType(ImageType type); + ImageType GetType() const; + void SetId(std::int64_t id); + std::int64_t GetId() const; + void SetSize(std::uint32_t size); + std::uint32_t GetSize() const; + +private: + DiscordImageHandle internal_; +}; + +class ImageDimensions final { +public: + void SetWidth(std::uint32_t width); + std::uint32_t GetWidth() const; + void SetHeight(std::uint32_t height); + std::uint32_t GetHeight() const; + +private: + DiscordImageDimensions internal_; +}; + +class ActivityTimestamps final { +public: + void SetStart(Timestamp start); + Timestamp GetStart() const; + void SetEnd(Timestamp end); + Timestamp GetEnd() const; + +private: + DiscordActivityTimestamps internal_; +}; + +class ActivityAssets final { +public: + void SetLargeImage(char const* largeImage); + char const* GetLargeImage() const; + void SetLargeText(char const* largeText); + char const* GetLargeText() const; + void SetSmallImage(char const* smallImage); + char const* GetSmallImage() const; + void SetSmallText(char const* smallText); + char const* GetSmallText() const; + +private: + DiscordActivityAssets internal_; +}; + +class PartySize final { +public: + void SetCurrentSize(std::int32_t currentSize); + std::int32_t GetCurrentSize() const; + void SetMaxSize(std::int32_t maxSize); + std::int32_t GetMaxSize() const; + +private: + DiscordPartySize internal_; +}; + +class ActivityParty final { +public: + void SetId(char const* id); + char const* GetId() const; + PartySize& GetSize(); + PartySize const& GetSize() const; + +private: + DiscordActivityParty internal_; +}; + +class ActivitySecrets final { +public: + void SetMatch(char const* match); + char const* GetMatch() const; + void SetJoin(char const* join); + char const* GetJoin() const; + void SetSpectate(char const* spectate); + char const* GetSpectate() const; + +private: + DiscordActivitySecrets internal_; +}; + +class Activity final { +public: + void SetType(ActivityType type); + ActivityType GetType() const; + void SetApplicationId(std::int64_t applicationId); + std::int64_t GetApplicationId() const; + void SetName(char const* name); + char const* GetName() const; + void SetState(char const* state); + char const* GetState() const; + void SetDetails(char const* details); + char const* GetDetails() const; + ActivityTimestamps& GetTimestamps(); + ActivityTimestamps const& GetTimestamps() const; + ActivityAssets& GetAssets(); + ActivityAssets const& GetAssets() const; + ActivityParty& GetParty(); + ActivityParty const& GetParty() const; + ActivitySecrets& GetSecrets(); + ActivitySecrets const& GetSecrets() const; + void SetInstance(bool instance); + bool GetInstance() const; + +private: + DiscordActivity internal_; +}; + +class Presence final { +public: + void SetStatus(Status status); + Status GetStatus() const; + Activity& GetActivity(); + Activity const& GetActivity() const; + +private: + DiscordPresence internal_; +}; + +class Relationship final { +public: + void SetType(RelationshipType type); + RelationshipType GetType() const; + User& GetUser(); + User const& GetUser() const; + Presence& GetPresence(); + Presence const& GetPresence() const; + +private: + DiscordRelationship internal_; +}; + +class Lobby final { +public: + void SetId(LobbyId id); + LobbyId GetId() const; + void SetType(LobbyType type); + LobbyType GetType() const; + void SetOwnerId(UserId ownerId); + UserId GetOwnerId() const; + void SetSecret(LobbySecret secret); + LobbySecret GetSecret() const; + void SetCapacity(std::uint32_t capacity); + std::uint32_t GetCapacity() const; + void SetLocked(bool locked); + bool GetLocked() const; + +private: + DiscordLobby internal_; +}; + +class FileStat final { +public: + void SetFilename(char const* filename); + char const* GetFilename() const; + void SetSize(std::uint64_t size); + std::uint64_t GetSize() const; + void SetLastModified(std::uint64_t lastModified); + std::uint64_t GetLastModified() const; + +private: + DiscordFileStat internal_; +}; + +class Entitlement final { +public: + void SetId(Snowflake id); + Snowflake GetId() const; + void SetType(EntitlementType type); + EntitlementType GetType() const; + void SetSkuId(Snowflake skuId); + Snowflake GetSkuId() const; + +private: + DiscordEntitlement internal_; +}; + +class SkuPrice final { +public: + void SetAmount(std::uint32_t amount); + std::uint32_t GetAmount() const; + void SetCurrency(char const* currency); + char const* GetCurrency() const; + +private: + DiscordSkuPrice internal_; +}; + +class Sku final { +public: + void SetId(Snowflake id); + Snowflake GetId() const; + void SetType(SkuType type); + SkuType GetType() const; + void SetName(char const* name); + char const* GetName() const; + SkuPrice& GetPrice(); + SkuPrice const& GetPrice() const; + +private: + DiscordSku internal_; +}; + +class LobbyTransaction final { +public: + Result SetType(LobbyType type); + Result SetOwner(UserId ownerId); + Result SetCapacity(std::uint32_t capacity); + Result SetMetadata(MetadataKey key, MetadataValue value); + Result DeleteMetadata(MetadataKey key); + Result SetLocked(bool locked); + + IDiscordLobbyTransaction** Receive() { return &internal_; } + IDiscordLobbyTransaction* Internal() { return internal_; } + +private: + IDiscordLobbyTransaction* internal_; +}; + +class LobbyMemberTransaction final { +public: + Result SetMetadata(MetadataKey key, MetadataValue value); + Result DeleteMetadata(MetadataKey key); + + IDiscordLobbyMemberTransaction** Receive() { return &internal_; } + IDiscordLobbyMemberTransaction* Internal() { return internal_; } + +private: + IDiscordLobbyMemberTransaction* internal_; +}; + +class LobbySearchQuery final { +public: + Result Filter(MetadataKey key, + LobbySearchComparison comparison, + LobbySearchCast cast, + MetadataValue value); + Result Sort(MetadataKey key, LobbySearchCast cast, MetadataValue value); + Result Limit(std::uint32_t limit); + Result Distance(LobbySearchDistance distance); + + IDiscordLobbySearchQuery** Receive() { return &internal_; } + IDiscordLobbySearchQuery* Internal() { return internal_; } + +private: + IDiscordLobbySearchQuery* internal_; +}; + +} // namespace discord diff --git a/source/application/discord/user_manager.cpp b/source/application/discord/user_manager.cpp new file mode 100644 index 0000000..f3ac0e2 --- /dev/null +++ b/source/application/discord/user_manager.cpp @@ -0,0 +1,58 @@ +#if !defined(_CRT_SECURE_NO_WARNINGS) +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include "user_manager.h" + +#include "core.h" + +#include +#include + +namespace discord { + +class UserEvents final { +public: + static void OnCurrentUserUpdate(void* callbackData) + { + auto* core = reinterpret_cast(callbackData); + if (!core) { + return; + } + + auto& module = core->UserManager(); + module.OnCurrentUserUpdate(); + } +}; + +IDiscordUserEvents UserManager::events_{ + &UserEvents::OnCurrentUserUpdate, +}; + +Result UserManager::GetCurrentUser(User* currentUser) +{ + if (!currentUser) { + return Result::InternalError; + } + + auto result = + internal_->get_current_user(internal_, reinterpret_cast(currentUser)); + return static_cast(result); +} + +void UserManager::GetUser(UserId userId, std::function callback) +{ + static auto wrapper = [](void* callbackData, EDiscordResult result, DiscordUser* user) -> void { + std::unique_ptr> cb( + reinterpret_cast*>(callbackData)); + if (!cb || !(*cb)) { + return; + } + (*cb)(static_cast(result), *reinterpret_cast(user)); + }; + std::unique_ptr> cb{}; + cb.reset(new std::function(std::move(callback))); + internal_->get_user(internal_, userId, cb.release(), wrapper); +} + +} // namespace discord diff --git a/source/application/discord/user_manager.h b/source/application/discord/user_manager.h new file mode 100644 index 0000000..98ed9da --- /dev/null +++ b/source/application/discord/user_manager.h @@ -0,0 +1,29 @@ +#pragma once + +#include "types.h" + +namespace discord { + +class UserManager final { +public: + ~UserManager() = default; + + Result GetCurrentUser(User* currentUser); + void GetUser(UserId userId, std::function callback); + + Event<> OnCurrentUserUpdate; + +private: + friend class Core; + + UserManager() = default; + UserManager(UserManager const& rhs) = delete; + UserManager& operator=(UserManager const& rhs) = delete; + UserManager(UserManager&& rhs) = delete; + UserManager& operator=(UserManager&& rhs) = delete; + + IDiscordUserManager* internal_; + static IDiscordUserEvents events_; +}; + +} // namespace discord diff --git a/source/base/CMakeLists.txt b/source/base/CMakeLists.txt new file mode 100644 index 0000000..03f767f --- /dev/null +++ b/source/base/CMakeLists.txt @@ -0,0 +1,37 @@ +INCLUDE_DIRECTORIES ( + ${STAR_EXTERN_INCLUDES} + ${STAR_CORE_INCLUDES} + ${STAR_BASE_INCLUDES} + ) + +SET (star_base_HEADERS + StarAnimatedPartSet.hpp + StarAssets.hpp + StarAssetSource.hpp + StarBlocksAlongLine.hpp + StarCellularLightArray.hpp + StarCellularLighting.hpp + StarCellularLiquid.hpp + StarConfiguration.hpp + StarDirectoryAssetSource.hpp + StarMixer.hpp + StarPackedAssetSource.hpp + StarVersion.hpp + StarVersionOptionParser.hpp + StarWorldGeometry.hpp + ) + +SET (star_base_SOURCES + StarAnimatedPartSet.cpp + StarAssets.cpp + StarCellularLighting.cpp + StarConfiguration.cpp + StarDirectoryAssetSource.cpp + StarMixer.cpp + StarPackedAssetSource.cpp + StarVersionOptionParser.cpp + StarWorldGeometry.cpp + ) + +CONFIGURE_FILE (StarVersion.cpp.in ${CMAKE_CURRENT_BINARY_DIR}/StarVersion.cpp) +ADD_LIBRARY (star_base OBJECT ${star_base_SOURCES} ${star_base_HEADERS} ${CMAKE_CURRENT_BINARY_DIR}/StarVersion.cpp) diff --git a/source/base/StarAnimatedPartSet.cpp b/source/base/StarAnimatedPartSet.cpp new file mode 100644 index 0000000..62bf786 --- /dev/null +++ b/source/base/StarAnimatedPartSet.cpp @@ -0,0 +1,305 @@ +#include "StarAnimatedPartSet.hpp" +#include "StarMathCommon.hpp" + +namespace Star { + +AnimatedPartSet::AnimatedPartSet() {} + +AnimatedPartSet::AnimatedPartSet(Json config) { + for (auto const& stateTypePair : config.get("stateTypes", JsonObject()).iterateObject()) { + auto const& stateTypeName = stateTypePair.first; + auto const& stateTypeConfig = stateTypePair.second; + + StateType newStateType; + newStateType.priority = stateTypeConfig.getFloat("priority", 0.0f); + newStateType.enabled = stateTypeConfig.getBool("enabled", true); + newStateType.defaultState = stateTypeConfig.getString("default", ""); + newStateType.stateTypeProperties = stateTypeConfig.getObject("properties", {}); + + for (auto const& statePair : stateTypeConfig.get("states", JsonObject()).iterateObject()) { + auto const& stateName = statePair.first; + auto const& stateConfig = statePair.second; + + auto newState = make_shared(); + newState->frames = stateConfig.getInt("frames", 1); + newState->cycle = stateConfig.getFloat("cycle", 1.0f); + newState->animationMode = stringToAnimationMode(stateConfig.getString("mode", "end")); + newState->transitionState = stateConfig.getString("transition", ""); + newState->stateProperties = stateConfig.getObject("properties", {}); + newState->stateFrameProperties = stateConfig.getObject("frameProperties", {}); + newStateType.states[stateName] = move(newState); + } + + newStateType.states.sortByKey(); + + newStateType.activeState.stateTypeName = stateTypeName; + newStateType.activeStateDirty = true; + + if (newStateType.defaultState.empty() && !newStateType.states.empty()) + newStateType.defaultState = newStateType.states.firstKey(); + + m_stateTypes[stateTypeName] = move(newStateType); + } + + // Sort state types by decreasing priority. + m_stateTypes.sort([](pair const& a, pair const& b) { + return b.second.priority < a.second.priority; + }); + + for (auto const& partPair : config.get("parts", JsonObject()).iterateObject()) { + auto const& partName = partPair.first; + auto const& partConfig = partPair.second; + + Part newPart; + newPart.partProperties = partConfig.getObject("properties", {}); + + for (auto const& partStateTypePair : partConfig.get("partStates", JsonObject()).iterateObject()) { + auto const& stateTypeName = partStateTypePair.first; + + for (auto const& partStatePair : partStateTypePair.second.toObject()) { + auto const& stateName = partStatePair.first; + auto const& stateConfig = partStatePair.second; + + PartState partState = {stateConfig.getObject("properties", {}), stateConfig.getObject("frameProperties", {})}; + newPart.partStates[stateTypeName][stateName] = move(partState); + } + } + newPart.activePart.partName = partPair.first; + newPart.activePartDirty = true; + + m_parts[partName] = move(newPart); + } + + for (auto const& pair : m_stateTypes) + setActiveState(pair.first, pair.second.defaultState, true); +} + +StringList AnimatedPartSet::stateTypes() const { + return m_stateTypes.keys(); +} + +void AnimatedPartSet::setStateTypeEnabled(String const& stateTypeName, bool enabled) { + auto& stateType = m_stateTypes.get(stateTypeName); + if (stateType.enabled != enabled) { + stateType.enabled = enabled; + for (auto& pair : m_parts) + pair.second.activePartDirty = true; + } +} + +void AnimatedPartSet::setEnabledStateTypes(StringList const& stateTypeNames) { + for (auto& pair : m_stateTypes) + pair.second.enabled = false; + + for (auto const& stateTypeName : stateTypeNames) + m_stateTypes.get(stateTypeName).enabled = true; + + for (auto& pair : m_parts) + pair.second.activePartDirty = true; +} + +bool AnimatedPartSet::stateTypeEnabled(String const& stateTypeName) const { + return m_stateTypes.get(stateTypeName).enabled; +} + +StringList AnimatedPartSet::states(String const& stateTypeName) const { + return m_stateTypes.get(stateTypeName).states.keys(); +} + +StringList AnimatedPartSet::parts() const { + return m_parts.keys(); +} + +bool AnimatedPartSet::setActiveState(String const& stateTypeName, String const& stateName, bool alwaysStart) { + auto& stateType = m_stateTypes.get(stateTypeName); + if (stateType.activeState.stateName != stateName || alwaysStart) { + stateType.activeState.stateName = stateName; + stateType.activeState.timer = 0.0f; + stateType.activeStatePointer = stateType.states.get(stateName).get(); + + stateType.activeStateDirty = true; + for (auto& pair : m_parts) + pair.second.activePartDirty = true; + + return true; + } else { + return false; + } +} + +void AnimatedPartSet::restartState(String const& stateTypeName) { + auto& stateType = m_stateTypes.get(stateTypeName); + stateType.activeState.timer = 0.0f; + + stateType.activeStateDirty = true; + for (auto& pair : m_parts) + pair.second.activePartDirty = true; +} + +AnimatedPartSet::ActiveStateInformation const& AnimatedPartSet::activeState(String const& stateTypeName) const { + auto& stateType = const_cast(m_stateTypes.get(stateTypeName)); + const_cast(this)->freshenActiveState(stateType); + return stateType.activeState; +} + +AnimatedPartSet::ActivePartInformation const& AnimatedPartSet::activePart(String const& partName) const { + auto& part = const_cast(m_parts.get(partName)); + const_cast(this)->freshenActivePart(part); + return part.activePart; +} + +void AnimatedPartSet::forEachActiveState(function callback) const { + for (auto const& p : m_stateTypes) { + const_cast(this)->freshenActiveState(const_cast(p.second)); + callback(p.first, p.second.activeState); + } +} + +void AnimatedPartSet::forEachActivePart(function callback) const { + for (auto const& p : m_parts) { + const_cast(this)->freshenActivePart(const_cast(p.second)); + callback(p.first, p.second.activePart); + } +} + +size_t AnimatedPartSet::activeStateIndex(String const& stateTypeName) const { + auto const& stateType = m_stateTypes.get(stateTypeName); + return *stateType.states.indexOf(stateType.activeState.stateName); +} + +bool AnimatedPartSet::setActiveStateIndex(String const& stateTypeName, size_t stateIndex, bool alwaysStart) { + auto const& stateType = m_stateTypes.get(stateTypeName); + String const& stateName = stateType.states.keyAt(stateIndex); + return setActiveState(stateTypeName, stateName, alwaysStart); +} + +void AnimatedPartSet::update(float dt) { + for (auto& pair : m_stateTypes) { + auto& stateType = pair.second; + auto const& state = *stateType.activeStatePointer; + + stateType.activeState.timer += dt; + if (stateType.activeState.timer > state.cycle) { + if (state.animationMode == End) { + stateType.activeState.timer = state.cycle; + } else if (state.animationMode == Loop) { + stateType.activeState.timer = std::fmod(stateType.activeState.timer, state.cycle); + } else if (state.animationMode == Transition) { + stateType.activeState.stateName = state.transitionState; + stateType.activeState.timer = 0.0f; + stateType.activeStatePointer = stateType.states.get(state.transitionState).get(); + } + } + + stateType.activeStateDirty = true; + } + + for (auto& pair : m_parts) + pair.second.activePartDirty = true; +} + +void AnimatedPartSet::finishAnimations() { + for (auto& pair : m_stateTypes) { + auto& stateType = pair.second; + + while (true) { + auto const& state = *stateType.activeStatePointer; + + if (state.animationMode == End) { + stateType.activeState.timer = state.cycle; + } else if (state.animationMode == Transition) { + stateType.activeState.stateName = state.transitionState; + stateType.activeState.timer = 0.0f; + stateType.activeStatePointer = stateType.states.get(state.transitionState).get(); + continue; + } + break; + } + + stateType.activeStateDirty = true; + } + + for (auto& pair : m_parts) + pair.second.activePartDirty = true; +} + +AnimatedPartSet::AnimationMode AnimatedPartSet::stringToAnimationMode(String const& string) { + if (string.equals("end", String::CaseInsensitive)) { + return End; + } else if (string.equals("loop", String::CaseInsensitive)) { + return Loop; + } else if (string.equals("transition", String::CaseInsensitive)) { + return Transition; + } else { + throw AnimatedPartSetException(strf("No such AnimationMode '%s'", string)); + } +} + +void AnimatedPartSet::freshenActiveState(StateType& stateType) { + if (stateType.activeStateDirty) { + auto const& state = *stateType.activeStatePointer; + auto& activeState = stateType.activeState; + activeState.frame = clamp(activeState.timer / state.cycle * state.frames, 0, state.frames - 1); + + activeState.properties = stateType.stateTypeProperties; + activeState.properties.merge(state.stateProperties, true); + + for (auto const& pair : state.stateFrameProperties) { + if (activeState.frame < pair.second.size()) + activeState.properties[pair.first] = pair.second.get(activeState.frame); + } + + stateType.activeStateDirty = false; + } +} + +void AnimatedPartSet::freshenActivePart(Part& part) { + if (part.activePartDirty) { + // First reset all the active part information assuming that no state type + // x state match exists. + auto& activePart = part.activePart; + activePart.activeState = {}; + activePart.properties = part.partProperties; + + // Then go through each of the state types and states and look for a part + // state match in order of priority. + for (auto& stateTypePair : m_stateTypes) { + auto const& stateTypeName = stateTypePair.first; + auto& stateType = stateTypePair.second; + + // Skip disabled state types + if (!stateType.enabled) + continue; + + auto partStateType = part.partStates.ptr(stateTypeName); + if (!partStateType) + continue; + + auto const& stateName = stateType.activeState.stateName; + auto partState = partStateType->ptr(stateName); + if (!partState) + continue; + + // If we have a partState match, then set the active state information. + freshenActiveState(stateType); + activePart.activeState = stateType.activeState; + unsigned frame = stateType.activeState.frame; + + // Then set the part state data, as well as any part state frame data if + // the current frame is within the list size. + activePart.properties.merge(partState->partStateProperties, true); + + for (auto const& pair : partState->partStateFrameProperties) { + if (frame < pair.second.size()) + activePart.properties[pair.first] = pair.second.get(frame); + } + + // Each part can only have one state type x state match, so we are done. + break; + } + + part.activePartDirty = false; + } +} + +} diff --git a/source/base/StarAnimatedPartSet.hpp b/source/base/StarAnimatedPartSet.hpp new file mode 100644 index 0000000..bb3df6c --- /dev/null +++ b/source/base/StarAnimatedPartSet.hpp @@ -0,0 +1,163 @@ +#ifndef STAR_ANIMATED_PART_SET_HPP +#define STAR_ANIMATED_PART_SET_HPP + +#include "StarOrderedMap.hpp" +#include "StarJson.hpp" + +namespace Star { + +STAR_EXCEPTION(AnimatedPartSetException, StarException); + +// Defines a "animated" data set constructed in such a way that it is very +// useful for doing generic animations with lots of additional animation data. +// It is made up of two concepts, "states" and "parts". +// +// States: +// +// There are N "state types" defined, which each defines a set of mutually +// exclusive states that each "state type" can be in. For example, one state +// type might be "movement", and the "movement" states might be "idle", "walk", +// and "run. Another state type might be "attack" which could have as its +// states "idle", and "melee". Each state type will have exactly one currently +// active state, so this class may, for example, be in the total state of +// "movement:idle" and "attack:melee". Each state within each state type is +// animated, so that over time the state frame increases and may loop around, +// or transition into another state so that that state type without interaction +// may go from "melee" to "idle" when the "melee" state animation is finished. +// This is defined by the individual state config in the configuration passed +// into the constructor. +// +// Parts: +// +// Each instance of this class also can have N "Parts" defined, which are +// groups of properties that "listen" to active states. Each part can "listen" +// to one or more state types, and the first matching state x state type pair +// (in order of state type priority which is specified in the config) is +// chosen, and the properties from that state type and state are merged into +// the part to produce the final active part information. Rather than having a +// single image or image set for each part, since this class is intended to be +// as generic as possible, all of this data is assumed to be queried from the +// part properties, so that things such as image data as well as other things +// like damage or collision polys can be stored along with the animation +// frames, the part state, the base part, whichever is most applicable. +class AnimatedPartSet { +public: + struct ActiveStateInformation { + String stateTypeName; + String stateName; + float timer; + unsigned frame; + JsonObject properties; + }; + + struct ActivePartInformation { + String partName; + // If a state match is found, this will be set. + Maybe activeState; + JsonObject properties; + }; + + AnimatedPartSet(); + AnimatedPartSet(Json config); + + // Returns the available state types. + StringList stateTypes() const; + + // If a state type is disabled, no parts will match against it even + // if they have entries for that state type. + void setStateTypeEnabled(String const& stateTypeName, bool enabled); + void setEnabledStateTypes(StringList const& stateTypeNames); + bool stateTypeEnabled(String const& stateTypeName) const; + + // Returns the available states for the given state type. + StringList states(String const& stateTypeName) const; + + StringList parts() const; + + // Sets the active state for this state type. If the state is different than + // the previously set state, will start the new states animation off at the + // beginning. If alwaysStart is true, then starts the state animation off at + // the beginning even if no state change has occurred. Returns true if a + // state animation reset was done. + bool setActiveState(String const& stateTypeName, String const& stateName, bool alwaysStart = false); + + // Restart this given state type's timer off at the beginning. + void restartState(String const& stateTypeName); + + ActiveStateInformation const& activeState(String const& stateTypeName) const; + ActivePartInformation const& activePart(String const& partName) const; + + // Function will be given the name of each state type, and the + // ActiveStateInformation for the active state for that state type. + void forEachActiveState(function callback) const; + + // Function will be given the name of each part, and the + // ActivePartInformation for the active part. + void forEachActivePart(function callback) const; + + // Useful for serializing state changes. Since each set of states for a + // state type is ordered, it is possible to simply serialize and deserialize + // the state index for that state type. + size_t activeStateIndex(String const& stateTypeName) const; + bool setActiveStateIndex(String const& stateTypeName, size_t stateIndex, bool alwaysStart = false); + + // Animate each state type forward 'dt' time, and either change state frames + // or transition to new states, depending on the config. + void update(float dt); + + // Pushes all the animations into their final state + void finishAnimations(); + +private: + enum AnimationMode { + End, + Loop, + Transition + }; + + struct State { + unsigned frames; + float cycle; + AnimationMode animationMode; + String transitionState; + JsonObject stateProperties; + JsonObject stateFrameProperties; + }; + + struct StateType { + float priority; + bool enabled; + String defaultState; + JsonObject stateTypeProperties; + OrderedHashMap> states; + + ActiveStateInformation activeState; + State const* activeStatePointer; + bool activeStateDirty; + }; + + struct PartState { + JsonObject partStateProperties; + JsonObject partStateFrameProperties; + }; + + struct Part { + JsonObject partProperties; + StringMap> partStates; + + ActivePartInformation activePart; + bool activePartDirty; + }; + + static AnimationMode stringToAnimationMode(String const& string); + + void freshenActiveState(StateType& stateType); + void freshenActivePart(Part& part); + + OrderedHashMap m_stateTypes; + StringMap m_parts; +}; + +} + +#endif diff --git a/source/base/StarAssetSource.hpp b/source/base/StarAssetSource.hpp new file mode 100644 index 0000000..b2cb245 --- /dev/null +++ b/source/base/StarAssetSource.hpp @@ -0,0 +1,35 @@ +#ifndef STAR_ASSET_SOURCE_HPP +#define STAR_ASSET_SOURCE_HPP + +#include "StarIODevice.hpp" +#include "StarJson.hpp" + +namespace Star { + +STAR_CLASS(AssetSource); + +STAR_EXCEPTION(AssetSourceException, StarException); + +// An asset source could be a directory on a filesystem, where assets are +// pulled directly from files, or a single pak-like file containing all assets, +// where assets are pulled from the correct region of the pak-like file. +class AssetSource { +public: + virtual ~AssetSource() = default; + + // An asset source can have arbitrary metadata attached. + virtual JsonObject metadata() const = 0; + + // Should return all the available assets in this source + virtual StringList assetPaths() const = 0; + + // Open the given path in this source and return an IODevicePtr to it. + virtual IODevicePtr open(String const& path) = 0; + + // Read the entirety of the given path into a buffer. + virtual ByteArray read(String const& path) = 0; +}; + +} + +#endif diff --git a/source/base/StarAssets.cpp b/source/base/StarAssets.cpp new file mode 100644 index 0000000..5066268 --- /dev/null +++ b/source/base/StarAssets.cpp @@ -0,0 +1,1113 @@ +#include "StarAssets.hpp" +#include "StarFile.hpp" +#include "StarTime.hpp" +#include "StarDirectoryAssetSource.hpp" +#include "StarPackedAssetSource.hpp" +#include "StarJsonBuilder.hpp" +#include "StarJsonExtra.hpp" +#include "StarJsonPatch.hpp" +#include "StarIterator.hpp" +#include "StarImageProcessing.hpp" +#include "StarLogging.hpp" +#include "StarRandom.hpp" +#include "StarFont.hpp" +#include "StarAudio.hpp" +#include "StarCasting.hpp" +#include "StarLexicalCast.hpp" +#include "StarSha256.hpp" +#include "StarDataStreamDevices.hpp" + +namespace Star { + +static void validatePath(AssetPath const& components, bool canContainSubPath, bool canContainDirectives) { + if (components.basePath.empty() || components.basePath[0] != '/') + throw AssetException(strf("Path '%s' must be absolute", components.basePath)); + + bool first = true; + bool slashed = true; + bool dotted = false; + for (auto c : components.basePath) { + if (c == '/') { + if (!first) { + if (slashed) + throw AssetException(strf("Path '%s' contains consecutive //, not allowed", components.basePath)); + else if (dotted) + throw AssetException(strf("Path '%s' '.' and '..' not allowed", components.basePath)); + } + slashed = true; + dotted = false; + } else if (c == ':') { + if (slashed) + throw AssetException(strf("Path '%s' has ':' after directory", components.basePath)); + break; + } else if (c == '?') { + if (slashed) + throw AssetException(strf("Path '%s' has '?' after directory", components.basePath)); + break; + } else { + slashed = false; + dotted = c == '.'; + } + first = false; + } + if (slashed) + throw AssetException(strf("Path '%s' cannot be a file", components.basePath)); + + if (!canContainSubPath && components.subPath) + throw AssetException::format("Path '%s' cannot contain sub-path", components); + if (!canContainDirectives && !components.directives.empty()) + throw AssetException::format("Path '%s' cannot contain directives", components); +} + +// The filename is everything after the last slash (excluding directives) and +// up to the first directive marker. +static Maybe> findFilenameRange(std::string const& pathUtf8) { + size_t firstDirectiveOrSubPath = pathUtf8.find_first_of(":?"); + size_t filenameStart = 0; + while (true) { + size_t find = pathUtf8.find('/', filenameStart); + if (find >= firstDirectiveOrSubPath) + break; + filenameStart = find + 1; + } + + if (filenameStart == NPos) { + return {}; + } else if (firstDirectiveOrSubPath == NPos) { + return {{filenameStart, pathUtf8.size()}}; + } else { + return {{filenameStart, firstDirectiveOrSubPath}}; + } +} + +AssetPath AssetPath::split(String const& path) { + auto i = path.begin(); + auto end = path.end(); + + AssetPath components; + + // base paths cannot have any ':' or '?' characters, stop at the first one. + while (i != end) { + String::Char c = *i; + if (c == ':' || c == '?') + break; + + components.basePath += c; + ++i; + } + + // Sub-paths must immediately follow base paths and must start with a ':', + // after this point any further ':' characters are not special. + if (i != end && *i == ':') { + ++i; + while (i != end) { + String::Char c = *i; + if (c == '?') + break; + + if (!components.subPath) + components.subPath.emplace(); + + *components.subPath += c; + ++i; + } + } + + // Directives must follow the base path and optional sub-path, and each + // directive is separated by one or more '?' characters. + while (i != end && *i == '?') { + ++i; + String directive; + while (i != end) { + String::Char c = *i; + if (c == '?') + break; + + directive += c; + ++i; + } + if (!directive.empty()) + components.directives.append(move(directive)); + } + + starAssert(i == end); + + return components; +} + +String AssetPath::join(AssetPath const& components) { + return toString(components); +} + +String AssetPath::setSubPath(String const& path, String const& subPath) { + auto components = split(path); + components.subPath = subPath; + return join(components); +} + +String AssetPath::removeSubPath(String const& path) { + auto components = split(path); + components.subPath.reset(); + return join(components); +} + +String AssetPath::getDirectives(String const& path) { + size_t firstDirective = path.find('?'); + if (firstDirective == NPos) + return {}; + return path.substr(firstDirective + 1); +} + +String AssetPath::addDirectives(String const& path, String const& directives) { + return String::joinWith("?", path, directives); +} + +String AssetPath::removeDirectives(String const& path) { + size_t firstDirective = path.find('?'); + if (firstDirective == NPos) + return path; + return path.substr(0, firstDirective); +} + +String AssetPath::directory(String const& path) { + if (auto p = findFilenameRange(path.utf8())) { + return String(path.utf8().substr(0, p->first)); + } else { + return String(); + } +} + +String AssetPath::filename(String const& path) { + if (auto p = findFilenameRange(path.utf8())) { + return String(path.utf8().substr(p->first, p->second)); + } else { + return String(); + } +} + +String AssetPath::extension(String const& path) { + auto file = filename(path); + auto lastDot = file.findLast("."); + if (lastDot == NPos) + return ""; + + return file.substr(lastDot + 1); +} + +String AssetPath::relativeTo(String const& sourcePath, String const& givenPath) { + if (!givenPath.empty() && givenPath[0] == '/') + return givenPath; + + auto path = directory(sourcePath); + path.append(givenPath); + return path; +} + +bool AssetPath::operator==(AssetPath const& rhs) const { + return tie(basePath, subPath, directives) == tie(rhs.basePath, rhs.subPath, rhs.directives); +} + +std::ostream& operator<<(std::ostream& os, AssetPath const& rhs) { + os << rhs.basePath; + if (rhs.subPath) { + os << ":"; + os << *rhs.subPath; + } + + for (auto const& directive : rhs.directives) { + os << "?"; + os << directive; + } + + return os; +} + +Maybe FramesSpecification::getRect(String const& frame) const { + if (auto alias = aliases.ptr(frame)) { + return frames.get(*alias); + } else { + return frames.maybe(frame); + } +} + +Assets::Assets(Settings settings, StringList assetSources) { + const char* const AssetsPatchSuffix = ".patch"; + + m_settings = move(settings); + m_stopThreads = false; + m_assetSources = move(assetSources); + + for (auto& sourcePath : m_assetSources) { + Logger::info("Loading assets from: '%s'", sourcePath); + AssetSourcePtr source; + if (File::isDirectory(sourcePath)) + source = make_shared(sourcePath, m_settings.pathIgnore); + else + source = make_shared(sourcePath); + + m_assetSourcePaths.add(sourcePath, source); + + for (auto const& filename : source->assetPaths()) { + if (filename.endsWith(AssetsPatchSuffix, String::CaseInsensitive)) { + auto targetPatchFile = filename.substr(0, filename.size() - strlen(AssetsPatchSuffix)); + if (auto p = m_files.ptr(targetPatchFile)) + p->patchSources.append({filename, source}); + } + auto& descriptor = m_files[filename]; + descriptor.sourceName = filename; + descriptor.source = source; + } + } + + Sha256Hasher digest; + + for (auto const& assetPath : m_files.keys().transformed([](String const& s) { + return s.toLower(); + }).sorted()) { + bool digestFile = true; + for (auto const& pattern : m_settings.digestIgnore) { + if (assetPath.regexMatch(pattern, false, false)) { + digestFile = false; + break; + } + } + + auto const& descriptor = m_files.get(assetPath); + + if (digestFile) { + digest.push(assetPath); + digest.push(DataStreamBuffer::serialize(descriptor.source->open(descriptor.sourceName)->size())); + for (auto const& pair : descriptor.patchSources) + digest.push(DataStreamBuffer::serialize(pair.second->open(pair.first)->size())); + } + } + + m_digest = digest.compute(); + + for (auto const& filename : m_files.keys()) + m_filesByExtension[AssetPath::extension(filename).toLower()].append(filename); + + int workerPoolSize = m_settings.workerPoolSize; + for (int i = 0; i < workerPoolSize; i++) + m_workerThreads.append(Thread::invoke("Assets::workerMain", mem_fn(&Assets::workerMain), this)); +} + +Assets::~Assets() { + m_stopThreads = true; + + { + // Should lock associated mutex to prevent loss of wakeups, + MutexLocker locker(m_assetsMutex); + // Notify all worker threads to allow them to stop + m_assetsQueued.broadcast(); + } + + // Join them all + m_workerThreads.clear(); +} + +StringList Assets::assetSources() const { + MutexLocker assetsLocker(m_assetsMutex); + return m_assetSources; +} + +JsonObject Assets::assetSourceMetadata(String const& sourceName) const { + MutexLocker assetsLocker(m_assetsMutex); + return m_assetSourcePaths.getRight(sourceName)->metadata(); +} + +ByteArray Assets::digest() const { + MutexLocker assetsLocker(m_assetsMutex); + return m_digest; +} + +bool Assets::assetExists(String const& path) const { + MutexLocker assetsLocker(m_assetsMutex); + return m_files.contains(path); +} + +String Assets::assetSource(String const& path) const { + MutexLocker assetsLocker(m_assetsMutex); + if (auto p = m_files.ptr(path)) + return m_assetSourcePaths.getLeft(p->source); + throw AssetException(strf("No such asset '%s'", path)); +} + +StringList Assets::scan(String const& suffix) const { + if (suffix.beginsWith(".") && !suffix.substr(1).hasChar('.')) { + return scanExtension(suffix); + } else { + StringList result; + for (auto const& fileEntry : m_files) { + String const& file = fileEntry.first; + if (file.endsWith(suffix, String::CaseInsensitive)) + result.append(file); + } + + return result; + } +} + +StringList Assets::scan(String const& prefix, String const& suffix) const { + StringList result; + if (suffix.beginsWith(".") && !suffix.substr(1).hasChar('.')) { + StringList filesWithExtension = scanExtension(suffix); + for (auto const& file : filesWithExtension) { + if (file.beginsWith(prefix, String::CaseInsensitive)) + result.append(file); + } + } else { + for (auto const& fileEntry : m_files) { + String const& file = fileEntry.first; + if (file.beginsWith(prefix, String::CaseInsensitive) && file.endsWith(suffix, String::CaseInsensitive)) + result.append(file); + } + } + return result; +} + +StringList Assets::scanExtension(String const& extension) const { + if (extension.beginsWith(".")) + return m_filesByExtension.value(extension.substr(1)); + else + return m_filesByExtension.value(extension); +} + +Json Assets::json(String const& path) const { + auto components = AssetPath::split(path); + validatePath(components, true, false); + + return as(getAsset(AssetId{AssetType::Json, move(components)}))->json; +} + +Json Assets::fetchJson(Json const& v, String const& dir) const { + if (v.isType(Json::Type::String)) + return Assets::json(AssetPath::relativeTo(dir, v.toString())); + else + return v; +} + +void Assets::queueJsons(StringList const& paths) const { + queueAssets(paths.transformed([](String const& path) { + auto components = AssetPath::split(path); + validatePath(components, true, false); + + return AssetId{AssetType::Json, {components.basePath, {}, {}}}; + })); +} + +ImageConstPtr Assets::image(String const& path) const { + auto components = AssetPath::split(path); + validatePath(components, true, true); + + return as(getAsset(AssetId{AssetType::Image, move(components)}))->image; +} + +void Assets::queueImages(StringList const& paths) const { + queueAssets(paths.transformed([](String const& path) { + auto components = AssetPath::split(path); + validatePath(components, true, true); + + return AssetId{AssetType::Image, move(components)}; + })); +} + +ImageConstPtr Assets::tryImage(String const& path) const { + auto components = AssetPath::split(path); + validatePath(components, true, true); + + if (auto imageData = as(tryAsset(AssetId{AssetType::Image, move(components)}))) + return imageData->image; + else + return {}; +} + +FramesSpecificationConstPtr Assets::imageFrames(String const& path) const { + auto components = AssetPath::split(path); + validatePath(components, false, false); + + MutexLocker assetsLocker(m_assetsMutex); + return bestFramesSpecification(path); +} + +AudioConstPtr Assets::audio(String const& path) const { + auto components = AssetPath::split(path); + validatePath(components, false, false); + + return as(getAsset(AssetId{AssetType::Audio, move(components)}))->audio; +} + +void Assets::queueAudios(StringList const& paths) const { + queueAssets(paths.transformed([](String const& path) { + auto components = AssetPath::split(path); + validatePath(components, false, false); + + return AssetId{AssetType::Audio, move(components)}; + })); +} + +AudioConstPtr Assets::tryAudio(String const& path) const { + auto components = AssetPath::split(path); + validatePath(components, false, false); + + if (auto audioData = as(tryAsset(AssetId{AssetType::Audio, move(components)}))) + return audioData->audio; + else + return {}; +} + +FontConstPtr Assets::font(String const& path) const { + auto components = AssetPath::split(path); + validatePath(components, false, false); + + return as(getAsset(AssetId{AssetType::Font, move(components)}))->font; +} + +ByteArrayConstPtr Assets::bytes(String const& path) const { + auto components = AssetPath::split(path); + validatePath(components, false, false); + + return as(getAsset(AssetId{AssetType::Bytes, move(components)}))->bytes; +} + +IODevicePtr Assets::openFile(String const& path) const { + return open(path); +} + +void Assets::clearCache() { + MutexLocker assetsLocker(m_assetsMutex); + + // Clear all assets that are not queued or broken. + auto it = makeSMutableMapIterator(m_assetsCache); + while (it.hasNext()) { + auto const& pair = it.next(); + // Don't clean up queued, persistent, or broken assets. + if (pair.second && !pair.second->shouldPersist() && !m_queue.contains(pair.first)) + it.remove(); + } +} + +void Assets::cleanup() { + MutexLocker assetsLocker(m_assetsMutex); + + double time = Time::monotonicTime(); + + auto it = makeSMutableMapIterator(m_assetsCache); + while (it.hasNext()) { + auto pair = it.next(); + // Don't clean up broken assets or queued assets. + if (pair.second && !m_queue.contains(pair.first)) { + double liveTime = time - pair.second->time; + if (liveTime > m_settings.assetTimeToLive) { + // If the asset should persist, just refresh the access time. + if (pair.second->shouldPersist()) + pair.second->time = time; + else + it.remove(); + } + } + } +} + +bool Assets::AssetId::operator==(AssetId const& assetId) const { + return tie(type, path) == tie(assetId.type, assetId.path); +} + +size_t Assets::AssetIdHash::operator()(AssetId const& id) const { + return hashOf(id.type, id.path.basePath, id.path.subPath, id.path.directives); +} + +bool Assets::JsonData::shouldPersist() const { + return !json.unique(); +} + +bool Assets::ImageData::shouldPersist() const { + return !alias && !image.unique(); +} + +bool Assets::AudioData::shouldPersist() const { + return !audio.unique(); +} + +bool Assets::FontData::shouldPersist() const { + return !font.unique(); +} + +bool Assets::BytesData::shouldPersist() const { + return !bytes.unique(); +} + +FramesSpecification Assets::parseFramesSpecification(Json const& frameConfig, String path) { + FramesSpecification framesSpecification; + + framesSpecification.framesFile = move(path); + + if (frameConfig.contains("frameList")) { + for (auto const& pair : frameConfig.get("frameList").toObject()) { + String frameName = pair.first; + RectU rect = RectU(jsonToRectI(pair.second)); + if (rect.isEmpty()) + throw AssetException( + strf("Empty rect in frame specification in image %s frame %s", framesSpecification.framesFile, frameName)); + else + framesSpecification.frames[frameName] = rect; + } + } + + if (frameConfig.contains("frameGrid")) { + auto grid = frameConfig.get("frameGrid").toObject(); + + Vec2U begin(jsonToVec2I(grid.value("begin", jsonFromVec2I(Vec2I())))); + Vec2U size(jsonToVec2I(grid.get("size"))); + Vec2U dimensions(jsonToVec2I(grid.get("dimensions"))); + + if (dimensions[0] == 0 || dimensions[1] == 0) + throw AssetException(strf("Image %s \"dimensions\" in frameGrid cannot be zero", framesSpecification.framesFile)); + + if (grid.contains("names")) { + auto nameList = grid.get("names"); + for (size_t y = 0; y < nameList.size(); ++y) { + if (y >= dimensions[1]) + throw AssetException(strf("Image %s row %s is out of bounds for y-dimension %s", + framesSpecification.framesFile, + y + 1, + dimensions[1])); + auto rowList = nameList.get(y); + if (rowList.isNull()) + continue; + for (unsigned x = 0; x < rowList.size(); ++x) { + if (x >= dimensions[0]) + throw AssetException(strf("Image %s column %s is out of bounds for x-dimension %s", + framesSpecification.framesFile, + x + 1, + dimensions[0])); + + auto frame = rowList.get(x); + if (frame.isNull()) + continue; + auto frameName = frame.toString(); + if (!frameName.empty()) + framesSpecification.frames[frameName] = + RectU::withSize(Vec2U(begin[0] + x * size[0], begin[1] + y * size[1]), size); + } + } + } else { + // If "names" not specified, use auto naming algorithm + for (size_t y = 0; y < dimensions[1]; ++y) + for (size_t x = 0; x < dimensions[0]; ++x) + framesSpecification.frames[strf("%s", y * dimensions[0] + x)] = + RectU::withSize(Vec2U(begin[0] + x * size[0], begin[1] + y * size[1]), size); + } + } + + if (auto aliasesConfig = frameConfig.opt("aliases")) { + auto aliases = aliasesConfig->objectPtr(); + for (auto const& pair : *aliases) { + String const& key = pair.first; + String value = pair.second.toString(); + + // Resolve aliases to aliases by checking to see if the alias value in + // the alias map itself. Don't do this more than aliases.size() times to + // avoid infinite cycles. + for (size_t i = 0; i <= aliases->size(); ++i) { + auto it = aliases->find(value); + if (it != aliases->end()) { + if (i == aliases->size()) + throw AssetException(strf("Infinite alias loop detected for alias '%s'", key)); + + value = it->second.toString(); + } else { + break; + } + } + + if (!framesSpecification.frames.contains(value)) + throw AssetException(strf("No such frame '%s' found for alias '%s'", value, key)); + framesSpecification.aliases[key] = move(value); + } + } + + return framesSpecification; +} + +void Assets::queueAssets(List const& assetIds) const { + MutexLocker assetsLocker(m_assetsMutex); + + for (auto const& id : assetIds) { + auto i = m_assetsCache.find(id); + if (i != m_assetsCache.end()) { + if (i->second) + freshen(i->second); + } else { + auto j = m_queue.find(id); + if (j == m_queue.end()) { + m_queue[id] = QueuePriority::Load; + m_assetsQueued.signal(); + } + } + } +} + +shared_ptr Assets::tryAsset(AssetId const& id) const { + MutexLocker assetsLocker(m_assetsMutex); + + auto i = m_assetsCache.find(id); + if (i != m_assetsCache.end()) { + if (i->second) { + freshen(i->second); + return i->second; + } else { + throw AssetException::format("Error loading asset %s", id.path); + } + } else { + auto j = m_queue.find(id); + if (j == m_queue.end()) { + m_queue[id] = QueuePriority::Load; + m_assetsQueued.signal(); + } + return {}; + } +} + +shared_ptr Assets::getAsset(AssetId const& id) const { + MutexLocker assetsLocker(m_assetsMutex); + + while (true) { + auto j = m_assetsCache.find(id); + if (j != m_assetsCache.end()) { + if (j->second) { + auto asset = j->second; + freshen(asset); + return asset; + } else { + throw AssetException::format("Error loading asset %s", id.path); + } + } else { + // Try to load the asset in-thread, if we cannot, then the asset has been + // queued so wait for a worker thread to finish it. + if (!doLoad(id)) + m_assetsDone.wait(m_assetsMutex); + } + } +} + +void Assets::workerMain() { + while (true) { + if (m_stopThreads) + break; + + MutexLocker assetsLocker(m_assetsMutex); + + AssetId assetId; + QueuePriority queuePriority = QueuePriority::None; + + // Find the highest priority queue entry + for (auto const& pair : m_queue) { + if (pair.second == QueuePriority::Load || pair.second == QueuePriority::PostProcess) { + assetId = pair.first; + queuePriority = pair.second; + if (pair.second == QueuePriority::Load) + break; + } + } + + if (queuePriority != QueuePriority::Load && queuePriority != QueuePriority::PostProcess) { + // Nothing in the queue that needs work + m_assetsQueued.wait(m_assetsMutex); + continue; + } + + bool workIsBlocking; + if (queuePriority == QueuePriority::PostProcess) + workIsBlocking = !doPost(assetId); + else + workIsBlocking = !doLoad(assetId); + + if (workIsBlocking) { + // We are blocking on some sort of busy asset, so need to wait on + // something to complete here, rather than spinning and burning cpu. + m_assetsDone.wait(m_assetsMutex); + continue; + } + + // After processing an asset, unlock the main asset mutex and yield so we + // don't starve other threads. + assetsLocker.unlock(); + Thread::yield(); + } +} + +template +decltype(auto) Assets::unlockDuring(Function f) const { + m_assetsMutex.unlock(); + try { + auto r = f(); + m_assetsMutex.lock(); + return r; + } catch (...) { + m_assetsMutex.lock(); + throw; + } +} + +FramesSpecificationConstPtr Assets::bestFramesSpecification(String const& image) const { + if (auto framesSpecification = m_framesSpecifications.maybe(image)) { + return *framesSpecification; + } + + String framesFile; + + if (auto bestFramesFile = m_bestFramesFiles.maybe(image)) { + framesFile = *bestFramesFile; + + } else { + String searchPath = AssetPath::directory(image); + String filePrefix = AssetPath::filename(image); + filePrefix = filePrefix.substr(0, filePrefix.findLast('.')); + + auto subdir = [](String const& dir) -> String { + auto dirsplit = dir.substr(0, dir.size() - 1).rsplit("/", 1); + if (dirsplit.size() < 2) + return ""; + else + return dirsplit[0] + "/"; + }; + + Maybe foundFramesFile; + + // look for .frames or default.frames up to root + while (!searchPath.empty()) { + String framesPath = searchPath + filePrefix + ".frames"; + if (m_files.contains(framesPath)) { + foundFramesFile = framesPath; + break; + } + + framesPath = searchPath + "default.frames"; + if (m_files.contains(framesPath)) { + foundFramesFile = framesPath; + break; + } + + searchPath = subdir(searchPath); + } + + if (foundFramesFile) { + framesFile = foundFramesFile.take(); + m_bestFramesFiles[image] = framesFile; + + } else { + return {}; + } + } + + auto framesSpecification = unlockDuring([&]() { + return make_shared(parseFramesSpecification(readJson(framesFile), framesFile)); + }); + m_framesSpecifications[image] = framesSpecification; + + return framesSpecification; +} + +IODevicePtr Assets::open(String const& path) const { + if (auto p = m_files.ptr(path)) + return p->source->open(p->sourceName); + throw AssetException(strf("No such asset '%s'", path)); +} + +ByteArray Assets::read(String const& path) const { + if (auto p = m_files.ptr(path)) + return p->source->read(p->sourceName); + throw AssetException(strf("No such asset '%s'", path)); +} + +Json Assets::readJson(String const& path) const { + ByteArray streamData = read(path); + try { + Json result = inputUtf8Json(streamData.begin(), streamData.end(), false); + for (auto const& pair : m_files.get(path).patchSources) { + auto patchStream = pair.second->read(pair.first); + auto patchData = inputUtf8Json(patchStream.begin(), patchStream.end(), false).toArray(); + try { + if (patchData.size()) { + if (patchData.at(0).type() == Json::Type::Array) { + for (auto const& patch : patchData) { + try { + result = jsonPatch(result, patch.toArray()); + } catch (JsonPatchTestFail const& e) { + Logger::debug("Patch test failure from file %s in source: %s. Caused by: %s", pair.first, m_assetSourcePaths.getLeft(pair.second), e.what()); + } + } + } else if (patchData.at(0).type() == Json::Type::Object) { + try { + result = jsonPatch(result, patchData); + } catch (JsonPatchTestFail const& e) { + Logger::debug("Patch test failure from file %s in source: %s. Caused by: %s", pair.first, m_assetSourcePaths.getLeft(pair.second), e.what()); + } + } else { + throw JsonPatchException(strf("Patch data is wrong type: %s", Json::typeName(patchData.at(0).type()))); + } + } + } catch (JsonPatchException const& e) { + Logger::error("Could not apply patch from file %s in source: %s. Caused by: %s", pair.first, m_assetSourcePaths.getLeft(pair.second), e.what()); + } + } + return result; + } catch (std::exception const& e) { + throw JsonParsingException(strf("Cannot parse json file: %s", path), e); + } +} + +bool Assets::doLoad(AssetId const& id) const { + try { + // loadAsset automatically manages the queue and freshens the asset + // data. + return (bool)loadAsset(id); + } catch (std::exception const& e) { + Logger::error("Exception caught loading asset: %s, %s", id.path, outputException(e, true)); + } catch (...) { + Logger::error("Unknown exception caught loading asset: %s", id.path); + } + + // There was an exception, remove the asset from the queue and fill the cache + // with null so that getAsset will throw. + m_assetsCache[id] = {}; + m_assetsDone.broadcast(); + m_queue.remove(id); + return true; +} + +bool Assets::doPost(AssetId const& id) const { + shared_ptr assetData; + try { + assetData = m_assetsCache.get(id); + if (id.type == AssetType::Audio) + assetData = postProcessAudio(assetData); + } catch (std::exception const& e) { + Logger::error("Exception caught post-processing asset: %s, %s", id.path, outputException(e, true)); + } catch (...) { + Logger::error("Unknown exception caught post-processing asset: %s", id.path); + } + + m_queue.remove(id); + if (assetData) { + assetData->needsPostProcessing = false; + m_assetsCache[id] = assetData; + freshen(assetData); + m_assetsDone.broadcast(); + } + + return true; +} + +shared_ptr Assets::loadAsset(AssetId const& id) const { + if (auto asset = m_assetsCache.value(id)) + return asset; + + if (m_queue.value(id, QueuePriority::None) == QueuePriority::Working) + return {}; + + try { + m_queue[id] = QueuePriority::Working; + shared_ptr assetData; + + try { + if (id.type == AssetType::Json) { + assetData = loadJson(id.path); + } else if (id.type == AssetType::Image) { + assetData = loadImage(id.path); + } else if (id.type == AssetType::Audio) { + assetData = loadAudio(id.path); + } else if (id.type == AssetType::Font) { + assetData = loadFont(id.path); + } else if (id.type == AssetType::Bytes) { + assetData = loadBytes(id.path); + } + + } catch (StarException const& e) { + if (id.type == AssetType::Image && m_settings.missingImage) { + Logger::error("Could not load image asset '%s', using placeholder default.\n%s", id.path, outputException(e, false)); + assetData = loadImage({*m_settings.missingImage, {}, {}}); + } else if (id.type == AssetType::Audio && m_settings.missingAudio) { + Logger::error("Could not load audio asset '%s', using placeholder default.\n%s", id.path, outputException(e, false)); + assetData = loadAudio({*m_settings.missingAudio, {}, {}}); + } else { + throw; + } + } + + if (assetData) { + if (assetData->needsPostProcessing) + m_queue[id] = QueuePriority::PostProcess; + else + m_queue.remove(id); + m_assetsCache[id] = assetData; + m_assetsDone.broadcast(); + freshen(assetData); + + } else { + // We have failed to load an asset because it depends on an asset + // currently being worked on. Mark it as needing loading and move it to + // the end of the queue. + m_queue[id] = QueuePriority::Load; + m_assetsQueued.signal(); + m_queue.toBack(id); + } + + return assetData; + + } catch (...) { + m_queue.remove(id); + m_assetsCache[id] = {}; + m_assetsDone.broadcast(); + throw; + } +} + +shared_ptr Assets::loadJson(AssetPath const& path) const { + Json json; + + if (path.subPath) { + auto topJson = + as(loadAsset(AssetId{AssetType::Json, {path.basePath, {}, {}}})); + if (!topJson) + return {}; + + try { + auto newData = make_shared(); + newData->json = topJson->json.query(*path.subPath); + return newData; + } catch (StarException const& e) { + throw AssetException(strf("Could not read JSON value %s", path), e); + } + } else { + return unlockDuring([&]() { + try { + auto newData = make_shared(); + newData->json = readJson(path.basePath); + return newData; + } catch (StarException const& e) { + throw AssetException(strf("Could not read JSON asset %s", path), e); + } + }); + } +} + +shared_ptr Assets::loadImage(AssetPath const& path) const { + if (!path.directives.empty()) { + shared_ptr source = + as(loadAsset(AssetId{AssetType::Image, {path.basePath, path.subPath, {}}})); + if (!source) + return {}; + List operations = path.directives.transformed(imageOperationFromString); + StringMap references; + for (auto const& ref : imageOperationReferences(operations)) { + auto components = AssetPath::split(ref); + validatePath(components, true, false); + auto refImage = as(loadAsset(AssetId{AssetType::Image, move(components)})); + if (!refImage) + return {}; + references[ref] = refImage->image; + } + + return unlockDuring([&]() { + auto newData = make_shared(); + newData->image = make_shared(processImageOperations( + operations, *source->image, [&](String const& ref) { return references.get(ref).get(); })); + return newData; + }); + + } else if (path.subPath) { + auto imageData = as(loadAsset(AssetId{AssetType::Image, {path.basePath, {}, {}}})); + if (!imageData) + return {}; + + // Base image must have frames data associated with it. + if (!imageData->frames) + throw AssetException::format("No associated frames file found for image '%s' while resolving image frame '%s'", path.basePath, path); + + if (auto alias = imageData->frames->aliases.ptr(*path.subPath)) { + imageData = as(loadAsset(AssetId{AssetType::Image, {path.basePath, *alias, path.directives}})); + if (!imageData) + return {}; + + auto newData = make_shared(); + newData->image = imageData->image; + newData->alias = true; + return newData; + + } else { + auto frameRect = imageData->frames->frames.ptr(*path.subPath); + if (!frameRect) + throw AssetException(strf("No such frame %s in frames spec %s", *path.subPath, imageData->frames->framesFile)); + + return unlockDuring([&]() { + // Need to flip frame coordinates because frame configs assume top + // down image coordinates + auto newData = make_shared(); + newData->image = make_shared(imageData->image->subImage( + Vec2U(frameRect->xMin(), imageData->image->height() - frameRect->yMax()), frameRect->size())); + return newData; + }); + } + + } else { + auto imageData = make_shared(); + imageData->image = unlockDuring([&]() { + return make_shared(Image::readPng(open(path.basePath))); + }); + imageData->frames = bestFramesSpecification(path.basePath); + + return imageData; + } +} + +shared_ptr Assets::loadAudio(AssetPath const& path) const { + return unlockDuring([&]() { + auto newData = make_shared(); + newData->audio = make_shared