Mod:CMake使用
基础 |
---|
入门 • 基础 • Lua脚本 • Data.wak • 实用工具 |
制作指南 |
音频 • 敌人 • 生物群系 • 天赋 • 法术 • 精灵表 • 材料 • 图像放射器 • 特殊行为 • CMake使用 |
组件/实体 |
组件文档 • 枚举 • 特殊标签 • 所有标签列表 |
Lua编程 |
Lua API • 实用脚本 |
其他信息 |
法术和天赋的ID • 声音事件 • 魔数(Magic Numbers) |
CMake是一个主要针对C和c++开发的构建系统 但它也可以用于许多其他事情,包括自动化Noita模型开发过程中的步骤。
在你有例如以下高级需求之前你并没有什么必须要使用它的理由
- 创建发布zip文件
- 下载文件
- 生成文件
- 编译本机代码
- 运行测试
本指南假定具有CMD/PowerShell和Noita模组开发的基本经验。 有使用CMake的经验也会有帮助。
安装
参见:
- https://cmake.org/install/
- https://ninja-build.org/ (非必要,本指南中使用的生成器)
确保 CMake 和 Ninja 在PATH
环境变量中。
基础
项目开始
Let's create a basic mod that uses CMake to automate some steps. The mod name is "HelloMod", this is the project structure: 创建一个基本mod, 由CMake自动执行一些步骤。 此mod名为"HelloMod" 这是项目结构:
HelloMod-source/ ├── CMakeLists.txt └── HelloMod ├── init.lua └── mod.xml
此mod仅作为演示,init.lua
与 mod.xml的内容并不重要。
Files that should be copied verbatim to the final mod folder are put in a subdirectory with the same name as the desired mod folder,(暂时保留原文)
需要被复制到最终mod目录中的文件要被置于一个与最终mod目录同名的一个子目录中
此处必须包含init.lua
与 mod.xml
不应该包含在发行版中或必须以某种方式生成/处理的文件放在此目录之外。
CMakeLists.txt
用来指定CMake应该做什么
这是我们如何定义一个基本可安装的项目:
cmake_minimum_required(VERSION 3.24)
project(HelloMod
VERSION 0.1.0
DESCRIPTION "Noita example mod"
HOMEPAGE_URL "https://github.com/dextercd/Noita-CMake-Example"
LANGUAGES # Empty
)
install(DIRECTORY HelloMod
DESTINATION .
COMPONENT HelloMod
)
cmake_minimum_required
specifies what version of CMake we want to useproject
lists some basic info about the projectinstall(DIRECTORY ...)
makes it so that the directory is copied to the install location when we run the installation step
这些事构建和安装mod的CMake命令:
PS Y:\> cmake -G Ninja -DCMAKE_INSTALL_PREFIX="C:\Program Files (x86)\Steam\steamapps\common\Noita\mods" -B Y:\HelloMod-build -S Y:\HelloMod-source -- Configuring done -- Generating done -- Build files have been written to: Y:/HelloMod-build PS Y:\> cd Y:\HelloMod-build PS Y:\HelloMod-build> cmake --build . ninja: no work to do. PS Y:\HelloMod-build> cmake --install . --component HelloMod -- Install configuration: "" -- Installing: C:/Program Files (x86)/Steam/steamapps/common/Noita/mods/./HelloMod -- Installing: C:/Program Files (x86)/Steam/steamapps/common/Noita/mods/./HelloMod/init.lua -- Installing: C:/Program Files (x86)/Steam/steamapps/common/Noita/mods/./HelloMod/mod.xml
通过使用第一个CMake命令来配置构建 它能通过这种方式获得源目录的位置以及构建输出的位置 剩余部分假设你正在运行构建目录下的CMake命令。.
——build
子命令检测对CMakeLists.txt
所做的更改,以便创建更新的构建文件。
在此之后,它构建目标(目前我们还没有目标)。
You can use the --install
subcommand to install the files to the CMAKE_INSTALL_PREFIX
path that was specified in the configure step.
Using your Noita mods folder for this makes a lot of sense when you are developing your mod.(暂时保留原文)
您可以使用——install
子命令将文件安装到在配置步骤中指定的CMAKE_INSTALL_PREFIX
路径中。
当你在开发mod时,使用你的Noita mod文件夹是很有意义的。
创建包
This basic CMake project was pretty simple to setup, but we didn't gain anything compared to just working directly in our Noita mods folder. Let's use CMake's packaging functionality to do something useful and automate the creation of a mod release zip file.
Packaging is handled by CPack which is included with CMake, using it requires some boilerplate code:
# ... previous CMakeLists.txt content is above here ...
# Packaging
set(CPACK_GENERATOR ZIP)
set(CPACK_INCLUDE_TOPLEVEL_DIRECTORY FALSE)
set(CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}-${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}")
set(CPACK_VERBATIM_VARIABLES TRUE)
include(CPack)
- CPACK_GENERATOR: list of default package generators to use, in this case only
ZIP
. Other options include7Z
(.7z
),TGZ
(.tar.gz
),TBZ2
(.tar.bz2
), and more. - CPACK_INCLUDE_TOPLEVEL_DIRECTORY: by default CPack adds an additional directory in the archive. Here we don't want that.
- CPACK_PACKAGE_FILE_NAME: normally the system you're building for is included in the package name, e.g. 'Linux' or 'win32'. We don't want this so we change it from the default.
- CPACK_VERBATIM_VARIABLES: this is false by default for backwards compatibility but in new projects you want to set this to true.
After adding these lines you can use CPack to create a zip file of your mod:
PS Y:\HelloMod-build> cmake --build . [0/1] Re-running CMake...-- Configuring done -- Generating done -- Build files have been written to: Y:/HelloMod-build ninja: no work to do. PS Y:\HelloMod-build> cpack CPack: Create package using ZIP CPack: Install projects CPack: - Install project: HelloMod [] CPack: Create package CPack: - package: Y:/HelloMod-build/HelloMod-0.1.0.zip generated.
Tada! If you want to make a 7Zip archive you just run cpack -G 7Z
instead.
Archives built this way have the following structure:
Archive: HelloMod-0.1.0.zip Length Date Time Name --------- ---------- ----- ---- 0 2022-12-29 20:53 HelloMod/ 77 2022-12-28 21:23 HelloMod/init.lua 80 2022-12-28 21:55 HelloMod/mod.xml
It has a top-level folder with the name we chose, to install the mod the user can open the zip then drag and drop this into their mods folder.
Downloading Files
Sometimes we want to include code or other files from some other project into our mod. A lot of the time the simplest/best solution is to copy the files into your repository, but if there are many files, large files, or binary files (e.g. .dll, .exe) then you may not want to do that. You can instead use CMake to manage these dependencies.
Let's say you want to use
EZWand and LuaJIT-MinHook
in your mod, this is what you would add to the CMakeLists.txt
file:
# ... basic project setup is above here ...
# Dependencies
include(FetchContent)
FetchContent_Declare(EZWand
URL https://github.com/TheHorscht/EZWand/releases/download/v1.5.0/EZWand.lua
DOWNLOAD_NO_EXTRACT TRUE
)
FetchContent_MakeAvailable(EZWand)
install(FILES ${ezwand_SOURCE_DIR}/EZWand.lua
DESTINATION HelloMod/lib/EZWand
COMPONENT HelloMod
)
FetchContent_Declare(LuaJIT-MinHook
URL https://github.com/dextercd/LuaJIT-MinHook/releases/download/release-1.1.1/LuaJIT-MinHook-1.1.1.zip
)
FetchContent_MakeAvailable(LuaJIT-MinHook)
install(DIRECTORY ${luajit-minhook_SOURCE_DIR}/
DESTINATION HelloMod/lib/MinHook
COMPONENT HelloMod
)
# ... CPack stuff should always come last ...
The FetchContent
module is used to download files/archives.
We specify the name and download location of dependencies using FetchContent_Declare
.
EZWand is a single Lua file, not an archive that must be extracted, so we must specify the DOWNLOAD_NO_EXTRACT TRUE
option.
FetchContent_MakeAvailable
will do the initial download and perform a redownload whenever we change the URL.
(This means updating dependencies should be pretty simple!)
After the FetchContent_MakeAvailable
command has run it
creates a <lowercaseName>_SOURCE_DIR
variable which is the path the files were downloaded to.
We use this to install
the files we want to include in the mod.
After re-running cmake --build .
and cpack
we have a zip archive containing these files:
Archive: HelloMod-0.1.0.zip Length Date Time Name --------- ---------- ----- ---- 0 2022-12-29 21:49 HelloMod/ 0 2022-12-29 21:49 HelloMod/lib/ 0 2022-12-29 21:49 HelloMod/lib/MinHook/ 3346 2022-12-29 21:33 HelloMod/lib/MinHook/minhook.lua 21504 2022-12-29 21:33 HelloMod/lib/MinHook/luajit_minhook.dll 0 2022-12-29 21:49 HelloMod/lib/EZWand/ 44068 2022-12-29 20:53 HelloMod/lib/EZWand/EZWand.lua 77 2022-12-28 21:23 HelloMod/init.lua 80 2022-12-28 21:55 HelloMod/mod.xml --------- ------- 69075 9 files
You can still use cmake --install . --component HelloMod
to place the files in your mod folder during development.
Building C/C++ Code
CMake is primarily a C and C++ build system so it's ideal for building executables and DLLs for your mod. Whether this native code is for testing parts of your mod, generating data, or an integral part of how your mod works, CMake can compile, execute, and package these files for you.
In this guide we will use these facilities to build a DLL with which we can set the contents of the clipboard. The mod will use this to put the world seed in the player's clipboard at the start of a run.
To start you need a C/C++ toolchain installed (most likely some version of VC++). Make sure CMake can find this toolchain, with VC++ you need to make it visible in the environment by launching an 'x86 Native Tools Command Prompt'.
Now we need to tell CMake that our project uses C++, we do this by changing the project
command in the CMakeLists.txt file.
project(HelloMod
VERSION 0.1.0
DESCRIPTION "Noita example mod"
HOMEPAGE_URL "https://github.com/dextercd/Noita-CMake-Example"
+ LANGUAGES CXX
)
If you run the --build
subcommand now it should show info about the compilation tools CMake found.
The project has a clipboard.cpp
file in the root directory.
This C++ code exports a single function that we can use from Lua which sets the contents of the clipboard using the win32 API.
clipboard.cpp |
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <cstring>
/**
* Set the clipboard to the provided text.
* @return true if the clipboard change was successful.
*/
extern "C" __declspec(dllexport)
bool set_clipboard(const char* text)
{
bool success = false;
bool clipboard_opened = false;
HGLOBAL global_data = nullptr;
// Need a window for the clipboard API, it's never made visible
HWND clipboard_window = CreateWindowA(
"Message", nullptr, 0, 0, 0, 0, 0, nullptr, nullptr, nullptr, nullptr);
if (!clipboard_window)
goto cleanup;
if (!(clipboard_opened = OpenClipboard(clipboard_window)) || !EmptyClipboard())
goto cleanup;
std::size_t data_length = std::strlen(text) + 1;
global_data = GlobalAlloc(GMEM_MOVEABLE, data_length);
if (!global_data)
goto cleanup;
void* data = GlobalLock(global_data);
if (!data)
goto cleanup;
std::memcpy(data, text, data_length);
if (!GlobalUnlock(global_data))
goto cleanup;
if (SetClipboardData(CF_TEXT, global_data)) {
global_data = nullptr; // System now manages the data
success = true;
}
cleanup:
if (global_data)
GlobalFree(global_data);
if (clipboard_opened)
CloseClipboard();
if (clipboard_window)
DestroyWindow(clipboard_window);
return success;
}
|
Now we tell CMake to build this file into a DLL and package it with the rest of the mod. Make sure this code is added above the packaging setup.
# ...
# C++ module
add_library(clipboard MODULE
clipboard.cpp
)
install(TARGETS clipboard
LIBRARY DESTINATION HelloMod/dll
COMPONENT HelloMod
)
# ... CPack stuff should always come last ...
If you build and install the project you should see clipboard.dll
in your mod inside of the dll
folder.
Now enable request_no_api_restrictions="1"
in mod.xml
,
and change the init.lua
file to this:
local ffi = require("ffi")
ffi.cdef([[
bool set_clipboard(const char* text);
]])
local clipboard = ffi.load("mods/HelloMod/dll/clipboard.dll")
function OnWorldInitialized()
local seed = StatsGetValue("world_seed")
local text = "Currently playing this seed: " .. seed
if not clipboard.set_clipboard(text) then
GamePrint("Couldn't set clipboard!")
end
end
And that should be everything! You now have a functioning mod that puts the current seed you're playing on in your clipboard. Installing and packaging using CMake should work exactly like before.
Note that this example uses LuaJIT's ffi
module instead of a C package that interfaces with Lua using the Lua C API.
The latter requires finding and linking against Lua which is a lot more tedious to setup (but definitely possible!)
Project Using CMake
Links to Noita mods that use CMake, perhaps there are some useful snippets that you can reuse:
- https://github.com/dextercd/Noita-CMake-Example - The example project used throughout this guide
- https://github.com/dextercd/Noita-Minidump - Building C++ code and adding the DLL into the mod folder
- https://github.com/dextercd/Noita-Shutdown - Generating sprite images using Python
- https://github.com/dextercd/Noita-Synchronise-Expansive-Worlds - Building C and C++ code and building documentation files
- https://github.com/dextercd/Noita-Component-Explorer - Generating Lua code from templates
Resources
This guide is here just to demonstrate some useful techniques, and to have some code that you can copy and paste to get a project started on CMake. It's not a replacement for CMake's documentation or other learning material.
- Reference Documentation: https://cmake.org/cmake/help/latest/
- Great book on CMake: https://crascit.com/professional-cmake/