mirror of
https://github.com/minetest/minetest.git
synced 2025-03-06 20:48:40 +01:00
Compare commits
233 commits
Author | SHA1 | Date | |
---|---|---|---|
|
587f6656a4 | ||
|
4377c03168 | ||
|
1e0520074c | ||
|
b9f6832347 | ||
|
f8bb0cd3d1 | ||
|
129aef758e | ||
|
94f55cf406 | ||
|
a20b758e19 | ||
|
128842becf | ||
|
d51331f51f | ||
|
4ef221a645 | ||
|
79010e972e | ||
|
0ca530e251 | ||
|
57b4d46dbc | ||
|
50df5e2f59 | ||
|
b6db2c7262 | ||
|
9441b69ad2 | ||
|
08e3d16a58 | ||
|
3f3049fdba | ||
|
41fb7a8a7e | ||
|
5b9828e094 | ||
|
96e35585b0 | ||
|
f035fe9336 | ||
|
44c2e33c78 | ||
|
2785dcbbbf | ||
|
9b03bd3243 | ||
|
8f30456ee3 | ||
|
38557ff635 | ||
|
7bc2cde4dd | ||
|
f065d3a06b | ||
|
21f7e3a987 | ||
|
9f688bc433 | ||
|
6e9d31d4fb | ||
|
e81c48526b | ||
|
b405985b80 | ||
|
8b010c5a9f | ||
|
1e7b5d6fdb | ||
|
a55982e7f0 | ||
|
1ac378063e | ||
|
d497c92684 | ||
|
677dc2c155 | ||
|
beea8deeb5 | ||
|
0d0f1a2fb2 | ||
|
439701ed7a | ||
|
d945d0129c | ||
|
cc91477308 | ||
|
ac139ec03d | ||
|
b4f0e834bf | ||
|
6e6cdc834f | ||
|
4b81ae1b35 | ||
|
d569dc45a8 | ||
|
23d49fda29 | ||
|
62ad2c3bc1 | ||
|
25373ad294 | ||
|
26d0c0fd8d | ||
|
3afffcd36b | ||
|
5440de1785 | ||
|
25a56f11c8 | ||
|
8271c6481f | ||
|
83e26f839d | ||
|
c3f7905d82 | ||
|
05b54a8d18 | ||
|
e5cfdd369e | ||
|
c61d8cfb85 | ||
|
27f4195471 | ||
|
b2596eda32 | ||
|
9379440fcb | ||
|
b7c502c8d1 | ||
|
3c16d2d9b4 | ||
|
6cbc03a418 | ||
|
15ecc0fa65 | ||
|
7f2f8cdad1 | ||
|
a64646cb7b | ||
|
05f531c538 | ||
|
7ca335446b | ||
|
99e96a6581 | ||
|
7200d8f90e | ||
|
7038837aca | ||
|
541dcc0e5a | ||
|
1060b5aabf | ||
|
6ea73c4982 | ||
|
cff847273a | ||
|
e669f8db9b | ||
|
461cc30842 | ||
|
13d7cb957c | ||
|
47a439a905 | ||
|
053fb96694 | ||
|
b72c1f7367 | ||
|
e90738575f | ||
|
35718eec9c | ||
|
8285a53152 | ||
|
116fe7815b | ||
|
4c2efd7da3 | ||
|
1a433e3185 | ||
|
fa98a00916 | ||
|
f4dd46ad60 | ||
|
41825ccfbb | ||
|
4d1a5f12c0 | ||
|
cc58d56c6d | ||
|
683ef07312 | ||
|
c77c78cef5 | ||
|
88517030a4 | ||
|
45471e8e63 | ||
|
4e620beb75 | ||
|
e7ab875b47 | ||
|
be74ed9ab6 | ||
|
52b2eacd37 | ||
|
6b4210a2ea | ||
|
740d0da5ee | ||
|
34883d356e | ||
|
aeafcce314 | ||
|
ae1d82c325 | ||
|
1c89a07226 | ||
|
43e262f13e | ||
|
e5f802ab5c | ||
|
847860fc5c | ||
|
77e936445f | ||
|
41beb74ef7 | ||
|
67be50b706 | ||
|
cd840b7c9d | ||
|
07903949ec | ||
|
d2156ddaad | ||
|
a84ff4b3ff | ||
|
c02f13d33f | ||
|
f6490859fd | ||
|
4b7816dbf4 | ||
|
86ffbc3ec5 | ||
|
67257f44a5 | ||
|
3086f5567a | ||
|
bb8acb095d | ||
|
24be3cbb5f | ||
|
9773191103 | ||
|
ddc703c3ec | ||
|
09ec204e4b | ||
|
8b6cafa0e0 | ||
|
1ee8be9d43 | ||
|
91bc190d21 | ||
|
4dc833b642 | ||
|
895e9f8d5c | ||
|
12906ff631 | ||
|
9c9bcff107 | ||
|
443ca5c63b | ||
|
1102b1fc4c | ||
|
0d089eab21 | ||
|
b44fada29e | ||
|
859ea160e6 | ||
|
1c49e2ffc0 | ||
|
140245c58d | ||
|
47adcced60 | ||
|
a2054deb12 | ||
|
30fdfd9ded | ||
|
a87a86eb92 | ||
|
49dfbcfbc8 | ||
|
c558a4f4f6 | ||
|
4f49a2248f | ||
|
4e57da42c1 | ||
|
4f6c9e206c | ||
|
699e1d4b77 | ||
|
fa2978b3b9 | ||
|
ffcdf741fc | ||
|
429d7f33d4 | ||
|
b6a229775e | ||
|
0a4580368a | ||
|
e5b191e1f1 | ||
|
a07acc067b | ||
|
01e763f879 | ||
|
a95e75261a | ||
|
dcaa7ac609 | ||
|
7d2ae10849 | ||
|
642cac3759 | ||
|
662af2edba | ||
|
6f97e15a8f | ||
|
ab2fa2ca9d | ||
|
4666c99b27 | ||
|
e38415c56d | ||
|
3f3fce4664 | ||
|
1407a7bc8a | ||
|
1d268f4b83 | ||
|
92aab79d27 | ||
|
0823bb1276 | ||
|
18e42efa01 | ||
|
0ea16328df | ||
|
b037efc5c6 | ||
|
839bb71389 | ||
|
2ef7df5cfe | ||
|
f5d2d79f75 | ||
|
8e5fa96ffc | ||
|
6dbbe07d50 | ||
|
3c23410753 | ||
|
8e35ab78b4 | ||
|
7f2911b7f0 | ||
|
d877e90301 | ||
|
93db4be726 | ||
|
1f419ad19f | ||
|
3c7f6508ad | ||
|
39449d4b63 | ||
|
28e05295fa | ||
|
60c18d3550 | ||
|
482ab186a2 | ||
|
4814195dc4 | ||
|
d215b7a10e | ||
|
0e52b78590 | ||
|
b3a9d607c4 | ||
|
c00081b62c | ||
|
5e4739b460 | ||
|
0d8f598df2 | ||
|
2ef04cc308 | ||
|
7a0884e2cd | ||
|
fa858530cc | ||
|
1c61fe5ed9 | ||
|
57409ef382 | ||
|
06ba826803 | ||
|
6e6ef9489f | ||
|
45bbe39d1f | ||
|
6c9fc13083 | ||
|
4c8a642388 | ||
|
6208c9d64f | ||
|
76325d0ba9 | ||
|
cf1802a6de | ||
|
538a7b12bd | ||
|
57e0f52aaa | ||
|
1ae0335b62 | ||
|
ca1bff6b66 | ||
|
10cc62d2ca | ||
|
dd451a8a00 | ||
|
444ec1e412 | ||
|
82739f4d7d | ||
|
19825d853e | ||
|
d8ece2e3e9 | ||
|
bf4deb0ce6 | ||
|
da4739a26c | ||
|
fc24bf0915 | ||
|
9329b99cba |
1878 changed files with 228370 additions and 459266 deletions
33
.clang-format
Normal file
33
.clang-format
Normal file
|
@ -0,0 +1,33 @@
|
|||
BasedOnStyle: LLVM
|
||||
IndentWidth: 4
|
||||
UseTab: Always
|
||||
TabWidth: 4
|
||||
BreakBeforeBraces: Custom
|
||||
Standard: Cpp11
|
||||
BraceWrapping:
|
||||
AfterClass: true
|
||||
AfterControlStatement: false
|
||||
AfterEnum: true
|
||||
AfterFunction: true
|
||||
AfterNamespace: true
|
||||
AfterStruct: true
|
||||
AfterUnion: true
|
||||
BeforeCatch: false
|
||||
BeforeElse: false
|
||||
FixNamespaceComments: false
|
||||
AllowShortIfStatementsOnASingleLine: false
|
||||
IndentCaseLabels: false
|
||||
AccessModifierOffset: -4
|
||||
ColumnLimit: 90
|
||||
AllowShortFunctionsOnASingleLine: InlineOnly
|
||||
SortIncludes: false
|
||||
IncludeCategories:
|
||||
- Regex: '^".*'
|
||||
Priority: 2
|
||||
- Regex: '^<.*'
|
||||
Priority: 1
|
||||
AlignAfterOpenBracket: DontAlign
|
||||
ContinuationIndentWidth: 8
|
||||
ConstructorInitializerIndentWidth: 8
|
||||
BreakConstructorInitializers: AfterColon
|
||||
AlwaysBreakTemplateDeclarations: Yes
|
|
@ -1,4 +1,4 @@
|
|||
Checks: '-*,modernize-use-emplace,modernize-avoid-bind,misc-throw-by-value-catch-by-reference,misc-unconventional-assign-operator,performance-*,-performance-avoid-endl,performance-inefficient-string-concatenation'
|
||||
Checks: '-*,modernize-use-emplace,modernize-avoid-bind,misc-throw-by-value-catch-by-reference,misc-unconventional-assign-operator,performance-*'
|
||||
WarningsAsErrors: '-*,modernize-use-emplace,performance-type-promotion-in-math-fn,performance-faster-string-find,performance-implicit-cast-in-loop'
|
||||
CheckOptions:
|
||||
- key: performance-unnecessary-value-param.AllowedTypes
|
||||
|
|
11
.editorconfig
Normal file → Executable file
11
.editorconfig
Normal file → Executable file
|
@ -1,16 +1,9 @@
|
|||
[*]
|
||||
end_of_line = lf
|
||||
|
||||
[*.{cpp,h,lua,txt,glsl,c,cmake,java,gradle}]
|
||||
charset = utf-8
|
||||
[*.{cpp,h,lua,txt,glsl,md,c,cmake,java,gradle}]
|
||||
charset = utf8
|
||||
indent_size = 4
|
||||
indent_style = tab
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.md]
|
||||
charset = utf-8
|
||||
indent_size = 4
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
|
6
.gitattributes
vendored
6
.gitattributes
vendored
|
@ -1,8 +1,2 @@
|
|||
# Forces all files which git considers text files to use LF line endings
|
||||
* text=auto eol=lf
|
||||
|
||||
*.cpp diff=cpp
|
||||
*.h diff=cpp
|
||||
|
||||
*.gltf binary
|
||||
*.x binary
|
||||
|
|
69
.github/CONTRIBUTING.md
vendored
69
.github/CONTRIBUTING.md
vendored
|
@ -14,31 +14,28 @@ Contributions are welcome! Here's how you can help:
|
|||
[clone](https://help.github.com/articles/cloning-a-repository/) your fork.
|
||||
|
||||
2. Before you start coding, consider opening an
|
||||
[issue on Github](https://github.com/luanti-org/luanti/issues) to discuss the
|
||||
[issue at Github](https://github.com/minetest/minetest/issues) to discuss the
|
||||
suitability and implementation of your intended contribution with the core
|
||||
developers.
|
||||
|
||||
Any Pull Request that isn't a bug fix and isn't covered by
|
||||
[the roadmap](../doc/direction.md) will be closed within a month unless it
|
||||
[the roadmap](../doc/direction.md) will be closed within a week unless it
|
||||
receives a concept approval from a Core Developer. For this reason, it is
|
||||
recommended that you open an issue for any such pull requests before doing
|
||||
the work, to avoid disappointment.
|
||||
|
||||
You may also benefit from discussing on our IRC development channel
|
||||
[#luanti-dev](http://www.luanti.org/irc/). Note that a proper IRC client
|
||||
[#minetest-dev](http://www.minetest.net/irc/). Note that a proper IRC client
|
||||
is required to speak on this channel.
|
||||
|
||||
3. Start coding!
|
||||
- Refer to the
|
||||
[Lua API](https://github.com/luanti-org/luanti/blob/master/doc/lua_api.md),
|
||||
[Developer Wiki](https://dev.luanti.org/) and other
|
||||
[documentation](https://github.com/luanti-org/luanti/tree/master/doc).
|
||||
- Follow the [C/C++](https://dev.luanti.org/Code_style_guidelines) and
|
||||
[Lua](https://dev.luanti.org/Lua_code_style_guidelines) code style guidelines.
|
||||
[Lua API](https://github.com/minetest/minetest/blob/master/doc/lua_api.txt),
|
||||
[Developer Wiki](http://dev.minetest.net/Main_Page) and other
|
||||
[documentation](https://github.com/minetest/minetest/tree/master/doc).
|
||||
- Follow the [C/C++](http://dev.minetest.net/Code_style_guidelines) and
|
||||
[Lua](http://dev.minetest.net/Lua_code_style_guidelines) code style guidelines.
|
||||
- Check your code works as expected and document any changes to the Lua API.
|
||||
- To avoid conflicting changes between contributions, do not do the following manually. They will be done before each release.
|
||||
- Run `updatepo.sh` or update `luanti.po{,t}` even if your code adds new translatable strings.
|
||||
- Update `minetest.conf.example` and `settings_translation_file.cpp` even if your code adds new core settings.
|
||||
|
||||
4. Commit & [push](https://help.github.com/articles/pushing-to-a-remote/) your changes to a new branch (not `master`, one change per branch)
|
||||
- Commit messages should:
|
||||
|
@ -53,7 +50,7 @@ Contributions are welcome! Here's how you can help:
|
|||
- The following lines should describe the commit, starting a new line for each point.
|
||||
|
||||
5. Once you are happy with your changes, submit a pull request.
|
||||
- Open the [pull-request form](https://github.com/luanti-org/luanti/pull/new/master).
|
||||
- Open the [pull-request form](https://github.com/minetest/minetest/pull/new/master).
|
||||
- Add a description explaining what you've done (or if it's a
|
||||
work-in-progress - what you need to do).
|
||||
- Make sure to fill out the pull request template.
|
||||
|
@ -64,26 +61,40 @@ Contributions are welcome! Here's how you can help:
|
|||
picture of the project.
|
||||
2. It works.
|
||||
3. It follows the code style for
|
||||
[C/C++](https://dev.luanti.org/Code_style_guidelines) or
|
||||
[Lua](https://dev.luanti.org/Lua_code_style_guidelines).
|
||||
[C/C++](http://dev.minetest.net/Code_style_guidelines) or
|
||||
[Lua](http://dev.minetest.net/Lua_code_style_guidelines).
|
||||
4. The code's interfaces are well designed, regardless of other aspects that
|
||||
might need more work in the future.
|
||||
5. It uses protocols and formats which include the required compatibility.
|
||||
|
||||
### Important note about automated GitHub checks
|
||||
|
||||
When you submit a pull request, GitHub automatically runs checks on the Minetest
|
||||
Engine combined with your changes. One of these checks is called 'cpp lint /
|
||||
clang format', which checks code formatting. Because formatting for readability
|
||||
requires human judgement this check often fails and often makes unsuitable
|
||||
formatting requests which make code readability worse.
|
||||
|
||||
If this check fails, look at the details to check for any clear mistakes and
|
||||
correct those. However, you should not apply everything ClangFormat requests.
|
||||
Ignore requests that make code readability worse and any other clearly
|
||||
unsuitable requests. Discuss in the pull request with a core developer about how
|
||||
to progress.
|
||||
|
||||
## Issues
|
||||
|
||||
If you experience an issue, we would like to know the details - especially when
|
||||
a stable release is on the way.
|
||||
|
||||
1. Do a quick search on GitHub to check if the issue has already been reported.
|
||||
2. Is it an issue with the Luanti *engine*? If not, report it
|
||||
[elsewhere](http://www.luanti.org/development/#reporting-issues).
|
||||
3. [Open an issue](https://github.com/luanti-org/luanti/issues/new) and describe
|
||||
2. Is it an issue with the Minetest *engine*? If not, report it
|
||||
[elsewhere](http://www.minetest.net/development/#reporting-issues).
|
||||
3. [Open an issue](https://github.com/minetest/minetest/issues/new) and describe
|
||||
the issue you are having - you could include:
|
||||
- Error logs (check the bottom of the `debug.txt` file).
|
||||
- Screenshots.
|
||||
- Ways you have tried to solve the issue, and whether they worked or not.
|
||||
- Your Luanti version and the content (games, mods or texture packs) you have installed.
|
||||
- Your Minetest version and the content (games, mods or texture packs) you have installed.
|
||||
- Your platform (e.g. Windows 10 or Ubuntu 15.04 x64).
|
||||
|
||||
After reporting you should aim to answer questions or clarifications as this
|
||||
|
@ -99,35 +110,35 @@ possible.
|
|||
|
||||
## Translations
|
||||
|
||||
The core translations of Luanti are performed using Weblate. You can access
|
||||
The core translations of Minetest are performed using Weblate. You can access
|
||||
the project page with a list of current languages
|
||||
[here](https://hosted.weblate.org/projects/minetest/minetest/).
|
||||
|
||||
Builtin (the component which contains things like server messages, chat command
|
||||
descriptions, privilege descriptions) is translated separately; it needs to be
|
||||
translated by editing a `.tr` text file. See
|
||||
[Translation](https://dev.luanti.org/Translation) for more information.
|
||||
[Translation](https://dev.minetest.net/Translation) for more information.
|
||||
|
||||
## Donations
|
||||
|
||||
If you'd like to monetarily support Luanti development, you can find donation
|
||||
methods on [our website](http://www.luanti.org/development/#donate).
|
||||
If you'd like to monetarily support Minetest development, you can find donation
|
||||
methods on [our website](http://www.minetest.net/development/#donate).
|
||||
|
||||
# Maintaining
|
||||
|
||||
* This is a concise version of the
|
||||
[Rules & Guidelines](https://dev.luanti.org/engine-dev-process/) on the developer wiki.*
|
||||
[Rules & Guidelines](http://dev.minetest.net/Category:Rules_and_Guidelines) on the developer wiki.*
|
||||
|
||||
These notes are for those who have push access Luanti (core developers / maintainers).
|
||||
These notes are for those who have push access Minetest (core developers / maintainers).
|
||||
|
||||
- See the [project organisation](https://dev.luanti.org/Organisation) for the people involved.
|
||||
- See the [project organisation](http://dev.minetest.net/Organisation) for the people involved.
|
||||
|
||||
## Concept approvals and roadmaps
|
||||
|
||||
If a Pull Request is not a bug fix:
|
||||
|
||||
* If it matches a goal in [the roadmap](../doc/direction.md), then the PR should
|
||||
be labeled as "Roadmap" and the goal stated by number in the description.
|
||||
be labelled as "Roadmap" and the goal stated by number in the description.
|
||||
* If it doesn't match a goal, then it needs to receive a concept approval within
|
||||
a week of being opened to remain open. This 1 week deadline does not apply to
|
||||
PRs opened before the roadmap was adopted; instead, they may remain open or be
|
||||
|
@ -159,14 +170,14 @@ Submit a :+1: (+1) or "Looks good" comment to show you believe the pull-request
|
|||
- The title should follow the commit guidelines (title starts with a capital letter, present tense, descriptive).
|
||||
- Don't modify history older than 10 minutes.
|
||||
- Use rebase, not merge to get linear history:
|
||||
- `curl -Ls https://github.com/luanti-org/luanti/pull/1.patch | git am`
|
||||
- `curl https://github.com/minetest/minetest/pull/1.patch | git am`
|
||||
|
||||
## Reviewing issues and feature requests
|
||||
|
||||
- If an issue does not get a response from its author within 1 month (when requiring more details), it can be closed.
|
||||
- When an issue is a duplicate, refer to the first ones and close the later ones.
|
||||
- Tag issues with the appropriate [labels](https://github.com/luanti-org/luanti/labels) for devices, platforms etc.
|
||||
- Tag issues with the appropriate [labels](https://github.com/minetest/minetest/labels) for devices, platforms etc.
|
||||
|
||||
## Releasing a new version
|
||||
|
||||
*Refer to [dev.luanti.org/Releasing_Luanti](https://dev.luanti.org/Releasing_Luanti)*
|
||||
*Refer to [dev.minetest.net/Releasing_Minetest](http://dev.minetest.net/Releasing_Minetest)*
|
||||
|
|
32
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
32
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
|
@ -0,0 +1,32 @@
|
|||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: Unconfirmed bug
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
##### Minetest version
|
||||
<!--
|
||||
Paste Minetest version between quotes below.
|
||||
If you are on a devel version, please add git commit hash.
|
||||
You can use `minetest --version` to find it.
|
||||
-->
|
||||
```
|
||||
|
||||
```
|
||||
|
||||
##### OS / Hardware
|
||||
<!-- General information about your hardware and operating system -->
|
||||
Operating system:
|
||||
CPU:
|
||||
|
||||
<!-- For graphical issues only -->
|
||||
GPU model:
|
||||
OpenGL version:
|
||||
|
||||
##### Summary
|
||||
<!-- Describe your problem here -->
|
||||
|
||||
##### Steps to reproduce
|
||||
<!-- Explain how the problem has happened, providing a minimal test (i.e. a code snippet reduced to the bone) where possible -->
|
79
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
79
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
|
@ -1,79 +0,0 @@
|
|||
name: Bug report
|
||||
description: Create a report to help us improve
|
||||
labels: ["Unconfirmed bug"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Please note the following:
|
||||
|
||||
1. **Please update Luanti to the latest stable or dev version** before submitting bug reports. Make sure the bug is still reproducible on the latest version.
|
||||
2. This page is for reporting the bugs of **the engine itself**. For bugs in a particular game, please [search for the game in the ContentDB](https://content.luanti.org/packages/?type=game) and submit a bug report in their issue trackers.
|
||||
* For example, you can submit issues about the Minetest Game [in its own repository](https://github.com/luanti-org/minetest_game/issues).
|
||||
3. Please provide as many details as possible for us to spot the problem quicker.
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Luanti version
|
||||
description: |
|
||||
Paste the Luanti version below.
|
||||
If you are on a dev version, please also indicate the git commit hash.
|
||||
Refer to the "About" tab of the menu or run `luanti --version` on the command line.
|
||||
placeholder: |
|
||||
Example:
|
||||
Luanti 5.10.0-3ad6aee9b (Linux)
|
||||
Using LuaJIT 2.1.1727870382
|
||||
Built by GCC 14.2
|
||||
Running on Linux/6.11.5 x86_64
|
||||
BUILD_TYPE=Release
|
||||
RUN_IN_PLACE=1
|
||||
USE_CURL=1
|
||||
USE_GETTEXT=1
|
||||
USE_SOUND=1
|
||||
STATIC_SHAREDIR="."
|
||||
STATIC_LOCALEDIR="locale"
|
||||
render: "true"
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: Operating system and version
|
||||
description: It is recommended to upgrade your operating system to see if the problem persists.
|
||||
placeholder: "Example: Ubuntu 22.04"
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: CPU model
|
||||
description: Usually found in OS/system settings.
|
||||
placeholder: "Example: Intel Core i5-2410M"
|
||||
validations:
|
||||
required: false
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: The GPU model and renderer can be omitted if the bug is not a graphical issue.
|
||||
- type: input
|
||||
attributes:
|
||||
label: GPU model
|
||||
description: Usually found in OS/system settings.
|
||||
placeholder: "Example: NVIDIA GeForce GTX 1660"
|
||||
validations:
|
||||
required: false
|
||||
- type: input
|
||||
attributes:
|
||||
label: Active renderer
|
||||
description: You can find this in the "About" tab in the main menu.
|
||||
placeholder: "Example: ES 3.2 / ogles2 / X11"
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Summary
|
||||
description: Describe your problem here.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Steps to reproduce
|
||||
description: Explain how the problem has happened, providing a minimal test (e.g. a minimized code snippet) where possible.
|
||||
validations:
|
||||
required: true
|
8
.github/ISSUE_TEMPLATE/config.yml
vendored
8
.github/ISSUE_TEMPLATE/config.yml
vendored
|
@ -1,8 +0,0 @@
|
|||
blank_issues_enabled: true
|
||||
contact_links:
|
||||
- name: Submit issues about Minetest Game
|
||||
url: https://github.com/luanti-org/minetest_game/issues/new
|
||||
about: Only submit issues of the engine in this repository's issue tracker. Submit those of Minetest Game in its own issue tracker.
|
||||
- name: Search for issue trackers of third-party games
|
||||
url: https://content.luanti.org/packages/?type=game
|
||||
about: For issues of third-party games, search for the game in the ContentDB and then submit an issue in their issue tracker.
|
25
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
25
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
|
@ -0,0 +1,25 @@
|
|||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: Feature request
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
## Problem
|
||||
|
||||
A clear and concise description of what the problem is.
|
||||
ie: Why is this needed?
|
||||
Ex. I'm always frustrated when [...]
|
||||
|
||||
## Solutions
|
||||
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
## Alternatives
|
||||
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
## Additional context
|
||||
|
||||
Add any other context or screenshots about the feature request here.
|
39
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
39
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
|
@ -1,39 +0,0 @@
|
|||
name: Feature request
|
||||
description: Suggest an idea for this project
|
||||
labels: ["Feature request"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Please note the following:
|
||||
1. Only submit a feature request if the feature does not exist on the latest dev version.
|
||||
2. This page is for suggesting changes to **the engine itself**. To suggest changes to games, please [search for the game in the ContentDB](https://content.luanti.org/packages/?type=game) and submit a feature request in their issue trackers.
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Problem
|
||||
description: |
|
||||
A clear and concise description of the problem, i.e. "Why is this needed?"
|
||||
Example: I'm always frustrated when [...]
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Solutions
|
||||
description: |
|
||||
A clear and concise description of what you want to happen.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Alternatives
|
||||
description: |
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional context
|
||||
description: |
|
||||
Add any other context or screenshots about the feature request here.
|
||||
validations:
|
||||
required: false
|
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
|
@ -3,7 +3,7 @@ Add compact, short information about your PR for easier understanding:
|
|||
- Goal of the PR
|
||||
- How does the PR work?
|
||||
- Does it resolve any reported issue?
|
||||
- Does this relate to a goal in [the roadmap](https://github.com/luanti-org/luanti/blob/master/doc/direction.md)?
|
||||
- Does this relate to a goal in [the roadmap](../doc/direction.md)?
|
||||
- If not a bug fix, why is this PR needed? What usecases does it solve?
|
||||
|
||||
## To do
|
||||
|
|
6
.github/SECURITY.md
vendored
6
.github/SECURITY.md
vendored
|
@ -3,7 +3,7 @@
|
|||
## Supported Versions
|
||||
|
||||
We only support the latest stable version for security issues.
|
||||
See the [releases page](https://github.com/luanti-org/luanti/releases).
|
||||
See the [releases page](https://github.com/minetest/minetest/releases).
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
|
@ -11,10 +11,10 @@ We ask that you report vulnerabilities privately, by contacting a core developer
|
|||
to give us time to fix them. You can do that by emailing one of the following addresses:
|
||||
|
||||
* celeron55@gmail.com
|
||||
* rw@rubenwardy.com
|
||||
* rubenwardy@minetest.net
|
||||
|
||||
Depending on severity, we will either create a private issue for the vulnerability
|
||||
and release a patch version of Luanti, or give you permission to file the issue publicly.
|
||||
and release a patch version of Minetest, or give you permission to file the issue publicly.
|
||||
|
||||
For more information on the justification of this policy, see
|
||||
[Responsible Disclosure](https://en.wikipedia.org/wiki/Responsible_disclosure).
|
||||
|
|
48
.github/workflows/android.yml
vendored
48
.github/workflows/android.yml
vendored
|
@ -8,11 +8,6 @@ on:
|
|||
- 'lib/**.cpp'
|
||||
- 'src/**.[ch]'
|
||||
- 'src/**.cpp'
|
||||
- 'irr/**.[ch]'
|
||||
- 'irr/**.cpp'
|
||||
- '**/CMakeLists.txt'
|
||||
- 'cmake/Modules/**'
|
||||
- 'po/**.po'
|
||||
- 'android/**'
|
||||
- '.github/workflows/android.yml'
|
||||
pull_request:
|
||||
|
@ -21,56 +16,37 @@ on:
|
|||
- 'lib/**.cpp'
|
||||
- 'src/**.[ch]'
|
||||
- 'src/**.cpp'
|
||||
- 'irr/**.[ch]'
|
||||
- 'irr/**.cpp'
|
||||
- '**/CMakeLists.txt'
|
||||
- 'cmake/Modules/**'
|
||||
- 'po/**.po'
|
||||
- 'android/**'
|
||||
- '.github/workflows/android.yml'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install deps
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y --no-install-recommends gettext
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '17'
|
||||
- name: Build AAB with Gradle
|
||||
# We build an AAB as well for uploading to the the Play Store.
|
||||
run: cd android; ./gradlew bundlerelease
|
||||
- name: Build APKs with Gradle
|
||||
# "assemblerelease" is very fast after "bundlerelease".
|
||||
sudo apt-get install -y --no-install-recommends gettext openjdk-11-jdk-headless
|
||||
- name: Build with Gradle
|
||||
run: cd android; ./gradlew assemblerelease
|
||||
- name: Save AAB artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Luanti-release.aab
|
||||
path: android/app/build/outputs/bundle/release/app-release.aab
|
||||
- name: Save armeabi artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: Luanti-armeabi-v7a.apk
|
||||
name: Minetest-armeabi-v7a.apk
|
||||
path: android/app/build/outputs/apk/release/app-armeabi-v7a-release-unsigned.apk
|
||||
- name: Save arm64 artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: Luanti-arm64-v8a.apk
|
||||
name: Minetest-arm64-v8a.apk
|
||||
path: android/app/build/outputs/apk/release/app-arm64-v8a-release-unsigned.apk
|
||||
- name: Save x86 artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: Luanti-x86.apk
|
||||
name: Minetest-x86.apk
|
||||
path: android/app/build/outputs/apk/release/app-x86-release-unsigned.apk
|
||||
- name: Save x86_64 artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: Luanti-x86_64.apk
|
||||
name: Minetest-x86_64.apk
|
||||
path: android/app/build/outputs/apk/release/app-x86_64-release-unsigned.apk
|
||||
|
|
271
.github/workflows/build.yml
vendored
Normal file
271
.github/workflows/build.yml
vendored
Normal file
|
@ -0,0 +1,271 @@
|
|||
name: build
|
||||
|
||||
# build on c/cpp changes or workflow changes
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'lib/**.[ch]'
|
||||
- 'lib/**.cpp'
|
||||
- 'src/**.[ch]'
|
||||
- 'src/**.cpp'
|
||||
- '**/CMakeLists.txt'
|
||||
- 'cmake/Modules/**'
|
||||
- 'util/buildbot/**'
|
||||
- 'util/ci/**'
|
||||
- '.github/workflows/**.yml'
|
||||
- 'Dockerfile'
|
||||
- '.dockerignore'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'lib/**.[ch]'
|
||||
- 'lib/**.cpp'
|
||||
- 'src/**.[ch]'
|
||||
- 'src/**.cpp'
|
||||
- '**/CMakeLists.txt'
|
||||
- 'cmake/Modules/**'
|
||||
- 'util/buildbot/**'
|
||||
- 'util/ci/**'
|
||||
- '.github/workflows/**.yml'
|
||||
- 'Dockerfile'
|
||||
- '.dockerignore'
|
||||
|
||||
jobs:
|
||||
# Older gcc version (should be close to our minimum supported version)
|
||||
gcc_5:
|
||||
runs-on: ubuntu-18.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install deps
|
||||
run: |
|
||||
source ./util/ci/common.sh
|
||||
install_linux_deps g++-5
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
./util/ci/build.sh
|
||||
env:
|
||||
CC: gcc-5
|
||||
CXX: g++-5
|
||||
|
||||
- name: Test
|
||||
run: |
|
||||
./bin/minetest --run-unittests
|
||||
|
||||
# Current gcc version
|
||||
gcc_12:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install deps
|
||||
run: |
|
||||
source ./util/ci/common.sh
|
||||
install_linux_deps g++-12 libluajit-5.1-dev
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
./util/ci/build.sh
|
||||
env:
|
||||
CC: gcc-12
|
||||
CXX: g++-12
|
||||
|
||||
- name: Test
|
||||
run: |
|
||||
./bin/minetest --run-unittests
|
||||
|
||||
# Older clang version (should be close to our minimum supported version)
|
||||
clang_3_9:
|
||||
runs-on: ubuntu-18.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install deps
|
||||
run: |
|
||||
source ./util/ci/common.sh
|
||||
install_linux_deps clang-3.9 valgrind
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
./util/ci/build.sh
|
||||
env:
|
||||
CC: clang-3.9
|
||||
CXX: clang++-3.9
|
||||
|
||||
- name: Unittest
|
||||
run: |
|
||||
./bin/minetest --run-unittests
|
||||
|
||||
- name: Valgrind
|
||||
run: |
|
||||
valgrind --leak-check=full --leak-check-heuristics=all --undef-value-errors=no --error-exitcode=9 ./bin/minetest --run-unittests
|
||||
|
||||
# Current clang version
|
||||
clang_14:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install deps
|
||||
run: |
|
||||
source ./util/ci/common.sh
|
||||
install_linux_deps clang-14 gdb
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
./util/ci/build.sh
|
||||
env:
|
||||
CC: clang-14
|
||||
CXX: clang++-14
|
||||
|
||||
- name: Test
|
||||
run: |
|
||||
./bin/minetest --run-unittests
|
||||
|
||||
- name: Integration test + devtest
|
||||
run: |
|
||||
./util/test_multiplayer.sh
|
||||
|
||||
# Build with prometheus-cpp (server-only)
|
||||
clang_9_prometheus:
|
||||
name: "clang_9 (PROMETHEUS=1)"
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install deps
|
||||
run: |
|
||||
source ./util/ci/common.sh
|
||||
install_linux_deps clang-9
|
||||
|
||||
- name: Build prometheus-cpp
|
||||
run: |
|
||||
./util/ci/build_prometheus_cpp.sh
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
./util/ci/build.sh
|
||||
env:
|
||||
CC: clang-9
|
||||
CXX: clang++-9
|
||||
CMAKE_FLAGS: "-DENABLE_PROMETHEUS=1 -DBUILD_CLIENT=0"
|
||||
|
||||
- name: Test
|
||||
run: |
|
||||
./bin/minetestserver --run-unittests
|
||||
|
||||
docker:
|
||||
name: "Docker image"
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Build docker image
|
||||
run: |
|
||||
docker build . -t minetest:latest
|
||||
docker run --rm minetest:latest /usr/local/bin/minetestserver --version
|
||||
|
||||
win32:
|
||||
name: "MinGW cross-compiler (32-bit)"
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install compiler
|
||||
run: |
|
||||
sudo apt-get update && sudo apt-get install -y gettext
|
||||
wget http://minetest.kitsunemimi.pw/mingw-w64-i686_11.2.0_ubuntu20.04.tar.xz -O mingw.tar.xz
|
||||
sudo tar -xaf mingw.tar.xz -C /usr
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
EXISTING_MINETEST_DIR=$PWD ./util/buildbot/buildwin32.sh winbuild
|
||||
env:
|
||||
NO_MINETEST_GAME: 1
|
||||
NO_PACKAGE: 1
|
||||
|
||||
win64:
|
||||
name: "MinGW cross-compiler (64-bit)"
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install compiler
|
||||
run: |
|
||||
sudo apt-get update && sudo apt-get install -y gettext
|
||||
wget http://minetest.kitsunemimi.pw/mingw-w64-x86_64_11.2.0_ubuntu20.04.tar.xz -O mingw.tar.xz
|
||||
sudo tar -xaf mingw.tar.xz -C /usr
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
EXISTING_MINETEST_DIR=$PWD ./util/buildbot/buildwin64.sh winbuild
|
||||
env:
|
||||
NO_MINETEST_GAME: 1
|
||||
NO_PACKAGE: 1
|
||||
|
||||
msvc:
|
||||
name: VS 2019 ${{ matrix.config.arch }}-${{ matrix.type }}
|
||||
runs-on: windows-2019
|
||||
env:
|
||||
VCPKG_VERSION: 5cf60186a241e84e8232641ee973395d4fde90e1
|
||||
# 2022.02
|
||||
vcpkg_packages: zlib zstd curl[winssl] openal-soft libvorbis libogg libjpeg-turbo sqlite3 freetype luajit gmp jsoncpp opengl-registry
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
config:
|
||||
- {
|
||||
arch: x86,
|
||||
generator: "-G'Visual Studio 16 2019' -A Win32",
|
||||
vcpkg_triplet: x86-windows
|
||||
}
|
||||
- {
|
||||
arch: x64,
|
||||
generator: "-G'Visual Studio 16 2019' -A x64",
|
||||
vcpkg_triplet: x64-windows
|
||||
}
|
||||
type: [portable]
|
||||
# type: [portable, installer]
|
||||
# The installer type is working, but disabled, to save runner jobs.
|
||||
# Enable it, when working on the installer.
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Checkout IrrlichtMt
|
||||
run: |
|
||||
$ref = @(Get-Content misc\irrlichtmt_tag.txt)
|
||||
git clone https://github.com/minetest/irrlicht lib\irrlichtmt --depth 1 -b $ref[0]
|
||||
|
||||
- name: Restore from cache and run vcpkg
|
||||
uses: lukka/run-vcpkg@v7
|
||||
with:
|
||||
vcpkgArguments: ${{env.vcpkg_packages}}
|
||||
vcpkgDirectory: '${{ github.workspace }}\vcpkg'
|
||||
appendedCacheKey: ${{ matrix.config.vcpkg_triplet }}
|
||||
vcpkgGitCommitId: ${{ env.VCPKG_VERSION }}
|
||||
vcpkgTriplet: ${{ matrix.config.vcpkg_triplet }}
|
||||
|
||||
- name: Minetest CMake
|
||||
run: |
|
||||
cmake ${{matrix.config.generator}} `
|
||||
-DCMAKE_TOOLCHAIN_FILE="${{ github.workspace }}\vcpkg\scripts\buildsystems\vcpkg.cmake" `
|
||||
-DCMAKE_BUILD_TYPE=Release `
|
||||
-DENABLE_POSTGRESQL=OFF `
|
||||
-DRUN_IN_PLACE=${{ contains(matrix.type, 'portable') }} .
|
||||
|
||||
- name: Build Minetest
|
||||
run: cmake --build . --config Release
|
||||
|
||||
- name: CPack
|
||||
run: |
|
||||
If ($env:TYPE -eq "installer")
|
||||
{
|
||||
cpack -G WIX -B "$env:GITHUB_WORKSPACE\Package"
|
||||
}
|
||||
ElseIf($env:TYPE -eq "portable")
|
||||
{
|
||||
cpack -G ZIP -B "$env:GITHUB_WORKSPACE\Package"
|
||||
}
|
||||
env:
|
||||
TYPE: ${{matrix.type}}
|
||||
|
||||
- name: Package Clean
|
||||
run: rm -r $env:GITHUB_WORKSPACE\Package\_CPack_Packages
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: msvc-${{ matrix.config.arch }}-${{ matrix.type }}
|
||||
path: .\Package\
|
30
.github/workflows/cpp_lint.yml
vendored
30
.github/workflows/cpp_lint.yml
vendored
|
@ -8,8 +8,6 @@ on:
|
|||
- 'lib/**.cpp'
|
||||
- 'src/**.[ch]'
|
||||
- 'src/**.cpp'
|
||||
- 'irr/**.[ch]'
|
||||
- 'irr/**.cpp'
|
||||
- '**/CMakeLists.txt'
|
||||
- 'cmake/Modules/**'
|
||||
- 'util/ci/**'
|
||||
|
@ -20,25 +18,37 @@ on:
|
|||
- 'lib/**.cpp'
|
||||
- 'src/**.[ch]'
|
||||
- 'src/**.cpp'
|
||||
- 'irr/**.[ch]'
|
||||
- 'irr/**.cpp'
|
||||
- '**/CMakeLists.txt'
|
||||
- 'cmake/Modules/**'
|
||||
- 'util/ci/**'
|
||||
- '.github/workflows/**.yml'
|
||||
|
||||
env:
|
||||
CLANG_TIDY: clang-tidy-15
|
||||
|
||||
jobs:
|
||||
|
||||
# clang_format:
|
||||
# runs-on: ubuntu-20.04
|
||||
# steps:
|
||||
# - uses: actions/checkout@v3
|
||||
# - name: Install clang-format
|
||||
# run: |
|
||||
# sudo apt-get update
|
||||
# sudo apt-get install -y clang-format-9
|
||||
#
|
||||
# - name: Run clang-format
|
||||
# run: |
|
||||
# source ./util/ci/clang-format.sh
|
||||
# check_format
|
||||
# env:
|
||||
# CLANG_FORMAT: clang-format-9
|
||||
|
||||
clang_tidy:
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install deps
|
||||
run: |
|
||||
source ./util/ci/common.sh
|
||||
install_linux_deps $CLANG_TIDY
|
||||
install_linux_deps clang-tidy-9
|
||||
|
||||
- name: Run clang-tidy
|
||||
run: |
|
||||
|
|
98
.github/workflows/docker_image.yml
vendored
98
.github/workflows/docker_image.yml
vendored
|
@ -1,98 +0,0 @@
|
|||
---
|
||||
name: docker_image
|
||||
|
||||
# https://docs.github.com/en/actions/publishing-packages/publishing-docker-images
|
||||
# https://docs.docker.com/build/ci/github-actions/multi-platform
|
||||
# https://github.com/opencontainers/image-spec/blob/main/annotations.md
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "master" ]
|
||||
# Publish semver tags as releases.
|
||||
tags: [ "*" ]
|
||||
pull_request:
|
||||
# Build docker image on pull requests. (but do not publish)
|
||||
paths:
|
||||
- 'lib/**.[ch]'
|
||||
- 'lib/**.cpp'
|
||||
- 'src/**.[ch]'
|
||||
- 'src/**.cpp'
|
||||
- 'irr/**.[ch]'
|
||||
- 'irr/**.cpp'
|
||||
- '**/CMakeLists.txt'
|
||||
- 'cmake/Modules/**'
|
||||
- 'util/ci/**'
|
||||
- 'misc/irrlichtmt_tag.txt'
|
||||
- 'Dockerfile'
|
||||
- '.dockerignore'
|
||||
- '.github/workflows/docker_image.yml'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
use_cache:
|
||||
description: "Use build cache"
|
||||
required: true
|
||||
type: boolean
|
||||
default: true
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
# github.repository as <account>/<repo>
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Docker buildx
|
||||
uses: docker/setup-buildx-action@v3.0.0
|
||||
|
||||
# Login against the Docker registry except on PR
|
||||
# https://github.com/docker/login-action
|
||||
- name: Log into registry ${{ env.REGISTRY }}
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@v3.0.0
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# Extract metadata (tags, labels) for Docker
|
||||
# https://github.com/docker/metadata-action
|
||||
- name: Extract Docker metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5.5.0
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
labels: |
|
||||
org.opencontainers.image.title=Luanti
|
||||
org.opencontainers.image.vendor=Luanti
|
||||
org.opencontainers.image.licenses=LGPL-2.1-only
|
||||
|
||||
# Build and push Docker image
|
||||
# https://github.com/docker/build-push-action
|
||||
# No arm support for now. Require cross-compilation support in Dockerfile to not use QEMU.
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v5.1.0
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
load: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
no-cache: ${{ (github.event_name == 'workflow_dispatch' && !inputs.use_cache) || startsWith(github.ref, 'refs/tags/') }}
|
||||
|
||||
- name: Test Docker Image
|
||||
run: |
|
||||
docker run --rm $(cut -d, -f1 <<<"$DOCKER_METADATA_OUTPUT_TAGS") luantiserver --version
|
||||
shell: bash
|
159
.github/workflows/linux.yml
vendored
159
.github/workflows/linux.yml
vendored
|
@ -1,159 +0,0 @@
|
|||
name: linux
|
||||
|
||||
# build on c/cpp changes or workflow changes
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'lib/**.[ch]'
|
||||
- 'lib/**.cpp'
|
||||
- 'src/**.[ch]'
|
||||
- 'src/**.cpp'
|
||||
- 'irr/**.[ch]'
|
||||
- 'irr/**.cpp'
|
||||
- '**/CMakeLists.txt'
|
||||
- 'cmake/Modules/**'
|
||||
- 'po/**.po'
|
||||
- 'util/ci/**'
|
||||
- '.github/workflows/linux.yml'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'lib/**.[ch]'
|
||||
- 'lib/**.cpp'
|
||||
- 'src/**.[ch]'
|
||||
- 'src/**.cpp'
|
||||
- 'irr/**.[ch]'
|
||||
- 'irr/**.cpp'
|
||||
- '**/CMakeLists.txt'
|
||||
- 'cmake/Modules/**'
|
||||
- 'po/**.po'
|
||||
- 'util/ci/**'
|
||||
- '.github/workflows/linux.yml'
|
||||
|
||||
env:
|
||||
MINETEST_POSTGRESQL_CONNECT_STRING: 'host=localhost user=minetest password=minetest dbname=minetest'
|
||||
|
||||
jobs:
|
||||
# Older gcc version (should be close to our minimum supported version)
|
||||
gcc_7:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install deps
|
||||
run: |
|
||||
source ./util/ci/common.sh
|
||||
install_linux_deps g++-7
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
./util/ci/build.sh
|
||||
env:
|
||||
CC: gcc-7
|
||||
CXX: g++-7
|
||||
# Test fallback SHA implementations
|
||||
CMAKE_FLAGS: '-DENABLE_OPENSSL=0'
|
||||
|
||||
- name: Test
|
||||
run: |
|
||||
./bin/luanti --run-unittests
|
||||
|
||||
# Current gcc version
|
||||
gcc_14:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install deps
|
||||
run: |
|
||||
source ./util/ci/common.sh
|
||||
install_linux_deps g++-14 libluajit-5.1-dev
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
./util/ci/build.sh
|
||||
env:
|
||||
CC: gcc-14
|
||||
CXX: g++-14
|
||||
|
||||
- name: Test
|
||||
run: |
|
||||
mkdir nowrite
|
||||
chmod a-w nowrite
|
||||
cd nowrite
|
||||
../bin/luanti --run-unittests
|
||||
|
||||
# Older clang version (should be close to our minimum supported version)
|
||||
clang_7:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install deps
|
||||
run: |
|
||||
source ./util/ci/common.sh
|
||||
install_linux_deps clang-7 llvm-7
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
./util/ci/build.sh
|
||||
env:
|
||||
CC: clang-7
|
||||
CXX: clang++-7
|
||||
CMAKE_FLAGS: '-DCMAKE_C_FLAGS="-fsanitize=address" -DCMAKE_CXX_FLAGS="-fsanitize=address"'
|
||||
|
||||
- name: Unittest
|
||||
run: |
|
||||
./bin/luanti --run-unittests
|
||||
|
||||
# Do this here because we have ASan and error paths are sensitive to dangling pointers
|
||||
- name: Test error cases
|
||||
run: |
|
||||
./util/test_error_cases.sh
|
||||
|
||||
# Current clang version
|
||||
clang_18:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install deps
|
||||
run: |
|
||||
source ./util/ci/common.sh
|
||||
install_linux_deps clang-18 lldb
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
./util/ci/build.sh
|
||||
env:
|
||||
CC: clang-18
|
||||
CXX: clang++-18
|
||||
|
||||
- name: Test
|
||||
run: |
|
||||
./bin/luanti --run-unittests
|
||||
|
||||
- name: Integration test + devtest
|
||||
run: |
|
||||
./util/test_multiplayer.sh
|
||||
|
||||
# Build with prometheus-cpp (server-only)
|
||||
clang_11_prometheus:
|
||||
name: "clang_11 (PROMETHEUS=1)"
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install deps
|
||||
run: |
|
||||
source ./util/ci/common.sh
|
||||
install_linux_deps clang-11
|
||||
|
||||
- name: Build prometheus-cpp
|
||||
run: ./util/ci/build_prometheus_cpp.sh
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
./util/ci/build.sh
|
||||
env:
|
||||
CC: clang-11
|
||||
CXX: clang++-11
|
||||
CMAKE_FLAGS: "-DENABLE_PROMETHEUS=1 -DBUILD_CLIENT=0 -DENABLE_CURSES=0"
|
||||
|
||||
- name: Test
|
||||
run: |
|
||||
./bin/luantiserver --run-unittests
|
30
.github/workflows/lua.yml
vendored
30
.github/workflows/lua.yml
vendored
|
@ -14,43 +14,47 @@ on:
|
|||
- '.github/workflows/**.yml'
|
||||
|
||||
jobs:
|
||||
# Note that the integration tests are also run in build.yml, but only when C++ code is changed.
|
||||
# Note that the integration tests are also run build.yml, but only when C++ code is changed.
|
||||
integration_tests:
|
||||
name: "Compile and run multiplayer tests"
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install deps
|
||||
run: |
|
||||
source ./util/ci/common.sh
|
||||
install_linux_deps clang gdb libluajit-5.1-dev
|
||||
install_linux_deps clang-10 gdb libluajit-5.1-dev
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
./util/ci/build.sh
|
||||
env:
|
||||
CC: clang
|
||||
CXX: clang++
|
||||
CMAKE_FLAGS: "-DENABLE_GETTEXT=0 -DBUILD_SERVER=0 -DBUILD_UNITTESTS=0"
|
||||
CC: clang-10
|
||||
CXX: clang++-10
|
||||
CMAKE_FLAGS: "-DENABLE_GETTEXT=0 -DBUILD_SERVER=0"
|
||||
|
||||
- name: Integration test + devtest
|
||||
run: |
|
||||
serverconf="profiler.load=true" ./util/test_multiplayer.sh
|
||||
./util/test_multiplayer.sh
|
||||
|
||||
luacheck:
|
||||
name: "Builtin Luacheck and Unit Tests"
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
steps:
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
- uses: leafo/gh-actions-lua@v10
|
||||
- uses: actions/checkout@v3
|
||||
- uses: leafo/gh-actions-lua@v9
|
||||
with:
|
||||
luaVersion: "5.1.5"
|
||||
- uses: leafo/gh-actions-luarocks@v4.3.0
|
||||
- uses: leafo/gh-actions-luarocks@v4
|
||||
|
||||
- name: Install LuaJIT
|
||||
run: ./util/ci/build_luajit.sh
|
||||
run: |
|
||||
cd $HOME
|
||||
git clone https://github.com/LuaJIT/LuaJIT/
|
||||
cd LuaJIT
|
||||
make -j$(nproc)
|
||||
|
||||
- name: Install luarocks tools
|
||||
run: |
|
||||
|
|
48
.github/workflows/lua_api_deploy.yml
vendored
48
.github/workflows/lua_api_deploy.yml
vendored
|
@ -1,48 +0,0 @@
|
|||
name: lua_api_deploy
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- '.github/workflows/lua_api_deploy.yml'
|
||||
- 'doc/lua_api.md'
|
||||
- 'doc/mkdocs/'
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: github.repository == 'luanti-org/luanti'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: 3.11
|
||||
|
||||
- name: Install mkdocs
|
||||
run: |
|
||||
pip install -U -r doc/mkdocs/requirements.txt
|
||||
|
||||
- name: Build documentation
|
||||
run: |
|
||||
cd doc/mkdocs/
|
||||
./build.sh
|
||||
|
||||
- name: Setup Pages
|
||||
uses: actions/configure-pages@v4
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
with:
|
||||
path: 'public/'
|
||||
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v4
|
76
.github/workflows/macos.yml
vendored
76
.github/workflows/macos.yml
vendored
|
@ -8,12 +8,8 @@ on:
|
|||
- 'lib/**.cpp'
|
||||
- 'src/**.[ch]'
|
||||
- 'src/**.cpp'
|
||||
- 'irr/**.[ch]'
|
||||
- 'irr/**.cpp'
|
||||
- 'irr/**.mm' # Objective-C(++)
|
||||
- '**/CMakeLists.txt'
|
||||
- 'cmake/Modules/**'
|
||||
- 'po/**.po'
|
||||
- '.github/workflows/macos.yml'
|
||||
pull_request:
|
||||
paths:
|
||||
|
@ -21,20 +17,20 @@ on:
|
|||
- 'lib/**.cpp'
|
||||
- 'src/**.[ch]'
|
||||
- 'src/**.cpp'
|
||||
- 'irr/**.[ch]'
|
||||
- 'irr/**.cpp'
|
||||
- 'irr/**.mm' # Objective-C(++)
|
||||
- '**/CMakeLists.txt'
|
||||
- 'cmake/Modules/**'
|
||||
- 'po/**.po'
|
||||
- '.github/workflows/macos.yml'
|
||||
|
||||
env:
|
||||
MINETEST_GAME_REPO: https://github.com/minetest/minetest_game.git
|
||||
MINETEST_GAME_BRANCH: master
|
||||
MINETEST_GAME_NAME: minetest_game
|
||||
|
||||
jobs:
|
||||
build-intel-macos:
|
||||
# use lowest possible macOS running on x86_64 supported by brew to support more users
|
||||
runs-on: macos-13
|
||||
build:
|
||||
runs-on: macos-11
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install deps
|
||||
run: |
|
||||
source ./util/ci/common.sh
|
||||
|
@ -42,72 +38,30 @@ jobs:
|
|||
|
||||
- name: Build
|
||||
run: |
|
||||
git clone -b $MINETEST_GAME_BRANCH $MINETEST_GAME_REPO games/$MINETEST_GAME_NAME
|
||||
git clone https://github.com/minetest/irrlicht lib/irrlichtmt --depth 1 -b $(cat misc/irrlichtmt_tag.txt)
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. \
|
||||
-DCMAKE_OSX_DEPLOYMENT_TARGET=10.14 \
|
||||
-DCMAKE_FIND_FRAMEWORK=LAST \
|
||||
-DCMAKE_INSTALL_PREFIX=../build/macos/ \
|
||||
-DRUN_IN_PLACE=FALSE -DENABLE_GETTEXT=TRUE \
|
||||
-DINSTALL_DEVTEST=TRUE
|
||||
cmake --build . -j$(sysctl -n hw.logicalcpu)
|
||||
-DRUN_IN_PLACE=FALSE -DENABLE_GETTEXT=TRUE
|
||||
make -j2
|
||||
make install
|
||||
|
||||
- name: Test
|
||||
run: |
|
||||
./build/macos/luanti.app/Contents/MacOS/luanti --run-unittests
|
||||
./build/macos/minetest.app/Contents/MacOS/minetest --run-unittests
|
||||
|
||||
# Zipping the built .app preserves permissions on the contained files,
|
||||
# which the GitHub artifact pipeline would otherwise strip away.
|
||||
- name: CPack
|
||||
run: |
|
||||
cd build
|
||||
rm -rf macos
|
||||
cmake .. -DINSTALL_DEVTEST=FALSE
|
||||
cpack -G ZIP -B macos
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: luanti-macos
|
||||
name: minetest-macos
|
||||
path: ./build/macos/*.zip
|
||||
build-arm-macos-xcode:
|
||||
runs-on: macos-14
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install deps
|
||||
run: |
|
||||
source ./util/ci/common.sh
|
||||
install_macos_deps
|
||||
# brew jsoncpp do not include libjsoncpp.a, and if installed header conflict caused build failure
|
||||
brew uninstall jsoncpp
|
||||
|
||||
- name: Build with Cmake
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. \
|
||||
-DCMAKE_OSX_DEPLOYMENT_TARGET=14 \
|
||||
-DCMAKE_FIND_FRAMEWORK=LAST \
|
||||
-DCMAKE_INSTALL_PREFIX=../build/macos/ \
|
||||
-DRUN_IN_PLACE=FALSE -DENABLE_GETTEXT=TRUE \
|
||||
-DENABLE_SYSTEM_JSONCPP=OFF
|
||||
cmake --build . -j$(sysctl -n hw.logicalcpu)
|
||||
make install
|
||||
|
||||
- name: Build and Archive with Xcode
|
||||
run: |
|
||||
mkdir build_xcode
|
||||
cd build_xcode
|
||||
../util/ci/build_xcode.sh
|
||||
|
||||
- name: Tests
|
||||
run: |
|
||||
mkdir -p "${HOME}/Library/Application Support/minetest/games/"
|
||||
ln -s "${PWD}/games/devtest" "${HOME}/Library/Application Support/minetest/games/"
|
||||
./build/macos/luanti.app/Contents/MacOS/luanti --run-unittests
|
||||
./build_xcode/luanti.xcarchive/Products/Applications/luanti.app/Contents/MacOS/luanti --run-unittests
|
||||
|
||||
- name: Diff Resources
|
||||
run: |
|
||||
diff -rd ./build/macos/luanti.app/Contents/Resources ./build_xcode/build/Release/luanti.app/Contents/Resources || exit 1
|
||||
diff -rd ./build/macos/luanti.app/Contents/Resources ./build_xcode/luanti.xcarchive/Products/Applications/luanti.app/Contents/Resources || exit 1
|
||||
|
|
26
.github/workflows/png_file_checks.yml
vendored
26
.github/workflows/png_file_checks.yml
vendored
|
@ -1,26 +0,0 @@
|
|||
name: png_file_checks
|
||||
|
||||
# Check whether all png files are in a valid format
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- '**.png'
|
||||
- '.github/workflows/**.yml'
|
||||
pull_request:
|
||||
paths:
|
||||
- '**.png'
|
||||
- '.github/workflows/**.yml'
|
||||
|
||||
jobs:
|
||||
png_optimized:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install deps
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt install -y optipng
|
||||
|
||||
- name: Check whether all png files are optimized
|
||||
run: |
|
||||
./util/ci/check_png_optimized.sh
|
118
.github/workflows/whitespace_checks.yml
vendored
118
.github/workflows/whitespace_checks.yml
vendored
|
@ -1,118 +0,0 @@
|
|||
name: whitespace_checks
|
||||
|
||||
# Check whitespaces of the following file types
|
||||
# Not checked: .lua, .yml, .properties, .conf, .java, .py, .svg, .gradle, .xml, ...
|
||||
# (luacheck already checks .lua files)
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- '**.txt'
|
||||
- '**.md'
|
||||
- '**.[ch]'
|
||||
- '**.cpp'
|
||||
- '**.hpp'
|
||||
- '**.sh'
|
||||
- '**.cmake'
|
||||
- '**.glsl'
|
||||
- '**.lua'
|
||||
pull_request:
|
||||
paths:
|
||||
- '**.txt'
|
||||
- '**.md'
|
||||
- '**.[ch]'
|
||||
- '**.cpp'
|
||||
- '**.hpp'
|
||||
- '**.sh'
|
||||
- '**.cmake'
|
||||
- '**.glsl'
|
||||
- '**.lua'
|
||||
|
||||
jobs:
|
||||
trailing_whitespaces:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
# Line endings are already ensured by .gitattributes
|
||||
- name: Check trailing whitespaces
|
||||
run: |
|
||||
if git ls-files |\
|
||||
grep -E '\.txt$|\.md$|\.[ch]$|\.cpp$|\.hpp$|\.sh$|\.cmake$|\.glsl$' |\
|
||||
xargs grep -n '\s$';\
|
||||
then\
|
||||
echo -e "\033[0;31mFound trailing whitespace";\
|
||||
(exit 1);\
|
||||
else\
|
||||
(exit 0);\
|
||||
fi
|
||||
|
||||
indent_spaces:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
# Line endings are already ensured by .gitattributes
|
||||
# Multiple multline comments in one line is not supported by this check
|
||||
# and there is no reason to use them
|
||||
# So lines like: "/* */ /*" or "*/ a = 5; /*" will result in error
|
||||
- name: Check for unsupported multiline comments
|
||||
run: |
|
||||
if git ls-files |\
|
||||
grep -E '^src/.*\.cpp$|^src/.*\.[ch]$' |\
|
||||
xargs grep -n '\*/.*/\*';\
|
||||
then
|
||||
echo -e "\033[0;31mUnsupported combination of multiline comments. New multiline comment should begin on new line.";\
|
||||
(exit 1);\
|
||||
else\
|
||||
(exit 0);\
|
||||
fi
|
||||
if git ls-files |\
|
||||
grep -E '\.lua$' |\
|
||||
xargs grep -n -e '\]\].*--\[\[';\
|
||||
then
|
||||
echo -e "\033[0;31mUnsupported combination of multiline comments. New multiline comment should begin on new line.";\
|
||||
(exit 1);\
|
||||
else\
|
||||
(exit 0);\
|
||||
fi
|
||||
# This prepare files for final check
|
||||
# See python script ./util/ci/indent_tab_preprocess.py for details.
|
||||
- name: Preprocess files
|
||||
run: |
|
||||
git ls-files |\
|
||||
grep -E '^src/.*\.cpp$|^src/.*\.[ch]$' |\
|
||||
xargs -L 1 -P $(($(nproc) + 1)) \
|
||||
python3 ./util/ci/indent_tab_preprocess.py "/*" "*/"
|
||||
git ls-files |\
|
||||
grep -E '\.lua$' |\
|
||||
xargs -L 1 -P $(($(nproc) + 1)) \
|
||||
python3 ./util/ci/indent_tab_preprocess.py "--[[" "]]"
|
||||
# Check for bad indent.
|
||||
# This runs over preprocessed files.
|
||||
# If there is any remaining space on line beginning or after tab,
|
||||
# error is generated
|
||||
- name: Check indent spaces
|
||||
run: |
|
||||
if git ls-files |\
|
||||
grep -E '^src/.*\.cpp$|^src/.*\.[ch]$|\.lua' |\
|
||||
xargs grep -n -P '^\t*[ ]';\
|
||||
then\
|
||||
echo -e "\033[0;31mFound incorrect indent whitespaces";\
|
||||
(exit 1);\
|
||||
else\
|
||||
(exit 0);\
|
||||
fi
|
||||
|
||||
tabs_lua_api_files:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
# Some files should not contain tabs
|
||||
- name: Check tabs in Lua API files
|
||||
run: |
|
||||
if grep -n $'\t' doc/lua_api.md doc/client_lua_api.md;\
|
||||
then\
|
||||
echo -e "\033[0;31mFound tab in markdown file";\
|
||||
(exit 1);\
|
||||
else\
|
||||
(exit 0);\
|
||||
fi
|
||||
|
146
.github/workflows/windows.yml
vendored
146
.github/workflows/windows.yml
vendored
|
@ -1,146 +0,0 @@
|
|||
name: windows
|
||||
|
||||
# build on c/cpp changes or workflow changes
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'lib/**.[ch]'
|
||||
- 'lib/**.cpp'
|
||||
- 'src/**.[ch]'
|
||||
- 'src/**.cpp'
|
||||
- 'irr/**.[ch]'
|
||||
- 'irr/**.cpp'
|
||||
- '**/CMakeLists.txt'
|
||||
- 'cmake/Modules/**'
|
||||
- 'po/**.po'
|
||||
- 'util/buildbot/**'
|
||||
- 'misc/*.manifest'
|
||||
- '.github/workflows/windows.yml'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'lib/**.[ch]'
|
||||
- 'lib/**.cpp'
|
||||
- 'src/**.[ch]'
|
||||
- 'src/**.cpp'
|
||||
- 'irr/**.[ch]'
|
||||
- 'irr/**.cpp'
|
||||
- '**/CMakeLists.txt'
|
||||
- 'cmake/Modules/**'
|
||||
- 'po/**.po'
|
||||
- 'util/buildbot/**'
|
||||
- 'misc/*.manifest'
|
||||
- '.github/workflows/windows.yml'
|
||||
|
||||
jobs:
|
||||
mingw:
|
||||
name: "MinGW cross-compiler (${{ matrix.bits }}-bit)"
|
||||
runs-on: ubuntu-22.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
bits: [32, 64]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install compiler
|
||||
run: |
|
||||
sudo dpkg --add-architecture i386
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y --no-install-recommends gettext wine wine${{ matrix.bits }}
|
||||
sudo ./util/buildbot/download_toolchain.sh /usr
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
EXISTING_MINETEST_DIR=$PWD \
|
||||
./util/buildbot/buildwin${{ matrix.bits }}.sh B
|
||||
|
||||
# Check that the resulting binary can run (DLLs etc.)
|
||||
- name: Runtime test
|
||||
run: |
|
||||
dest=$(mktemp -d)
|
||||
unzip -q -d "$dest" B/build/*.zip
|
||||
cd "$dest"/luanti-*-win*
|
||||
wine bin/luanti.exe --version
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: "mingw${{ matrix.bits }}"
|
||||
path: B/build/*.zip
|
||||
if-no-files-found: error
|
||||
|
||||
msvc:
|
||||
name: VS 2019 ${{ matrix.config.arch }}-${{ matrix.type }}
|
||||
runs-on: windows-2019
|
||||
env:
|
||||
VCPKG_VERSION: 01f602195983451bc83e72f4214af2cbc495aa94
|
||||
# 2024.05.24
|
||||
vcpkg_packages: zlib zstd curl[winssl] openal-soft libvorbis libogg libjpeg-turbo sqlite3 freetype luajit gmp jsoncpp sdl2
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
config:
|
||||
- {
|
||||
arch: x86,
|
||||
generator: "-G'Visual Studio 16 2019' -A Win32",
|
||||
vcpkg_triplet: x86-windows
|
||||
}
|
||||
- {
|
||||
arch: x64,
|
||||
generator: "-G'Visual Studio 16 2019' -A x64",
|
||||
vcpkg_triplet: x64-windows
|
||||
}
|
||||
type: [portable]
|
||||
# type: [portable, installer]
|
||||
# The installer type is working, but disabled, to save runner jobs.
|
||||
# Enable it, when working on the installer.
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Restore from cache and run vcpkg
|
||||
uses: lukka/run-vcpkg@v7
|
||||
with:
|
||||
vcpkgArguments: ${{env.vcpkg_packages}}
|
||||
vcpkgDirectory: '${{ github.workspace }}\vcpkg'
|
||||
appendedCacheKey: ${{ matrix.config.vcpkg_triplet }}
|
||||
vcpkgGitCommitId: ${{ env.VCPKG_VERSION }}
|
||||
vcpkgTriplet: ${{ matrix.config.vcpkg_triplet }}
|
||||
|
||||
- name: CMake
|
||||
run: |
|
||||
cmake ${{matrix.config.generator}} `
|
||||
-DCMAKE_TOOLCHAIN_FILE="${{ github.workspace }}\vcpkg\scripts\buildsystems\vcpkg.cmake" `
|
||||
-DCMAKE_BUILD_TYPE=Release `
|
||||
-DENABLE_POSTGRESQL=OFF `
|
||||
-DENABLE_LUAJIT=TRUE `
|
||||
-DREQUIRE_LUAJIT=TRUE `
|
||||
-DRUN_IN_PLACE=${{ contains(matrix.type, 'portable') }} .
|
||||
|
||||
- name: Build
|
||||
run: cmake --build . --config Release
|
||||
|
||||
- name: Unittests
|
||||
# need this workaround for stdout to work
|
||||
run: |
|
||||
$proc = start .\bin\Release\luanti.exe --run-unittests -NoNewWindow -Wait -PassThru
|
||||
exit $proc.ExitCode
|
||||
continue-on-error: true # FIXME!!
|
||||
|
||||
- name: CPack
|
||||
run: |
|
||||
If ($env:TYPE -eq "installer")
|
||||
{
|
||||
cpack -G WIX -B "$env:GITHUB_WORKSPACE\Package"
|
||||
}
|
||||
ElseIf($env:TYPE -eq "portable")
|
||||
{
|
||||
cpack -G ZIP -B "$env:GITHUB_WORKSPACE\Package"
|
||||
}
|
||||
rm -r $env:GITHUB_WORKSPACE\Package\_CPack_Packages
|
||||
env:
|
||||
TYPE: ${{matrix.type}}
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: msvc-${{ matrix.config.arch }}-${{ matrix.type }}
|
||||
path: .\Package\
|
||||
if-no-files-found: error
|
47
.gitignore
vendored
47
.gitignore
vendored
|
@ -1,8 +1,5 @@
|
|||
## Editors and development environments
|
||||
*~
|
||||
.cmake
|
||||
CMakeUserPresets.json
|
||||
Testing/*
|
||||
*.swp
|
||||
*.bak*
|
||||
*.orig
|
||||
|
@ -26,61 +23,45 @@ tags
|
|||
!tags/
|
||||
gtags.files
|
||||
.idea
|
||||
.qtcreator/
|
||||
# Codelite
|
||||
*.project
|
||||
# Visual Studio Code & plugins
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.vscode/
|
||||
build/.cmake/
|
||||
# Fleet
|
||||
.fleet
|
||||
# Gradle
|
||||
.gradle
|
||||
# Clang
|
||||
.cache
|
||||
# AppImage
|
||||
*.AppImage
|
||||
*.zsync
|
||||
appimage-build
|
||||
AppDir
|
||||
# Direnv
|
||||
.direnv/
|
||||
# Nix
|
||||
/result
|
||||
|
||||
## Files related to Luanti development cycle
|
||||
## Files related to Minetest development cycle
|
||||
/*.patch
|
||||
*.diff
|
||||
# GNU Patch reject file
|
||||
*.rej
|
||||
|
||||
## Non-static Luanti directories or symlinks to these
|
||||
## Non-static Minetest directories or symlinks to these
|
||||
/bin/
|
||||
/games/*
|
||||
!/games/devtest/
|
||||
/games/devtest/mods/soundstuff/sounds/gitignored_sounds/*
|
||||
!/games/devtest/mods/soundstuff/sounds/gitignored_sounds/custom_sounds_here.txt
|
||||
/cache
|
||||
/textures/*
|
||||
!/textures/base/
|
||||
/screenshots
|
||||
/sounds
|
||||
/mods/*
|
||||
!/mods/mods_here.txt
|
||||
/worlds/*
|
||||
!/worlds/worlds_here.txt
|
||||
!/mods/minetest/
|
||||
/mods/minetest/*
|
||||
!/mods/minetest/mods_here.txt
|
||||
/worlds
|
||||
/world/
|
||||
/clientmods/*
|
||||
!/clientmods/preview/
|
||||
/client/mod_storage/
|
||||
/mod_data
|
||||
|
||||
## Configuration/log files
|
||||
minetest.conf
|
||||
debug.txt
|
||||
debug.txt.1
|
||||
|
||||
## Other files generated by Luanti
|
||||
## Other files generated by Minetest
|
||||
screenshot_*.png
|
||||
testbm.txt
|
||||
|
||||
|
@ -102,26 +83,24 @@ cmake_install.cmake
|
|||
CMakeCache.txt
|
||||
CPackConfig.cmake
|
||||
CPackSourceConfig.cmake
|
||||
src/test_config.h
|
||||
src/cmake_config.h
|
||||
src/cmake_config_githash.h
|
||||
src/unittest/test_world/world.mt
|
||||
games/devtest/mods/testnodes/textures/testnodes_generated_*.png
|
||||
/locale/
|
||||
.directory
|
||||
*.cbp
|
||||
*.layout
|
||||
*.o
|
||||
*.a
|
||||
*.dump
|
||||
*.dmp
|
||||
*.ninja
|
||||
.ninja*
|
||||
*.gch
|
||||
*.iml
|
||||
test_config.h
|
||||
cmake-build-debug/
|
||||
cmake-build-minsizerel/
|
||||
cmake-build-release/
|
||||
cmake-build-relwithdebinfo/
|
||||
cmake-build-default/
|
||||
cmake_config.h
|
||||
cmake_config_githash.h
|
||||
CMakeDoxy*
|
||||
|
@ -133,7 +112,7 @@ compile_commands.json
|
|||
*.sln
|
||||
.vs/
|
||||
|
||||
# Old irrlichtmt. Still ignored to make bisecting easier.
|
||||
# Optional user provided library folder
|
||||
lib/irrlichtmt
|
||||
|
||||
# Generated mod storage database
|
||||
|
|
275
.gitlab-ci.yml
275
.gitlab-ci.yml
|
@ -3,14 +3,287 @@
|
|||
# https://gitlab.com/minetest/minetest
|
||||
# Pipelines URL: https://gitlab.com/minetest/minetest/pipelines
|
||||
|
||||
stages:
|
||||
- build
|
||||
- package
|
||||
- deploy
|
||||
|
||||
variables:
|
||||
MINETEST_GAME_REPO: "https://github.com/minetest/minetest_game.git"
|
||||
CONTAINER_IMAGE: registry.gitlab.com/$CI_PROJECT_PATH
|
||||
|
||||
.build_template:
|
||||
stage: build
|
||||
before_script:
|
||||
- apt-get update
|
||||
- DEBIAN_FRONTEND=noninteractive apt-get -y install build-essential git cmake libpng-dev libjpeg-dev libxxf86vm-dev libgl1-mesa-dev libsqlite3-dev libleveldb-dev libogg-dev libvorbis-dev libopenal-dev libcurl4-gnutls-dev libfreetype6-dev zlib1g-dev libgmp-dev libjsoncpp-dev libzstd-dev
|
||||
script:
|
||||
- git clone https://github.com/minetest/irrlicht lib/irrlichtmt --depth 1 -b $(cat misc/irrlichtmt_tag.txt)
|
||||
- mkdir build && cd build
|
||||
- cmake -DCMAKE_INSTALL_PREFIX=../artifact/minetest/usr/ -DRUN_IN_PLACE=FALSE -DENABLE_GETTEXT=TRUE -DBUILD_SERVER=TRUE ..
|
||||
- make -j $(($(nproc) + 1))
|
||||
- make install
|
||||
artifacts:
|
||||
when: on_success
|
||||
expire_in: 1h
|
||||
paths:
|
||||
- artifact/*
|
||||
|
||||
.debpkg_template:
|
||||
stage: package
|
||||
before_script:
|
||||
- apt-get update
|
||||
- apt-get install -y git
|
||||
- mkdir -p build/deb/minetest/DEBIAN/
|
||||
- cp misc/debpkg-control build/deb/minetest/DEBIAN/control
|
||||
- cp -a artifact/minetest/usr build/deb/minetest/
|
||||
script:
|
||||
- git clone $MINETEST_GAME_REPO build/deb/minetest/usr/share/minetest/games/minetest_game
|
||||
- rm -rf build/deb/minetest/usr/share/minetest/games/minetest/.git
|
||||
- sed -i 's/DATEPLACEHOLDER/'$(date +%y.%m.%d)'/g' build/deb/minetest/DEBIAN/control
|
||||
- sed -i 's/JPEG_PLACEHOLDER/'$JPEG_PKG'/g' build/deb/minetest/DEBIAN/control
|
||||
- sed -i 's/LEVELDB_PLACEHOLDER/'$LEVELDB_PKG'/g' build/deb/minetest/DEBIAN/control
|
||||
- sed -i 's/JSONCPP_PLACEHOLDER/'$JSONCPP_PKG'/g' build/deb/minetest/DEBIAN/control
|
||||
- cd build/deb/ && dpkg-deb -b minetest/ && mv minetest.deb ../../
|
||||
artifacts:
|
||||
expire_in: 90 day
|
||||
paths:
|
||||
- ./*.deb
|
||||
|
||||
.debpkg_install:
|
||||
stage: deploy
|
||||
before_script:
|
||||
- apt-get update -qy
|
||||
script:
|
||||
- apt-get install -y ./*.deb
|
||||
- minetest --version
|
||||
|
||||
##
|
||||
## Debian
|
||||
##
|
||||
|
||||
# Stretch
|
||||
|
||||
build:debian-9:
|
||||
extends: .build_template
|
||||
image: debian:9
|
||||
|
||||
package:debian-9:
|
||||
extends: .debpkg_template
|
||||
image: debian:9
|
||||
needs:
|
||||
- build:debian-9
|
||||
variables:
|
||||
JSONCPP_PKG: libjsoncpp1
|
||||
LEVELDB_PKG: libleveldb1v5
|
||||
JPEG_PKG: libjpeg62-turbo
|
||||
|
||||
deploy:debian-9:
|
||||
extends: .debpkg_install
|
||||
image: debian:9
|
||||
needs:
|
||||
- package:debian-9
|
||||
|
||||
# Buster
|
||||
|
||||
build:debian-10:
|
||||
extends: .build_template
|
||||
image: debian:10
|
||||
|
||||
package:debian-10:
|
||||
extends: .debpkg_template
|
||||
image: debian:10
|
||||
needs:
|
||||
- build:debian-10
|
||||
variables:
|
||||
JSONCPP_PKG: libjsoncpp1
|
||||
LEVELDB_PKG: libleveldb1d
|
||||
JPEG_PKG: libjpeg62-turbo
|
||||
|
||||
deploy:debian-10:
|
||||
extends: .debpkg_install
|
||||
image: debian:10
|
||||
needs:
|
||||
- package:debian-10
|
||||
|
||||
# Bullseye
|
||||
|
||||
build:debian-11:
|
||||
extends: .build_template
|
||||
image: debian:11
|
||||
|
||||
package:debian-11:
|
||||
extends: .debpkg_template
|
||||
image: debian:11
|
||||
needs:
|
||||
- build:debian-11
|
||||
variables:
|
||||
JSONCPP_PKG: libjsoncpp24
|
||||
LEVELDB_PKG: libleveldb1d
|
||||
JPEG_PKG: libjpeg62-turbo
|
||||
|
||||
deploy:debian-11:
|
||||
extends: .debpkg_install
|
||||
image: debian:11
|
||||
needs:
|
||||
- package:debian-11
|
||||
|
||||
##
|
||||
## Ubuntu
|
||||
##
|
||||
|
||||
# Bionic
|
||||
|
||||
build:ubuntu-18.04:
|
||||
extends: .build_template
|
||||
image: ubuntu:bionic
|
||||
|
||||
package:ubuntu-18.04:
|
||||
extends: .debpkg_template
|
||||
image: ubuntu:bionic
|
||||
needs:
|
||||
- build:ubuntu-18.04
|
||||
variables:
|
||||
JSONCPP_PKG: libjsoncpp1
|
||||
LEVELDB_PKG: libleveldb1v5
|
||||
JPEG_PKG: libjpeg-turbo8
|
||||
|
||||
deploy:ubuntu-18.04:
|
||||
extends: .debpkg_install
|
||||
image: ubuntu:bionic
|
||||
needs:
|
||||
- package:ubuntu-18.04
|
||||
|
||||
# Focal
|
||||
|
||||
build:ubuntu-20.04:
|
||||
extends: .build_template
|
||||
image: ubuntu:focal
|
||||
|
||||
package:ubuntu-20.04:
|
||||
extends: .debpkg_template
|
||||
image: ubuntu:focal
|
||||
needs:
|
||||
- build:ubuntu-20.04
|
||||
variables:
|
||||
JSONCPP_PKG: libjsoncpp1
|
||||
LEVELDB_PKG: libleveldb1d
|
||||
JPEG_PKG: libjpeg-turbo8
|
||||
|
||||
deploy:ubuntu-20.04:
|
||||
extends: .debpkg_install
|
||||
image: ubuntu:focal
|
||||
needs:
|
||||
- package:ubuntu-20.04
|
||||
|
||||
##
|
||||
## Fedora
|
||||
##
|
||||
|
||||
# Fedora 28 <-> RHEL 8
|
||||
build:fedora-28:
|
||||
extends: .build_template
|
||||
image: fedora:28
|
||||
before_script:
|
||||
- dnf -y install make git gcc gcc-c++ kernel-devel cmake libjpeg-devel libpng-devel libcurl-devel openal-soft-devel libvorbis-devel libXxf86vm-devel libogg-devel freetype-devel mesa-libGL-devel zlib-devel jsoncpp-devel gmp-devel sqlite-devel luajit-devel leveldb-devel ncurses-devel spatialindex-devel libzstd-devel
|
||||
|
||||
##
|
||||
## MinGW for Windows
|
||||
##
|
||||
|
||||
.generic_win_template:
|
||||
image: ubuntu:focal
|
||||
before_script:
|
||||
- apt-get update
|
||||
- DEBIAN_FRONTEND=noninteractive apt-get install -y wget xz-utils unzip git cmake gettext
|
||||
- wget -nv http://minetest.kitsunemimi.pw/mingw-w64-${WIN_ARCH}_11.2.0_ubuntu20.04.tar.xz -O mingw.tar.xz
|
||||
- tar -xaf mingw.tar.xz -C /usr
|
||||
|
||||
.build_win_template:
|
||||
extends: .generic_win_template
|
||||
stage: build
|
||||
artifacts:
|
||||
expire_in: 90 day
|
||||
paths:
|
||||
- minetest-*-win*/*
|
||||
|
||||
build:win32:
|
||||
extends: .build_win_template
|
||||
script:
|
||||
- EXISTING_MINETEST_DIR=$PWD ./util/buildbot/buildwin32.sh build
|
||||
- unzip -q build/build/*.zip
|
||||
variables:
|
||||
WIN_ARCH: "i686"
|
||||
|
||||
build:win64:
|
||||
extends: .build_win_template
|
||||
script:
|
||||
- EXISTING_MINETEST_DIR=$PWD ./util/buildbot/buildwin64.sh build
|
||||
- unzip -q build/build/*.zip
|
||||
variables:
|
||||
WIN_ARCH: "x86_64"
|
||||
|
||||
##
|
||||
## Docker
|
||||
##
|
||||
|
||||
package:docker:
|
||||
stage: package
|
||||
image: docker:stable
|
||||
services:
|
||||
- docker:dind
|
||||
before_script:
|
||||
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN registry.gitlab.com
|
||||
script:
|
||||
- docker build . -t ${CONTAINER_IMAGE}/server:$CI_COMMIT_SHA -t ${CONTAINER_IMAGE}/server:$CI_COMMIT_REF_NAME -t ${CONTAINER_IMAGE}/server:latest
|
||||
- docker push ${CONTAINER_IMAGE}/server:$CI_COMMIT_SHA
|
||||
- docker push ${CONTAINER_IMAGE}/server:$CI_COMMIT_REF_NAME
|
||||
- docker push ${CONTAINER_IMAGE}/server:latest
|
||||
|
||||
##
|
||||
## Gitlab Pages (Lua API documentation)
|
||||
##
|
||||
|
||||
pages:
|
||||
stage: deploy
|
||||
image: python:3.8
|
||||
before_script:
|
||||
- pip install git+https://github.com/Python-Markdown/markdown.git
|
||||
- pip install git+https://github.com/mkdocs/mkdocs.git
|
||||
- pip install pygments
|
||||
script:
|
||||
- ./misc/make_redirects.sh
|
||||
- cd doc/mkdocs && ./build.sh
|
||||
artifacts:
|
||||
paths:
|
||||
- public
|
||||
only:
|
||||
- master
|
||||
|
||||
##
|
||||
## AppImage
|
||||
##
|
||||
|
||||
package:appimage-client:
|
||||
stage: package
|
||||
image: appimagecrafters/appimage-builder
|
||||
needs:
|
||||
- build:ubuntu-18.04
|
||||
before_script:
|
||||
- apt-get update -y
|
||||
- apt-get install -y git
|
||||
# Collect files
|
||||
- mkdir AppDir
|
||||
- cp -a artifact/minetest/usr/ AppDir/usr/
|
||||
- rm AppDir/usr/bin/minetestserver
|
||||
- cp -a clientmods AppDir/usr/share/minetest
|
||||
script:
|
||||
- git clone $MINETEST_GAME_REPO AppDir/usr/share/minetest/games/minetest_game
|
||||
- rm -rf AppDir/usr/share/minetest/games/minetest/.git
|
||||
- export VERSION=$CI_COMMIT_REF_NAME-$CI_COMMIT_SHORT_SHA
|
||||
# Remove PrefersNonDefaultGPU property due to validation errors
|
||||
- sed -i '/PrefersNonDefaultGPU/d' AppDir/usr/share/applications/net.minetest.minetest.desktop
|
||||
- appimage-builder --skip-test
|
||||
artifacts:
|
||||
expire_in: 90 day
|
||||
paths:
|
||||
- ./*.AppImage
|
||||
|
|
17
.luacheckrc
17
.luacheckrc
|
@ -10,19 +10,16 @@ ignore = {
|
|||
read_globals = {
|
||||
"ItemStack",
|
||||
"INIT",
|
||||
"PLATFORM",
|
||||
"DIR_DELIM",
|
||||
"dump", "dump2",
|
||||
"fgettext", "fgettext_ne",
|
||||
"vector",
|
||||
"VoxelArea",
|
||||
"VoxelManip",
|
||||
"profiler",
|
||||
"Settings",
|
||||
"PerlinNoise", "PerlinNoiseMap",
|
||||
|
||||
string = {fields = {"split", "trim"}},
|
||||
table = {fields = {"copy", "getn", "indexof", "keyof", "insert_all"}},
|
||||
table = {fields = {"copy", "getn", "indexof", "insert_all"}},
|
||||
math = {fields = {"hypot", "round"}},
|
||||
}
|
||||
|
||||
|
@ -39,12 +36,6 @@ files["builtin/client/register.lua"] = {
|
|||
}
|
||||
}
|
||||
|
||||
files["builtin/common/math.lua"] = {
|
||||
globals = {
|
||||
"math",
|
||||
},
|
||||
}
|
||||
|
||||
files["builtin/common/misc_helpers.lua"] = {
|
||||
globals = {
|
||||
"dump", "dump2", "table", "math", "string",
|
||||
|
@ -54,7 +45,7 @@ files["builtin/common/misc_helpers.lua"] = {
|
|||
}
|
||||
|
||||
files["builtin/common/vector.lua"] = {
|
||||
globals = { "vector", "math" },
|
||||
globals = { "vector" },
|
||||
}
|
||||
|
||||
files["builtin/game/voxelarea.lua"] = {
|
||||
|
@ -76,6 +67,10 @@ files["builtin/mainmenu"] = {
|
|||
globals = {
|
||||
"gamedata",
|
||||
},
|
||||
|
||||
read_globals = {
|
||||
"PLATFORM",
|
||||
},
|
||||
}
|
||||
|
||||
files["builtin/common/tests"] = {
|
||||
|
|
10
.mailmap
10
.mailmap
|
@ -37,7 +37,7 @@ numzero <numzer0@yandex.ru> <silverunicorn2011@yandex.ru>
|
|||
Jean-Patrick Guerrero <kilbith@users.noreply.github.com>
|
||||
Jean-Patrick Guerrero <kilbith@users.noreply.github.com> <jeanpatrick.guerrero@gmail.com>
|
||||
HybridDog <3192173+HybridDog@users.noreply.github.com> <ovvv@web.de>
|
||||
srifqi <muhammadrifqipriyosusanto@gmail.com>
|
||||
srfqi <muhammadrifqipriyosusanto@gmail.com>
|
||||
Dániel Juhász <juhdanad@gmail.com>
|
||||
rubenwardy <rw@rubenwardy.com>
|
||||
rubenwardy <rw@rubenwardy.com> <rubenwardy@gmail.com>
|
||||
|
@ -48,8 +48,7 @@ ClobberXD <ClobberXD@gmail.com> <ClobberXD@protonmail.com>
|
|||
ClobberXD <ClobberXD@gmail.com> <36130650+ClobberXD@users.noreply.github.com>
|
||||
Auke Kok <sofar+github@foo-projects.org>
|
||||
Auke Kok <sofar+github@foo-projects.org> <sofar@foo-projects.org>
|
||||
DS <ds.desour@proton.me>
|
||||
DS <ds.desour@proton.me> <vorunbekannt75@web.de>
|
||||
Desour <vorunbekannt75@web.de>
|
||||
Nathanaëlle Courant <Ekdohibs@users.noreply.github.com> <nathanael.courant@laposte.net>
|
||||
Ezhh <owlecho@live.com>
|
||||
paramat <paramat@users.noreply.github.com>
|
||||
|
@ -68,8 +67,3 @@ gregorycu <gregory.currie@gmail.com>
|
|||
Rogier <rogier777@gmail.com>
|
||||
Rogier <rogier777@gmail.com> <Rogier-5@users.noreply.github.com>
|
||||
x2048 <codeforsmile@gmail.com>
|
||||
Lars Müller <appgurulars@gmx.de>
|
||||
Lars Müller <appgurulars@gmx.de> <34514239+appgurueu@users.noreply.github.com>
|
||||
ROllerozxa <rollerozxa@voxelmanip.se>
|
||||
ROllerozxa <rollerozxa@voxelmanip.se> <temporaryemail4meh+github@gmail.com>
|
||||
Ælla Chiana Moskopp <erle@dieweltistgarnichtso.net> <nils@dieweltistgarnichtso.net>
|
||||
|
|
5
.vscode/extensions.json
vendored
5
.vscode/extensions.json
vendored
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"recommendations": [
|
||||
"ms-vscode.cpptools-extension-pack"
|
||||
]
|
||||
}
|
54
AppImageBuilder.yml
Normal file
54
AppImageBuilder.yml
Normal file
|
@ -0,0 +1,54 @@
|
|||
version: 1
|
||||
|
||||
AppDir:
|
||||
path: ./AppDir
|
||||
|
||||
app_info:
|
||||
id: minetest
|
||||
name: Minetest
|
||||
icon: minetest
|
||||
version: !ENV ${VERSION}
|
||||
exec: usr/bin/minetest
|
||||
exec_args: $@
|
||||
runtime:
|
||||
env:
|
||||
APPDIR_LIBRARY_PATH: $APPDIR/usr/lib/x86_64-linux-gnu
|
||||
|
||||
apt:
|
||||
arch: amd64
|
||||
sources:
|
||||
- sourceline: deb http://archive.ubuntu.com/ubuntu/ bionic main universe
|
||||
key_url: 'http://keyserver.ubuntu.com/pks/lookup?op=get&search=0x3b4fe6acc0b21f32'
|
||||
- sourceline: deb http://archive.ubuntu.com/ubuntu/ bionic-updates main universe
|
||||
- sourceline: deb http://archive.ubuntu.com/ubuntu/ bionic-backports main universe
|
||||
- sourceline: deb http://archive.ubuntu.com/ubuntu/ bionic-security main universe
|
||||
|
||||
include:
|
||||
- libc6
|
||||
- libcurl3-gnutls
|
||||
- libfreetype6
|
||||
- libgl1
|
||||
- libjpeg-turbo8
|
||||
- libjsoncpp1
|
||||
- libleveldb1v5
|
||||
- libopenal1
|
||||
- libpng16-16
|
||||
- libsqlite3-0
|
||||
- libstdc++6
|
||||
- libvorbisfile3
|
||||
- libx11-6
|
||||
- libxxf86vm1
|
||||
- zlib1g
|
||||
|
||||
files:
|
||||
exclude:
|
||||
- usr/share/man
|
||||
- usr/share/doc/*/README.*
|
||||
- usr/share/doc/*/changelog.*
|
||||
- usr/share/doc/*/NEWS.*
|
||||
- usr/share/doc/*/TODO.*
|
||||
|
||||
AppImage:
|
||||
update-information: None
|
||||
sign-key: None
|
||||
arch: x86_64
|
275
CMakeLists.txt
275
CMakeLists.txt
|
@ -1,22 +1,29 @@
|
|||
cmake_minimum_required(VERSION 3.12)
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
# Set policies up to 3.9 since we want to enable the IPO option
|
||||
if(${CMAKE_VERSION} VERSION_LESS 3.9)
|
||||
cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
|
||||
else()
|
||||
cmake_policy(VERSION 3.9)
|
||||
endif()
|
||||
|
||||
# This can be read from ${PROJECT_NAME} after project() is called
|
||||
project(luanti)
|
||||
set(PROJECT_NAME_CAPITALIZED "Luanti")
|
||||
project(minetest)
|
||||
set(PROJECT_NAME_CAPITALIZED "Minetest")
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD 14)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED TRUE)
|
||||
set(GCC_MINIMUM_VERSION "7.5")
|
||||
set(CLANG_MINIMUM_VERSION "7.0.1")
|
||||
set(GCC_MINIMUM_VERSION "5.1")
|
||||
set(CLANG_MINIMUM_VERSION "3.5")
|
||||
|
||||
# You should not need to edit these manually, use util/bump_version.sh
|
||||
# Also remember to set PROTOCOL_VERSION in network/networkprotocol.h when releasing
|
||||
set(VERSION_MAJOR 5)
|
||||
set(VERSION_MINOR 12)
|
||||
set(VERSION_PATCH 0)
|
||||
set(VERSION_MINOR 6)
|
||||
set(VERSION_PATCH 1)
|
||||
set(VERSION_EXTRA "" CACHE STRING "Stuff to append to version string")
|
||||
|
||||
# Change to false for releases
|
||||
set(DEVELOPMENT_BUILD TRUE)
|
||||
set(DEVELOPMENT_BUILD FALSE)
|
||||
|
||||
set(VERSION_STRING "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}")
|
||||
if(VERSION_EXTRA)
|
||||
|
@ -30,37 +37,10 @@ if (CMAKE_BUILD_TYPE STREQUAL Debug)
|
|||
set(VERSION_STRING "${VERSION_STRING}-debug")
|
||||
endif()
|
||||
|
||||
message(STATUS "*** Will build version ${VERSION_STRING} ***")
|
||||
|
||||
|
||||
# Configuration options
|
||||
set(BUILD_CLIENT TRUE CACHE BOOL "Build client")
|
||||
set(BUILD_SERVER FALSE CACHE BOOL "Build server")
|
||||
set(BUILD_UNITTESTS TRUE CACHE BOOL "Build unittests")
|
||||
set(BUILD_BENCHMARKS FALSE CACHE BOOL "Build benchmarks")
|
||||
set(BUILD_DOCUMENTATION TRUE CACHE BOOL "Build documentation")
|
||||
|
||||
set(DEFAULT_ENABLE_LTO TRUE)
|
||||
# by default don't enable on Debug builds to get faster builds
|
||||
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
set(DEFAULT_ENABLE_LTO FALSE)
|
||||
endif()
|
||||
#### LTO testing list ####
|
||||
# - Linux: seems to work always
|
||||
# - win32/msvc: works
|
||||
# - win32/gcc: fails to link
|
||||
# - win32/clang: works
|
||||
# - macOS on x86: seems to be fine
|
||||
# - macOS on ARM: crashes, see <https://github.com/luanti-org/luanti/issues/14397>
|
||||
# Note: since CMake has no easy architecture detection disabling for Mac entirely
|
||||
#### ####
|
||||
if((WIN32 AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU") OR APPLE)
|
||||
set(DEFAULT_ENABLE_LTO FALSE)
|
||||
endif()
|
||||
set(ENABLE_LTO ${DEFAULT_ENABLE_LTO} CACHE BOOL "Use Link Time Optimization")
|
||||
|
||||
set(BUILD_WITH_TRACY FALSE CACHE BOOL
|
||||
"Fetch and build with the Tracy profiler client")
|
||||
set(FETCH_TRACY_GIT_TAG "master" CACHE STRING
|
||||
"Git tag for fetching Tracy client. Match with your server (gui) version")
|
||||
|
||||
set(DEFAULT_RUN_IN_PLACE FALSE)
|
||||
if(WIN32)
|
||||
set(DEFAULT_RUN_IN_PLACE TRUE)
|
||||
|
@ -68,13 +48,11 @@ endif()
|
|||
set(RUN_IN_PLACE ${DEFAULT_RUN_IN_PLACE} CACHE BOOL
|
||||
"Run directly in source directory structure")
|
||||
|
||||
message(STATUS "*** Will build version ${VERSION_STRING} ***")
|
||||
message(STATUS "BUILD_CLIENT: " ${BUILD_CLIENT})
|
||||
message(STATUS "BUILD_SERVER: " ${BUILD_SERVER})
|
||||
message(STATUS "BUILD_UNITTESTS: " ${BUILD_UNITTESTS})
|
||||
message(STATUS "BUILD_BENCHMARKS: " ${BUILD_BENCHMARKS})
|
||||
message(STATUS "BUILD_DOCUMENTATION: " ${BUILD_DOCUMENTATION})
|
||||
message(STATUS "RUN_IN_PLACE: " ${RUN_IN_PLACE})
|
||||
|
||||
set(BUILD_CLIENT TRUE CACHE BOOL "Build client")
|
||||
set(BUILD_SERVER FALSE CACHE BOOL "Build server")
|
||||
set(BUILD_UNITTESTS TRUE CACHE BOOL "Build unittests")
|
||||
set(BUILD_BENCHMARKS FALSE CACHE BOOL "Build benchmarks")
|
||||
|
||||
set(WARN_ALL TRUE CACHE BOOL "Enable -Wall for Release build")
|
||||
|
||||
|
@ -89,17 +67,27 @@ set(ENABLE_UPDATE_CHECKER (NOT ${DEVELOPMENT_BUILD}) CACHE BOOL
|
|||
# Included stuff
|
||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/Modules/")
|
||||
|
||||
# Load default options for Android
|
||||
if(ANDROID)
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
include(AndroidLibs)
|
||||
endif()
|
||||
|
||||
set(IRRLICHTMT_BUILD_DIR "" CACHE PATH "Path to IrrlichtMt build directory.")
|
||||
if(NOT "${IRRLICHTMT_BUILD_DIR}" STREQUAL "")
|
||||
find_package(IrrlichtMt QUIET
|
||||
PATHS "${IRRLICHTMT_BUILD_DIR}"
|
||||
NO_DEFAULT_PATH
|
||||
)
|
||||
|
||||
if(TRUE)
|
||||
message(STATUS "Using imported IrrlichtMt at subdirectory 'irr'")
|
||||
if(NOT TARGET IrrlichtMt::IrrlichtMt)
|
||||
# find_package() searches certain subdirectories. ${PATH}/cmake is not
|
||||
# the only one, but it is the one where IrrlichtMt is supposed to export
|
||||
# IrrlichtMtConfig.cmake
|
||||
message(FATAL_ERROR "Could not find IrrlichtMtConfig.cmake in ${IRRLICHTMT_BUILD_DIR}/cmake.")
|
||||
endif()
|
||||
elseif(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/lib/irrlichtmt")
|
||||
message(STATUS "Using user-provided IrrlichtMt at subdirectory 'lib/irrlichtmt'")
|
||||
if(BUILD_CLIENT)
|
||||
add_subdirectory(irr EXCLUDE_FROM_ALL)
|
||||
# tell IrrlichtMt to create a static library
|
||||
set(BUILD_SHARED_LIBS OFF CACHE BOOL "Build shared library" FORCE)
|
||||
add_subdirectory(lib/irrlichtmt EXCLUDE_FROM_ALL)
|
||||
unset(BUILD_SHARED_LIBS CACHE)
|
||||
|
||||
if(NOT TARGET IrrlichtMt)
|
||||
message(FATAL_ERROR "IrrlichtMt project is missing a CMake target?!")
|
||||
|
@ -107,36 +95,48 @@ if(TRUE)
|
|||
else()
|
||||
add_library(IrrlichtMt::IrrlichtMt INTERFACE IMPORTED)
|
||||
set_target_properties(IrrlichtMt::IrrlichtMt PROPERTIES
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${CMAKE_CURRENT_SOURCE_DIR}/irr/include")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (ENABLE_LTO OR CMAKE_INTERPROCEDURAL_OPTIMIZATION)
|
||||
include(CheckIPOSupported)
|
||||
check_ipo_supported(RESULT lto_supported OUTPUT lto_output)
|
||||
if(lto_supported)
|
||||
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE)
|
||||
message(STATUS "LTO/IPO is enabled")
|
||||
else()
|
||||
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION FALSE)
|
||||
message(STATUS "LTO/IPO was requested but is not supported by the compiler: ${lto_output}")
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${CMAKE_CURRENT_SOURCE_DIR}/lib/irrlichtmt/include")
|
||||
endif()
|
||||
else()
|
||||
message(STATUS "LTO/IPO is not enabled")
|
||||
find_package(IrrlichtMt QUIET)
|
||||
if(NOT TARGET IrrlichtMt::IrrlichtMt)
|
||||
string(CONCAT explanation_msg
|
||||
"The Minetest team has forked Irrlicht to make their own customizations. "
|
||||
"It can be found here: https://github.com/minetest/irrlicht\n"
|
||||
"For example use: git clone --depth=1 https://github.com/minetest/irrlicht lib/irrlichtmt\n")
|
||||
if(BUILD_CLIENT)
|
||||
message(FATAL_ERROR "IrrlichtMt is required to build the client, but it was not found.\n${explanation_msg}")
|
||||
endif()
|
||||
|
||||
include(MinetestFindIrrlichtHeaders)
|
||||
if(NOT IRRLICHT_INCLUDE_DIR)
|
||||
message(FATAL_ERROR "IrrlichtMt headers are required to build the server, but none found.\n${explanation_msg}")
|
||||
endif()
|
||||
message(STATUS "Found IrrlichtMt headers: ${IRRLICHT_INCLUDE_DIR}")
|
||||
add_library(IrrlichtMt::IrrlichtMt INTERFACE IMPORTED)
|
||||
# Note that we can't use target_include_directories() since that doesn't work for IMPORTED targets before CMake 3.11
|
||||
set_target_properties(IrrlichtMt::IrrlichtMt PROPERTIES
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${IRRLICHT_INCLUDE_DIR}")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
||||
if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "${GCC_MINIMUM_VERSION}")
|
||||
message(FATAL_ERROR "Insufficient gcc version, found ${CMAKE_CXX_COMPILER_VERSION}. "
|
||||
"Version ${GCC_MINIMUM_VERSION} or higher is required.")
|
||||
if(BUILD_CLIENT AND TARGET IrrlichtMt::IrrlichtMt)
|
||||
# retrieve version somehow
|
||||
if(NOT IrrlichtMt_VERSION)
|
||||
get_target_property(IrrlichtMt_VERSION IrrlichtMt VERSION)
|
||||
endif()
|
||||
elseif(CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?Clang")
|
||||
if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS "${CLANG_MINIMUM_VERSION}")
|
||||
message(FATAL_ERROR "Insufficient clang version, found ${CMAKE_CXX_COMPILER_VERSION}. "
|
||||
"Version ${CLANG_MINIMUM_VERSION} or higher is required.")
|
||||
message(STATUS "Found IrrlichtMt ${IrrlichtMt_VERSION}")
|
||||
|
||||
set(TARGET_VER_S 1.9.0mt8)
|
||||
string(REPLACE "mt" "." TARGET_VER ${TARGET_VER_S})
|
||||
if(IrrlichtMt_VERSION VERSION_LESS ${TARGET_VER})
|
||||
message(FATAL_ERROR "At least IrrlichtMt ${TARGET_VER_S} is required to build")
|
||||
elseif(NOT DEVELOPMENT_BUILD AND IrrlichtMt_VERSION VERSION_GREATER ${TARGET_VER})
|
||||
message(FATAL_ERROR "IrrlichtMt ${TARGET_VER_S} is required to build")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
|
||||
# Installation
|
||||
|
||||
if(WIN32)
|
||||
|
@ -161,7 +161,7 @@ elseif(UNIX) # Linux, BSD etc
|
|||
set(EXAMPLE_CONF_DIR ".")
|
||||
set(MANDIR "unix/man")
|
||||
set(XDG_APPS_DIR "unix/applications")
|
||||
set(METAINFODIR "unix/metainfo")
|
||||
set(APPDATADIR "unix/metainfo")
|
||||
set(ICONDIR "unix/icons")
|
||||
set(LOCALEDIR "locale")
|
||||
else()
|
||||
|
@ -172,7 +172,7 @@ elseif(UNIX) # Linux, BSD etc
|
|||
set(MANDIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_MANDIR}")
|
||||
set(EXAMPLE_CONF_DIR ${DOCDIR})
|
||||
set(XDG_APPS_DIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATADIR}/applications")
|
||||
set(METAINFODIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATADIR}/metainfo")
|
||||
set(APPDATADIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATADIR}/metainfo")
|
||||
set(ICONDIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATADIR}/icons")
|
||||
set(LOCALEDIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LOCALEDIR}")
|
||||
endif()
|
||||
|
@ -236,12 +236,10 @@ if(RUN_IN_PLACE)
|
|||
install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/textures/texture_packs_here.txt" DESTINATION "${SHAREDIR}/textures")
|
||||
endif()
|
||||
|
||||
set(INSTALL_DEVTEST FALSE CACHE BOOL "Install Development Test")
|
||||
|
||||
if(INSTALL_DEVTEST)
|
||||
install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/games/devtest" DESTINATION "${SHAREDIR}/games/"
|
||||
PATTERN ".git*" EXCLUDE )
|
||||
endif()
|
||||
install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/games/minetest_game" DESTINATION "${SHAREDIR}/games/"
|
||||
COMPONENT "SUBGAME_MINETEST_GAME" OPTIONAL PATTERN ".git*" EXCLUDE )
|
||||
install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/games/devtest" DESTINATION "${SHAREDIR}/games/"
|
||||
COMPONENT "SUBGAME_MINIMAL" OPTIONAL PATTERN ".git*" EXCLUDE )
|
||||
|
||||
if(BUILD_CLIENT)
|
||||
install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/client/shaders" DESTINATION "${SHAREDIR}/client")
|
||||
|
@ -253,51 +251,59 @@ if(BUILD_CLIENT)
|
|||
endif()
|
||||
|
||||
install(FILES "README.md" DESTINATION "${DOCDIR}" COMPONENT "Docs")
|
||||
install(FILES "doc/lua_api.md" DESTINATION "${DOCDIR}" COMPONENT "Docs")
|
||||
install(FILES "doc/client_lua_api.md" DESTINATION "${DOCDIR}" COMPONENT "Docs")
|
||||
install(FILES "doc/menu_lua_api.md" DESTINATION "${DOCDIR}" COMPONENT "Docs")
|
||||
install(FILES "doc/texture_packs.md" DESTINATION "${DOCDIR}" COMPONENT "Docs")
|
||||
install(FILES "doc/world_format.md" DESTINATION "${DOCDIR}" COMPONENT "Docs")
|
||||
install(FILES "doc/lua_api.txt" DESTINATION "${DOCDIR}" COMPONENT "Docs")
|
||||
install(FILES "doc/client_lua_api.txt" DESTINATION "${DOCDIR}" COMPONENT "Docs")
|
||||
install(FILES "doc/menu_lua_api.txt" DESTINATION "${DOCDIR}" COMPONENT "Docs")
|
||||
install(FILES "doc/texture_packs.txt" DESTINATION "${DOCDIR}" COMPONENT "Docs")
|
||||
install(FILES "doc/world_format.txt" DESTINATION "${DOCDIR}" COMPONENT "Docs")
|
||||
install(FILES "minetest.conf.example" DESTINATION "${EXAMPLE_CONF_DIR}")
|
||||
|
||||
if(UNIX AND NOT APPLE)
|
||||
install(FILES "doc/luanti.6" "doc/luantiserver.6" DESTINATION "${MANDIR}/man6")
|
||||
install(FILES "doc/minetest.6" "doc/minetestserver.6" DESTINATION "${MANDIR}/man6")
|
||||
install(FILES "misc/net.minetest.minetest.desktop" DESTINATION "${XDG_APPS_DIR}")
|
||||
install(FILES "misc/net.minetest.minetest.metainfo.xml" DESTINATION "${METAINFODIR}")
|
||||
install(FILES "misc/luanti.svg" DESTINATION "${ICONDIR}/hicolor/scalable/apps")
|
||||
install(FILES "misc/luanti-xorg-icon-128.png"
|
||||
install(FILES "misc/net.minetest.minetest.appdata.xml" DESTINATION "${APPDATADIR}")
|
||||
install(FILES "misc/minetest.svg" DESTINATION "${ICONDIR}/hicolor/scalable/apps")
|
||||
install(FILES "misc/minetest-xorg-icon-128.png"
|
||||
DESTINATION "${ICONDIR}/hicolor/128x128/apps"
|
||||
RENAME "luanti.png")
|
||||
RENAME "minetest.png")
|
||||
endif()
|
||||
|
||||
if(APPLE)
|
||||
install(FILES "misc/luanti-icon.icns" DESTINATION "${SHAREDIR}")
|
||||
install(FILES "${CMAKE_BINARY_DIR}/Info.plist" DESTINATION "${BUNDLE_PATH}/Contents")
|
||||
endif()
|
||||
|
||||
if(CMAKE_GENERATOR STREQUAL "Xcode")
|
||||
set(client_RESOURCES "${CMAKE_SOURCE_DIR}/misc/luanti-icon.icns")
|
||||
install(FILES "misc/minetest-icon.icns" DESTINATION "${SHAREDIR}")
|
||||
install(FILES "misc/Info.plist" DESTINATION "${BUNDLE_PATH}/Contents")
|
||||
endif()
|
||||
|
||||
# Library pack
|
||||
find_package(GMP REQUIRED)
|
||||
find_package(Json 1.0.0 REQUIRED)
|
||||
find_package(Json REQUIRED)
|
||||
find_package(Lua REQUIRED)
|
||||
if(NOT USE_LUAJIT)
|
||||
set(LUA_BIT_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/lib/bitop)
|
||||
set(LUA_BIT_LIBRARY bitop)
|
||||
add_subdirectory(lib/bitop)
|
||||
endif()
|
||||
add_subdirectory(lib/sha256)
|
||||
|
||||
if(BUILD_UNITTESTS OR BUILD_BENCHMARKS)
|
||||
add_subdirectory(lib/catch2)
|
||||
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
||||
if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "${GCC_MINIMUM_VERSION}")
|
||||
message(FATAL_ERROR "Insufficient gcc version, found ${CMAKE_CXX_COMPILER_VERSION}. "
|
||||
"Version ${GCC_MINIMUM_VERSION} or higher is required.")
|
||||
endif()
|
||||
elseif(CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?Clang")
|
||||
if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS "${CLANG_MINIMUM_VERSION}")
|
||||
message(FATAL_ERROR "Insufficient clang version, found ${CMAKE_CXX_COMPILER_VERSION}. "
|
||||
"Version ${CLANG_MINIMUM_VERSION} or higher is required.")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
add_subdirectory(lib/tiniergltf)
|
||||
if(BUILD_BENCHMARKS)
|
||||
add_subdirectory(lib/catch2)
|
||||
endif()
|
||||
|
||||
# Subdirectories
|
||||
# Be sure to add all relevant definitions above this
|
||||
add_subdirectory(src)
|
||||
|
||||
|
||||
# CPack
|
||||
|
||||
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "A free open-source voxel game engine with easy modding and game creation.")
|
||||
|
@ -311,11 +317,28 @@ include(CPackComponent)
|
|||
|
||||
cpack_add_component(Docs
|
||||
DISPLAY_NAME "Documentation"
|
||||
DESCRIPTION "Documentation about ${PROJECT_NAME_CAPITALIZED} and ${PROJECT_NAME_CAPITALIZED} modding"
|
||||
DESCRIPTION "Documentation about Minetest and Minetest modding"
|
||||
)
|
||||
|
||||
cpack_add_component(SUBGAME_MINETEST_GAME
|
||||
DISPLAY_NAME "Minetest Game"
|
||||
DESCRIPTION "The default game bundled in the Minetest engine. Mainly used as a modding base."
|
||||
GROUP "Games"
|
||||
)
|
||||
|
||||
cpack_add_component(SUBGAME_MINIMAL
|
||||
DISPLAY_NAME "Development Test"
|
||||
DESCRIPTION "A basic testing environment used for engine development and sometimes for testing mods."
|
||||
DISABLED #DISABLED does not mean it is disabled, and is just not selected by default.
|
||||
GROUP "Games"
|
||||
)
|
||||
|
||||
cpack_add_component_group(Subgames
|
||||
DESCRIPTION "Games for the Minetest engine."
|
||||
)
|
||||
|
||||
if(WIN32)
|
||||
# Include all dynamically linked runtime libraries such as MSVCRxxx.dll
|
||||
# Include all dynamically linked runtime libaries such as MSVCRxxx.dll
|
||||
include(InstallRequiredSystemLibraries)
|
||||
|
||||
if(RUN_IN_PLACE)
|
||||
|
@ -335,7 +358,7 @@ if(WIN32)
|
|||
set(CPACK_CREATE_DESKTOP_LINKS ${PROJECT_NAME})
|
||||
set(CPACK_PACKAGING_INSTALL_PREFIX "/${PROJECT_NAME_CAPITALIZED}")
|
||||
|
||||
set(CPACK_WIX_PRODUCT_ICON "${CMAKE_CURRENT_SOURCE_DIR}/misc/luanti-icon.ico")
|
||||
set(CPACK_WIX_PRODUCT_ICON "${CMAKE_CURRENT_SOURCE_DIR}/misc/minetest-icon.ico")
|
||||
# Supported languages can be found at
|
||||
# http://wixtoolset.org/documentation/manual/v3/wixui/wixui_localization.html
|
||||
#set(CPACK_WIX_CULTURES "ar-SA,bg-BG,ca-ES,hr-HR,cs-CZ,da-DK,nl-NL,en-US,et-EE,fi-FI,fr-FR,de-DE")
|
||||
|
@ -367,31 +390,13 @@ include(CPack)
|
|||
|
||||
|
||||
# Add a target to generate API documentation with Doxygen
|
||||
if(BUILD_DOCUMENTATION)
|
||||
find_package(Doxygen)
|
||||
if(DOXYGEN_FOUND)
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/doc/Doxyfile.in
|
||||
${CMAKE_CURRENT_BINARY_DIR}/doc/Doxyfile @ONLY)
|
||||
add_custom_target(doc
|
||||
${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/doc/Doxyfile
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/doc
|
||||
COMMENT "Generating API documentation with Doxygen" VERBATIM
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Fetch Tracy
|
||||
if(BUILD_WITH_TRACY)
|
||||
include(FetchContent)
|
||||
|
||||
message(STATUS "Fetching Tracy (${FETCH_TRACY_GIT_TAG})...")
|
||||
FetchContent_Declare(
|
||||
tracy
|
||||
GIT_REPOSITORY https://github.com/wolfpld/tracy.git
|
||||
GIT_TAG ${FETCH_TRACY_GIT_TAG}
|
||||
GIT_SHALLOW TRUE
|
||||
GIT_PROGRESS TRUE
|
||||
find_package(Doxygen)
|
||||
if(DOXYGEN_FOUND)
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/doc/Doxyfile.in
|
||||
${CMAKE_CURRENT_BINARY_DIR}/doc/Doxyfile @ONLY)
|
||||
add_custom_target(doc
|
||||
${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/doc/Doxyfile
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/doc
|
||||
COMMENT "Generating API documentation with Doxygen" VERBATIM
|
||||
)
|
||||
FetchContent_MakeAvailable(tracy)
|
||||
message(STATUS "Fetching Tracy - done")
|
||||
endif()
|
||||
|
|
|
@ -1,41 +0,0 @@
|
|||
{
|
||||
"version": 3,
|
||||
"cmakeMinimumRequired": {
|
||||
"major": 3,
|
||||
"minor": 12
|
||||
},
|
||||
"configurePresets": [
|
||||
{
|
||||
"name": "Debug",
|
||||
"displayName": "Debug",
|
||||
"description": "Debug preset with debug symbols and no optimizations",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Debug"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Release",
|
||||
"displayName": "Release",
|
||||
"description": "Release preset with optimizations and no debug symbols",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Release"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "RelWithDebInfo",
|
||||
"displayName": "RelWithDebInfo",
|
||||
"description": "Release with debug symbols",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "RelWithDebInfo"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "MinSizeRel",
|
||||
"displayName": "MinSizeRel",
|
||||
"description": "Release with minimal code size",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "MinSizeRel"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
1
CNAME
1
CNAME
|
@ -1 +0,0 @@
|
|||
api.luanti.org
|
502
COPYING.LESSER
502
COPYING.LESSER
|
@ -1,502 +0,0 @@
|
|||
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
|
||||
the 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.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Libraries
|
||||
|
||||
If you develop a new library, and you want it to be of the greatest
|
||||
possible use to the public, we recommend making it free software that
|
||||
everyone can redistribute and change. You can do so by permitting
|
||||
redistribution under these terms (or, alternatively, under the terms of the
|
||||
ordinary General Public License).
|
||||
|
||||
To apply these terms, attach the following notices to the library. It is
|
||||
safest to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least the
|
||||
"copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the library's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the library, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the
|
||||
library `Frob' (a library for tweaking knobs) written by James Random Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1990
|
||||
Ty Coon, President of Vice
|
||||
|
||||
That's all there is to it!
|
99
Dockerfile
99
Dockerfile
|
@ -1,82 +1,71 @@
|
|||
ARG DOCKER_IMAGE=alpine:3.19
|
||||
FROM $DOCKER_IMAGE AS dev
|
||||
ARG DOCKER_IMAGE=alpine:3.14
|
||||
FROM $DOCKER_IMAGE AS builder
|
||||
|
||||
ENV LUAJIT_VERSION v2.1
|
||||
ENV MINETEST_GAME_VERSION master
|
||||
ENV IRRLICHT_VERSION master
|
||||
|
||||
RUN apk add --no-cache git build-base cmake curl-dev zlib-dev zstd-dev \
|
||||
sqlite-dev postgresql-dev hiredis-dev leveldb-dev \
|
||||
gmp-dev jsoncpp-dev ninja ca-certificates
|
||||
COPY .git /usr/src/minetest/.git
|
||||
COPY CMakeLists.txt /usr/src/minetest/CMakeLists.txt
|
||||
COPY README.md /usr/src/minetest/README.md
|
||||
COPY minetest.conf.example /usr/src/minetest/minetest.conf.example
|
||||
COPY builtin /usr/src/minetest/builtin
|
||||
COPY cmake /usr/src/minetest/cmake
|
||||
COPY doc /usr/src/minetest/doc
|
||||
COPY fonts /usr/src/minetest/fonts
|
||||
COPY lib /usr/src/minetest/lib
|
||||
COPY misc /usr/src/minetest/misc
|
||||
COPY po /usr/src/minetest/po
|
||||
COPY src /usr/src/minetest/src
|
||||
COPY textures /usr/src/minetest/textures
|
||||
|
||||
WORKDIR /usr/src/minetest
|
||||
|
||||
RUN apk add --no-cache git build-base cmake sqlite-dev curl-dev zlib-dev zstd-dev \
|
||||
gmp-dev jsoncpp-dev postgresql-dev ninja luajit-dev ca-certificates && \
|
||||
git clone --depth=1 -b ${MINETEST_GAME_VERSION} https://github.com/minetest/minetest_game.git ./games/minetest_game && \
|
||||
rm -fr ./games/minetest_game/.git
|
||||
|
||||
WORKDIR /usr/src/
|
||||
RUN git clone --recursive https://github.com/jupp0r/prometheus-cpp && \
|
||||
cd prometheus-cpp && \
|
||||
cmake -B build \
|
||||
-DCMAKE_INSTALL_PREFIX=/usr/local \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DENABLE_TESTING=0 \
|
||||
-GNinja && \
|
||||
cmake --build build && \
|
||||
cmake --install build && \
|
||||
cd /usr/src/ && \
|
||||
git clone --recursive https://github.com/libspatialindex/libspatialindex && \
|
||||
cd libspatialindex && \
|
||||
cmake -B build \
|
||||
-DCMAKE_INSTALL_PREFIX=/usr/local && \
|
||||
cmake --build build && \
|
||||
cmake --install build && \
|
||||
cd /usr/src/ && \
|
||||
git clone --recursive https://luajit.org/git/luajit.git -b ${LUAJIT_VERSION} && \
|
||||
cd luajit && \
|
||||
make amalg && make install && \
|
||||
cd /usr/src/
|
||||
RUN git clone --recursive https://github.com/jupp0r/prometheus-cpp/ && \
|
||||
cd prometheus-cpp && \
|
||||
cmake -B build \
|
||||
-DCMAKE_INSTALL_PREFIX=/usr/local \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DENABLE_TESTING=0 \
|
||||
-GNinja && \
|
||||
cmake --build build && \
|
||||
cmake --install build
|
||||
|
||||
FROM dev as builder
|
||||
RUN git clone --depth=1 https://github.com/minetest/irrlicht/ -b ${IRRLICHT_VERSION} && \
|
||||
cp -r irrlicht/include /usr/include/irrlichtmt
|
||||
|
||||
COPY .git /usr/src/luanti/.git
|
||||
COPY CMakeLists.txt /usr/src/luanti/CMakeLists.txt
|
||||
COPY README.md /usr/src/luanti/README.md
|
||||
COPY minetest.conf.example /usr/src/luanti/minetest.conf.example
|
||||
COPY builtin /usr/src/luanti/builtin
|
||||
COPY cmake /usr/src/luanti/cmake
|
||||
COPY doc /usr/src/luanti/doc
|
||||
COPY fonts /usr/src/luanti/fonts
|
||||
COPY lib /usr/src/luanti/lib
|
||||
COPY misc /usr/src/luanti/misc
|
||||
COPY po /usr/src/luanti/po
|
||||
COPY src /usr/src/luanti/src
|
||||
COPY irr /usr/src/luanti/irr
|
||||
COPY textures /usr/src/luanti/textures
|
||||
|
||||
WORKDIR /usr/src/luanti
|
||||
WORKDIR /usr/src/minetest
|
||||
RUN cmake -B build \
|
||||
-DCMAKE_INSTALL_PREFIX=/usr/local \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DBUILD_SERVER=TRUE \
|
||||
-DENABLE_PROMETHEUS=TRUE \
|
||||
-DBUILD_UNITTESTS=FALSE -DBUILD_BENCHMARKS=FALSE \
|
||||
-DBUILD_UNITTESTS=FALSE \
|
||||
-DBUILD_CLIENT=FALSE \
|
||||
-GNinja && \
|
||||
cmake --build build && \
|
||||
cmake --install build
|
||||
|
||||
ARG DOCKER_IMAGE=alpine:3.14
|
||||
FROM $DOCKER_IMAGE AS runtime
|
||||
|
||||
RUN apk add --no-cache curl gmp libstdc++ libgcc libpq jsoncpp zstd-libs \
|
||||
sqlite-libs postgresql hiredis leveldb && \
|
||||
RUN apk add --no-cache sqlite-libs curl gmp libstdc++ libgcc libpq luajit jsoncpp zstd-libs && \
|
||||
adduser -D minetest --uid 30000 -h /var/lib/minetest && \
|
||||
chown -R minetest:minetest /var/lib/minetest
|
||||
|
||||
WORKDIR /var/lib/minetest
|
||||
|
||||
COPY --from=builder /usr/local/share/luanti /usr/local/share/luanti
|
||||
COPY --from=builder /usr/local/bin/luantiserver /usr/local/bin/luantiserver
|
||||
COPY --from=builder /usr/local/share/doc/luanti/minetest.conf.example /etc/minetest/minetest.conf
|
||||
COPY --from=builder /usr/local/lib/libspatialindex* /usr/local/lib/
|
||||
COPY --from=builder /usr/local/lib/libluajit* /usr/local/lib/
|
||||
COPY --from=builder /usr/local/share/minetest /usr/local/share/minetest
|
||||
COPY --from=builder /usr/local/bin/minetestserver /usr/local/bin/minetestserver
|
||||
COPY --from=builder /usr/local/share/doc/minetest/minetest.conf.example /etc/minetest/minetest.conf
|
||||
|
||||
USER minetest:minetest
|
||||
|
||||
EXPOSE 30000/udp 30000/tcp
|
||||
VOLUME /var/lib/minetest/ /etc/minetest/
|
||||
|
||||
ENTRYPOINT ["/usr/local/bin/luantiserver"]
|
||||
CMD ["--config", "/etc/minetest/minetest.conf"]
|
||||
CMD ["/usr/local/bin/minetestserver", "--config", "/etc/minetest/minetest.conf"]
|
||||
|
|
65
LICENSE.txt
65
LICENSE.txt
|
@ -1,8 +1,8 @@
|
|||
|
||||
License of Luanti textures and sounds
|
||||
License of Minetest textures and sounds
|
||||
---------------------------------------
|
||||
|
||||
This applies to textures and sounds contained in the main Luanti
|
||||
This applies to textures and sounds contained in the main Minetest
|
||||
distribution.
|
||||
|
||||
Attribution-ShareAlike 3.0 Unported (CC BY-SA 3.0)
|
||||
|
@ -14,12 +14,6 @@ https://www.apache.org/licenses/LICENSE-2.0.html
|
|||
Textures by Zughy are under CC BY-SA 4.0
|
||||
https://creativecommons.org/licenses/by-sa/4.0/
|
||||
|
||||
Textures by cx384 are under CC BY-SA 4.0
|
||||
https://creativecommons.org/licenses/by-sa/4.0/
|
||||
|
||||
Media files by DS are under CC BY-SA 4.0
|
||||
https://creativecommons.org/licenses/by-sa/4.0/
|
||||
|
||||
textures/base/pack/server_public.png is under CC-BY 4.0, taken from Twitter's Twemoji set
|
||||
https://creativecommons.org/licenses/by/4.0/
|
||||
|
||||
|
@ -32,6 +26,7 @@ ShadowNinja:
|
|||
textures/base/pack/smoke_puff.png
|
||||
|
||||
paramat:
|
||||
textures/base/pack/menu_header.png
|
||||
textures/base/pack/next_icon.png
|
||||
textures/base/pack/prev_icon.png
|
||||
textures/base/pack/clear.png
|
||||
|
@ -41,10 +36,10 @@ rubenwardy, paramat:
|
|||
textures/base/pack/start_icon.png
|
||||
textures/base/pack/end_icon.png
|
||||
|
||||
erle:
|
||||
misc/luanti-icon-24x24.png
|
||||
misc/luanti-icon.ico
|
||||
misc/luanti.svg
|
||||
erlehmann:
|
||||
misc/minetest-icon-24x24.png
|
||||
misc/minetest-icon.ico
|
||||
misc/minetest.svg
|
||||
textures/base/pack/logo.png
|
||||
|
||||
JRottm:
|
||||
|
@ -59,56 +54,30 @@ srifqi:
|
|||
textures/base/pack/minimap_btn.png
|
||||
|
||||
Zughy:
|
||||
textures/base/pack/cdb_add.png
|
||||
textures/base/pack/cdb_clear.png
|
||||
textures/base/pack/cdb_downloading.png
|
||||
textures/base/pack/cdb_queued.png
|
||||
textures/base/pack/cdb_update.png
|
||||
textures/base/pack/cdb_update_cropped.png
|
||||
textures/base/pack/settings_btn.png
|
||||
textures/base/pack/settings_info.png
|
||||
textures/base/pack/settings_reset.png
|
||||
textures/base/pack/server_url.png
|
||||
textures/base/pack/server_url_unavailable.png
|
||||
textures/base/pack/server_view_clients.png
|
||||
textures/base/pack/server_view_clients_unavailable.png
|
||||
|
||||
cx384:
|
||||
textures/base/pack/server_view_mods.png
|
||||
textures/base/pack/server_view_mods_unavailable.png
|
||||
textures/base/pack/cdb_viewonline.png
|
||||
|
||||
appgurueu:
|
||||
textures/base/pack/server_incompatible.png
|
||||
|
||||
erle, Warr1024, rollerozxa:
|
||||
|
||||
erlehmann, Warr1024, rollerozxa:
|
||||
textures/base/pack/no_screenshot.png
|
||||
|
||||
kilbith:
|
||||
textures/base/pack/server_favorite.png
|
||||
textures/base/pack/progress_bar.png
|
||||
textures/base/pack/progress_bar_bg.png
|
||||
|
||||
SmallJoker:
|
||||
SmallJoker
|
||||
textures/base/pack/server_favorite_delete.png (based on server_favorite.png)
|
||||
|
||||
DS:
|
||||
games/devtest/mods/soundstuff/textures/soundstuff_bigfoot.png
|
||||
games/devtest/mods/soundstuff/textures/soundstuff_jukebox.png
|
||||
games/devtest/mods/soundstuff/textures/soundstuff_racecar.png
|
||||
games/devtest/mods/soundstuff/sounds/soundstuff_sinus.ogg
|
||||
games/devtest/mods/testtools/textures/testtools_branding_iron.png
|
||||
|
||||
grorp:
|
||||
textures/base/pack/exit_btn.png
|
||||
textures/base/pack/menu_header.png
|
||||
using the font "undefined medium" (https://undefined-medium.com/),
|
||||
which is licensed under the SIL Open Font License, Version 1.1
|
||||
modified by DS
|
||||
|
||||
License of Luanti source code
|
||||
License of Minetest source code
|
||||
-------------------------------
|
||||
|
||||
Luanti
|
||||
Copyright (C) 2010-2024 celeron55, Perttu Ahola <celeron55@gmail.com>
|
||||
and contributors (see source file comments and the version control log)
|
||||
Minetest
|
||||
Copyright (C) 2010-2018 celeron55, Perttu Ahola <celeron55@gmail.com>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
|
@ -127,7 +96,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
Irrlicht
|
||||
---------------
|
||||
|
||||
This program uses IrrlichtMt, Luanti's fork of
|
||||
This program uses IrrlichtMt, Minetest's fork of
|
||||
the Irrlicht Engine. http://irrlicht.sourceforge.net/
|
||||
|
||||
The Irrlicht Engine License
|
||||
|
|
384
README.md
384
README.md
|
@ -1,17 +1,21 @@
|
|||
<div align="center">
|
||||
<img src="textures/base/pack/logo.png" width="32%">
|
||||
<h1>Luanti (formerly Minetest)</h1>
|
||||
<img src="https://github.com/luanti-org/luanti/workflows/build/badge.svg" alt="Build Status">
|
||||
<a href="https://hosted.weblate.org/engage/minetest/?utm_source=widget"><img src="https://hosted.weblate.org/widgets/minetest/-/svg-badge.svg" alt="Translation status"></a>
|
||||
<a href="https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html"><img src="https://img.shields.io/badge/license-LGPLv2.1%2B-blue.svg" alt="License"></a>
|
||||
</div>
|
||||
<br><br>
|
||||
Minetest
|
||||
========
|
||||
|
||||
Luanti is a free open-source voxel game engine with easy modding and game creation.
|
||||

|
||||
[](https://hosted.weblate.org/engage/minetest/?utm_source=widget)
|
||||
[](https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html)
|
||||
|
||||
Copyright (C) 2010-2025 Perttu Ahola <celeron55@gmail.com>
|
||||
Minetest is a free open-source voxel game engine with easy modding and game creation.
|
||||
|
||||
Copyright (C) 2010-2022 Perttu Ahola <celeron55@gmail.com>
|
||||
and contributors (see source file comments and the version control log)
|
||||
|
||||
In case you downloaded the source code
|
||||
--------------------------------------
|
||||
If you downloaded the Minetest Engine source code in which this file is
|
||||
contained, you probably want to download the [Minetest Game](https://github.com/minetest/minetest_game/)
|
||||
project too. See its README.txt for more information.
|
||||
|
||||
Table of Contents
|
||||
------------------
|
||||
|
||||
|
@ -27,11 +31,11 @@ Table of Contents
|
|||
|
||||
Further documentation
|
||||
----------------------
|
||||
- Website: https://www.luanti.org/
|
||||
- Wiki: https://wiki.luanti.org/
|
||||
- Forum: https://forum.luanti.org/
|
||||
- GitHub: https://github.com/luanti-org/luanti/
|
||||
- [Developer documentation](doc/developing/)
|
||||
- Website: https://minetest.net/
|
||||
- Wiki: https://wiki.minetest.net/
|
||||
- Developer wiki: https://dev.minetest.net/
|
||||
- Forum: https://forum.minetest.net/
|
||||
- GitHub: https://github.com/minetest/minetest/
|
||||
- [doc/](doc/) directory of source distribution
|
||||
|
||||
Default controls
|
||||
|
@ -47,7 +51,7 @@ Some can be changed in the key config dialog in the settings tab.
|
|||
| Shift | Sneak/move down |
|
||||
| Q | Drop itemstack |
|
||||
| Shift + Q | Drop single item |
|
||||
| Left mouse button | Dig/punch/use |
|
||||
| Left mouse button | Dig/punch/take item |
|
||||
| Right mouse button | Place/use |
|
||||
| Shift + right mouse button | Build (without using) |
|
||||
| I | Inventory menu |
|
||||
|
@ -57,9 +61,11 @@ Some can be changed in the key config dialog in the settings tab.
|
|||
| T | Chat |
|
||||
| / | Command |
|
||||
| Esc | Pause menu/abort/exit (pauses only singleplayer game) |
|
||||
| R | Enable/disable full range view |
|
||||
| + | Increase view range |
|
||||
| - | Decrease view range |
|
||||
| K | Enable/disable fly mode (needs fly privilege) |
|
||||
| P | Enable/disable pitch move mode |
|
||||
| J | Enable/disable fast mode (needs fast privilege) |
|
||||
| H | Enable/disable noclip mode (needs noclip privilege) |
|
||||
| E | Aux1 (Move fast in fast mode. Games may add special features) |
|
||||
|
@ -92,15 +98,15 @@ Where each location is on each platform:
|
|||
* Windows installed:
|
||||
* `bin` = `C:\Program Files\Minetest\bin (Depends on the install location)`
|
||||
* `share` = `C:\Program Files\Minetest (Depends on the install location)`
|
||||
* `user` = `%APPDATA%\Minetest` or `%MINETEST_USER_PATH%`
|
||||
* `user` = `%APPDATA%\Minetest`
|
||||
* Linux installed:
|
||||
* `bin` = `/usr/bin`
|
||||
* `share` = `/usr/share/minetest`
|
||||
* `user` = `~/.minetest` or `$MINETEST_USER_PATH`
|
||||
* `user` = `~/.minetest`
|
||||
* macOS:
|
||||
* `bin` = `Contents/MacOS`
|
||||
* `share` = `Contents/Resources`
|
||||
* `user` = `Contents/User` or `~/Library/Application Support/minetest` or `$MINETEST_USER_PATH`
|
||||
* `user` = `Contents/User OR ~/Library/Application Support/minetest`
|
||||
|
||||
Worlds can be found as separate folders in: `user/worlds/`
|
||||
|
||||
|
@ -108,7 +114,7 @@ Configuration file
|
|||
------------------
|
||||
- Default location:
|
||||
`user/minetest.conf`
|
||||
- This file is created by closing Luanti for the first time.
|
||||
- This file is created by closing Minetest for the first time.
|
||||
- A specific file can be specified on the command line:
|
||||
`--config <path-to-file>`
|
||||
- A run-in-place build will look for the configuration file in
|
||||
|
@ -120,17 +126,343 @@ Command-line options
|
|||
|
||||
Compiling
|
||||
---------
|
||||
### Compiling on GNU/Linux
|
||||
|
||||
- [Compiling - common information](doc/compiling/README.md)
|
||||
- [Compiling on GNU/Linux](doc/compiling/linux.md)
|
||||
- [Compiling on Windows](doc/compiling/windows.md)
|
||||
- [Compiling on MacOS](doc/compiling/macos.md)
|
||||
#### Dependencies
|
||||
|
||||
| Dependency | Version | Commentary |
|
||||
|------------|---------|------------|
|
||||
| GCC | 5.1+ | or Clang 3.5+ |
|
||||
| CMake | 3.5+ | |
|
||||
| IrrlichtMt | - | Custom version of Irrlicht, see https://github.com/minetest/irrlicht |
|
||||
| Freetype | 2.0+ | |
|
||||
| SQLite3 | 3+ | |
|
||||
| Zstd | 1.0+ | |
|
||||
| LuaJIT | 2.0+ | Bundled Lua 5.1 is used if not present |
|
||||
| GMP | 5.0.0+ | Bundled mini-GMP is used if not present |
|
||||
| JsonCPP | 1.0.0+ | Bundled JsonCPP is used if not present |
|
||||
|
||||
For Debian/Ubuntu users:
|
||||
|
||||
sudo apt install g++ make libc6-dev cmake libpng-dev libjpeg-dev libxi-dev libgl1-mesa-dev libsqlite3-dev libogg-dev libvorbis-dev libopenal-dev libcurl4-gnutls-dev libfreetype6-dev zlib1g-dev libgmp-dev libjsoncpp-dev libzstd-dev libluajit-5.1-dev
|
||||
|
||||
For Fedora users:
|
||||
|
||||
sudo dnf install make automake gcc gcc-c++ kernel-devel cmake libcurl-devel openal-soft-devel libvorbis-devel libXi-devel libogg-devel freetype-devel mesa-libGL-devel zlib-devel jsoncpp-devel gmp-devel sqlite-devel luajit-devel leveldb-devel ncurses-devel spatialindex-devel libzstd-devel
|
||||
|
||||
For Arch users:
|
||||
|
||||
sudo pacman -S base-devel libcurl-gnutls cmake libxi libpng sqlite libogg libvorbis openal freetype2 jsoncpp gmp luajit leveldb ncurses zstd
|
||||
|
||||
For Alpine users:
|
||||
|
||||
sudo apk add build-base cmake libpng-dev jpeg-dev libxi-dev mesa-dev sqlite-dev libogg-dev libvorbis-dev openal-soft-dev curl-dev freetype-dev zlib-dev gmp-dev jsoncpp-dev luajit-dev zstd-dev
|
||||
|
||||
#### Download
|
||||
|
||||
You can install Git for easily keeping your copy up to date.
|
||||
If you don’t want Git, read below on how to get the source without Git.
|
||||
This is an example for installing Git on Debian/Ubuntu:
|
||||
|
||||
sudo apt install git
|
||||
|
||||
For Fedora users:
|
||||
|
||||
sudo dnf install git
|
||||
|
||||
Download source (this is the URL to the latest of source repository, which might not work at all times) using Git:
|
||||
|
||||
git clone --depth 1 https://github.com/minetest/minetest.git
|
||||
cd minetest
|
||||
|
||||
Download minetest_game (otherwise only the "Development Test" game is available) using Git:
|
||||
|
||||
git clone --depth 1 https://github.com/minetest/minetest_game.git games/minetest_game
|
||||
|
||||
Download IrrlichtMt to `lib/irrlichtmt`, it will be used to satisfy the IrrlichtMt dependency that way:
|
||||
|
||||
git clone --depth 1 https://github.com/minetest/irrlicht.git lib/irrlichtmt
|
||||
|
||||
Download source, without using Git:
|
||||
|
||||
wget https://github.com/minetest/minetest/archive/master.tar.gz
|
||||
tar xf master.tar.gz
|
||||
cd minetest-master
|
||||
|
||||
Download minetest_game, without using Git:
|
||||
|
||||
cd games/
|
||||
wget https://github.com/minetest/minetest_game/archive/master.tar.gz
|
||||
tar xf master.tar.gz
|
||||
mv minetest_game-master minetest_game
|
||||
cd ..
|
||||
|
||||
Download IrrlichtMt, without using Git:
|
||||
|
||||
cd lib/
|
||||
wget https://github.com/minetest/irrlicht/archive/master.tar.gz
|
||||
tar xf master.tar.gz
|
||||
mv irrlicht-master irrlichtmt
|
||||
cd ..
|
||||
|
||||
#### Build
|
||||
|
||||
Build a version that runs directly from the source directory:
|
||||
|
||||
cmake . -DRUN_IN_PLACE=TRUE
|
||||
make -j$(nproc)
|
||||
|
||||
Run it:
|
||||
|
||||
./bin/minetest
|
||||
|
||||
- Use `cmake . -LH` to see all CMake options and their current state.
|
||||
- If you want to install it system-wide (or are making a distribution package),
|
||||
you will want to use `-DRUN_IN_PLACE=FALSE`.
|
||||
- You can build a bare server by specifying `-DBUILD_SERVER=TRUE`.
|
||||
- You can disable the client build by specifying `-DBUILD_CLIENT=FALSE`.
|
||||
- You can select between Release and Debug build by `-DCMAKE_BUILD_TYPE=<Debug or Release>`.
|
||||
- Debug build is slower, but gives much more useful output in a debugger.
|
||||
- If you build a bare server you don't need to compile IrrlichtMt, just the headers suffice.
|
||||
- In that case use `-DIRRLICHT_INCLUDE_DIR=/some/where/irrlichtmt/include`.
|
||||
|
||||
- Minetest will use the IrrlichtMt package that is found first, given by the following order:
|
||||
1. Specified `IRRLICHTMT_BUILD_DIR` CMake variable
|
||||
2. `${PROJECT_SOURCE_DIR}/lib/irrlichtmt` (if existent)
|
||||
3. Installation of IrrlichtMt in the system-specific library paths
|
||||
4. For server builds with disabled `BUILD_CLIENT` variable, the headers from `IRRLICHT_INCLUDE_DIR` will be used.
|
||||
- NOTE: Changing the IrrlichtMt build directory (includes system installs) requires regenerating the CMake cache (`rm CMakeCache.txt`)
|
||||
|
||||
### CMake options
|
||||
|
||||
General options and their default values:
|
||||
|
||||
BUILD_CLIENT=TRUE - Build Minetest client
|
||||
BUILD_SERVER=FALSE - Build Minetest server
|
||||
BUILD_UNITTESTS=TRUE - Build unittest sources
|
||||
BUILD_BENCHMARKS=FALSE - Build benchmark sources
|
||||
CMAKE_BUILD_TYPE=Release - Type of build (Release vs. Debug)
|
||||
Release - Release build
|
||||
Debug - Debug build
|
||||
SemiDebug - Partially optimized debug build
|
||||
RelWithDebInfo - Release build with debug information
|
||||
MinSizeRel - Release build with -Os passed to compiler to make executable as small as possible
|
||||
ENABLE_CURL=ON - Build with cURL; Enables use of online mod repo, public serverlist and remote media fetching via http
|
||||
ENABLE_CURSES=ON - Build with (n)curses; Enables a server side terminal (command line option: --terminal)
|
||||
ENABLE_GETTEXT=ON - Build with Gettext; Allows using translations
|
||||
ENABLE_GLES=OFF - Enable extra support code for OpenGL ES (requires support by IrrlichtMt)
|
||||
ENABLE_LEVELDB=ON - Build with LevelDB; Enables use of LevelDB map backend
|
||||
ENABLE_POSTGRESQL=ON - Build with libpq; Enables use of PostgreSQL map backend (PostgreSQL 9.5 or greater recommended)
|
||||
ENABLE_REDIS=ON - Build with libhiredis; Enables use of Redis map backend
|
||||
ENABLE_SPATIAL=ON - Build with LibSpatial; Speeds up AreaStores
|
||||
ENABLE_SOUND=ON - Build with OpenAL, libogg & libvorbis; in-game sounds
|
||||
ENABLE_LUAJIT=ON - Build with LuaJIT (much faster than non-JIT Lua)
|
||||
ENABLE_PROMETHEUS=OFF - Build with Prometheus metrics exporter (listens on tcp/30000 by default)
|
||||
ENABLE_SYSTEM_GMP=ON - Use GMP from system (much faster than bundled mini-gmp)
|
||||
ENABLE_SYSTEM_JSONCPP=ON - Use JsonCPP from system
|
||||
RUN_IN_PLACE=FALSE - Create a portable install (worlds, settings etc. in current directory)
|
||||
ENABLE_UPDATE_CHECKER=TRUE - Whether to enable update checks by default
|
||||
USE_GPROF=FALSE - Enable profiling using GProf
|
||||
VERSION_EXTRA= - Text to append to version (e.g. VERSION_EXTRA=foobar -> Minetest 0.4.9-foobar)
|
||||
ENABLE_TOUCH=FALSE - Enable Touchscreen support (requires support by IrrlichtMt)
|
||||
|
||||
Library specific options:
|
||||
|
||||
CURL_DLL - Only if building with cURL on Windows; path to libcurl.dll
|
||||
CURL_INCLUDE_DIR - Only if building with cURL; directory where curl.h is located
|
||||
CURL_LIBRARY - Only if building with cURL; path to libcurl.a/libcurl.so/libcurl.lib
|
||||
EGL_INCLUDE_DIR - Only if building with GLES; directory that contains egl.h
|
||||
EGL_LIBRARY - Only if building with GLES; path to libEGL.a/libEGL.so
|
||||
EXTRA_DLL - Only on Windows; optional paths to additional DLLs that should be packaged
|
||||
FREETYPE_INCLUDE_DIR_freetype2 - Directory that contains files such as ftimage.h
|
||||
FREETYPE_INCLUDE_DIR_ft2build - Directory that contains ft2build.h
|
||||
FREETYPE_LIBRARY - Path to libfreetype.a/libfreetype.so/freetype.lib
|
||||
FREETYPE_DLL - Only on Windows; path to libfreetype-6.dll
|
||||
GETTEXT_DLL - Only when building with gettext on Windows; paths to libintl + libiconv DLLs
|
||||
GETTEXT_INCLUDE_DIR - Only when building with gettext; directory that contains iconv.h
|
||||
GETTEXT_LIBRARY - Only when building with gettext on Windows; path to libintl.dll.a
|
||||
GETTEXT_MSGFMT - Only when building with gettext; path to msgfmt/msgfmt.exe
|
||||
IRRLICHT_DLL - Only on Windows; path to IrrlichtMt.dll
|
||||
IRRLICHT_INCLUDE_DIR - Directory that contains IrrCompileConfig.h (usable for server build only)
|
||||
LEVELDB_INCLUDE_DIR - Only when building with LevelDB; directory that contains db.h
|
||||
LEVELDB_LIBRARY - Only when building with LevelDB; path to libleveldb.a/libleveldb.so/libleveldb.dll.a
|
||||
LEVELDB_DLL - Only when building with LevelDB on Windows; path to libleveldb.dll
|
||||
PostgreSQL_INCLUDE_DIR - Only when building with PostgreSQL; directory that contains libpq-fe.h
|
||||
PostgreSQL_LIBRARY - Only when building with PostgreSQL; path to libpq.a/libpq.so/libpq.lib
|
||||
REDIS_INCLUDE_DIR - Only when building with Redis; directory that contains hiredis.h
|
||||
REDIS_LIBRARY - Only when building with Redis; path to libhiredis.a/libhiredis.so
|
||||
SPATIAL_INCLUDE_DIR - Only when building with LibSpatial; directory that contains spatialindex/SpatialIndex.h
|
||||
SPATIAL_LIBRARY - Only when building with LibSpatial; path to libspatialindex_c.so/spatialindex-32.lib
|
||||
LUA_INCLUDE_DIR - Only if you want to use LuaJIT; directory where luajit.h is located
|
||||
LUA_LIBRARY - Only if you want to use LuaJIT; path to libluajit.a/libluajit.so
|
||||
OGG_DLL - Only if building with sound on Windows; path to libogg.dll
|
||||
OGG_INCLUDE_DIR - Only if building with sound; directory that contains an ogg directory which contains ogg.h
|
||||
OGG_LIBRARY - Only if building with sound; path to libogg.a/libogg.so/libogg.dll.a
|
||||
OPENAL_DLL - Only if building with sound on Windows; path to OpenAL32.dll
|
||||
OPENAL_INCLUDE_DIR - Only if building with sound; directory where al.h is located
|
||||
OPENAL_LIBRARY - Only if building with sound; path to libopenal.a/libopenal.so/OpenAL32.lib
|
||||
SQLITE3_INCLUDE_DIR - Directory that contains sqlite3.h
|
||||
SQLITE3_LIBRARY - Path to libsqlite3.a/libsqlite3.so/sqlite3.lib
|
||||
VORBISFILE_LIBRARY - Only if building with sound; path to libvorbisfile.a/libvorbisfile.so/libvorbisfile.dll.a
|
||||
VORBIS_DLL - Only if building with sound on Windows; paths to vorbis DLLs
|
||||
VORBIS_INCLUDE_DIR - Only if building with sound; directory that contains a directory vorbis with vorbisenc.h inside
|
||||
VORBIS_LIBRARY - Only if building with sound; path to libvorbis.a/libvorbis.so/libvorbis.dll.a
|
||||
ZLIB_DLL - Only on Windows; path to zlib1.dll
|
||||
ZLIB_INCLUDE_DIR - Directory that contains zlib.h
|
||||
ZLIB_LIBRARY - Path to libz.a/libz.so/zlib.lib
|
||||
ZSTD_DLL - Only on Windows; path to libzstd.dll
|
||||
ZSTD_INCLUDE_DIR - Directory that contains zstd.h
|
||||
ZSTD_LIBRARY - Path to libzstd.a/libzstd.so/ztd.lib
|
||||
|
||||
### Compiling on Windows using MSVC
|
||||
|
||||
### Requirements
|
||||
|
||||
- [Visual Studio 2015 or newer](https://visualstudio.microsoft.com)
|
||||
- [CMake](https://cmake.org/download/)
|
||||
- [vcpkg](https://github.com/Microsoft/vcpkg)
|
||||
- [Git](https://git-scm.com/downloads)
|
||||
|
||||
### Compiling and installing the dependencies
|
||||
|
||||
It is highly recommended to use vcpkg as package manager.
|
||||
|
||||
After you successfully built vcpkg you can easily install the required libraries:
|
||||
```powershell
|
||||
vcpkg install zlib zstd curl[winssl] openal-soft libvorbis libogg libjpeg-turbo sqlite3 freetype luajit gmp jsoncpp opengl-registry --triplet x64-windows
|
||||
```
|
||||
|
||||
- **Don't forget about IrrlichtMt.** The easiest way is to clone it to `lib/irrlichtmt` as described in the Linux section.
|
||||
- `curl` is optional, but required to read the serverlist, `curl[winssl]` is required to use the content store.
|
||||
- `openal-soft`, `libvorbis` and `libogg` are optional, but required to use sound.
|
||||
- `luajit` is optional, it replaces the integrated Lua interpreter with a faster just-in-time interpreter.
|
||||
- `gmp` and `jsoncpp` are optional, otherwise the bundled versions will be compiled
|
||||
|
||||
There are other optional libraries, but they are not tested if they can build and link correctly.
|
||||
|
||||
Use `--triplet` to specify the target triplet, e.g. `x64-windows` or `x86-windows`.
|
||||
|
||||
### Compile Minetest
|
||||
|
||||
#### a) Using the vcpkg toolchain and CMake GUI
|
||||
1. Start up the CMake GUI
|
||||
2. Select **Browse Source...** and select DIR/minetest
|
||||
3. Select **Browse Build...** and select DIR/minetest-build
|
||||
4. Select **Configure**
|
||||
5. Choose the right visual Studio version and target platform. It has to match the version of the installed dependencies
|
||||
6. Choose **Specify toolchain file for cross-compiling**
|
||||
7. Click **Next**
|
||||
8. Select the vcpkg toolchain file e.g. `D:/vcpkg/scripts/buildsystems/vcpkg.cmake`
|
||||
9. Click Finish
|
||||
10. Wait until cmake have generated the cash file
|
||||
11. If there are any errors, solve them and hit **Configure**
|
||||
12. Click **Generate**
|
||||
13. Click **Open Project**
|
||||
14. Compile Minetest inside Visual studio.
|
||||
|
||||
#### b) Using the vcpkg toolchain and the commandline
|
||||
|
||||
Run the following script in PowerShell:
|
||||
|
||||
```powershell
|
||||
cmake . -G"Visual Studio 15 2017 Win64" -DCMAKE_TOOLCHAIN_FILE=D:/vcpkg/scripts/buildsystems/vcpkg.cmake -DCMAKE_BUILD_TYPE=Release -DENABLE_GETTEXT=OFF -DENABLE_CURSES=OFF
|
||||
cmake --build . --config Release
|
||||
```
|
||||
Make sure that the right compiler is selected and the path to the vcpkg toolchain is correct.
|
||||
|
||||
### Windows Installer using WiX Toolset
|
||||
|
||||
Requirements:
|
||||
* [Visual Studio 2017](https://visualstudio.microsoft.com/)
|
||||
* [WiX Toolset](https://wixtoolset.org/)
|
||||
|
||||
In the Visual Studio 2017 Installer select **Optional Features -> WiX Toolset**.
|
||||
|
||||
Build the binaries as described above, but make sure you unselect `RUN_IN_PLACE`.
|
||||
|
||||
Open the generated project file with Visual Studio. Right-click **Package** and choose **Generate**.
|
||||
It may take some minutes to generate the installer.
|
||||
|
||||
### Compiling on MacOS
|
||||
|
||||
#### Requirements
|
||||
- [Homebrew](https://brew.sh/)
|
||||
- [Git](https://git-scm.com/downloads)
|
||||
|
||||
Install dependencies with homebrew:
|
||||
|
||||
```
|
||||
brew install cmake freetype gettext gmp hiredis jpeg jsoncpp leveldb libogg libpng libvorbis luajit zstd
|
||||
```
|
||||
|
||||
#### Download
|
||||
|
||||
Download source (this is the URL to the latest of source repository, which might not work at all times) using Git:
|
||||
|
||||
```bash
|
||||
git clone --depth 1 https://github.com/minetest/minetest.git
|
||||
cd minetest
|
||||
```
|
||||
|
||||
Download minetest_game (otherwise only the "Development Test" game is available) using Git:
|
||||
|
||||
```
|
||||
git clone --depth 1 https://github.com/minetest/minetest_game.git games/minetest_game
|
||||
```
|
||||
|
||||
Download Minetest's fork of Irrlicht:
|
||||
|
||||
```
|
||||
git clone --depth 1 https://github.com/minetest/irrlicht.git lib/irrlichtmt
|
||||
```
|
||||
|
||||
#### Build
|
||||
|
||||
```bash
|
||||
mkdir build
|
||||
cd build
|
||||
|
||||
cmake .. \
|
||||
-DCMAKE_OSX_DEPLOYMENT_TARGET=10.14 \
|
||||
-DCMAKE_FIND_FRAMEWORK=LAST \
|
||||
-DCMAKE_INSTALL_PREFIX=../build/macos/ \
|
||||
-DRUN_IN_PLACE=FALSE -DENABLE_GETTEXT=TRUE
|
||||
|
||||
make -j$(sysctl -n hw.logicalcpu)
|
||||
make install
|
||||
```
|
||||
|
||||
#### Run
|
||||
|
||||
```
|
||||
open ./build/macos/minetest.app
|
||||
```
|
||||
|
||||
Docker
|
||||
------
|
||||
We provide Minetest server Docker images using the GitLab mirror registry.
|
||||
|
||||
Images are built on each commit and available using the following tag scheme:
|
||||
|
||||
* `registry.gitlab.com/minetest/minetest/server:latest` (latest build)
|
||||
* `registry.gitlab.com/minetest/minetest/server:<branch/tag>` (current branch or current tag)
|
||||
* `registry.gitlab.com/minetest/minetest/server:<commit-id>` (current commit id)
|
||||
|
||||
If you want to test it on a Docker server you can easily run:
|
||||
|
||||
sudo docker run registry.gitlab.com/minetest/minetest/server:<docker tag>
|
||||
|
||||
If you want to use it in a production environment you should use volumes bound to the Docker host
|
||||
to persist data and modify the configuration:
|
||||
|
||||
sudo docker create -v /home/minetest/data/:/var/lib/minetest/ -v /home/minetest/conf/:/etc/minetest/ registry.gitlab.com/minetest/minetest/server:master
|
||||
|
||||
Data will be written to `/home/minetest/data` on the host, and configuration will be read from `/home/minetest/conf/minetest.conf`.
|
||||
|
||||
**Note:** If you don't understand the previous commands please read the official Docker documentation before use.
|
||||
|
||||
You can also host your Minetest server inside a Kubernetes cluster. See our example implementation in [`misc/kubernetes.yml`](misc/kubernetes.yml).
|
||||
|
||||
- [Developing minetestserver with Docker](doc/developing/docker.md)
|
||||
- [Running a server with Docker](doc/docker_server.md)
|
||||
|
||||
Version scheme
|
||||
--------------
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
diff --git a/android/app/src/main/java/org/libsdl/app/SDLActivity.java b/android/app/src/main/java/org/libsdl/app/SDLActivity.java
|
||||
--- a/android/app/src/main/java/org/libsdl/app/SDLActivity.java
|
||||
+++ b/android/app/src/main/java/org/libsdl/app/SDLActivity.java
|
||||
@@ -1345,7 +1345,12 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
|
||||
}
|
||||
}
|
||||
|
||||
- if ((source & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE) {
|
||||
+ if ((source & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE ||
|
||||
+ /*
|
||||
+ * CUSTOM ADDITION FOR LUANTI
|
||||
+ * should be upstreamed
|
||||
+ */
|
||||
+ (source & InputDevice.SOURCE_MOUSE_RELATIVE) == InputDevice.SOURCE_MOUSE_RELATIVE) {
|
||||
// on some devices key events are sent for mouse BUTTON_BACK/FORWARD presses
|
||||
// they are ignored here because sending them as mouse input to SDL is messy
|
||||
if ((keyCode == KeyEvent.KEYCODE_BACK) || (keyCode == KeyEvent.KEYCODE_FORWARD)) {
|
|
@ -1,18 +1,14 @@
|
|||
apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
compileSdkVersion 30
|
||||
buildToolsVersion '30.0.3'
|
||||
ndkVersion "$ndk_version"
|
||||
defaultConfig {
|
||||
applicationId 'net.minetest.minetest'
|
||||
minSdkVersion 21
|
||||
compileSdk 34
|
||||
targetSdkVersion 34
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 30
|
||||
versionName "${versionMajor}.${versionMinor}.${versionPatch}"
|
||||
versionCode versionMajor * 1000000 + versionMinor * 10000 + versionPatch * 100 + versionBuild
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
buildConfig true
|
||||
versionCode project.versionCode
|
||||
}
|
||||
|
||||
// load properties
|
||||
|
@ -52,71 +48,69 @@ android {
|
|||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
namespace 'net.minetest.minetest'
|
||||
}
|
||||
|
||||
task prepareAssets() {
|
||||
def assetsFolder = "build/assets"
|
||||
def projRoot = rootDir.parent
|
||||
def projRoot = "../.."
|
||||
def gameToCopy = "minetest_game"
|
||||
|
||||
// See issue #4638
|
||||
def unsupportedLanguages = new File("${projRoot}/src/unsupported_language_list.txt").text.readLines()
|
||||
|
||||
doFirst {
|
||||
logger.lifecycle('Preparing assets at {}', assetsFolder)
|
||||
copy {
|
||||
from "${projRoot}/minetest.conf.example", "${projRoot}/README.md" into assetsFolder
|
||||
}
|
||||
doLast {
|
||||
copy {
|
||||
from "${projRoot}/minetest.conf.example", "${projRoot}/README.md" into assetsFolder
|
||||
copy {
|
||||
from "${projRoot}/doc/lgpl-2.1.txt" into "${assetsFolder}"
|
||||
}
|
||||
copy {
|
||||
from "${projRoot}/builtin" into "${assetsFolder}/builtin"
|
||||
}
|
||||
copy {
|
||||
from "${projRoot}/client/shaders" into "${assetsFolder}/client/shaders"
|
||||
}
|
||||
copy {
|
||||
from "../native/deps/armeabi-v7a/Irrlicht/Shaders" into "${assetsFolder}/client/shaders/Irrlicht"
|
||||
}
|
||||
copy {
|
||||
from "${projRoot}/fonts" include "*.ttf" into "${assetsFolder}/fonts"
|
||||
}
|
||||
copy {
|
||||
from "${projRoot}/games/${gameToCopy}" into "${assetsFolder}/games/${gameToCopy}"
|
||||
}
|
||||
fileTree("${projRoot}/po").include("**/*.po").forEach { poFile ->
|
||||
def moPath = "${assetsFolder}/locale/${poFile.parentFile.name}/LC_MESSAGES/"
|
||||
file(moPath).mkdirs()
|
||||
exec {
|
||||
commandLine 'msgfmt', '-o', "${moPath}/minetest.mo", poFile
|
||||
}
|
||||
copy {
|
||||
from "${projRoot}/doc/lgpl-2.1.txt" into assetsFolder
|
||||
}
|
||||
copy {
|
||||
from "${projRoot}/builtin" into "${assetsFolder}/builtin"
|
||||
}
|
||||
copy {
|
||||
from "${projRoot}/client/shaders" into "${assetsFolder}/client/shaders"
|
||||
}
|
||||
copy {
|
||||
from "${projRoot}/irr/media/Shaders" into "${assetsFolder}/client/shaders/Irrlicht"
|
||||
}
|
||||
copy {
|
||||
from "${projRoot}/fonts" include "*.ttf" into "${assetsFolder}/fonts"
|
||||
}
|
||||
copy {
|
||||
from "${projRoot}/textures/base/pack" into "${assetsFolder}/textures/base/pack"
|
||||
}
|
||||
|
||||
// compile translations
|
||||
fileTree("${projRoot}/po").include("**/*.po").grep {
|
||||
it.parentFile.name !in unsupportedLanguages
|
||||
}.forEach { poFile ->
|
||||
def moPath = "${assetsFolder}/locale/${poFile.parentFile.name}/LC_MESSAGES/"
|
||||
file(moPath).mkdirs()
|
||||
exec {
|
||||
commandLine 'msgfmt', '-o', "${moPath}/luanti.mo", poFile
|
||||
}
|
||||
}
|
||||
|
||||
file("${assetsFolder}/.nomedia").text = ""
|
||||
}
|
||||
copy {
|
||||
from "${projRoot}/textures" into "${assetsFolder}/textures"
|
||||
}
|
||||
|
||||
task zipAssets(dependsOn: prepareAssets, type: Zip) {
|
||||
archiveFileName = "assets.zip"
|
||||
from assetsFolder
|
||||
destinationDirectory = file("src/main/assets")
|
||||
file("${assetsFolder}/.nomedia").text = "";
|
||||
|
||||
task zipAssets(type: Zip) {
|
||||
archiveName "Minetest.zip"
|
||||
from "${assetsFolder}"
|
||||
destinationDir file("src/main/assets")
|
||||
}
|
||||
}
|
||||
|
||||
preBuild.dependsOn zipAssets
|
||||
prepareAssets.dependsOn ':native:getDeps'
|
||||
|
||||
clean {
|
||||
delete new File("src/main/assets", "assets.zip")
|
||||
// Map for the version code that gives each ABI a value.
|
||||
import com.android.build.OutputFile
|
||||
|
||||
def abiCodes = ['armeabi-v7a': 0, 'arm64-v8a': 1]
|
||||
android.applicationVariants.all { variant ->
|
||||
variant.outputs.each {
|
||||
output ->
|
||||
def abiName = output.getFilter(OutputFile.ABI)
|
||||
output.versionCodeOverride = abiCodes.get(abiName, 0) + variant.versionCode
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(':native')
|
||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||
implementation 'androidx.appcompat:appcompat:1.3.1'
|
||||
}
|
||||
|
|
|
@ -1,16 +1,23 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:installLocation="auto">
|
||||
package="net.minetest.minetest"
|
||||
android:installLocation="auto">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<uses-feature android:glEsVersion="0x00020000" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
|
||||
<!--
|
||||
`android:requestLegacyExternalStorage="true"` is workaround for using `/sdcard`
|
||||
instead of the `getFilesDir()` patch for assets. Check link below for more information:
|
||||
https://developer.android.com/training/data-storage/compatibility
|
||||
-->
|
||||
|
||||
<application
|
||||
android:allowBackup="false"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/label"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:resizeableActivity="false"
|
||||
tools:ignore="UnusedAttribute">
|
||||
|
||||
|
@ -43,6 +50,9 @@
|
|||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
</intent-filter>
|
||||
<meta-data
|
||||
android:name="android.app.lib_name"
|
||||
android:value="Minetest" />
|
||||
</activity>
|
||||
|
||||
<service
|
||||
|
|
|
@ -2,8 +2,6 @@
|
|||
Minetest
|
||||
Copyright (C) 2014-2020 MoNTE48, Maksim Gamarnik <MoNTE48@mail.ua>
|
||||
Copyright (C) 2014-2020 ubulem, Bektur Mambetov <berkut87@gmail.com>
|
||||
Copyright (C) 2023 srifqi, Muhammad Rifqi Priyo Susanto
|
||||
<muhammadrifqipriyosusanto@gmail.com>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
|
@ -31,52 +29,17 @@ import androidx.appcompat.widget.AppCompatEditText;
|
|||
import java.util.Objects;
|
||||
|
||||
public class CustomEditText extends AppCompatEditText {
|
||||
private int editType = 2; // single line text input as default
|
||||
private boolean wantsToShowKeyboard = false;
|
||||
|
||||
public CustomEditText(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public CustomEditText(Context context, int _editType) {
|
||||
super(context);
|
||||
editType = _editType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onKeyPreIme(int keyCode, KeyEvent event) {
|
||||
// For multi-line, do not close the dialog after pressing back button
|
||||
if (editType != 1 && keyCode == KeyEvent.KEYCODE_BACK) {
|
||||
if (keyCode == KeyEvent.KEYCODE_BACK) {
|
||||
InputMethodManager mgr = (InputMethodManager)
|
||||
getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
Objects.requireNonNull(mgr).hideSoftInputFromWindow(this.getWindowToken(), 0);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWindowFocusChanged(boolean hasWindowFocus) {
|
||||
super.onWindowFocusChanged(hasWindowFocus);
|
||||
tryShowKeyboard();
|
||||
}
|
||||
|
||||
public void requestFocusTryShow() {
|
||||
requestFocus();
|
||||
wantsToShowKeyboard = true;
|
||||
tryShowKeyboard();
|
||||
}
|
||||
|
||||
private void tryShowKeyboard() {
|
||||
if (hasWindowFocus() && wantsToShowKeyboard) {
|
||||
if (isFocused()) {
|
||||
CustomEditText that = this;
|
||||
post(() -> {
|
||||
final InputMethodManager imm = (InputMethodManager)
|
||||
getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
imm.showSoftInput(that, 0);
|
||||
});
|
||||
}
|
||||
wantsToShowKeyboard = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,11 +20,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
|
||||
package net.minetest.minetest;
|
||||
|
||||
import org.libsdl.app.SDLActivity;
|
||||
|
||||
import android.app.NativeActivity;
|
||||
import android.content.Intent;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.text.InputType;
|
||||
import android.util.Log;
|
||||
|
@ -33,85 +32,87 @@ import android.view.View;
|
|||
import android.view.WindowManager;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.Toast;
|
||||
import android.content.res.Configuration;
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.core.content.FileProvider;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
|
||||
// Native code finds these methods by name (see porting_android.cpp).
|
||||
// This annotation prevents the minifier/Proguard from mangling them.
|
||||
@Keep
|
||||
@SuppressWarnings("unused")
|
||||
public class GameActivity extends SDLActivity {
|
||||
@Override
|
||||
protected String getMainSharedObject() {
|
||||
return getContext().getApplicationInfo().nativeLibraryDir + "/libluanti.so";
|
||||
public class GameActivity extends NativeActivity {
|
||||
static {
|
||||
System.loadLibrary("c++_shared");
|
||||
System.loadLibrary("Minetest");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMainFunction() {
|
||||
return "SDL_Main";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String[] getLibraries() {
|
||||
return new String[] {
|
||||
"luanti"
|
||||
};
|
||||
}
|
||||
|
||||
// Prevent SDL from changing orientation settings since we already set the
|
||||
// correct orientation in our AndroidManifest.xml
|
||||
@Override
|
||||
public void setOrientationBis(int w, int h, boolean resizable, String hint) {}
|
||||
|
||||
enum DialogType { TEXT_INPUT, SELECTION_INPUT }
|
||||
enum DialogState { DIALOG_SHOWN, DIALOG_INPUTTED, DIALOG_CANCELED }
|
||||
|
||||
private DialogType lastDialogType = DialogType.TEXT_INPUT;
|
||||
private DialogState inputDialogState = DialogState.DIALOG_CANCELED;
|
||||
private int messageReturnCode = -1;
|
||||
private String messageReturnValue = "";
|
||||
private int selectionReturnValue = 0;
|
||||
|
||||
private native void saveSettings();
|
||||
public static native void putMessageBoxResult(String text);
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
// Avoid losing setting changes in case the app is onDestroy()ed later.
|
||||
// Saving stuff in onStop() is recommended in the Android activity
|
||||
// lifecycle documentation.
|
||||
saveSettings();
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
}
|
||||
|
||||
public void showTextInputDialog(String hint, String current, int editType) {
|
||||
runOnUiThread(() -> showTextInputDialogUI(hint, current, editType));
|
||||
private void makeFullScreen() {
|
||||
if (Build.VERSION.SDK_INT >= 19)
|
||||
this.getWindow().getDecorView().setSystemUiVisibility(
|
||||
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
|
||||
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
|
||||
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
|
||||
}
|
||||
|
||||
public void showSelectionInputDialog(String[] optionList, int selectedIdx) {
|
||||
runOnUiThread(() -> showSelectionInputDialogUI(optionList, selectedIdx));
|
||||
@Override
|
||||
public void onWindowFocusChanged(boolean hasFocus) {
|
||||
super.onWindowFocusChanged(hasFocus);
|
||||
if (hasFocus)
|
||||
makeFullScreen();
|
||||
}
|
||||
|
||||
private void showTextInputDialogUI(String hint, String current, int editType) {
|
||||
lastDialogType = DialogType.TEXT_INPUT;
|
||||
inputDialogState = DialogState.DIALOG_SHOWN;
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
makeFullScreen();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
// Ignore the back press so Minetest can handle it
|
||||
}
|
||||
|
||||
public void showDialog(String acceptButton, String hint, String current, int editType) {
|
||||
runOnUiThread(() -> showDialogUI(hint, current, editType));
|
||||
}
|
||||
|
||||
private void showDialogUI(String hint, String current, int editType) {
|
||||
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
LinearLayout container = new LinearLayout(this);
|
||||
container.setOrientation(LinearLayout.VERTICAL);
|
||||
builder.setView(container);
|
||||
AlertDialog alertDialog = builder.create();
|
||||
CustomEditText editText = new CustomEditText(this, editType);
|
||||
EditText editText;
|
||||
// For multi-line, do not close the dialog after pressing back button
|
||||
if (editType == 1) {
|
||||
editText = new EditText(this);
|
||||
} else {
|
||||
editText = new CustomEditText(this);
|
||||
}
|
||||
container.addView(editText);
|
||||
editText.setMaxLines(8);
|
||||
editText.requestFocus();
|
||||
editText.setHint(hint);
|
||||
editText.setText(current);
|
||||
final InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
|
||||
Objects.requireNonNull(imm).toggleSoftInput(InputMethodManager.SHOW_FORCED,
|
||||
InputMethodManager.HIDE_IMPLICIT_ONLY);
|
||||
if (editType == 1)
|
||||
editText.setInputType(InputType.TYPE_CLASS_TEXT |
|
||||
InputType.TYPE_TEXT_FLAG_MULTI_LINE);
|
||||
|
@ -120,13 +121,12 @@ public class GameActivity extends SDLActivity {
|
|||
InputType.TYPE_TEXT_VARIATION_PASSWORD);
|
||||
else
|
||||
editText.setInputType(InputType.TYPE_CLASS_TEXT);
|
||||
editText.setSelection(Objects.requireNonNull(editText.getText()).length());
|
||||
final InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
|
||||
editText.setSelection(editText.getText().length());
|
||||
editText.setOnKeyListener((view, keyCode, event) -> {
|
||||
// For multi-line, do not submit the text after pressing Enter key
|
||||
if (keyCode == KeyEvent.KEYCODE_ENTER && editType != 1) {
|
||||
imm.hideSoftInputFromWindow(editText.getWindowToken(), 0);
|
||||
inputDialogState = DialogState.DIALOG_INPUTTED;
|
||||
messageReturnCode = 0;
|
||||
messageReturnValue = editText.getText().toString();
|
||||
alertDialog.dismiss();
|
||||
return true;
|
||||
|
@ -140,55 +140,28 @@ public class GameActivity extends SDLActivity {
|
|||
doneButton.setText(R.string.ime_dialog_done);
|
||||
doneButton.setOnClickListener((view -> {
|
||||
imm.hideSoftInputFromWindow(editText.getWindowToken(), 0);
|
||||
inputDialogState = DialogState.DIALOG_INPUTTED;
|
||||
messageReturnCode = 0;
|
||||
messageReturnValue = editText.getText().toString();
|
||||
alertDialog.dismiss();
|
||||
}));
|
||||
}
|
||||
alertDialog.show();
|
||||
alertDialog.setOnCancelListener(dialog -> {
|
||||
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
|
||||
inputDialogState = DialogState.DIALOG_CANCELED;
|
||||
messageReturnValue = current;
|
||||
messageReturnCode = -1;
|
||||
});
|
||||
alertDialog.show();
|
||||
editText.requestFocusTryShow();
|
||||
}
|
||||
|
||||
public void showSelectionInputDialogUI(String[] optionList, int selectedIdx) {
|
||||
lastDialogType = DialogType.SELECTION_INPUT;
|
||||
inputDialogState = DialogState.DIALOG_SHOWN;
|
||||
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setSingleChoiceItems(optionList, selectedIdx, (dialog, selection) -> {
|
||||
inputDialogState = DialogState.DIALOG_INPUTTED;
|
||||
selectionReturnValue = selection;
|
||||
dialog.dismiss();
|
||||
});
|
||||
builder.setOnCancelListener(dialog -> {
|
||||
inputDialogState = DialogState.DIALOG_CANCELED;
|
||||
selectionReturnValue = selectedIdx;
|
||||
});
|
||||
AlertDialog alertDialog = builder.create();
|
||||
alertDialog.show();
|
||||
public int getDialogState() {
|
||||
return messageReturnCode;
|
||||
}
|
||||
|
||||
public int getLastDialogType() {
|
||||
return lastDialogType.ordinal();
|
||||
}
|
||||
|
||||
public int getInputDialogState() {
|
||||
return inputDialogState.ordinal();
|
||||
}
|
||||
|
||||
public String getDialogMessage() {
|
||||
inputDialogState = DialogState.DIALOG_CANCELED;
|
||||
public String getDialogValue() {
|
||||
messageReturnCode = -1;
|
||||
return messageReturnValue;
|
||||
}
|
||||
|
||||
public int getDialogSelection() {
|
||||
inputDialogState = DialogState.DIALOG_CANCELED;
|
||||
return selectionReturnValue;
|
||||
}
|
||||
|
||||
public float getDensity() {
|
||||
return getResources().getDisplayMetrics().density;
|
||||
}
|
||||
|
@ -203,11 +176,7 @@ public class GameActivity extends SDLActivity {
|
|||
|
||||
public void openURI(String uri) {
|
||||
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(uri));
|
||||
try {
|
||||
startActivity(browserIntent);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
runOnUiThread(() -> Toast.makeText(this, R.string.no_web_browser, Toast.LENGTH_SHORT).show());
|
||||
}
|
||||
startActivity(browserIntent);
|
||||
}
|
||||
|
||||
public String getUserDataPath() {
|
||||
|
@ -235,32 +204,4 @@ public class GameActivity extends SDLActivity {
|
|||
Intent shareIntent = Intent.createChooser(intent, null);
|
||||
startActivity(shareIntent);
|
||||
}
|
||||
|
||||
public String getLanguage() {
|
||||
String langCode = Locale.getDefault().getLanguage();
|
||||
|
||||
// getLanguage() still uses old language codes to preserve compatibility.
|
||||
// List of code changes in ISO 639-2:
|
||||
// https://www.loc.gov/standards/iso639-2/php/code_changes.php
|
||||
switch (langCode) {
|
||||
case "in":
|
||||
langCode = "id"; // Indonesian
|
||||
break;
|
||||
case "iw":
|
||||
langCode = "he"; // Hebrew
|
||||
break;
|
||||
case "ji":
|
||||
langCode = "yi"; // Yiddish
|
||||
break;
|
||||
case "jw":
|
||||
langCode = "jv"; // Javanese
|
||||
break;
|
||||
}
|
||||
|
||||
return langCode;
|
||||
}
|
||||
|
||||
public boolean hasPhysicalKeyboard() {
|
||||
return getContext().getResources().getConfiguration().keyboard != Configuration.KEYBOARD_NOKEYS;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,31 +20,38 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
|
||||
package net.minetest.minetest;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.Manifest;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.view.View;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static net.minetest.minetest.UnzipService.*;
|
||||
|
||||
public class MainActivity extends AppCompatActivity {
|
||||
public static final String NOTIFICATION_CHANNEL_ID = "Minetest channel";
|
||||
|
||||
private final static int versionCode = BuildConfig.VERSION_CODE;
|
||||
private final static int PERMISSIONS = 1;
|
||||
private static final String[] REQUIRED_SDK_PERMISSIONS =
|
||||
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE};
|
||||
private static final String SETTINGS = "MinetestSettings";
|
||||
private static final String TAG_VERSION_CODE = "versionCode";
|
||||
|
||||
|
@ -84,30 +91,63 @@ public class MainActivity extends AppCompatActivity {
|
|||
}
|
||||
};
|
||||
|
||||
@SuppressLint("UnspecifiedRegisterReceiverFlag")
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
IntentFilter filter = new IntentFilter(ACTION_UPDATE);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
registerReceiver(myReceiver, filter, RECEIVER_NOT_EXPORTED);
|
||||
} else {
|
||||
registerReceiver(myReceiver, filter);
|
||||
}
|
||||
|
||||
registerReceiver(myReceiver, filter);
|
||||
mProgressBar = findViewById(R.id.progressBar);
|
||||
mTextView = findViewById(R.id.textView);
|
||||
sharedPreferences = getSharedPreferences(SETTINGS, Context.MODE_PRIVATE);
|
||||
|
||||
checkAppVersion();
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
|
||||
Build.VERSION.SDK_INT < Build.VERSION_CODES.R)
|
||||
checkPermission();
|
||||
else
|
||||
checkAppVersion();
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
|
||||
createNotificationChannel();
|
||||
private void checkPermission() {
|
||||
final List<String> missingPermissions = new ArrayList<>();
|
||||
for (final String permission : REQUIRED_SDK_PERMISSIONS) {
|
||||
final int result = ContextCompat.checkSelfPermission(this, permission);
|
||||
if (result != PackageManager.PERMISSION_GRANTED)
|
||||
missingPermissions.add(permission);
|
||||
}
|
||||
if (!missingPermissions.isEmpty()) {
|
||||
final String[] permissions = missingPermissions
|
||||
.toArray(new String[0]);
|
||||
ActivityCompat.requestPermissions(this, permissions, PERMISSIONS);
|
||||
} else {
|
||||
final int[] grantResults = new int[REQUIRED_SDK_PERMISSIONS.length];
|
||||
Arrays.fill(grantResults, PackageManager.PERMISSION_GRANTED);
|
||||
onRequestPermissionsResult(PERMISSIONS, REQUIRED_SDK_PERMISSIONS, grantResults);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode,
|
||||
@NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
if (requestCode == PERMISSIONS) {
|
||||
for (int grantResult : grantResults) {
|
||||
if (grantResult != PackageManager.PERMISSION_GRANTED) {
|
||||
Toast.makeText(this, R.string.not_granted, Toast.LENGTH_LONG).show();
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
}
|
||||
checkAppVersion();
|
||||
}
|
||||
}
|
||||
|
||||
private void checkAppVersion() {
|
||||
if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
|
||||
Toast.makeText(this, R.string.no_external_storage, Toast.LENGTH_LONG).show();
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
if (UnzipService.getIsRunning()) {
|
||||
mProgressBar.setVisibility(View.VISIBLE);
|
||||
mProgressBar.setIndeterminate(true);
|
||||
|
@ -132,28 +172,6 @@ public class MainActivity extends AppCompatActivity {
|
|||
startActivity(intent);
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
private void createNotificationChannel() {
|
||||
NotificationManager notifyManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
if (notifyManager == null)
|
||||
return;
|
||||
|
||||
NotificationChannel notifyChannel = new NotificationChannel(
|
||||
NOTIFICATION_CHANNEL_ID,
|
||||
getString(R.string.notification_channel_name),
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
);
|
||||
notifyChannel.setDescription(getString(R.string.notification_channel_description));
|
||||
// Configure the notification channel without sound set
|
||||
notifyChannel.setSound(null, null);
|
||||
notifyChannel.enableLights(false);
|
||||
notifyChannel.enableVibration(false);
|
||||
|
||||
// It is fine to always create the notification channel because creating a channel
|
||||
// with the same ID is the same as overriding it (only its name and description).
|
||||
notifyManager.createNotificationChannel(notifyChannel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
// Prevent abrupt interruption when copy game files from assets
|
||||
|
|
|
@ -22,15 +22,17 @@ package net.minetest.minetest;
|
|||
|
||||
import android.app.IntentService;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
import android.os.Environment;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.annotation.StringRes;
|
||||
|
||||
import java.io.File;
|
||||
|
@ -57,11 +59,9 @@ public class UnzipService extends IntentService {
|
|||
private String failureMessage;
|
||||
|
||||
private static boolean isRunning = false;
|
||||
|
||||
public static synchronized boolean getIsRunning() {
|
||||
return isRunning;
|
||||
}
|
||||
|
||||
private static synchronized void setIsRunning(boolean v) {
|
||||
isRunning = v;
|
||||
}
|
||||
|
@ -73,10 +73,13 @@ public class UnzipService extends IntentService {
|
|||
@Override
|
||||
protected void onHandleIntent(Intent intent) {
|
||||
Notification.Builder notificationBuilder = createNotification();
|
||||
final File zipFile = new File(getCacheDir(), "assets.zip");
|
||||
final File zipFile = new File(getCacheDir(), "Minetest.zip");
|
||||
try {
|
||||
setIsRunning(true);
|
||||
File userDataDirectory = Utils.getUserDataDirectory(this);
|
||||
if (userDataDirectory == null) {
|
||||
throw new IOException("Unable to find user data directory");
|
||||
}
|
||||
|
||||
try (InputStream in = this.getAssets().open(zipFile.getName())) {
|
||||
try (OutputStream out = new FileOutputStream(zipFile)) {
|
||||
|
@ -88,25 +91,39 @@ public class UnzipService extends IntentService {
|
|||
}
|
||||
}
|
||||
|
||||
migrate(notificationBuilder, userDataDirectory);
|
||||
unzip(notificationBuilder, zipFile, userDataDirectory);
|
||||
} catch (IOException e) {
|
||||
isSuccess = false;
|
||||
failureMessage = e.getLocalizedMessage();
|
||||
} finally {
|
||||
setIsRunning(false);
|
||||
if (!zipFile.delete()) {
|
||||
Log.w("UnzipService", "Minetest installation ZIP cannot be deleted");
|
||||
}
|
||||
zipFile.delete();
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private Notification.Builder createNotification() {
|
||||
String name = "net.minetest.minetest";
|
||||
String channelId = "Minetest channel";
|
||||
String description = "notifications from Minetest";
|
||||
Notification.Builder builder;
|
||||
if (mNotifyManager == null)
|
||||
mNotifyManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
builder = new Notification.Builder(this, MainActivity.NOTIFICATION_CHANNEL_ID);
|
||||
int importance = NotificationManager.IMPORTANCE_LOW;
|
||||
NotificationChannel mChannel = null;
|
||||
if (mNotifyManager != null)
|
||||
mChannel = mNotifyManager.getNotificationChannel(channelId);
|
||||
if (mChannel == null) {
|
||||
mChannel = new NotificationChannel(channelId, name, importance);
|
||||
mChannel.setDescription(description);
|
||||
// Configure the notification channel, NO SOUND
|
||||
mChannel.setSound(null, null);
|
||||
mChannel.enableLights(false);
|
||||
mChannel.enableVibration(false);
|
||||
mNotifyManager.createNotificationChannel(mChannel);
|
||||
}
|
||||
builder = new Notification.Builder(this, channelId);
|
||||
} else {
|
||||
builder = new Notification.Builder(this);
|
||||
}
|
||||
|
@ -114,16 +131,12 @@ public class UnzipService extends IntentService {
|
|||
Intent notificationIntent = new Intent(this, MainActivity.class);
|
||||
notificationIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP
|
||||
| Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||
int pendingIntentFlag = 0;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
pendingIntentFlag = PendingIntent.FLAG_MUTABLE;
|
||||
}
|
||||
PendingIntent intent = PendingIntent.getActivity(this, 0,
|
||||
notificationIntent, pendingIntentFlag);
|
||||
notificationIntent, 0);
|
||||
|
||||
builder.setContentTitle(getString(R.string.unzip_notification_title))
|
||||
builder.setContentTitle(getString(R.string.notification_title))
|
||||
.setSmallIcon(R.mipmap.ic_launcher)
|
||||
.setContentText(getString(R.string.unzip_notification_description))
|
||||
.setContentText(getString(R.string.notification_description))
|
||||
.setContentIntent(intent)
|
||||
.setOngoing(true)
|
||||
.setProgress(0, 0, true);
|
||||
|
@ -166,9 +179,9 @@ public class UnzipService extends IntentService {
|
|||
try {
|
||||
Process p = new ProcessBuilder("/system/bin/mv",
|
||||
src.getAbsolutePath(), dst.getAbsolutePath()).start();
|
||||
int exitCode = p.waitFor();
|
||||
if (exitCode != 0)
|
||||
throw new IOException("Move failed with exit code " + exitCode);
|
||||
int exitcode = p.waitFor();
|
||||
if (exitcode != 0)
|
||||
throw new IOException("Move failed with exit code " + exitcode);
|
||||
} catch (InterruptedException e) {
|
||||
throw new IOException("Move operation interrupted");
|
||||
}
|
||||
|
@ -184,9 +197,42 @@ public class UnzipService extends IntentService {
|
|||
}
|
||||
}
|
||||
|
||||
private void publishProgress(@Nullable Notification.Builder notificationBuilder, @StringRes int message, int progress) {
|
||||
/**
|
||||
* Migrates user data from deprecated external storage to app scoped storage
|
||||
*/
|
||||
private void migrate(Notification.Builder notificationBuilder, File newLocation) throws IOException {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
return;
|
||||
}
|
||||
|
||||
File oldLocation = new File(Environment.getExternalStorageDirectory(), "Minetest");
|
||||
if (!oldLocation.isDirectory())
|
||||
return;
|
||||
|
||||
publishProgress(notificationBuilder, R.string.migrating, 0);
|
||||
newLocation.mkdir();
|
||||
|
||||
String[] dirs = new String[] { "worlds", "games", "mods", "textures", "client" };
|
||||
for (int i = 0; i < dirs.length; i++) {
|
||||
publishProgress(notificationBuilder, R.string.migrating, 100 * i / dirs.length);
|
||||
File dir = new File(oldLocation, dirs[i]), dir2 = new File(newLocation, dirs[i]);
|
||||
if (dir.isDirectory() && !dir2.isDirectory()) {
|
||||
moveFileOrDir(dir, dir2);
|
||||
}
|
||||
}
|
||||
|
||||
for (String filename : new String[] { "minetest.conf" }) {
|
||||
File file = new File(oldLocation, filename), file2 = new File(newLocation, filename);
|
||||
if (file.isFile() && !file2.isFile()) {
|
||||
moveFileOrDir(file, file2);
|
||||
}
|
||||
}
|
||||
|
||||
recursivelyDeleteDirectory(oldLocation);
|
||||
}
|
||||
|
||||
private void publishProgress(@Nullable Notification.Builder notificationBuilder, @StringRes int message, int progress) {
|
||||
Intent intentUpdate = new Intent(ACTION_UPDATE);
|
||||
intentUpdate.setPackage(getPackageName());
|
||||
intentUpdate.putExtra(ACTION_PROGRESS, progress);
|
||||
intentUpdate.putExtra(ACTION_PROGRESS_MESSAGE, message);
|
||||
if (!isSuccess)
|
||||
|
|
|
@ -1,43 +1,37 @@
|
|||
package net.minetest.minetest;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import java.io.File;
|
||||
import java.util.Objects;
|
||||
|
||||
public class Utils {
|
||||
@NonNull
|
||||
public static File createDirs(@NonNull File root, @NonNull String dir) {
|
||||
public static @NonNull File createDirs(File root, String dir) {
|
||||
File f = new File(root, dir);
|
||||
if (!f.isDirectory())
|
||||
if (!f.mkdirs())
|
||||
Log.e("Utils", "Directory " + dir + " cannot be created");
|
||||
f.mkdirs();
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static File getUserDataDirectory(@NonNull Context context) {
|
||||
File extDir = Objects.requireNonNull(
|
||||
context.getExternalFilesDir(null),
|
||||
"Cannot get external file directory"
|
||||
);
|
||||
public static @Nullable File getUserDataDirectory(Context context) {
|
||||
File extDir = context.getExternalFilesDir(null);
|
||||
if (extDir == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return createDirs(extDir, "Minetest");
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static File getCacheDirectory(@NonNull Context context) {
|
||||
return Objects.requireNonNull(
|
||||
context.getCacheDir(),
|
||||
"Cannot get cache directory"
|
||||
);
|
||||
public static @Nullable File getCacheDirectory(Context context) {
|
||||
return context.getCacheDir();
|
||||
}
|
||||
|
||||
public static boolean isInstallValid(@NonNull Context context) {
|
||||
public static boolean isInstallValid(Context context) {
|
||||
File userDataDirectory = getUserDataDirectory(context);
|
||||
return userDataDirectory.isDirectory() &&
|
||||
return userDataDirectory != null && userDataDirectory.isDirectory() &&
|
||||
new File(userDataDirectory, "games").isDirectory() &&
|
||||
new File(userDataDirectory, "builtin").isDirectory() &&
|
||||
new File(userDataDirectory, "client").isDirectory() &&
|
||||
new File(userDataDirectory, "textures").isDirectory();
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
package org.libsdl.app;
|
||||
|
||||
import android.hardware.usb.UsbDevice;
|
||||
|
||||
interface HIDDevice
|
||||
{
|
||||
public int getId();
|
||||
public int getVendorId();
|
||||
public int getProductId();
|
||||
public String getSerialNumber();
|
||||
public int getVersion();
|
||||
public String getManufacturerName();
|
||||
public String getProductName();
|
||||
public UsbDevice getDevice();
|
||||
public boolean open();
|
||||
public int sendFeatureReport(byte[] report);
|
||||
public int sendOutputReport(byte[] report);
|
||||
public boolean getFeatureReport(byte[] report);
|
||||
public void setFrozen(boolean frozen);
|
||||
public void close();
|
||||
public void shutdown();
|
||||
}
|
|
@ -1,650 +0,0 @@
|
|||
package org.libsdl.app;
|
||||
|
||||
import android.content.Context;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothGatt;
|
||||
import android.bluetooth.BluetoothGattCallback;
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
import android.bluetooth.BluetoothGattDescriptor;
|
||||
import android.bluetooth.BluetoothManager;
|
||||
import android.bluetooth.BluetoothProfile;
|
||||
import android.bluetooth.BluetoothGattService;
|
||||
import android.hardware.usb.UsbDevice;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
import android.os.*;
|
||||
|
||||
//import com.android.internal.util.HexDump;
|
||||
|
||||
import java.lang.Runnable;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
import java.util.UUID;
|
||||
|
||||
class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDevice {
|
||||
|
||||
private static final String TAG = "hidapi";
|
||||
private HIDDeviceManager mManager;
|
||||
private BluetoothDevice mDevice;
|
||||
private int mDeviceId;
|
||||
private BluetoothGatt mGatt;
|
||||
private boolean mIsRegistered = false;
|
||||
private boolean mIsConnected = false;
|
||||
private boolean mIsChromebook = false;
|
||||
private boolean mIsReconnecting = false;
|
||||
private boolean mFrozen = false;
|
||||
private LinkedList<GattOperation> mOperations;
|
||||
GattOperation mCurrentOperation = null;
|
||||
private Handler mHandler;
|
||||
|
||||
private static final int TRANSPORT_AUTO = 0;
|
||||
private static final int TRANSPORT_BREDR = 1;
|
||||
private static final int TRANSPORT_LE = 2;
|
||||
|
||||
private static final int CHROMEBOOK_CONNECTION_CHECK_INTERVAL = 10000;
|
||||
|
||||
static public final UUID steamControllerService = UUID.fromString("100F6C32-1735-4313-B402-38567131E5F3");
|
||||
static public final UUID inputCharacteristic = UUID.fromString("100F6C33-1735-4313-B402-38567131E5F3");
|
||||
static public final UUID reportCharacteristic = UUID.fromString("100F6C34-1735-4313-B402-38567131E5F3");
|
||||
static private final byte[] enterValveMode = new byte[] { (byte)0xC0, (byte)0x87, 0x03, 0x08, 0x07, 0x00 };
|
||||
|
||||
static class GattOperation {
|
||||
private enum Operation {
|
||||
CHR_READ,
|
||||
CHR_WRITE,
|
||||
ENABLE_NOTIFICATION
|
||||
}
|
||||
|
||||
Operation mOp;
|
||||
UUID mUuid;
|
||||
byte[] mValue;
|
||||
BluetoothGatt mGatt;
|
||||
boolean mResult = true;
|
||||
|
||||
private GattOperation(BluetoothGatt gatt, GattOperation.Operation operation, UUID uuid) {
|
||||
mGatt = gatt;
|
||||
mOp = operation;
|
||||
mUuid = uuid;
|
||||
}
|
||||
|
||||
private GattOperation(BluetoothGatt gatt, GattOperation.Operation operation, UUID uuid, byte[] value) {
|
||||
mGatt = gatt;
|
||||
mOp = operation;
|
||||
mUuid = uuid;
|
||||
mValue = value;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
// This is executed in main thread
|
||||
BluetoothGattCharacteristic chr;
|
||||
|
||||
switch (mOp) {
|
||||
case CHR_READ:
|
||||
chr = getCharacteristic(mUuid);
|
||||
//Log.v(TAG, "Reading characteristic " + chr.getUuid());
|
||||
if (!mGatt.readCharacteristic(chr)) {
|
||||
Log.e(TAG, "Unable to read characteristic " + mUuid.toString());
|
||||
mResult = false;
|
||||
break;
|
||||
}
|
||||
mResult = true;
|
||||
break;
|
||||
case CHR_WRITE:
|
||||
chr = getCharacteristic(mUuid);
|
||||
//Log.v(TAG, "Writing characteristic " + chr.getUuid() + " value=" + HexDump.toHexString(value));
|
||||
chr.setValue(mValue);
|
||||
if (!mGatt.writeCharacteristic(chr)) {
|
||||
Log.e(TAG, "Unable to write characteristic " + mUuid.toString());
|
||||
mResult = false;
|
||||
break;
|
||||
}
|
||||
mResult = true;
|
||||
break;
|
||||
case ENABLE_NOTIFICATION:
|
||||
chr = getCharacteristic(mUuid);
|
||||
//Log.v(TAG, "Writing descriptor of " + chr.getUuid());
|
||||
if (chr != null) {
|
||||
BluetoothGattDescriptor cccd = chr.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"));
|
||||
if (cccd != null) {
|
||||
int properties = chr.getProperties();
|
||||
byte[] value;
|
||||
if ((properties & BluetoothGattCharacteristic.PROPERTY_NOTIFY) == BluetoothGattCharacteristic.PROPERTY_NOTIFY) {
|
||||
value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE;
|
||||
} else if ((properties & BluetoothGattCharacteristic.PROPERTY_INDICATE) == BluetoothGattCharacteristic.PROPERTY_INDICATE) {
|
||||
value = BluetoothGattDescriptor.ENABLE_INDICATION_VALUE;
|
||||
} else {
|
||||
Log.e(TAG, "Unable to start notifications on input characteristic");
|
||||
mResult = false;
|
||||
return;
|
||||
}
|
||||
|
||||
mGatt.setCharacteristicNotification(chr, true);
|
||||
cccd.setValue(value);
|
||||
if (!mGatt.writeDescriptor(cccd)) {
|
||||
Log.e(TAG, "Unable to write descriptor " + mUuid.toString());
|
||||
mResult = false;
|
||||
return;
|
||||
}
|
||||
mResult = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean finish() {
|
||||
return mResult;
|
||||
}
|
||||
|
||||
private BluetoothGattCharacteristic getCharacteristic(UUID uuid) {
|
||||
BluetoothGattService valveService = mGatt.getService(steamControllerService);
|
||||
if (valveService == null)
|
||||
return null;
|
||||
return valveService.getCharacteristic(uuid);
|
||||
}
|
||||
|
||||
static public GattOperation readCharacteristic(BluetoothGatt gatt, UUID uuid) {
|
||||
return new GattOperation(gatt, Operation.CHR_READ, uuid);
|
||||
}
|
||||
|
||||
static public GattOperation writeCharacteristic(BluetoothGatt gatt, UUID uuid, byte[] value) {
|
||||
return new GattOperation(gatt, Operation.CHR_WRITE, uuid, value);
|
||||
}
|
||||
|
||||
static public GattOperation enableNotification(BluetoothGatt gatt, UUID uuid) {
|
||||
return new GattOperation(gatt, Operation.ENABLE_NOTIFICATION, uuid);
|
||||
}
|
||||
}
|
||||
|
||||
public HIDDeviceBLESteamController(HIDDeviceManager manager, BluetoothDevice device) {
|
||||
mManager = manager;
|
||||
mDevice = device;
|
||||
mDeviceId = mManager.getDeviceIDForIdentifier(getIdentifier());
|
||||
mIsRegistered = false;
|
||||
mIsChromebook = mManager.getContext().getPackageManager().hasSystemFeature("org.chromium.arc.device_management");
|
||||
mOperations = new LinkedList<GattOperation>();
|
||||
mHandler = new Handler(Looper.getMainLooper());
|
||||
|
||||
mGatt = connectGatt();
|
||||
// final HIDDeviceBLESteamController finalThis = this;
|
||||
// mHandler.postDelayed(new Runnable() {
|
||||
// @Override
|
||||
// public void run() {
|
||||
// finalThis.checkConnectionForChromebookIssue();
|
||||
// }
|
||||
// }, CHROMEBOOK_CONNECTION_CHECK_INTERVAL);
|
||||
}
|
||||
|
||||
public String getIdentifier() {
|
||||
return String.format("SteamController.%s", mDevice.getAddress());
|
||||
}
|
||||
|
||||
public BluetoothGatt getGatt() {
|
||||
return mGatt;
|
||||
}
|
||||
|
||||
// Because on Chromebooks we show up as a dual-mode device, it will attempt to connect TRANSPORT_AUTO, which will use TRANSPORT_BREDR instead
|
||||
// of TRANSPORT_LE. Let's force ourselves to connect low energy.
|
||||
private BluetoothGatt connectGatt(boolean managed) {
|
||||
if (Build.VERSION.SDK_INT >= 23 /* Android 6.0 (M) */) {
|
||||
try {
|
||||
return mDevice.connectGatt(mManager.getContext(), managed, this, TRANSPORT_LE);
|
||||
} catch (Exception e) {
|
||||
return mDevice.connectGatt(mManager.getContext(), managed, this);
|
||||
}
|
||||
} else {
|
||||
return mDevice.connectGatt(mManager.getContext(), managed, this);
|
||||
}
|
||||
}
|
||||
|
||||
private BluetoothGatt connectGatt() {
|
||||
return connectGatt(false);
|
||||
}
|
||||
|
||||
protected int getConnectionState() {
|
||||
|
||||
Context context = mManager.getContext();
|
||||
if (context == null) {
|
||||
// We are lacking any context to get our Bluetooth information. We'll just assume disconnected.
|
||||
return BluetoothProfile.STATE_DISCONNECTED;
|
||||
}
|
||||
|
||||
BluetoothManager btManager = (BluetoothManager)context.getSystemService(Context.BLUETOOTH_SERVICE);
|
||||
if (btManager == null) {
|
||||
// This device doesn't support Bluetooth. We should never be here, because how did
|
||||
// we instantiate a device to start with?
|
||||
return BluetoothProfile.STATE_DISCONNECTED;
|
||||
}
|
||||
|
||||
return btManager.getConnectionState(mDevice, BluetoothProfile.GATT);
|
||||
}
|
||||
|
||||
public void reconnect() {
|
||||
|
||||
if (getConnectionState() != BluetoothProfile.STATE_CONNECTED) {
|
||||
mGatt.disconnect();
|
||||
mGatt = connectGatt();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected void checkConnectionForChromebookIssue() {
|
||||
if (!mIsChromebook) {
|
||||
// We only do this on Chromebooks, because otherwise it's really annoying to just attempt
|
||||
// over and over.
|
||||
return;
|
||||
}
|
||||
|
||||
int connectionState = getConnectionState();
|
||||
|
||||
switch (connectionState) {
|
||||
case BluetoothProfile.STATE_CONNECTED:
|
||||
if (!mIsConnected) {
|
||||
// We are in the Bad Chromebook Place. We can force a disconnect
|
||||
// to try to recover.
|
||||
Log.v(TAG, "Chromebook: We are in a very bad state; the controller shows as connected in the underlying Bluetooth layer, but we never received a callback. Forcing a reconnect.");
|
||||
mIsReconnecting = true;
|
||||
mGatt.disconnect();
|
||||
mGatt = connectGatt(false);
|
||||
break;
|
||||
}
|
||||
else if (!isRegistered()) {
|
||||
if (mGatt.getServices().size() > 0) {
|
||||
Log.v(TAG, "Chromebook: We are connected to a controller, but never got our registration. Trying to recover.");
|
||||
probeService(this);
|
||||
}
|
||||
else {
|
||||
Log.v(TAG, "Chromebook: We are connected to a controller, but never discovered services. Trying to recover.");
|
||||
mIsReconnecting = true;
|
||||
mGatt.disconnect();
|
||||
mGatt = connectGatt(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
Log.v(TAG, "Chromebook: We are connected, and registered. Everything's good!");
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case BluetoothProfile.STATE_DISCONNECTED:
|
||||
Log.v(TAG, "Chromebook: We have either been disconnected, or the Chromebook BtGatt.ContextMap bug has bitten us. Attempting a disconnect/reconnect, but we may not be able to recover.");
|
||||
|
||||
mIsReconnecting = true;
|
||||
mGatt.disconnect();
|
||||
mGatt = connectGatt(false);
|
||||
break;
|
||||
|
||||
case BluetoothProfile.STATE_CONNECTING:
|
||||
Log.v(TAG, "Chromebook: We're still trying to connect. Waiting a bit longer.");
|
||||
break;
|
||||
}
|
||||
|
||||
final HIDDeviceBLESteamController finalThis = this;
|
||||
mHandler.postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
finalThis.checkConnectionForChromebookIssue();
|
||||
}
|
||||
}, CHROMEBOOK_CONNECTION_CHECK_INTERVAL);
|
||||
}
|
||||
|
||||
private boolean isRegistered() {
|
||||
return mIsRegistered;
|
||||
}
|
||||
|
||||
private void setRegistered() {
|
||||
mIsRegistered = true;
|
||||
}
|
||||
|
||||
private boolean probeService(HIDDeviceBLESteamController controller) {
|
||||
|
||||
if (isRegistered()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!mIsConnected) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Log.v(TAG, "probeService controller=" + controller);
|
||||
|
||||
for (BluetoothGattService service : mGatt.getServices()) {
|
||||
if (service.getUuid().equals(steamControllerService)) {
|
||||
Log.v(TAG, "Found Valve steam controller service " + service.getUuid());
|
||||
|
||||
for (BluetoothGattCharacteristic chr : service.getCharacteristics()) {
|
||||
if (chr.getUuid().equals(inputCharacteristic)) {
|
||||
Log.v(TAG, "Found input characteristic");
|
||||
// Start notifications
|
||||
BluetoothGattDescriptor cccd = chr.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"));
|
||||
if (cccd != null) {
|
||||
enableNotification(chr.getUuid());
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if ((mGatt.getServices().size() == 0) && mIsChromebook && !mIsReconnecting) {
|
||||
Log.e(TAG, "Chromebook: Discovered services were empty; this almost certainly means the BtGatt.ContextMap bug has bitten us.");
|
||||
mIsConnected = false;
|
||||
mIsReconnecting = true;
|
||||
mGatt.disconnect();
|
||||
mGatt = connectGatt(false);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private void finishCurrentGattOperation() {
|
||||
GattOperation op = null;
|
||||
synchronized (mOperations) {
|
||||
if (mCurrentOperation != null) {
|
||||
op = mCurrentOperation;
|
||||
mCurrentOperation = null;
|
||||
}
|
||||
}
|
||||
if (op != null) {
|
||||
boolean result = op.finish(); // TODO: Maybe in main thread as well?
|
||||
|
||||
// Our operation failed, let's add it back to the beginning of our queue.
|
||||
if (!result) {
|
||||
mOperations.addFirst(op);
|
||||
}
|
||||
}
|
||||
executeNextGattOperation();
|
||||
}
|
||||
|
||||
private void executeNextGattOperation() {
|
||||
synchronized (mOperations) {
|
||||
if (mCurrentOperation != null)
|
||||
return;
|
||||
|
||||
if (mOperations.isEmpty())
|
||||
return;
|
||||
|
||||
mCurrentOperation = mOperations.removeFirst();
|
||||
}
|
||||
|
||||
// Run in main thread
|
||||
mHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
synchronized (mOperations) {
|
||||
if (mCurrentOperation == null) {
|
||||
Log.e(TAG, "Current operation null in executor?");
|
||||
return;
|
||||
}
|
||||
|
||||
mCurrentOperation.run();
|
||||
// now wait for the GATT callback and when it comes, finish this operation
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void queueGattOperation(GattOperation op) {
|
||||
synchronized (mOperations) {
|
||||
mOperations.add(op);
|
||||
}
|
||||
executeNextGattOperation();
|
||||
}
|
||||
|
||||
private void enableNotification(UUID chrUuid) {
|
||||
GattOperation op = HIDDeviceBLESteamController.GattOperation.enableNotification(mGatt, chrUuid);
|
||||
queueGattOperation(op);
|
||||
}
|
||||
|
||||
public void writeCharacteristic(UUID uuid, byte[] value) {
|
||||
GattOperation op = HIDDeviceBLESteamController.GattOperation.writeCharacteristic(mGatt, uuid, value);
|
||||
queueGattOperation(op);
|
||||
}
|
||||
|
||||
public void readCharacteristic(UUID uuid) {
|
||||
GattOperation op = HIDDeviceBLESteamController.GattOperation.readCharacteristic(mGatt, uuid);
|
||||
queueGattOperation(op);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
////////////// BluetoothGattCallback overridden methods
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public void onConnectionStateChange(BluetoothGatt g, int status, int newState) {
|
||||
//Log.v(TAG, "onConnectionStateChange status=" + status + " newState=" + newState);
|
||||
mIsReconnecting = false;
|
||||
if (newState == 2) {
|
||||
mIsConnected = true;
|
||||
// Run directly, without GattOperation
|
||||
if (!isRegistered()) {
|
||||
mHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mGatt.discoverServices();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
else if (newState == 0) {
|
||||
mIsConnected = false;
|
||||
}
|
||||
|
||||
// Disconnection is handled in SteamLink using the ACTION_ACL_DISCONNECTED Intent.
|
||||
}
|
||||
|
||||
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
|
||||
//Log.v(TAG, "onServicesDiscovered status=" + status);
|
||||
if (status == 0) {
|
||||
if (gatt.getServices().size() == 0) {
|
||||
Log.v(TAG, "onServicesDiscovered returned zero services; something has gone horribly wrong down in Android's Bluetooth stack.");
|
||||
mIsReconnecting = true;
|
||||
mIsConnected = false;
|
||||
gatt.disconnect();
|
||||
mGatt = connectGatt(false);
|
||||
}
|
||||
else {
|
||||
probeService(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
|
||||
//Log.v(TAG, "onCharacteristicRead status=" + status + " uuid=" + characteristic.getUuid());
|
||||
|
||||
if (characteristic.getUuid().equals(reportCharacteristic) && !mFrozen) {
|
||||
mManager.HIDDeviceFeatureReport(getId(), characteristic.getValue());
|
||||
}
|
||||
|
||||
finishCurrentGattOperation();
|
||||
}
|
||||
|
||||
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
|
||||
//Log.v(TAG, "onCharacteristicWrite status=" + status + " uuid=" + characteristic.getUuid());
|
||||
|
||||
if (characteristic.getUuid().equals(reportCharacteristic)) {
|
||||
// Only register controller with the native side once it has been fully configured
|
||||
if (!isRegistered()) {
|
||||
Log.v(TAG, "Registering Steam Controller with ID: " + getId());
|
||||
mManager.HIDDeviceConnected(getId(), getIdentifier(), getVendorId(), getProductId(), getSerialNumber(), getVersion(), getManufacturerName(), getProductName(), 0, 0, 0, 0);
|
||||
setRegistered();
|
||||
}
|
||||
}
|
||||
|
||||
finishCurrentGattOperation();
|
||||
}
|
||||
|
||||
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
|
||||
// Enable this for verbose logging of controller input reports
|
||||
//Log.v(TAG, "onCharacteristicChanged uuid=" + characteristic.getUuid() + " data=" + HexDump.dumpHexString(characteristic.getValue()));
|
||||
|
||||
if (characteristic.getUuid().equals(inputCharacteristic) && !mFrozen) {
|
||||
mManager.HIDDeviceInputReport(getId(), characteristic.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
|
||||
//Log.v(TAG, "onDescriptorRead status=" + status);
|
||||
}
|
||||
|
||||
public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
|
||||
BluetoothGattCharacteristic chr = descriptor.getCharacteristic();
|
||||
//Log.v(TAG, "onDescriptorWrite status=" + status + " uuid=" + chr.getUuid() + " descriptor=" + descriptor.getUuid());
|
||||
|
||||
if (chr.getUuid().equals(inputCharacteristic)) {
|
||||
boolean hasWrittenInputDescriptor = true;
|
||||
BluetoothGattCharacteristic reportChr = chr.getService().getCharacteristic(reportCharacteristic);
|
||||
if (reportChr != null) {
|
||||
Log.v(TAG, "Writing report characteristic to enter valve mode");
|
||||
reportChr.setValue(enterValveMode);
|
||||
gatt.writeCharacteristic(reportChr);
|
||||
}
|
||||
}
|
||||
|
||||
finishCurrentGattOperation();
|
||||
}
|
||||
|
||||
public void onReliableWriteCompleted(BluetoothGatt gatt, int status) {
|
||||
//Log.v(TAG, "onReliableWriteCompleted status=" + status);
|
||||
}
|
||||
|
||||
public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
|
||||
//Log.v(TAG, "onReadRemoteRssi status=" + status);
|
||||
}
|
||||
|
||||
public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
|
||||
//Log.v(TAG, "onMtuChanged status=" + status);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//////// Public API
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public int getId() {
|
||||
return mDeviceId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVendorId() {
|
||||
// Valve Corporation
|
||||
final int VALVE_USB_VID = 0x28DE;
|
||||
return VALVE_USB_VID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getProductId() {
|
||||
// We don't have an easy way to query from the Bluetooth device, but we know what it is
|
||||
final int D0G_BLE2_PID = 0x1106;
|
||||
return D0G_BLE2_PID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSerialNumber() {
|
||||
// This will be read later via feature report by Steam
|
||||
return "12345";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVersion() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getManufacturerName() {
|
||||
return "Valve Corporation";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProductName() {
|
||||
return "Steam Controller";
|
||||
}
|
||||
|
||||
@Override
|
||||
public UsbDevice getDevice() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean open() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int sendFeatureReport(byte[] report) {
|
||||
if (!isRegistered()) {
|
||||
Log.e(TAG, "Attempted sendFeatureReport before Steam Controller is registered!");
|
||||
if (mIsConnected) {
|
||||
probeService(this);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// We need to skip the first byte, as that doesn't go over the air
|
||||
byte[] actual_report = Arrays.copyOfRange(report, 1, report.length - 1);
|
||||
//Log.v(TAG, "sendFeatureReport " + HexDump.dumpHexString(actual_report));
|
||||
writeCharacteristic(reportCharacteristic, actual_report);
|
||||
return report.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int sendOutputReport(byte[] report) {
|
||||
if (!isRegistered()) {
|
||||
Log.e(TAG, "Attempted sendOutputReport before Steam Controller is registered!");
|
||||
if (mIsConnected) {
|
||||
probeService(this);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
//Log.v(TAG, "sendFeatureReport " + HexDump.dumpHexString(report));
|
||||
writeCharacteristic(reportCharacteristic, report);
|
||||
return report.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getFeatureReport(byte[] report) {
|
||||
if (!isRegistered()) {
|
||||
Log.e(TAG, "Attempted getFeatureReport before Steam Controller is registered!");
|
||||
if (mIsConnected) {
|
||||
probeService(this);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//Log.v(TAG, "getFeatureReport");
|
||||
readCharacteristic(reportCharacteristic);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFrozen(boolean frozen) {
|
||||
mFrozen = frozen;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
close();
|
||||
|
||||
BluetoothGatt g = mGatt;
|
||||
if (g != null) {
|
||||
g.disconnect();
|
||||
g.close();
|
||||
mGatt = null;
|
||||
}
|
||||
mManager = null;
|
||||
mIsRegistered = false;
|
||||
mIsConnected = false;
|
||||
mOperations.clear();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,698 +0,0 @@
|
|||
package org.libsdl.app;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.PendingIntent;
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothManager;
|
||||
import android.bluetooth.BluetoothProfile;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.hardware.usb.*;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
public class HIDDeviceManager {
|
||||
private static final String TAG = "hidapi";
|
||||
private static final String ACTION_USB_PERMISSION = "org.libsdl.app.USB_PERMISSION";
|
||||
|
||||
private static HIDDeviceManager sManager;
|
||||
private static int sManagerRefCount = 0;
|
||||
|
||||
public static HIDDeviceManager acquire(Context context) {
|
||||
if (sManagerRefCount == 0) {
|
||||
sManager = new HIDDeviceManager(context);
|
||||
}
|
||||
++sManagerRefCount;
|
||||
return sManager;
|
||||
}
|
||||
|
||||
public static void release(HIDDeviceManager manager) {
|
||||
if (manager == sManager) {
|
||||
--sManagerRefCount;
|
||||
if (sManagerRefCount == 0) {
|
||||
sManager.close();
|
||||
sManager = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Context mContext;
|
||||
private HashMap<Integer, HIDDevice> mDevicesById = new HashMap<Integer, HIDDevice>();
|
||||
private HashMap<BluetoothDevice, HIDDeviceBLESteamController> mBluetoothDevices = new HashMap<BluetoothDevice, HIDDeviceBLESteamController>();
|
||||
private int mNextDeviceId = 0;
|
||||
private SharedPreferences mSharedPreferences = null;
|
||||
private boolean mIsChromebook = false;
|
||||
private UsbManager mUsbManager;
|
||||
private Handler mHandler;
|
||||
private BluetoothManager mBluetoothManager;
|
||||
private List<BluetoothDevice> mLastBluetoothDevices;
|
||||
|
||||
private final BroadcastReceiver mUsbBroadcast = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String action = intent.getAction();
|
||||
if (action.equals(UsbManager.ACTION_USB_DEVICE_ATTACHED)) {
|
||||
UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
|
||||
handleUsbDeviceAttached(usbDevice);
|
||||
} else if (action.equals(UsbManager.ACTION_USB_DEVICE_DETACHED)) {
|
||||
UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
|
||||
handleUsbDeviceDetached(usbDevice);
|
||||
} else if (action.equals(HIDDeviceManager.ACTION_USB_PERMISSION)) {
|
||||
UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
|
||||
handleUsbDevicePermission(usbDevice, intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private final BroadcastReceiver mBluetoothBroadcast = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String action = intent.getAction();
|
||||
// Bluetooth device was connected. If it was a Steam Controller, handle it
|
||||
if (action.equals(BluetoothDevice.ACTION_ACL_CONNECTED)) {
|
||||
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
|
||||
Log.d(TAG, "Bluetooth device connected: " + device);
|
||||
|
||||
if (isSteamController(device)) {
|
||||
connectBluetoothDevice(device);
|
||||
}
|
||||
}
|
||||
|
||||
// Bluetooth device was disconnected, remove from controller manager (if any)
|
||||
if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) {
|
||||
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
|
||||
Log.d(TAG, "Bluetooth device disconnected: " + device);
|
||||
|
||||
disconnectBluetoothDevice(device);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private HIDDeviceManager(final Context context) {
|
||||
mContext = context;
|
||||
|
||||
HIDDeviceRegisterCallback();
|
||||
|
||||
mSharedPreferences = mContext.getSharedPreferences("hidapi", Context.MODE_PRIVATE);
|
||||
mIsChromebook = mContext.getPackageManager().hasSystemFeature("org.chromium.arc.device_management");
|
||||
|
||||
// if (shouldClear) {
|
||||
// SharedPreferences.Editor spedit = mSharedPreferences.edit();
|
||||
// spedit.clear();
|
||||
// spedit.commit();
|
||||
// }
|
||||
// else
|
||||
{
|
||||
mNextDeviceId = mSharedPreferences.getInt("next_device_id", 0);
|
||||
}
|
||||
}
|
||||
|
||||
public Context getContext() {
|
||||
return mContext;
|
||||
}
|
||||
|
||||
public int getDeviceIDForIdentifier(String identifier) {
|
||||
SharedPreferences.Editor spedit = mSharedPreferences.edit();
|
||||
|
||||
int result = mSharedPreferences.getInt(identifier, 0);
|
||||
if (result == 0) {
|
||||
result = mNextDeviceId++;
|
||||
spedit.putInt("next_device_id", mNextDeviceId);
|
||||
}
|
||||
|
||||
spedit.putInt(identifier, result);
|
||||
spedit.commit();
|
||||
return result;
|
||||
}
|
||||
|
||||
private void initializeUSB() {
|
||||
mUsbManager = (UsbManager)mContext.getSystemService(Context.USB_SERVICE);
|
||||
if (mUsbManager == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
// Logging
|
||||
for (UsbDevice device : mUsbManager.getDeviceList().values()) {
|
||||
Log.i(TAG,"Path: " + device.getDeviceName());
|
||||
Log.i(TAG,"Manufacturer: " + device.getManufacturerName());
|
||||
Log.i(TAG,"Product: " + device.getProductName());
|
||||
Log.i(TAG,"ID: " + device.getDeviceId());
|
||||
Log.i(TAG,"Class: " + device.getDeviceClass());
|
||||
Log.i(TAG,"Protocol: " + device.getDeviceProtocol());
|
||||
Log.i(TAG,"Vendor ID " + device.getVendorId());
|
||||
Log.i(TAG,"Product ID: " + device.getProductId());
|
||||
Log.i(TAG,"Interface count: " + device.getInterfaceCount());
|
||||
Log.i(TAG,"---------------------------------------");
|
||||
|
||||
// Get interface details
|
||||
for (int index = 0; index < device.getInterfaceCount(); index++) {
|
||||
UsbInterface mUsbInterface = device.getInterface(index);
|
||||
Log.i(TAG," ***** *****");
|
||||
Log.i(TAG," Interface index: " + index);
|
||||
Log.i(TAG," Interface ID: " + mUsbInterface.getId());
|
||||
Log.i(TAG," Interface class: " + mUsbInterface.getInterfaceClass());
|
||||
Log.i(TAG," Interface subclass: " + mUsbInterface.getInterfaceSubclass());
|
||||
Log.i(TAG," Interface protocol: " + mUsbInterface.getInterfaceProtocol());
|
||||
Log.i(TAG," Endpoint count: " + mUsbInterface.getEndpointCount());
|
||||
|
||||
// Get endpoint details
|
||||
for (int epi = 0; epi < mUsbInterface.getEndpointCount(); epi++)
|
||||
{
|
||||
UsbEndpoint mEndpoint = mUsbInterface.getEndpoint(epi);
|
||||
Log.i(TAG," ++++ ++++ ++++");
|
||||
Log.i(TAG," Endpoint index: " + epi);
|
||||
Log.i(TAG," Attributes: " + mEndpoint.getAttributes());
|
||||
Log.i(TAG," Direction: " + mEndpoint.getDirection());
|
||||
Log.i(TAG," Number: " + mEndpoint.getEndpointNumber());
|
||||
Log.i(TAG," Interval: " + mEndpoint.getInterval());
|
||||
Log.i(TAG," Packet size: " + mEndpoint.getMaxPacketSize());
|
||||
Log.i(TAG," Type: " + mEndpoint.getType());
|
||||
}
|
||||
}
|
||||
}
|
||||
Log.i(TAG," No more devices connected.");
|
||||
*/
|
||||
|
||||
// Register for USB broadcasts and permission completions
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
|
||||
filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
|
||||
filter.addAction(HIDDeviceManager.ACTION_USB_PERMISSION);
|
||||
mContext.registerReceiver(mUsbBroadcast, filter);
|
||||
|
||||
for (UsbDevice usbDevice : mUsbManager.getDeviceList().values()) {
|
||||
handleUsbDeviceAttached(usbDevice);
|
||||
}
|
||||
}
|
||||
|
||||
UsbManager getUSBManager() {
|
||||
return mUsbManager;
|
||||
}
|
||||
|
||||
private void shutdownUSB() {
|
||||
try {
|
||||
mContext.unregisterReceiver(mUsbBroadcast);
|
||||
} catch (Exception e) {
|
||||
// We may not have registered, that's okay
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isHIDDeviceInterface(UsbDevice usbDevice, UsbInterface usbInterface) {
|
||||
if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_HID) {
|
||||
return true;
|
||||
}
|
||||
if (isXbox360Controller(usbDevice, usbInterface) || isXboxOneController(usbDevice, usbInterface)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isXbox360Controller(UsbDevice usbDevice, UsbInterface usbInterface) {
|
||||
final int XB360_IFACE_SUBCLASS = 93;
|
||||
final int XB360_IFACE_PROTOCOL = 1; // Wired
|
||||
final int XB360W_IFACE_PROTOCOL = 129; // Wireless
|
||||
final int[] SUPPORTED_VENDORS = {
|
||||
0x0079, // GPD Win 2
|
||||
0x044f, // Thrustmaster
|
||||
0x045e, // Microsoft
|
||||
0x046d, // Logitech
|
||||
0x056e, // Elecom
|
||||
0x06a3, // Saitek
|
||||
0x0738, // Mad Catz
|
||||
0x07ff, // Mad Catz
|
||||
0x0e6f, // PDP
|
||||
0x0f0d, // Hori
|
||||
0x1038, // SteelSeries
|
||||
0x11c9, // Nacon
|
||||
0x12ab, // Unknown
|
||||
0x1430, // RedOctane
|
||||
0x146b, // BigBen
|
||||
0x1532, // Razer Sabertooth
|
||||
0x15e4, // Numark
|
||||
0x162e, // Joytech
|
||||
0x1689, // Razer Onza
|
||||
0x1949, // Lab126, Inc.
|
||||
0x1bad, // Harmonix
|
||||
0x20d6, // PowerA
|
||||
0x24c6, // PowerA
|
||||
0x2c22, // Qanba
|
||||
0x2dc8, // 8BitDo
|
||||
0x9886, // ASTRO Gaming
|
||||
};
|
||||
|
||||
if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_VENDOR_SPEC &&
|
||||
usbInterface.getInterfaceSubclass() == XB360_IFACE_SUBCLASS &&
|
||||
(usbInterface.getInterfaceProtocol() == XB360_IFACE_PROTOCOL ||
|
||||
usbInterface.getInterfaceProtocol() == XB360W_IFACE_PROTOCOL)) {
|
||||
int vendor_id = usbDevice.getVendorId();
|
||||
for (int supportedVid : SUPPORTED_VENDORS) {
|
||||
if (vendor_id == supportedVid) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isXboxOneController(UsbDevice usbDevice, UsbInterface usbInterface) {
|
||||
final int XB1_IFACE_SUBCLASS = 71;
|
||||
final int XB1_IFACE_PROTOCOL = 208;
|
||||
final int[] SUPPORTED_VENDORS = {
|
||||
0x03f0, // HP
|
||||
0x044f, // Thrustmaster
|
||||
0x045e, // Microsoft
|
||||
0x0738, // Mad Catz
|
||||
0x0b05, // ASUS
|
||||
0x0e6f, // PDP
|
||||
0x0f0d, // Hori
|
||||
0x10f5, // Turtle Beach
|
||||
0x1532, // Razer Wildcat
|
||||
0x20d6, // PowerA
|
||||
0x24c6, // PowerA
|
||||
0x2dc8, // 8BitDo
|
||||
0x2e24, // Hyperkin
|
||||
0x3537, // GameSir
|
||||
};
|
||||
|
||||
if (usbInterface.getId() == 0 &&
|
||||
usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_VENDOR_SPEC &&
|
||||
usbInterface.getInterfaceSubclass() == XB1_IFACE_SUBCLASS &&
|
||||
usbInterface.getInterfaceProtocol() == XB1_IFACE_PROTOCOL) {
|
||||
int vendor_id = usbDevice.getVendorId();
|
||||
for (int supportedVid : SUPPORTED_VENDORS) {
|
||||
if (vendor_id == supportedVid) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void handleUsbDeviceAttached(UsbDevice usbDevice) {
|
||||
connectHIDDeviceUSB(usbDevice);
|
||||
}
|
||||
|
||||
private void handleUsbDeviceDetached(UsbDevice usbDevice) {
|
||||
List<Integer> devices = new ArrayList<Integer>();
|
||||
for (HIDDevice device : mDevicesById.values()) {
|
||||
if (usbDevice.equals(device.getDevice())) {
|
||||
devices.add(device.getId());
|
||||
}
|
||||
}
|
||||
for (int id : devices) {
|
||||
HIDDevice device = mDevicesById.get(id);
|
||||
mDevicesById.remove(id);
|
||||
device.shutdown();
|
||||
HIDDeviceDisconnected(id);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleUsbDevicePermission(UsbDevice usbDevice, boolean permission_granted) {
|
||||
for (HIDDevice device : mDevicesById.values()) {
|
||||
if (usbDevice.equals(device.getDevice())) {
|
||||
boolean opened = false;
|
||||
if (permission_granted) {
|
||||
opened = device.open();
|
||||
}
|
||||
HIDDeviceOpenResult(device.getId(), opened);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void connectHIDDeviceUSB(UsbDevice usbDevice) {
|
||||
synchronized (this) {
|
||||
int interface_mask = 0;
|
||||
for (int interface_index = 0; interface_index < usbDevice.getInterfaceCount(); interface_index++) {
|
||||
UsbInterface usbInterface = usbDevice.getInterface(interface_index);
|
||||
if (isHIDDeviceInterface(usbDevice, usbInterface)) {
|
||||
// Check to see if we've already added this interface
|
||||
// This happens with the Xbox Series X controller which has a duplicate interface 0, which is inactive
|
||||
int interface_id = usbInterface.getId();
|
||||
if ((interface_mask & (1 << interface_id)) != 0) {
|
||||
continue;
|
||||
}
|
||||
interface_mask |= (1 << interface_id);
|
||||
|
||||
HIDDeviceUSB device = new HIDDeviceUSB(this, usbDevice, interface_index);
|
||||
int id = device.getId();
|
||||
mDevicesById.put(id, device);
|
||||
HIDDeviceConnected(id, device.getIdentifier(), device.getVendorId(), device.getProductId(), device.getSerialNumber(), device.getVersion(), device.getManufacturerName(), device.getProductName(), usbInterface.getId(), usbInterface.getInterfaceClass(), usbInterface.getInterfaceSubclass(), usbInterface.getInterfaceProtocol());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeBluetooth() {
|
||||
Log.d(TAG, "Initializing Bluetooth");
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 31 /* Android 12 */ &&
|
||||
mContext.getPackageManager().checkPermission(android.Manifest.permission.BLUETOOTH_CONNECT, mContext.getPackageName()) != PackageManager.PERMISSION_GRANTED) {
|
||||
Log.d(TAG, "Couldn't initialize Bluetooth, missing android.permission.BLUETOOTH_CONNECT");
|
||||
return;
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT <= 30 /* Android 11.0 (R) */ &&
|
||||
mContext.getPackageManager().checkPermission(android.Manifest.permission.BLUETOOTH, mContext.getPackageName()) != PackageManager.PERMISSION_GRANTED) {
|
||||
Log.d(TAG, "Couldn't initialize Bluetooth, missing android.permission.BLUETOOTH");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE) || (Build.VERSION.SDK_INT < 18 /* Android 4.3 (JELLY_BEAN_MR2) */)) {
|
||||
Log.d(TAG, "Couldn't initialize Bluetooth, this version of Android does not support Bluetooth LE");
|
||||
return;
|
||||
}
|
||||
|
||||
// Find bonded bluetooth controllers and create SteamControllers for them
|
||||
mBluetoothManager = (BluetoothManager)mContext.getSystemService(Context.BLUETOOTH_SERVICE);
|
||||
if (mBluetoothManager == null) {
|
||||
// This device doesn't support Bluetooth.
|
||||
return;
|
||||
}
|
||||
|
||||
BluetoothAdapter btAdapter = mBluetoothManager.getAdapter();
|
||||
if (btAdapter == null) {
|
||||
// This device has Bluetooth support in the codebase, but has no available adapters.
|
||||
return;
|
||||
}
|
||||
|
||||
// Get our bonded devices.
|
||||
for (BluetoothDevice device : btAdapter.getBondedDevices()) {
|
||||
|
||||
Log.d(TAG, "Bluetooth device available: " + device);
|
||||
if (isSteamController(device)) {
|
||||
connectBluetoothDevice(device);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// NOTE: These don't work on Chromebooks, to my undying dismay.
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
|
||||
filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
|
||||
mContext.registerReceiver(mBluetoothBroadcast, filter);
|
||||
|
||||
if (mIsChromebook) {
|
||||
mHandler = new Handler(Looper.getMainLooper());
|
||||
mLastBluetoothDevices = new ArrayList<BluetoothDevice>();
|
||||
|
||||
// final HIDDeviceManager finalThis = this;
|
||||
// mHandler.postDelayed(new Runnable() {
|
||||
// @Override
|
||||
// public void run() {
|
||||
// finalThis.chromebookConnectionHandler();
|
||||
// }
|
||||
// }, 5000);
|
||||
}
|
||||
}
|
||||
|
||||
private void shutdownBluetooth() {
|
||||
try {
|
||||
mContext.unregisterReceiver(mBluetoothBroadcast);
|
||||
} catch (Exception e) {
|
||||
// We may not have registered, that's okay
|
||||
}
|
||||
}
|
||||
|
||||
// Chromebooks do not pass along ACTION_ACL_CONNECTED / ACTION_ACL_DISCONNECTED properly.
|
||||
// This function provides a sort of dummy version of that, watching for changes in the
|
||||
// connected devices and attempting to add controllers as things change.
|
||||
public void chromebookConnectionHandler() {
|
||||
if (!mIsChromebook) {
|
||||
return;
|
||||
}
|
||||
|
||||
ArrayList<BluetoothDevice> disconnected = new ArrayList<BluetoothDevice>();
|
||||
ArrayList<BluetoothDevice> connected = new ArrayList<BluetoothDevice>();
|
||||
|
||||
List<BluetoothDevice> currentConnected = mBluetoothManager.getConnectedDevices(BluetoothProfile.GATT);
|
||||
|
||||
for (BluetoothDevice bluetoothDevice : currentConnected) {
|
||||
if (!mLastBluetoothDevices.contains(bluetoothDevice)) {
|
||||
connected.add(bluetoothDevice);
|
||||
}
|
||||
}
|
||||
for (BluetoothDevice bluetoothDevice : mLastBluetoothDevices) {
|
||||
if (!currentConnected.contains(bluetoothDevice)) {
|
||||
disconnected.add(bluetoothDevice);
|
||||
}
|
||||
}
|
||||
|
||||
mLastBluetoothDevices = currentConnected;
|
||||
|
||||
for (BluetoothDevice bluetoothDevice : disconnected) {
|
||||
disconnectBluetoothDevice(bluetoothDevice);
|
||||
}
|
||||
for (BluetoothDevice bluetoothDevice : connected) {
|
||||
connectBluetoothDevice(bluetoothDevice);
|
||||
}
|
||||
|
||||
final HIDDeviceManager finalThis = this;
|
||||
mHandler.postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
finalThis.chromebookConnectionHandler();
|
||||
}
|
||||
}, 10000);
|
||||
}
|
||||
|
||||
public boolean connectBluetoothDevice(BluetoothDevice bluetoothDevice) {
|
||||
Log.v(TAG, "connectBluetoothDevice device=" + bluetoothDevice);
|
||||
synchronized (this) {
|
||||
if (mBluetoothDevices.containsKey(bluetoothDevice)) {
|
||||
Log.v(TAG, "Steam controller with address " + bluetoothDevice + " already exists, attempting reconnect");
|
||||
|
||||
HIDDeviceBLESteamController device = mBluetoothDevices.get(bluetoothDevice);
|
||||
device.reconnect();
|
||||
|
||||
return false;
|
||||
}
|
||||
HIDDeviceBLESteamController device = new HIDDeviceBLESteamController(this, bluetoothDevice);
|
||||
int id = device.getId();
|
||||
mBluetoothDevices.put(bluetoothDevice, device);
|
||||
mDevicesById.put(id, device);
|
||||
|
||||
// The Steam Controller will mark itself connected once initialization is complete
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void disconnectBluetoothDevice(BluetoothDevice bluetoothDevice) {
|
||||
synchronized (this) {
|
||||
HIDDeviceBLESteamController device = mBluetoothDevices.get(bluetoothDevice);
|
||||
if (device == null)
|
||||
return;
|
||||
|
||||
int id = device.getId();
|
||||
mBluetoothDevices.remove(bluetoothDevice);
|
||||
mDevicesById.remove(id);
|
||||
device.shutdown();
|
||||
HIDDeviceDisconnected(id);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isSteamController(BluetoothDevice bluetoothDevice) {
|
||||
// Sanity check. If you pass in a null device, by definition it is never a Steam Controller.
|
||||
if (bluetoothDevice == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the device has no local name, we really don't want to try an equality check against it.
|
||||
if (bluetoothDevice.getName() == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return bluetoothDevice.getName().equals("SteamController") && ((bluetoothDevice.getType() & BluetoothDevice.DEVICE_TYPE_LE) != 0);
|
||||
}
|
||||
|
||||
private void close() {
|
||||
shutdownUSB();
|
||||
shutdownBluetooth();
|
||||
synchronized (this) {
|
||||
for (HIDDevice device : mDevicesById.values()) {
|
||||
device.shutdown();
|
||||
}
|
||||
mDevicesById.clear();
|
||||
mBluetoothDevices.clear();
|
||||
HIDDeviceReleaseCallback();
|
||||
}
|
||||
}
|
||||
|
||||
public void setFrozen(boolean frozen) {
|
||||
synchronized (this) {
|
||||
for (HIDDevice device : mDevicesById.values()) {
|
||||
device.setFrozen(frozen);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private HIDDevice getDevice(int id) {
|
||||
synchronized (this) {
|
||||
HIDDevice result = mDevicesById.get(id);
|
||||
if (result == null) {
|
||||
Log.v(TAG, "No device for id: " + id);
|
||||
Log.v(TAG, "Available devices: " + mDevicesById.keySet());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
////////// JNI interface functions
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public boolean initialize(boolean usb, boolean bluetooth) {
|
||||
Log.v(TAG, "initialize(" + usb + ", " + bluetooth + ")");
|
||||
|
||||
if (usb) {
|
||||
initializeUSB();
|
||||
}
|
||||
if (bluetooth) {
|
||||
initializeBluetooth();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean openDevice(int deviceID) {
|
||||
Log.v(TAG, "openDevice deviceID=" + deviceID);
|
||||
HIDDevice device = getDevice(deviceID);
|
||||
if (device == null) {
|
||||
HIDDeviceDisconnected(deviceID);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Look to see if this is a USB device and we have permission to access it
|
||||
UsbDevice usbDevice = device.getDevice();
|
||||
if (usbDevice != null && !mUsbManager.hasPermission(usbDevice)) {
|
||||
HIDDeviceOpenPending(deviceID);
|
||||
try {
|
||||
final int FLAG_MUTABLE = 0x02000000; // PendingIntent.FLAG_MUTABLE, but don't require SDK 31
|
||||
int flags;
|
||||
if (Build.VERSION.SDK_INT >= 31 /* Android 12.0 (S) */) {
|
||||
flags = FLAG_MUTABLE;
|
||||
} else {
|
||||
flags = 0;
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= 33 /* Android 14.0 (U) */) {
|
||||
Intent intent = new Intent(HIDDeviceManager.ACTION_USB_PERMISSION);
|
||||
intent.setPackage(mContext.getPackageName());
|
||||
mUsbManager.requestPermission(usbDevice, PendingIntent.getBroadcast(mContext, 0, intent, flags));
|
||||
} else {
|
||||
mUsbManager.requestPermission(usbDevice, PendingIntent.getBroadcast(mContext, 0, new Intent(HIDDeviceManager.ACTION_USB_PERMISSION), flags));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.v(TAG, "Couldn't request permission for USB device " + usbDevice);
|
||||
HIDDeviceOpenResult(deviceID, false);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
return device.open();
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Got exception: " + Log.getStackTraceString(e));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public int sendOutputReport(int deviceID, byte[] report) {
|
||||
try {
|
||||
//Log.v(TAG, "sendOutputReport deviceID=" + deviceID + " length=" + report.length);
|
||||
HIDDevice device;
|
||||
device = getDevice(deviceID);
|
||||
if (device == null) {
|
||||
HIDDeviceDisconnected(deviceID);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return device.sendOutputReport(report);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Got exception: " + Log.getStackTraceString(e));
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public int sendFeatureReport(int deviceID, byte[] report) {
|
||||
try {
|
||||
//Log.v(TAG, "sendFeatureReport deviceID=" + deviceID + " length=" + report.length);
|
||||
HIDDevice device;
|
||||
device = getDevice(deviceID);
|
||||
if (device == null) {
|
||||
HIDDeviceDisconnected(deviceID);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return device.sendFeatureReport(report);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Got exception: " + Log.getStackTraceString(e));
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public boolean getFeatureReport(int deviceID, byte[] report) {
|
||||
try {
|
||||
//Log.v(TAG, "getFeatureReport deviceID=" + deviceID);
|
||||
HIDDevice device;
|
||||
device = getDevice(deviceID);
|
||||
if (device == null) {
|
||||
HIDDeviceDisconnected(deviceID);
|
||||
return false;
|
||||
}
|
||||
|
||||
return device.getFeatureReport(report);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Got exception: " + Log.getStackTraceString(e));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void closeDevice(int deviceID) {
|
||||
try {
|
||||
Log.v(TAG, "closeDevice deviceID=" + deviceID);
|
||||
HIDDevice device;
|
||||
device = getDevice(deviceID);
|
||||
if (device == null) {
|
||||
HIDDeviceDisconnected(deviceID);
|
||||
return;
|
||||
}
|
||||
|
||||
device.close();
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Got exception: " + Log.getStackTraceString(e));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/////////////// Native methods
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private native void HIDDeviceRegisterCallback();
|
||||
private native void HIDDeviceReleaseCallback();
|
||||
|
||||
native void HIDDeviceConnected(int deviceID, String identifier, int vendorId, int productId, String serial_number, int release_number, String manufacturer_string, String product_string, int interface_number, int interface_class, int interface_subclass, int interface_protocol);
|
||||
native void HIDDeviceOpenPending(int deviceID);
|
||||
native void HIDDeviceOpenResult(int deviceID, boolean opened);
|
||||
native void HIDDeviceDisconnected(int deviceID);
|
||||
|
||||
native void HIDDeviceInputReport(int deviceID, byte[] report);
|
||||
native void HIDDeviceFeatureReport(int deviceID, byte[] report);
|
||||
}
|
|
@ -1,309 +0,0 @@
|
|||
package org.libsdl.app;
|
||||
|
||||
import android.hardware.usb.*;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
import java.util.Arrays;
|
||||
|
||||
class HIDDeviceUSB implements HIDDevice {
|
||||
|
||||
private static final String TAG = "hidapi";
|
||||
|
||||
protected HIDDeviceManager mManager;
|
||||
protected UsbDevice mDevice;
|
||||
protected int mInterfaceIndex;
|
||||
protected int mInterface;
|
||||
protected int mDeviceId;
|
||||
protected UsbDeviceConnection mConnection;
|
||||
protected UsbEndpoint mInputEndpoint;
|
||||
protected UsbEndpoint mOutputEndpoint;
|
||||
protected InputThread mInputThread;
|
||||
protected boolean mRunning;
|
||||
protected boolean mFrozen;
|
||||
|
||||
public HIDDeviceUSB(HIDDeviceManager manager, UsbDevice usbDevice, int interface_index) {
|
||||
mManager = manager;
|
||||
mDevice = usbDevice;
|
||||
mInterfaceIndex = interface_index;
|
||||
mInterface = mDevice.getInterface(mInterfaceIndex).getId();
|
||||
mDeviceId = manager.getDeviceIDForIdentifier(getIdentifier());
|
||||
mRunning = false;
|
||||
}
|
||||
|
||||
public String getIdentifier() {
|
||||
return String.format("%s/%x/%x/%d", mDevice.getDeviceName(), mDevice.getVendorId(), mDevice.getProductId(), mInterfaceIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getId() {
|
||||
return mDeviceId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVendorId() {
|
||||
return mDevice.getVendorId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getProductId() {
|
||||
return mDevice.getProductId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSerialNumber() {
|
||||
String result = null;
|
||||
if (Build.VERSION.SDK_INT >= 21 /* Android 5.0 (LOLLIPOP) */) {
|
||||
try {
|
||||
result = mDevice.getSerialNumber();
|
||||
}
|
||||
catch (SecurityException exception) {
|
||||
//Log.w(TAG, "App permissions mean we cannot get serial number for device " + getDeviceName() + " message: " + exception.getMessage());
|
||||
}
|
||||
}
|
||||
if (result == null) {
|
||||
result = "";
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVersion() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getManufacturerName() {
|
||||
String result = null;
|
||||
if (Build.VERSION.SDK_INT >= 21 /* Android 5.0 (LOLLIPOP) */) {
|
||||
result = mDevice.getManufacturerName();
|
||||
}
|
||||
if (result == null) {
|
||||
result = String.format("%x", getVendorId());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProductName() {
|
||||
String result = null;
|
||||
if (Build.VERSION.SDK_INT >= 21 /* Android 5.0 (LOLLIPOP) */) {
|
||||
result = mDevice.getProductName();
|
||||
}
|
||||
if (result == null) {
|
||||
result = String.format("%x", getProductId());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UsbDevice getDevice() {
|
||||
return mDevice;
|
||||
}
|
||||
|
||||
public String getDeviceName() {
|
||||
return getManufacturerName() + " " + getProductName() + "(0x" + String.format("%x", getVendorId()) + "/0x" + String.format("%x", getProductId()) + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean open() {
|
||||
mConnection = mManager.getUSBManager().openDevice(mDevice);
|
||||
if (mConnection == null) {
|
||||
Log.w(TAG, "Unable to open USB device " + getDeviceName());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Force claim our interface
|
||||
UsbInterface iface = mDevice.getInterface(mInterfaceIndex);
|
||||
if (!mConnection.claimInterface(iface, true)) {
|
||||
Log.w(TAG, "Failed to claim interfaces on USB device " + getDeviceName());
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Find the endpoints
|
||||
for (int j = 0; j < iface.getEndpointCount(); j++) {
|
||||
UsbEndpoint endpt = iface.getEndpoint(j);
|
||||
switch (endpt.getDirection()) {
|
||||
case UsbConstants.USB_DIR_IN:
|
||||
if (mInputEndpoint == null) {
|
||||
mInputEndpoint = endpt;
|
||||
}
|
||||
break;
|
||||
case UsbConstants.USB_DIR_OUT:
|
||||
if (mOutputEndpoint == null) {
|
||||
mOutputEndpoint = endpt;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure the required endpoints were present
|
||||
if (mInputEndpoint == null || mOutputEndpoint == null) {
|
||||
Log.w(TAG, "Missing required endpoint on USB device " + getDeviceName());
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Start listening for input
|
||||
mRunning = true;
|
||||
mInputThread = new InputThread();
|
||||
mInputThread.start();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int sendFeatureReport(byte[] report) {
|
||||
int res = -1;
|
||||
int offset = 0;
|
||||
int length = report.length;
|
||||
boolean skipped_report_id = false;
|
||||
byte report_number = report[0];
|
||||
|
||||
if (report_number == 0x0) {
|
||||
++offset;
|
||||
--length;
|
||||
skipped_report_id = true;
|
||||
}
|
||||
|
||||
res = mConnection.controlTransfer(
|
||||
UsbConstants.USB_TYPE_CLASS | 0x01 /*RECIPIENT_INTERFACE*/ | UsbConstants.USB_DIR_OUT,
|
||||
0x09/*HID set_report*/,
|
||||
(3/*HID feature*/ << 8) | report_number,
|
||||
mInterface,
|
||||
report, offset, length,
|
||||
1000/*timeout millis*/);
|
||||
|
||||
if (res < 0) {
|
||||
Log.w(TAG, "sendFeatureReport() returned " + res + " on device " + getDeviceName());
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (skipped_report_id) {
|
||||
++length;
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int sendOutputReport(byte[] report) {
|
||||
int r = mConnection.bulkTransfer(mOutputEndpoint, report, report.length, 1000);
|
||||
if (r != report.length) {
|
||||
Log.w(TAG, "sendOutputReport() returned " + r + " on device " + getDeviceName());
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getFeatureReport(byte[] report) {
|
||||
int res = -1;
|
||||
int offset = 0;
|
||||
int length = report.length;
|
||||
boolean skipped_report_id = false;
|
||||
byte report_number = report[0];
|
||||
|
||||
if (report_number == 0x0) {
|
||||
/* Offset the return buffer by 1, so that the report ID
|
||||
will remain in byte 0. */
|
||||
++offset;
|
||||
--length;
|
||||
skipped_report_id = true;
|
||||
}
|
||||
|
||||
res = mConnection.controlTransfer(
|
||||
UsbConstants.USB_TYPE_CLASS | 0x01 /*RECIPIENT_INTERFACE*/ | UsbConstants.USB_DIR_IN,
|
||||
0x01/*HID get_report*/,
|
||||
(3/*HID feature*/ << 8) | report_number,
|
||||
mInterface,
|
||||
report, offset, length,
|
||||
1000/*timeout millis*/);
|
||||
|
||||
if (res < 0) {
|
||||
Log.w(TAG, "getFeatureReport() returned " + res + " on device " + getDeviceName());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (skipped_report_id) {
|
||||
++res;
|
||||
++length;
|
||||
}
|
||||
|
||||
byte[] data;
|
||||
if (res == length) {
|
||||
data = report;
|
||||
} else {
|
||||
data = Arrays.copyOfRange(report, 0, res);
|
||||
}
|
||||
mManager.HIDDeviceFeatureReport(mDeviceId, data);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
mRunning = false;
|
||||
if (mInputThread != null) {
|
||||
while (mInputThread.isAlive()) {
|
||||
mInputThread.interrupt();
|
||||
try {
|
||||
mInputThread.join();
|
||||
} catch (InterruptedException e) {
|
||||
// Keep trying until we're done
|
||||
}
|
||||
}
|
||||
mInputThread = null;
|
||||
}
|
||||
if (mConnection != null) {
|
||||
UsbInterface iface = mDevice.getInterface(mInterfaceIndex);
|
||||
mConnection.releaseInterface(iface);
|
||||
mConnection.close();
|
||||
mConnection = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
close();
|
||||
mManager = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFrozen(boolean frozen) {
|
||||
mFrozen = frozen;
|
||||
}
|
||||
|
||||
protected class InputThread extends Thread {
|
||||
@Override
|
||||
public void run() {
|
||||
int packetSize = mInputEndpoint.getMaxPacketSize();
|
||||
byte[] packet = new byte[packetSize];
|
||||
while (mRunning) {
|
||||
int r;
|
||||
try
|
||||
{
|
||||
r = mConnection.bulkTransfer(mInputEndpoint, packet, packetSize, 1000);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.v(TAG, "Exception in UsbDeviceConnection bulktransfer: " + e);
|
||||
break;
|
||||
}
|
||||
if (r < 0) {
|
||||
// Could be a timeout or an I/O error
|
||||
}
|
||||
if (r > 0) {
|
||||
byte[] data;
|
||||
if (r == packetSize) {
|
||||
data = packet;
|
||||
} else {
|
||||
data = Arrays.copyOfRange(packet, 0, r);
|
||||
}
|
||||
|
||||
if (!mFrozen) {
|
||||
mManager.HIDDeviceInputReport(mDeviceId, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,90 +0,0 @@
|
|||
package org.libsdl.app;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import java.lang.Class;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
SDL library initialization
|
||||
*/
|
||||
public class SDL {
|
||||
|
||||
// This function should be called first and sets up the native code
|
||||
// so it can call into the Java classes
|
||||
public static void setupJNI() {
|
||||
SDLActivity.nativeSetupJNI();
|
||||
SDLAudioManager.nativeSetupJNI();
|
||||
SDLControllerManager.nativeSetupJNI();
|
||||
}
|
||||
|
||||
// This function should be called each time the activity is started
|
||||
public static void initialize() {
|
||||
setContext(null);
|
||||
|
||||
SDLActivity.initialize();
|
||||
SDLAudioManager.initialize();
|
||||
SDLControllerManager.initialize();
|
||||
}
|
||||
|
||||
// This function stores the current activity (SDL or not)
|
||||
public static void setContext(Context context) {
|
||||
SDLAudioManager.setContext(context);
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
public static Context getContext() {
|
||||
return mContext;
|
||||
}
|
||||
|
||||
public static void loadLibrary(String libraryName) throws UnsatisfiedLinkError, SecurityException, NullPointerException {
|
||||
loadLibrary(libraryName, mContext);
|
||||
}
|
||||
|
||||
public static void loadLibrary(String libraryName, Context context) throws UnsatisfiedLinkError, SecurityException, NullPointerException {
|
||||
|
||||
if (libraryName == null) {
|
||||
throw new NullPointerException("No library name provided.");
|
||||
}
|
||||
|
||||
try {
|
||||
// Let's see if we have ReLinker available in the project. This is necessary for
|
||||
// some projects that have huge numbers of local libraries bundled, and thus may
|
||||
// trip a bug in Android's native library loader which ReLinker works around. (If
|
||||
// loadLibrary works properly, ReLinker will simply use the normal Android method
|
||||
// internally.)
|
||||
//
|
||||
// To use ReLinker, just add it as a dependency. For more information, see
|
||||
// https://github.com/KeepSafe/ReLinker for ReLinker's repository.
|
||||
//
|
||||
Class<?> relinkClass = context.getClassLoader().loadClass("com.getkeepsafe.relinker.ReLinker");
|
||||
Class<?> relinkListenerClass = context.getClassLoader().loadClass("com.getkeepsafe.relinker.ReLinker$LoadListener");
|
||||
Class<?> contextClass = context.getClassLoader().loadClass("android.content.Context");
|
||||
Class<?> stringClass = context.getClassLoader().loadClass("java.lang.String");
|
||||
|
||||
// Get a 'force' instance of the ReLinker, so we can ensure libraries are reinstalled if
|
||||
// they've changed during updates.
|
||||
Method forceMethod = relinkClass.getDeclaredMethod("force");
|
||||
Object relinkInstance = forceMethod.invoke(null);
|
||||
Class<?> relinkInstanceClass = relinkInstance.getClass();
|
||||
|
||||
// Actually load the library!
|
||||
Method loadMethod = relinkInstanceClass.getDeclaredMethod("loadLibrary", contextClass, stringClass, stringClass, relinkListenerClass);
|
||||
loadMethod.invoke(relinkInstance, context, libraryName, null, null);
|
||||
}
|
||||
catch (final Throwable e) {
|
||||
// Fall back
|
||||
try {
|
||||
System.loadLibrary(libraryName);
|
||||
}
|
||||
catch (final UnsatisfiedLinkError ule) {
|
||||
throw ule;
|
||||
}
|
||||
catch (final SecurityException se) {
|
||||
throw se;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected static Context mContext;
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -1,514 +0,0 @@
|
|||
package org.libsdl.app;
|
||||
|
||||
import android.content.Context;
|
||||
import android.media.AudioDeviceCallback;
|
||||
import android.media.AudioDeviceInfo;
|
||||
import android.media.AudioFormat;
|
||||
import android.media.AudioManager;
|
||||
import android.media.AudioRecord;
|
||||
import android.media.AudioTrack;
|
||||
import android.media.MediaRecorder;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class SDLAudioManager {
|
||||
protected static final String TAG = "SDLAudio";
|
||||
|
||||
protected static AudioTrack mAudioTrack;
|
||||
protected static AudioRecord mAudioRecord;
|
||||
protected static Context mContext;
|
||||
|
||||
private static final int[] NO_DEVICES = {};
|
||||
|
||||
private static AudioDeviceCallback mAudioDeviceCallback;
|
||||
|
||||
public static void initialize() {
|
||||
mAudioTrack = null;
|
||||
mAudioRecord = null;
|
||||
mAudioDeviceCallback = null;
|
||||
|
||||
if(Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */)
|
||||
{
|
||||
mAudioDeviceCallback = new AudioDeviceCallback() {
|
||||
@Override
|
||||
public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {
|
||||
Arrays.stream(addedDevices).forEach(deviceInfo -> addAudioDevice(deviceInfo.isSink(), deviceInfo.getId()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) {
|
||||
Arrays.stream(removedDevices).forEach(deviceInfo -> removeAudioDevice(deviceInfo.isSink(), deviceInfo.getId()));
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public static void setContext(Context context) {
|
||||
mContext = context;
|
||||
if (context != null) {
|
||||
registerAudioDeviceCallback();
|
||||
}
|
||||
}
|
||||
|
||||
public static void release(Context context) {
|
||||
unregisterAudioDeviceCallback(context);
|
||||
}
|
||||
|
||||
// Audio
|
||||
|
||||
protected static String getAudioFormatString(int audioFormat) {
|
||||
switch (audioFormat) {
|
||||
case AudioFormat.ENCODING_PCM_8BIT:
|
||||
return "8-bit";
|
||||
case AudioFormat.ENCODING_PCM_16BIT:
|
||||
return "16-bit";
|
||||
case AudioFormat.ENCODING_PCM_FLOAT:
|
||||
return "float";
|
||||
default:
|
||||
return Integer.toString(audioFormat);
|
||||
}
|
||||
}
|
||||
|
||||
protected static int[] open(boolean isCapture, int sampleRate, int audioFormat, int desiredChannels, int desiredFrames, int deviceId) {
|
||||
int channelConfig;
|
||||
int sampleSize;
|
||||
int frameSize;
|
||||
|
||||
Log.v(TAG, "Opening " + (isCapture ? "capture" : "playback") + ", requested " + desiredFrames + " frames of " + desiredChannels + " channel " + getAudioFormatString(audioFormat) + " audio at " + sampleRate + " Hz");
|
||||
|
||||
/* On older devices let's use known good settings */
|
||||
if (Build.VERSION.SDK_INT < 21 /* Android 5.0 (LOLLIPOP) */) {
|
||||
if (desiredChannels > 2) {
|
||||
desiredChannels = 2;
|
||||
}
|
||||
}
|
||||
|
||||
/* AudioTrack has sample rate limitation of 48000 (fixed in 5.0.2) */
|
||||
if (Build.VERSION.SDK_INT < 22 /* Android 5.1 (LOLLIPOP_MR1) */) {
|
||||
if (sampleRate < 8000) {
|
||||
sampleRate = 8000;
|
||||
} else if (sampleRate > 48000) {
|
||||
sampleRate = 48000;
|
||||
}
|
||||
}
|
||||
|
||||
if (audioFormat == AudioFormat.ENCODING_PCM_FLOAT) {
|
||||
int minSDKVersion = (isCapture ? 23 /* Android 6.0 (M) */ : 21 /* Android 5.0 (LOLLIPOP) */);
|
||||
if (Build.VERSION.SDK_INT < minSDKVersion) {
|
||||
audioFormat = AudioFormat.ENCODING_PCM_16BIT;
|
||||
}
|
||||
}
|
||||
switch (audioFormat)
|
||||
{
|
||||
case AudioFormat.ENCODING_PCM_8BIT:
|
||||
sampleSize = 1;
|
||||
break;
|
||||
case AudioFormat.ENCODING_PCM_16BIT:
|
||||
sampleSize = 2;
|
||||
break;
|
||||
case AudioFormat.ENCODING_PCM_FLOAT:
|
||||
sampleSize = 4;
|
||||
break;
|
||||
default:
|
||||
Log.v(TAG, "Requested format " + audioFormat + ", getting ENCODING_PCM_16BIT");
|
||||
audioFormat = AudioFormat.ENCODING_PCM_16BIT;
|
||||
sampleSize = 2;
|
||||
break;
|
||||
}
|
||||
|
||||
if (isCapture) {
|
||||
switch (desiredChannels) {
|
||||
case 1:
|
||||
channelConfig = AudioFormat.CHANNEL_IN_MONO;
|
||||
break;
|
||||
case 2:
|
||||
channelConfig = AudioFormat.CHANNEL_IN_STEREO;
|
||||
break;
|
||||
default:
|
||||
Log.v(TAG, "Requested " + desiredChannels + " channels, getting stereo");
|
||||
desiredChannels = 2;
|
||||
channelConfig = AudioFormat.CHANNEL_IN_STEREO;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
switch (desiredChannels) {
|
||||
case 1:
|
||||
channelConfig = AudioFormat.CHANNEL_OUT_MONO;
|
||||
break;
|
||||
case 2:
|
||||
channelConfig = AudioFormat.CHANNEL_OUT_STEREO;
|
||||
break;
|
||||
case 3:
|
||||
channelConfig = AudioFormat.CHANNEL_OUT_STEREO | AudioFormat.CHANNEL_OUT_FRONT_CENTER;
|
||||
break;
|
||||
case 4:
|
||||
channelConfig = AudioFormat.CHANNEL_OUT_QUAD;
|
||||
break;
|
||||
case 5:
|
||||
channelConfig = AudioFormat.CHANNEL_OUT_QUAD | AudioFormat.CHANNEL_OUT_FRONT_CENTER;
|
||||
break;
|
||||
case 6:
|
||||
channelConfig = AudioFormat.CHANNEL_OUT_5POINT1;
|
||||
break;
|
||||
case 7:
|
||||
channelConfig = AudioFormat.CHANNEL_OUT_5POINT1 | AudioFormat.CHANNEL_OUT_BACK_CENTER;
|
||||
break;
|
||||
case 8:
|
||||
if (Build.VERSION.SDK_INT >= 23 /* Android 6.0 (M) */) {
|
||||
channelConfig = AudioFormat.CHANNEL_OUT_7POINT1_SURROUND;
|
||||
} else {
|
||||
Log.v(TAG, "Requested " + desiredChannels + " channels, getting 5.1 surround");
|
||||
desiredChannels = 6;
|
||||
channelConfig = AudioFormat.CHANNEL_OUT_5POINT1;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
Log.v(TAG, "Requested " + desiredChannels + " channels, getting stereo");
|
||||
desiredChannels = 2;
|
||||
channelConfig = AudioFormat.CHANNEL_OUT_STEREO;
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
Log.v(TAG, "Speaker configuration (and order of channels):");
|
||||
|
||||
if ((channelConfig & 0x00000004) != 0) {
|
||||
Log.v(TAG, " CHANNEL_OUT_FRONT_LEFT");
|
||||
}
|
||||
if ((channelConfig & 0x00000008) != 0) {
|
||||
Log.v(TAG, " CHANNEL_OUT_FRONT_RIGHT");
|
||||
}
|
||||
if ((channelConfig & 0x00000010) != 0) {
|
||||
Log.v(TAG, " CHANNEL_OUT_FRONT_CENTER");
|
||||
}
|
||||
if ((channelConfig & 0x00000020) != 0) {
|
||||
Log.v(TAG, " CHANNEL_OUT_LOW_FREQUENCY");
|
||||
}
|
||||
if ((channelConfig & 0x00000040) != 0) {
|
||||
Log.v(TAG, " CHANNEL_OUT_BACK_LEFT");
|
||||
}
|
||||
if ((channelConfig & 0x00000080) != 0) {
|
||||
Log.v(TAG, " CHANNEL_OUT_BACK_RIGHT");
|
||||
}
|
||||
if ((channelConfig & 0x00000100) != 0) {
|
||||
Log.v(TAG, " CHANNEL_OUT_FRONT_LEFT_OF_CENTER");
|
||||
}
|
||||
if ((channelConfig & 0x00000200) != 0) {
|
||||
Log.v(TAG, " CHANNEL_OUT_FRONT_RIGHT_OF_CENTER");
|
||||
}
|
||||
if ((channelConfig & 0x00000400) != 0) {
|
||||
Log.v(TAG, " CHANNEL_OUT_BACK_CENTER");
|
||||
}
|
||||
if ((channelConfig & 0x00000800) != 0) {
|
||||
Log.v(TAG, " CHANNEL_OUT_SIDE_LEFT");
|
||||
}
|
||||
if ((channelConfig & 0x00001000) != 0) {
|
||||
Log.v(TAG, " CHANNEL_OUT_SIDE_RIGHT");
|
||||
}
|
||||
*/
|
||||
}
|
||||
frameSize = (sampleSize * desiredChannels);
|
||||
|
||||
// Let the user pick a larger buffer if they really want -- but ye
|
||||
// gods they probably shouldn't, the minimums are horrifyingly high
|
||||
// latency already
|
||||
int minBufferSize;
|
||||
if (isCapture) {
|
||||
minBufferSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat);
|
||||
} else {
|
||||
minBufferSize = AudioTrack.getMinBufferSize(sampleRate, channelConfig, audioFormat);
|
||||
}
|
||||
desiredFrames = Math.max(desiredFrames, (minBufferSize + frameSize - 1) / frameSize);
|
||||
|
||||
int[] results = new int[4];
|
||||
|
||||
if (isCapture) {
|
||||
if (mAudioRecord == null) {
|
||||
mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.DEFAULT, sampleRate,
|
||||
channelConfig, audioFormat, desiredFrames * frameSize);
|
||||
|
||||
// see notes about AudioTrack state in audioOpen(), above. Probably also applies here.
|
||||
if (mAudioRecord.getState() != AudioRecord.STATE_INITIALIZED) {
|
||||
Log.e(TAG, "Failed during initialization of AudioRecord");
|
||||
mAudioRecord.release();
|
||||
mAudioRecord = null;
|
||||
return null;
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */ && deviceId != 0) {
|
||||
mAudioRecord.setPreferredDevice(getOutputAudioDeviceInfo(deviceId));
|
||||
}
|
||||
|
||||
mAudioRecord.startRecording();
|
||||
}
|
||||
|
||||
results[0] = mAudioRecord.getSampleRate();
|
||||
results[1] = mAudioRecord.getAudioFormat();
|
||||
results[2] = mAudioRecord.getChannelCount();
|
||||
|
||||
} else {
|
||||
if (mAudioTrack == null) {
|
||||
mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, channelConfig, audioFormat, desiredFrames * frameSize, AudioTrack.MODE_STREAM);
|
||||
|
||||
// Instantiating AudioTrack can "succeed" without an exception and the track may still be invalid
|
||||
// Ref: https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/media/java/android/media/AudioTrack.java
|
||||
// Ref: http://developer.android.com/reference/android/media/AudioTrack.html#getState()
|
||||
if (mAudioTrack.getState() != AudioTrack.STATE_INITIALIZED) {
|
||||
/* Try again, with safer values */
|
||||
|
||||
Log.e(TAG, "Failed during initialization of Audio Track");
|
||||
mAudioTrack.release();
|
||||
mAudioTrack = null;
|
||||
return null;
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */ && deviceId != 0) {
|
||||
mAudioTrack.setPreferredDevice(getInputAudioDeviceInfo(deviceId));
|
||||
}
|
||||
|
||||
mAudioTrack.play();
|
||||
}
|
||||
|
||||
results[0] = mAudioTrack.getSampleRate();
|
||||
results[1] = mAudioTrack.getAudioFormat();
|
||||
results[2] = mAudioTrack.getChannelCount();
|
||||
}
|
||||
results[3] = desiredFrames;
|
||||
|
||||
Log.v(TAG, "Opening " + (isCapture ? "capture" : "playback") + ", got " + results[3] + " frames of " + results[2] + " channel " + getAudioFormatString(results[1]) + " audio at " + results[0] + " Hz");
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private static AudioDeviceInfo getInputAudioDeviceInfo(int deviceId) {
|
||||
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
|
||||
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
|
||||
return Arrays.stream(audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS))
|
||||
.filter(deviceInfo -> deviceInfo.getId() == deviceId)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static AudioDeviceInfo getOutputAudioDeviceInfo(int deviceId) {
|
||||
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
|
||||
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
|
||||
return Arrays.stream(audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS))
|
||||
.filter(deviceInfo -> deviceInfo.getId() == deviceId)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static void registerAudioDeviceCallback() {
|
||||
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
|
||||
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
|
||||
audioManager.registerAudioDeviceCallback(mAudioDeviceCallback, null);
|
||||
}
|
||||
}
|
||||
|
||||
private static void unregisterAudioDeviceCallback(Context context) {
|
||||
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
|
||||
AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
|
||||
audioManager.unregisterAudioDeviceCallback(mAudioDeviceCallback);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called by SDL using JNI.
|
||||
*/
|
||||
public static int[] getAudioOutputDevices() {
|
||||
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
|
||||
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
|
||||
return Arrays.stream(audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)).mapToInt(AudioDeviceInfo::getId).toArray();
|
||||
} else {
|
||||
return NO_DEVICES;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called by SDL using JNI.
|
||||
*/
|
||||
public static int[] getAudioInputDevices() {
|
||||
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
|
||||
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
|
||||
return Arrays.stream(audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)).mapToInt(AudioDeviceInfo::getId).toArray();
|
||||
} else {
|
||||
return NO_DEVICES;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called by SDL using JNI.
|
||||
*/
|
||||
public static int[] audioOpen(int sampleRate, int audioFormat, int desiredChannels, int desiredFrames, int deviceId) {
|
||||
return open(false, sampleRate, audioFormat, desiredChannels, desiredFrames, deviceId);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called by SDL using JNI.
|
||||
*/
|
||||
public static void audioWriteFloatBuffer(float[] buffer) {
|
||||
if (mAudioTrack == null) {
|
||||
Log.e(TAG, "Attempted to make audio call with uninitialized audio!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (android.os.Build.VERSION.SDK_INT < 21 /* Android 5.0 (LOLLIPOP) */) {
|
||||
Log.e(TAG, "Attempted to make an incompatible audio call with uninitialized audio! (floating-point output is supported since Android 5.0 Lollipop)");
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < buffer.length;) {
|
||||
int result = mAudioTrack.write(buffer, i, buffer.length - i, AudioTrack.WRITE_BLOCKING);
|
||||
if (result > 0) {
|
||||
i += result;
|
||||
} else if (result == 0) {
|
||||
try {
|
||||
Thread.sleep(1);
|
||||
} catch(InterruptedException e) {
|
||||
// Nom nom
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "SDL audio: error return from write(float)");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called by SDL using JNI.
|
||||
*/
|
||||
public static void audioWriteShortBuffer(short[] buffer) {
|
||||
if (mAudioTrack == null) {
|
||||
Log.e(TAG, "Attempted to make audio call with uninitialized audio!");
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < buffer.length;) {
|
||||
int result = mAudioTrack.write(buffer, i, buffer.length - i);
|
||||
if (result > 0) {
|
||||
i += result;
|
||||
} else if (result == 0) {
|
||||
try {
|
||||
Thread.sleep(1);
|
||||
} catch(InterruptedException e) {
|
||||
// Nom nom
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "SDL audio: error return from write(short)");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called by SDL using JNI.
|
||||
*/
|
||||
public static void audioWriteByteBuffer(byte[] buffer) {
|
||||
if (mAudioTrack == null) {
|
||||
Log.e(TAG, "Attempted to make audio call with uninitialized audio!");
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < buffer.length; ) {
|
||||
int result = mAudioTrack.write(buffer, i, buffer.length - i);
|
||||
if (result > 0) {
|
||||
i += result;
|
||||
} else if (result == 0) {
|
||||
try {
|
||||
Thread.sleep(1);
|
||||
} catch(InterruptedException e) {
|
||||
// Nom nom
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "SDL audio: error return from write(byte)");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called by SDL using JNI.
|
||||
*/
|
||||
public static int[] captureOpen(int sampleRate, int audioFormat, int desiredChannels, int desiredFrames, int deviceId) {
|
||||
return open(true, sampleRate, audioFormat, desiredChannels, desiredFrames, deviceId);
|
||||
}
|
||||
|
||||
/** This method is called by SDL using JNI. */
|
||||
public static int captureReadFloatBuffer(float[] buffer, boolean blocking) {
|
||||
if (Build.VERSION.SDK_INT < 23 /* Android 6.0 (M) */) {
|
||||
return 0;
|
||||
} else {
|
||||
return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);
|
||||
}
|
||||
}
|
||||
|
||||
/** This method is called by SDL using JNI. */
|
||||
public static int captureReadShortBuffer(short[] buffer, boolean blocking) {
|
||||
if (Build.VERSION.SDK_INT < 23 /* Android 6.0 (M) */) {
|
||||
return mAudioRecord.read(buffer, 0, buffer.length);
|
||||
} else {
|
||||
return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);
|
||||
}
|
||||
}
|
||||
|
||||
/** This method is called by SDL using JNI. */
|
||||
public static int captureReadByteBuffer(byte[] buffer, boolean blocking) {
|
||||
if (Build.VERSION.SDK_INT < 23 /* Android 6.0 (M) */) {
|
||||
return mAudioRecord.read(buffer, 0, buffer.length);
|
||||
} else {
|
||||
return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);
|
||||
}
|
||||
}
|
||||
|
||||
/** This method is called by SDL using JNI. */
|
||||
public static void audioClose() {
|
||||
if (mAudioTrack != null) {
|
||||
mAudioTrack.stop();
|
||||
mAudioTrack.release();
|
||||
mAudioTrack = null;
|
||||
}
|
||||
}
|
||||
|
||||
/** This method is called by SDL using JNI. */
|
||||
public static void captureClose() {
|
||||
if (mAudioRecord != null) {
|
||||
mAudioRecord.stop();
|
||||
mAudioRecord.release();
|
||||
mAudioRecord = null;
|
||||
}
|
||||
}
|
||||
|
||||
/** This method is called by SDL using JNI. */
|
||||
public static void audioSetThreadPriority(boolean iscapture, int device_id) {
|
||||
try {
|
||||
|
||||
/* Set thread name */
|
||||
if (iscapture) {
|
||||
Thread.currentThread().setName("SDLAudioC" + device_id);
|
||||
} else {
|
||||
Thread.currentThread().setName("SDLAudioP" + device_id);
|
||||
}
|
||||
|
||||
/* Set thread priority */
|
||||
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_AUDIO);
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.v(TAG, "modify thread properties failed " + e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
public static native int nativeSetupJNI();
|
||||
|
||||
public static native void removeAudioDevice(boolean isCapture, int deviceId);
|
||||
|
||||
public static native void addAudioDevice(boolean isCapture, int deviceId);
|
||||
|
||||
}
|
|
@ -1,856 +0,0 @@
|
|||
package org.libsdl.app;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.os.VibrationEffect;
|
||||
import android.os.Vibrator;
|
||||
import android.util.Log;
|
||||
import android.view.InputDevice;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
|
||||
|
||||
public class SDLControllerManager
|
||||
{
|
||||
|
||||
public static native int nativeSetupJNI();
|
||||
|
||||
public static native int nativeAddJoystick(int device_id, String name, String desc,
|
||||
int vendor_id, int product_id,
|
||||
boolean is_accelerometer, int button_mask,
|
||||
int naxes, int axis_mask, int nhats, int nballs);
|
||||
public static native int nativeRemoveJoystick(int device_id);
|
||||
public static native int nativeAddHaptic(int device_id, String name);
|
||||
public static native int nativeRemoveHaptic(int device_id);
|
||||
public static native int onNativePadDown(int device_id, int keycode);
|
||||
public static native int onNativePadUp(int device_id, int keycode);
|
||||
public static native void onNativeJoy(int device_id, int axis,
|
||||
float value);
|
||||
public static native void onNativeHat(int device_id, int hat_id,
|
||||
int x, int y);
|
||||
|
||||
protected static SDLJoystickHandler mJoystickHandler;
|
||||
protected static SDLHapticHandler mHapticHandler;
|
||||
|
||||
private static final String TAG = "SDLControllerManager";
|
||||
|
||||
public static void initialize() {
|
||||
if (mJoystickHandler == null) {
|
||||
if (Build.VERSION.SDK_INT >= 19 /* Android 4.4 (KITKAT) */) {
|
||||
mJoystickHandler = new SDLJoystickHandler_API19();
|
||||
} else {
|
||||
mJoystickHandler = new SDLJoystickHandler_API16();
|
||||
}
|
||||
}
|
||||
|
||||
if (mHapticHandler == null) {
|
||||
if (Build.VERSION.SDK_INT >= 26 /* Android 8.0 (O) */) {
|
||||
mHapticHandler = new SDLHapticHandler_API26();
|
||||
} else {
|
||||
mHapticHandler = new SDLHapticHandler();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Joystick glue code, just a series of stubs that redirect to the SDLJoystickHandler instance
|
||||
public static boolean handleJoystickMotionEvent(MotionEvent event) {
|
||||
return mJoystickHandler.handleMotionEvent(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called by SDL using JNI.
|
||||
*/
|
||||
public static void pollInputDevices() {
|
||||
mJoystickHandler.pollInputDevices();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called by SDL using JNI.
|
||||
*/
|
||||
public static void pollHapticDevices() {
|
||||
mHapticHandler.pollHapticDevices();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called by SDL using JNI.
|
||||
*/
|
||||
public static void hapticRun(int device_id, float intensity, int length) {
|
||||
mHapticHandler.run(device_id, intensity, length);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called by SDL using JNI.
|
||||
*/
|
||||
public static void hapticStop(int device_id)
|
||||
{
|
||||
mHapticHandler.stop(device_id);
|
||||
}
|
||||
|
||||
// Check if a given device is considered a possible SDL joystick
|
||||
public static boolean isDeviceSDLJoystick(int deviceId) {
|
||||
InputDevice device = InputDevice.getDevice(deviceId);
|
||||
// We cannot use InputDevice.isVirtual before API 16, so let's accept
|
||||
// only nonnegative device ids (VIRTUAL_KEYBOARD equals -1)
|
||||
if ((device == null) || (deviceId < 0)) {
|
||||
return false;
|
||||
}
|
||||
int sources = device.getSources();
|
||||
|
||||
/* This is called for every button press, so let's not spam the logs */
|
||||
/*
|
||||
if ((sources & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
|
||||
Log.v(TAG, "Input device " + device.getName() + " has class joystick.");
|
||||
}
|
||||
if ((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD) {
|
||||
Log.v(TAG, "Input device " + device.getName() + " is a dpad.");
|
||||
}
|
||||
if ((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) {
|
||||
Log.v(TAG, "Input device " + device.getName() + " is a gamepad.");
|
||||
}
|
||||
*/
|
||||
|
||||
return ((sources & InputDevice.SOURCE_CLASS_JOYSTICK) != 0 ||
|
||||
((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD) ||
|
||||
((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class SDLJoystickHandler {
|
||||
|
||||
/**
|
||||
* Handles given MotionEvent.
|
||||
* @param event the event to be handled.
|
||||
* @return if given event was processed.
|
||||
*/
|
||||
public boolean handleMotionEvent(MotionEvent event) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles adding and removing of input devices.
|
||||
*/
|
||||
public void pollInputDevices() {
|
||||
}
|
||||
}
|
||||
|
||||
/* Actual joystick functionality available for API >= 12 devices */
|
||||
class SDLJoystickHandler_API16 extends SDLJoystickHandler {
|
||||
|
||||
static class SDLJoystick {
|
||||
public int device_id;
|
||||
public String name;
|
||||
public String desc;
|
||||
public ArrayList<InputDevice.MotionRange> axes;
|
||||
public ArrayList<InputDevice.MotionRange> hats;
|
||||
}
|
||||
static class RangeComparator implements Comparator<InputDevice.MotionRange> {
|
||||
@Override
|
||||
public int compare(InputDevice.MotionRange arg0, InputDevice.MotionRange arg1) {
|
||||
// Some controllers, like the Moga Pro 2, return AXIS_GAS (22) for right trigger and AXIS_BRAKE (23) for left trigger - swap them so they're sorted in the right order for SDL
|
||||
int arg0Axis = arg0.getAxis();
|
||||
int arg1Axis = arg1.getAxis();
|
||||
if (arg0Axis == MotionEvent.AXIS_GAS) {
|
||||
arg0Axis = MotionEvent.AXIS_BRAKE;
|
||||
} else if (arg0Axis == MotionEvent.AXIS_BRAKE) {
|
||||
arg0Axis = MotionEvent.AXIS_GAS;
|
||||
}
|
||||
if (arg1Axis == MotionEvent.AXIS_GAS) {
|
||||
arg1Axis = MotionEvent.AXIS_BRAKE;
|
||||
} else if (arg1Axis == MotionEvent.AXIS_BRAKE) {
|
||||
arg1Axis = MotionEvent.AXIS_GAS;
|
||||
}
|
||||
|
||||
// Make sure the AXIS_Z is sorted between AXIS_RY and AXIS_RZ.
|
||||
// This is because the usual pairing are:
|
||||
// - AXIS_X + AXIS_Y (left stick).
|
||||
// - AXIS_RX, AXIS_RY (sometimes the right stick, sometimes triggers).
|
||||
// - AXIS_Z, AXIS_RZ (sometimes the right stick, sometimes triggers).
|
||||
// This sorts the axes in the above order, which tends to be correct
|
||||
// for Xbox-ish game pads that have the right stick on RX/RY and the
|
||||
// triggers on Z/RZ.
|
||||
//
|
||||
// Gamepads that don't have AXIS_Z/AXIS_RZ but use
|
||||
// AXIS_LTRIGGER/AXIS_RTRIGGER are unaffected by this.
|
||||
//
|
||||
// References:
|
||||
// - https://developer.android.com/develop/ui/views/touch-and-input/game-controllers/controller-input
|
||||
// - https://www.kernel.org/doc/html/latest/input/gamepad.html
|
||||
if (arg0Axis == MotionEvent.AXIS_Z) {
|
||||
arg0Axis = MotionEvent.AXIS_RZ - 1;
|
||||
} else if (arg0Axis > MotionEvent.AXIS_Z && arg0Axis < MotionEvent.AXIS_RZ) {
|
||||
--arg0Axis;
|
||||
}
|
||||
if (arg1Axis == MotionEvent.AXIS_Z) {
|
||||
arg1Axis = MotionEvent.AXIS_RZ - 1;
|
||||
} else if (arg1Axis > MotionEvent.AXIS_Z && arg1Axis < MotionEvent.AXIS_RZ) {
|
||||
--arg1Axis;
|
||||
}
|
||||
|
||||
return arg0Axis - arg1Axis;
|
||||
}
|
||||
}
|
||||
|
||||
private final ArrayList<SDLJoystick> mJoysticks;
|
||||
|
||||
public SDLJoystickHandler_API16() {
|
||||
|
||||
mJoysticks = new ArrayList<SDLJoystick>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pollInputDevices() {
|
||||
int[] deviceIds = InputDevice.getDeviceIds();
|
||||
|
||||
for (int device_id : deviceIds) {
|
||||
if (SDLControllerManager.isDeviceSDLJoystick(device_id)) {
|
||||
SDLJoystick joystick = getJoystick(device_id);
|
||||
if (joystick == null) {
|
||||
InputDevice joystickDevice = InputDevice.getDevice(device_id);
|
||||
joystick = new SDLJoystick();
|
||||
joystick.device_id = device_id;
|
||||
joystick.name = joystickDevice.getName();
|
||||
joystick.desc = getJoystickDescriptor(joystickDevice);
|
||||
joystick.axes = new ArrayList<InputDevice.MotionRange>();
|
||||
joystick.hats = new ArrayList<InputDevice.MotionRange>();
|
||||
|
||||
List<InputDevice.MotionRange> ranges = joystickDevice.getMotionRanges();
|
||||
Collections.sort(ranges, new RangeComparator());
|
||||
for (InputDevice.MotionRange range : ranges) {
|
||||
if ((range.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
|
||||
if (range.getAxis() == MotionEvent.AXIS_HAT_X || range.getAxis() == MotionEvent.AXIS_HAT_Y) {
|
||||
joystick.hats.add(range);
|
||||
} else {
|
||||
joystick.axes.add(range);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mJoysticks.add(joystick);
|
||||
SDLControllerManager.nativeAddJoystick(joystick.device_id, joystick.name, joystick.desc,
|
||||
getVendorId(joystickDevice), getProductId(joystickDevice), false,
|
||||
getButtonMask(joystickDevice), joystick.axes.size(), getAxisMask(joystick.axes), joystick.hats.size()/2, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Check removed devices */
|
||||
ArrayList<Integer> removedDevices = null;
|
||||
for (SDLJoystick joystick : mJoysticks) {
|
||||
int device_id = joystick.device_id;
|
||||
int i;
|
||||
for (i = 0; i < deviceIds.length; i++) {
|
||||
if (device_id == deviceIds[i]) break;
|
||||
}
|
||||
if (i == deviceIds.length) {
|
||||
if (removedDevices == null) {
|
||||
removedDevices = new ArrayList<Integer>();
|
||||
}
|
||||
removedDevices.add(device_id);
|
||||
}
|
||||
}
|
||||
|
||||
if (removedDevices != null) {
|
||||
for (int device_id : removedDevices) {
|
||||
SDLControllerManager.nativeRemoveJoystick(device_id);
|
||||
for (int i = 0; i < mJoysticks.size(); i++) {
|
||||
if (mJoysticks.get(i).device_id == device_id) {
|
||||
mJoysticks.remove(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected SDLJoystick getJoystick(int device_id) {
|
||||
for (SDLJoystick joystick : mJoysticks) {
|
||||
if (joystick.device_id == device_id) {
|
||||
return joystick;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleMotionEvent(MotionEvent event) {
|
||||
int actionPointerIndex = event.getActionIndex();
|
||||
int action = event.getActionMasked();
|
||||
if (action == MotionEvent.ACTION_MOVE) {
|
||||
SDLJoystick joystick = getJoystick(event.getDeviceId());
|
||||
if (joystick != null) {
|
||||
for (int i = 0; i < joystick.axes.size(); i++) {
|
||||
InputDevice.MotionRange range = joystick.axes.get(i);
|
||||
/* Normalize the value to -1...1 */
|
||||
float value = (event.getAxisValue(range.getAxis(), actionPointerIndex) - range.getMin()) / range.getRange() * 2.0f - 1.0f;
|
||||
SDLControllerManager.onNativeJoy(joystick.device_id, i, value);
|
||||
}
|
||||
for (int i = 0; i < joystick.hats.size() / 2; i++) {
|
||||
int hatX = Math.round(event.getAxisValue(joystick.hats.get(2 * i).getAxis(), actionPointerIndex));
|
||||
int hatY = Math.round(event.getAxisValue(joystick.hats.get(2 * i + 1).getAxis(), actionPointerIndex));
|
||||
SDLControllerManager.onNativeHat(joystick.device_id, i, hatX, hatY);
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public String getJoystickDescriptor(InputDevice joystickDevice) {
|
||||
String desc = joystickDevice.getDescriptor();
|
||||
|
||||
if (desc != null && !desc.isEmpty()) {
|
||||
return desc;
|
||||
}
|
||||
|
||||
return joystickDevice.getName();
|
||||
}
|
||||
public int getProductId(InputDevice joystickDevice) {
|
||||
return 0;
|
||||
}
|
||||
public int getVendorId(InputDevice joystickDevice) {
|
||||
return 0;
|
||||
}
|
||||
public int getAxisMask(List<InputDevice.MotionRange> ranges) {
|
||||
return -1;
|
||||
}
|
||||
public int getButtonMask(InputDevice joystickDevice) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
class SDLJoystickHandler_API19 extends SDLJoystickHandler_API16 {
|
||||
|
||||
@Override
|
||||
public int getProductId(InputDevice joystickDevice) {
|
||||
return joystickDevice.getProductId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVendorId(InputDevice joystickDevice) {
|
||||
return joystickDevice.getVendorId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAxisMask(List<InputDevice.MotionRange> ranges) {
|
||||
// For compatibility, keep computing the axis mask like before,
|
||||
// only really distinguishing 2, 4 and 6 axes.
|
||||
int axis_mask = 0;
|
||||
if (ranges.size() >= 2) {
|
||||
// ((1 << SDL_GAMEPAD_AXIS_LEFTX) | (1 << SDL_GAMEPAD_AXIS_LEFTY))
|
||||
axis_mask |= 0x0003;
|
||||
}
|
||||
if (ranges.size() >= 4) {
|
||||
// ((1 << SDL_GAMEPAD_AXIS_RIGHTX) | (1 << SDL_GAMEPAD_AXIS_RIGHTY))
|
||||
axis_mask |= 0x000c;
|
||||
}
|
||||
if (ranges.size() >= 6) {
|
||||
// ((1 << SDL_GAMEPAD_AXIS_LEFT_TRIGGER) | (1 << SDL_GAMEPAD_AXIS_RIGHT_TRIGGER))
|
||||
axis_mask |= 0x0030;
|
||||
}
|
||||
// Also add an indicator bit for whether the sorting order has changed.
|
||||
// This serves to disable outdated gamecontrollerdb.txt mappings.
|
||||
boolean have_z = false;
|
||||
boolean have_past_z_before_rz = false;
|
||||
for (InputDevice.MotionRange range : ranges) {
|
||||
int axis = range.getAxis();
|
||||
if (axis == MotionEvent.AXIS_Z) {
|
||||
have_z = true;
|
||||
} else if (axis > MotionEvent.AXIS_Z && axis < MotionEvent.AXIS_RZ) {
|
||||
have_past_z_before_rz = true;
|
||||
}
|
||||
}
|
||||
if (have_z && have_past_z_before_rz) {
|
||||
// If both these exist, the compare() function changed sorting order.
|
||||
// Set a bit to indicate this fact.
|
||||
axis_mask |= 0x8000;
|
||||
}
|
||||
return axis_mask;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getButtonMask(InputDevice joystickDevice) {
|
||||
int button_mask = 0;
|
||||
int[] keys = new int[] {
|
||||
KeyEvent.KEYCODE_BUTTON_A,
|
||||
KeyEvent.KEYCODE_BUTTON_B,
|
||||
KeyEvent.KEYCODE_BUTTON_X,
|
||||
KeyEvent.KEYCODE_BUTTON_Y,
|
||||
KeyEvent.KEYCODE_BACK,
|
||||
KeyEvent.KEYCODE_MENU,
|
||||
KeyEvent.KEYCODE_BUTTON_MODE,
|
||||
KeyEvent.KEYCODE_BUTTON_START,
|
||||
KeyEvent.KEYCODE_BUTTON_THUMBL,
|
||||
KeyEvent.KEYCODE_BUTTON_THUMBR,
|
||||
KeyEvent.KEYCODE_BUTTON_L1,
|
||||
KeyEvent.KEYCODE_BUTTON_R1,
|
||||
KeyEvent.KEYCODE_DPAD_UP,
|
||||
KeyEvent.KEYCODE_DPAD_DOWN,
|
||||
KeyEvent.KEYCODE_DPAD_LEFT,
|
||||
KeyEvent.KEYCODE_DPAD_RIGHT,
|
||||
KeyEvent.KEYCODE_BUTTON_SELECT,
|
||||
KeyEvent.KEYCODE_DPAD_CENTER,
|
||||
|
||||
// These don't map into any SDL controller buttons directly
|
||||
KeyEvent.KEYCODE_BUTTON_L2,
|
||||
KeyEvent.KEYCODE_BUTTON_R2,
|
||||
KeyEvent.KEYCODE_BUTTON_C,
|
||||
KeyEvent.KEYCODE_BUTTON_Z,
|
||||
KeyEvent.KEYCODE_BUTTON_1,
|
||||
KeyEvent.KEYCODE_BUTTON_2,
|
||||
KeyEvent.KEYCODE_BUTTON_3,
|
||||
KeyEvent.KEYCODE_BUTTON_4,
|
||||
KeyEvent.KEYCODE_BUTTON_5,
|
||||
KeyEvent.KEYCODE_BUTTON_6,
|
||||
KeyEvent.KEYCODE_BUTTON_7,
|
||||
KeyEvent.KEYCODE_BUTTON_8,
|
||||
KeyEvent.KEYCODE_BUTTON_9,
|
||||
KeyEvent.KEYCODE_BUTTON_10,
|
||||
KeyEvent.KEYCODE_BUTTON_11,
|
||||
KeyEvent.KEYCODE_BUTTON_12,
|
||||
KeyEvent.KEYCODE_BUTTON_13,
|
||||
KeyEvent.KEYCODE_BUTTON_14,
|
||||
KeyEvent.KEYCODE_BUTTON_15,
|
||||
KeyEvent.KEYCODE_BUTTON_16,
|
||||
};
|
||||
int[] masks = new int[] {
|
||||
(1 << 0), // A -> A
|
||||
(1 << 1), // B -> B
|
||||
(1 << 2), // X -> X
|
||||
(1 << 3), // Y -> Y
|
||||
(1 << 4), // BACK -> BACK
|
||||
(1 << 6), // MENU -> START
|
||||
(1 << 5), // MODE -> GUIDE
|
||||
(1 << 6), // START -> START
|
||||
(1 << 7), // THUMBL -> LEFTSTICK
|
||||
(1 << 8), // THUMBR -> RIGHTSTICK
|
||||
(1 << 9), // L1 -> LEFTSHOULDER
|
||||
(1 << 10), // R1 -> RIGHTSHOULDER
|
||||
(1 << 11), // DPAD_UP -> DPAD_UP
|
||||
(1 << 12), // DPAD_DOWN -> DPAD_DOWN
|
||||
(1 << 13), // DPAD_LEFT -> DPAD_LEFT
|
||||
(1 << 14), // DPAD_RIGHT -> DPAD_RIGHT
|
||||
(1 << 4), // SELECT -> BACK
|
||||
(1 << 0), // DPAD_CENTER -> A
|
||||
(1 << 15), // L2 -> ??
|
||||
(1 << 16), // R2 -> ??
|
||||
(1 << 17), // C -> ??
|
||||
(1 << 18), // Z -> ??
|
||||
(1 << 20), // 1 -> ??
|
||||
(1 << 21), // 2 -> ??
|
||||
(1 << 22), // 3 -> ??
|
||||
(1 << 23), // 4 -> ??
|
||||
(1 << 24), // 5 -> ??
|
||||
(1 << 25), // 6 -> ??
|
||||
(1 << 26), // 7 -> ??
|
||||
(1 << 27), // 8 -> ??
|
||||
(1 << 28), // 9 -> ??
|
||||
(1 << 29), // 10 -> ??
|
||||
(1 << 30), // 11 -> ??
|
||||
(1 << 31), // 12 -> ??
|
||||
// We're out of room...
|
||||
0xFFFFFFFF, // 13 -> ??
|
||||
0xFFFFFFFF, // 14 -> ??
|
||||
0xFFFFFFFF, // 15 -> ??
|
||||
0xFFFFFFFF, // 16 -> ??
|
||||
};
|
||||
boolean[] has_keys = joystickDevice.hasKeys(keys);
|
||||
for (int i = 0; i < keys.length; ++i) {
|
||||
if (has_keys[i]) {
|
||||
button_mask |= masks[i];
|
||||
}
|
||||
}
|
||||
return button_mask;
|
||||
}
|
||||
}
|
||||
|
||||
class SDLHapticHandler_API26 extends SDLHapticHandler {
|
||||
@Override
|
||||
public void run(int device_id, float intensity, int length) {
|
||||
SDLHaptic haptic = getHaptic(device_id);
|
||||
if (haptic != null) {
|
||||
Log.d("SDL", "Rtest: Vibe with intensity " + intensity + " for " + length);
|
||||
if (intensity == 0.0f) {
|
||||
stop(device_id);
|
||||
return;
|
||||
}
|
||||
|
||||
int vibeValue = Math.round(intensity * 255);
|
||||
|
||||
if (vibeValue > 255) {
|
||||
vibeValue = 255;
|
||||
}
|
||||
if (vibeValue < 1) {
|
||||
stop(device_id);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
haptic.vib.vibrate(VibrationEffect.createOneShot(length, vibeValue));
|
||||
}
|
||||
catch (Exception e) {
|
||||
// Fall back to the generic method, which uses DEFAULT_AMPLITUDE, but works even if
|
||||
// something went horribly wrong with the Android 8.0 APIs.
|
||||
haptic.vib.vibrate(length);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SDLHapticHandler {
|
||||
|
||||
static class SDLHaptic {
|
||||
public int device_id;
|
||||
public String name;
|
||||
public Vibrator vib;
|
||||
}
|
||||
|
||||
private final ArrayList<SDLHaptic> mHaptics;
|
||||
|
||||
public SDLHapticHandler() {
|
||||
mHaptics = new ArrayList<SDLHaptic>();
|
||||
}
|
||||
|
||||
public void run(int device_id, float intensity, int length) {
|
||||
SDLHaptic haptic = getHaptic(device_id);
|
||||
if (haptic != null) {
|
||||
haptic.vib.vibrate(length);
|
||||
}
|
||||
}
|
||||
|
||||
public void stop(int device_id) {
|
||||
SDLHaptic haptic = getHaptic(device_id);
|
||||
if (haptic != null) {
|
||||
haptic.vib.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
public void pollHapticDevices() {
|
||||
|
||||
final int deviceId_VIBRATOR_SERVICE = 999999;
|
||||
boolean hasVibratorService = false;
|
||||
|
||||
int[] deviceIds = InputDevice.getDeviceIds();
|
||||
// It helps processing the device ids in reverse order
|
||||
// For example, in the case of the XBox 360 wireless dongle,
|
||||
// so the first controller seen by SDL matches what the receiver
|
||||
// considers to be the first controller
|
||||
|
||||
for (int i = deviceIds.length - 1; i > -1; i--) {
|
||||
SDLHaptic haptic = getHaptic(deviceIds[i]);
|
||||
if (haptic == null) {
|
||||
InputDevice device = InputDevice.getDevice(deviceIds[i]);
|
||||
Vibrator vib = device.getVibrator();
|
||||
if (vib != null) {
|
||||
if (vib.hasVibrator()) {
|
||||
haptic = new SDLHaptic();
|
||||
haptic.device_id = deviceIds[i];
|
||||
haptic.name = device.getName();
|
||||
haptic.vib = vib;
|
||||
mHaptics.add(haptic);
|
||||
SDLControllerManager.nativeAddHaptic(haptic.device_id, haptic.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Check VIBRATOR_SERVICE */
|
||||
Vibrator vib = (Vibrator) SDL.getContext().getSystemService(Context.VIBRATOR_SERVICE);
|
||||
if (vib != null) {
|
||||
hasVibratorService = vib.hasVibrator();
|
||||
|
||||
if (hasVibratorService) {
|
||||
SDLHaptic haptic = getHaptic(deviceId_VIBRATOR_SERVICE);
|
||||
if (haptic == null) {
|
||||
haptic = new SDLHaptic();
|
||||
haptic.device_id = deviceId_VIBRATOR_SERVICE;
|
||||
haptic.name = "VIBRATOR_SERVICE";
|
||||
haptic.vib = vib;
|
||||
mHaptics.add(haptic);
|
||||
SDLControllerManager.nativeAddHaptic(haptic.device_id, haptic.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Check removed devices */
|
||||
ArrayList<Integer> removedDevices = null;
|
||||
for (SDLHaptic haptic : mHaptics) {
|
||||
int device_id = haptic.device_id;
|
||||
int i;
|
||||
for (i = 0; i < deviceIds.length; i++) {
|
||||
if (device_id == deviceIds[i]) break;
|
||||
}
|
||||
|
||||
if (device_id != deviceId_VIBRATOR_SERVICE || !hasVibratorService) {
|
||||
if (i == deviceIds.length) {
|
||||
if (removedDevices == null) {
|
||||
removedDevices = new ArrayList<Integer>();
|
||||
}
|
||||
removedDevices.add(device_id);
|
||||
}
|
||||
} // else: don't remove the vibrator if it is still present
|
||||
}
|
||||
|
||||
if (removedDevices != null) {
|
||||
for (int device_id : removedDevices) {
|
||||
SDLControllerManager.nativeRemoveHaptic(device_id);
|
||||
for (int i = 0; i < mHaptics.size(); i++) {
|
||||
if (mHaptics.get(i).device_id == device_id) {
|
||||
mHaptics.remove(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected SDLHaptic getHaptic(int device_id) {
|
||||
for (SDLHaptic haptic : mHaptics) {
|
||||
if (haptic.device_id == device_id) {
|
||||
return haptic;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
class SDLGenericMotionListener_API12 implements View.OnGenericMotionListener {
|
||||
// Generic Motion (mouse hover, joystick...) events go here
|
||||
@Override
|
||||
public boolean onGenericMotion(View v, MotionEvent event) {
|
||||
float x, y;
|
||||
int action;
|
||||
|
||||
switch ( event.getSource() ) {
|
||||
case InputDevice.SOURCE_JOYSTICK:
|
||||
return SDLControllerManager.handleJoystickMotionEvent(event);
|
||||
|
||||
case InputDevice.SOURCE_MOUSE:
|
||||
action = event.getActionMasked();
|
||||
switch (action) {
|
||||
case MotionEvent.ACTION_SCROLL:
|
||||
x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);
|
||||
y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);
|
||||
SDLActivity.onNativeMouse(0, action, x, y, false);
|
||||
return true;
|
||||
|
||||
case MotionEvent.ACTION_HOVER_MOVE:
|
||||
x = event.getX(0);
|
||||
y = event.getY(0);
|
||||
|
||||
SDLActivity.onNativeMouse(0, action, x, y, false);
|
||||
return true;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Event was not managed
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean supportsRelativeMouse() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean inRelativeMode() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean setRelativeMouseEnabled(boolean enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void reclaimRelativeMouseModeIfNeeded()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public float getEventX(MotionEvent event) {
|
||||
return event.getX(0);
|
||||
}
|
||||
|
||||
public float getEventY(MotionEvent event) {
|
||||
return event.getY(0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class SDLGenericMotionListener_API24 extends SDLGenericMotionListener_API12 {
|
||||
// Generic Motion (mouse hover, joystick...) events go here
|
||||
|
||||
private boolean mRelativeModeEnabled;
|
||||
|
||||
@Override
|
||||
public boolean onGenericMotion(View v, MotionEvent event) {
|
||||
|
||||
// Handle relative mouse mode
|
||||
if (mRelativeModeEnabled) {
|
||||
if (event.getSource() == InputDevice.SOURCE_MOUSE) {
|
||||
int action = event.getActionMasked();
|
||||
if (action == MotionEvent.ACTION_HOVER_MOVE) {
|
||||
float x = event.getAxisValue(MotionEvent.AXIS_RELATIVE_X);
|
||||
float y = event.getAxisValue(MotionEvent.AXIS_RELATIVE_Y);
|
||||
SDLActivity.onNativeMouse(0, action, x, y, true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Event was not managed, call SDLGenericMotionListener_API12 method
|
||||
return super.onGenericMotion(v, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsRelativeMouse() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean inRelativeMode() {
|
||||
return mRelativeModeEnabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setRelativeMouseEnabled(boolean enabled) {
|
||||
mRelativeModeEnabled = enabled;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getEventX(MotionEvent event) {
|
||||
if (mRelativeModeEnabled) {
|
||||
return event.getAxisValue(MotionEvent.AXIS_RELATIVE_X);
|
||||
} else {
|
||||
return event.getX(0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getEventY(MotionEvent event) {
|
||||
if (mRelativeModeEnabled) {
|
||||
return event.getAxisValue(MotionEvent.AXIS_RELATIVE_Y);
|
||||
} else {
|
||||
return event.getY(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SDLGenericMotionListener_API26 extends SDLGenericMotionListener_API24 {
|
||||
// Generic Motion (mouse hover, joystick...) events go here
|
||||
private boolean mRelativeModeEnabled;
|
||||
|
||||
@Override
|
||||
public boolean onGenericMotion(View v, MotionEvent event) {
|
||||
float x, y;
|
||||
int action;
|
||||
|
||||
switch ( event.getSource() ) {
|
||||
case InputDevice.SOURCE_JOYSTICK:
|
||||
return SDLControllerManager.handleJoystickMotionEvent(event);
|
||||
|
||||
case InputDevice.SOURCE_MOUSE:
|
||||
// DeX desktop mouse cursor is a separate non-standard input type.
|
||||
case InputDevice.SOURCE_MOUSE | InputDevice.SOURCE_TOUCHSCREEN:
|
||||
action = event.getActionMasked();
|
||||
switch (action) {
|
||||
case MotionEvent.ACTION_SCROLL:
|
||||
x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);
|
||||
y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);
|
||||
SDLActivity.onNativeMouse(0, action, x, y, false);
|
||||
return true;
|
||||
|
||||
case MotionEvent.ACTION_HOVER_MOVE:
|
||||
x = event.getX(0);
|
||||
y = event.getY(0);
|
||||
SDLActivity.onNativeMouse(0, action, x, y, false);
|
||||
return true;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case InputDevice.SOURCE_MOUSE_RELATIVE:
|
||||
action = event.getActionMasked();
|
||||
switch (action) {
|
||||
case MotionEvent.ACTION_SCROLL:
|
||||
x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);
|
||||
y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);
|
||||
SDLActivity.onNativeMouse(0, action, x, y, false);
|
||||
return true;
|
||||
|
||||
case MotionEvent.ACTION_HOVER_MOVE:
|
||||
x = event.getX(0);
|
||||
y = event.getY(0);
|
||||
SDLActivity.onNativeMouse(0, action, x, y, true);
|
||||
return true;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Event was not managed
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsRelativeMouse() {
|
||||
return (!SDLActivity.isDeXMode() || Build.VERSION.SDK_INT >= 27 /* Android 8.1 (O_MR1) */);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean inRelativeMode() {
|
||||
return mRelativeModeEnabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setRelativeMouseEnabled(boolean enabled) {
|
||||
if (!SDLActivity.isDeXMode() || Build.VERSION.SDK_INT >= 27 /* Android 8.1 (O_MR1) */) {
|
||||
if (enabled) {
|
||||
SDLActivity.getContentView().requestPointerCapture();
|
||||
} else {
|
||||
SDLActivity.getContentView().releasePointerCapture();
|
||||
}
|
||||
mRelativeModeEnabled = enabled;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reclaimRelativeMouseModeIfNeeded()
|
||||
{
|
||||
if (mRelativeModeEnabled && !SDLActivity.isDeXMode()) {
|
||||
SDLActivity.getContentView().requestPointerCapture();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getEventX(MotionEvent event) {
|
||||
// Relative mouse in capture mode will only have relative for X/Y
|
||||
return event.getX(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getEventY(MotionEvent event) {
|
||||
// Relative mouse in capture mode will only have relative for X/Y
|
||||
return event.getY(0);
|
||||
}
|
||||
}
|
|
@ -1,405 +0,0 @@
|
|||
package org.libsdl.app;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.hardware.Sensor;
|
||||
import android.hardware.SensorEvent;
|
||||
import android.hardware.SensorEventListener;
|
||||
import android.hardware.SensorManager;
|
||||
import android.os.Build;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.view.Display;
|
||||
import android.view.InputDevice;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.Surface;
|
||||
import android.view.SurfaceHolder;
|
||||
import android.view.SurfaceView;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
|
||||
|
||||
/**
|
||||
SDLSurface. This is what we draw on, so we need to know when it's created
|
||||
in order to do anything useful.
|
||||
|
||||
Because of this, that's where we set up the SDL thread
|
||||
*/
|
||||
public class SDLSurface extends SurfaceView implements SurfaceHolder.Callback,
|
||||
View.OnKeyListener, View.OnTouchListener, SensorEventListener {
|
||||
|
||||
// Sensors
|
||||
protected SensorManager mSensorManager;
|
||||
protected Display mDisplay;
|
||||
|
||||
// Keep track of the surface size to normalize touch events
|
||||
protected float mWidth, mHeight;
|
||||
|
||||
// Is SurfaceView ready for rendering
|
||||
public boolean mIsSurfaceReady;
|
||||
|
||||
// Startup
|
||||
public SDLSurface(Context context) {
|
||||
super(context);
|
||||
getHolder().addCallback(this);
|
||||
|
||||
setFocusable(true);
|
||||
setFocusableInTouchMode(true);
|
||||
requestFocus();
|
||||
setOnKeyListener(this);
|
||||
setOnTouchListener(this);
|
||||
|
||||
mDisplay = ((WindowManager)context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
|
||||
mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
|
||||
|
||||
setOnGenericMotionListener(SDLActivity.getMotionListener());
|
||||
|
||||
// Some arbitrary defaults to avoid a potential division by zero
|
||||
mWidth = 1.0f;
|
||||
mHeight = 1.0f;
|
||||
|
||||
mIsSurfaceReady = false;
|
||||
}
|
||||
|
||||
public void handlePause() {
|
||||
enableSensor(Sensor.TYPE_ACCELEROMETER, false);
|
||||
}
|
||||
|
||||
public void handleResume() {
|
||||
setFocusable(true);
|
||||
setFocusableInTouchMode(true);
|
||||
requestFocus();
|
||||
setOnKeyListener(this);
|
||||
setOnTouchListener(this);
|
||||
enableSensor(Sensor.TYPE_ACCELEROMETER, true);
|
||||
}
|
||||
|
||||
public Surface getNativeSurface() {
|
||||
return getHolder().getSurface();
|
||||
}
|
||||
|
||||
// Called when we have a valid drawing surface
|
||||
@Override
|
||||
public void surfaceCreated(SurfaceHolder holder) {
|
||||
Log.v("SDL", "surfaceCreated()");
|
||||
SDLActivity.onNativeSurfaceCreated();
|
||||
}
|
||||
|
||||
// Called when we lose the surface
|
||||
@Override
|
||||
public void surfaceDestroyed(SurfaceHolder holder) {
|
||||
Log.v("SDL", "surfaceDestroyed()");
|
||||
|
||||
// Transition to pause, if needed
|
||||
SDLActivity.mNextNativeState = SDLActivity.NativeState.PAUSED;
|
||||
SDLActivity.handleNativeState();
|
||||
|
||||
mIsSurfaceReady = false;
|
||||
SDLActivity.onNativeSurfaceDestroyed();
|
||||
}
|
||||
|
||||
// Called when the surface is resized
|
||||
@Override
|
||||
public void surfaceChanged(SurfaceHolder holder,
|
||||
int format, int width, int height) {
|
||||
Log.v("SDL", "surfaceChanged()");
|
||||
|
||||
if (SDLActivity.mSingleton == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
mWidth = width;
|
||||
mHeight = height;
|
||||
int nDeviceWidth = width;
|
||||
int nDeviceHeight = height;
|
||||
try
|
||||
{
|
||||
if (Build.VERSION.SDK_INT >= 17 /* Android 4.2 (JELLY_BEAN_MR1) */) {
|
||||
DisplayMetrics realMetrics = new DisplayMetrics();
|
||||
mDisplay.getRealMetrics( realMetrics );
|
||||
nDeviceWidth = realMetrics.widthPixels;
|
||||
nDeviceHeight = realMetrics.heightPixels;
|
||||
}
|
||||
} catch(Exception ignored) {
|
||||
}
|
||||
|
||||
synchronized(SDLActivity.getContext()) {
|
||||
// In case we're waiting on a size change after going fullscreen, send a notification.
|
||||
SDLActivity.getContext().notifyAll();
|
||||
}
|
||||
|
||||
Log.v("SDL", "Window size: " + width + "x" + height);
|
||||
Log.v("SDL", "Device size: " + nDeviceWidth + "x" + nDeviceHeight);
|
||||
SDLActivity.nativeSetScreenResolution(width, height, nDeviceWidth, nDeviceHeight, mDisplay.getRefreshRate());
|
||||
SDLActivity.onNativeResize();
|
||||
|
||||
// Prevent a screen distortion glitch,
|
||||
// for instance when the device is in Landscape and a Portrait App is resumed.
|
||||
boolean skip = false;
|
||||
int requestedOrientation = SDLActivity.mSingleton.getRequestedOrientation();
|
||||
|
||||
if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT) {
|
||||
if (mWidth > mHeight) {
|
||||
skip = true;
|
||||
}
|
||||
} else if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE) {
|
||||
if (mWidth < mHeight) {
|
||||
skip = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Special Patch for Square Resolution: Black Berry Passport
|
||||
if (skip) {
|
||||
double min = Math.min(mWidth, mHeight);
|
||||
double max = Math.max(mWidth, mHeight);
|
||||
|
||||
if (max / min < 1.20) {
|
||||
Log.v("SDL", "Don't skip on such aspect-ratio. Could be a square resolution.");
|
||||
skip = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Don't skip in MultiWindow.
|
||||
if (skip) {
|
||||
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
|
||||
if (SDLActivity.mSingleton.isInMultiWindowMode()) {
|
||||
Log.v("SDL", "Don't skip in Multi-Window");
|
||||
skip = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (skip) {
|
||||
Log.v("SDL", "Skip .. Surface is not ready.");
|
||||
mIsSurfaceReady = false;
|
||||
return;
|
||||
}
|
||||
|
||||
/* If the surface has been previously destroyed by onNativeSurfaceDestroyed, recreate it here */
|
||||
SDLActivity.onNativeSurfaceChanged();
|
||||
|
||||
/* Surface is ready */
|
||||
mIsSurfaceReady = true;
|
||||
|
||||
SDLActivity.mNextNativeState = SDLActivity.NativeState.RESUMED;
|
||||
SDLActivity.handleNativeState();
|
||||
}
|
||||
|
||||
// Key events
|
||||
@Override
|
||||
public boolean onKey(View v, int keyCode, KeyEvent event) {
|
||||
return SDLActivity.handleKeyEvent(v, keyCode, event, null);
|
||||
}
|
||||
|
||||
// Touch events
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent event) {
|
||||
/* Ref: http://developer.android.com/training/gestures/multi.html */
|
||||
int touchDevId = event.getDeviceId();
|
||||
final int pointerCount = event.getPointerCount();
|
||||
int action = event.getActionMasked();
|
||||
int pointerFingerId;
|
||||
int i = -1;
|
||||
float x,y,p;
|
||||
|
||||
/*
|
||||
* Prevent id to be -1, since it's used in SDL internal for synthetic events
|
||||
* Appears when using Android emulator, eg:
|
||||
* adb shell input mouse tap 100 100
|
||||
* adb shell input touchscreen tap 100 100
|
||||
*/
|
||||
if (touchDevId < 0) {
|
||||
touchDevId -= 1;
|
||||
}
|
||||
|
||||
// 12290 = Samsung DeX mode desktop mouse
|
||||
// 12290 = 0x3002 = 0x2002 | 0x1002 = SOURCE_MOUSE | SOURCE_TOUCHSCREEN
|
||||
// 0x2 = SOURCE_CLASS_POINTER
|
||||
if (event.getSource() == InputDevice.SOURCE_MOUSE || event.getSource() == (InputDevice.SOURCE_MOUSE | InputDevice.SOURCE_TOUCHSCREEN)) {
|
||||
int mouseButton = 1;
|
||||
try {
|
||||
Object object = event.getClass().getMethod("getButtonState").invoke(event);
|
||||
if (object != null) {
|
||||
mouseButton = (Integer) object;
|
||||
}
|
||||
} catch(Exception ignored) {
|
||||
}
|
||||
|
||||
// We need to check if we're in relative mouse mode and get the axis offset rather than the x/y values
|
||||
// if we are. We'll leverage our existing mouse motion listener
|
||||
SDLGenericMotionListener_API12 motionListener = SDLActivity.getMotionListener();
|
||||
x = motionListener.getEventX(event);
|
||||
y = motionListener.getEventY(event);
|
||||
|
||||
SDLActivity.onNativeMouse(mouseButton, action, x, y, motionListener.inRelativeMode());
|
||||
} else {
|
||||
switch(action) {
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
for (i = 0; i < pointerCount; i++) {
|
||||
pointerFingerId = event.getPointerId(i);
|
||||
x = event.getX(i) / mWidth;
|
||||
y = event.getY(i) / mHeight;
|
||||
p = event.getPressure(i);
|
||||
if (p > 1.0f) {
|
||||
// may be larger than 1.0f on some devices
|
||||
// see the documentation of getPressure(i)
|
||||
p = 1.0f;
|
||||
}
|
||||
SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p);
|
||||
}
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_UP:
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
// Primary pointer up/down, the index is always zero
|
||||
i = 0;
|
||||
/* fallthrough */
|
||||
case MotionEvent.ACTION_POINTER_UP:
|
||||
case MotionEvent.ACTION_POINTER_DOWN:
|
||||
// Non primary pointer up/down
|
||||
if (i == -1) {
|
||||
i = event.getActionIndex();
|
||||
}
|
||||
|
||||
pointerFingerId = event.getPointerId(i);
|
||||
x = event.getX(i) / mWidth;
|
||||
y = event.getY(i) / mHeight;
|
||||
p = event.getPressure(i);
|
||||
if (p > 1.0f) {
|
||||
// may be larger than 1.0f on some devices
|
||||
// see the documentation of getPressure(i)
|
||||
p = 1.0f;
|
||||
}
|
||||
SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p);
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
for (i = 0; i < pointerCount; i++) {
|
||||
pointerFingerId = event.getPointerId(i);
|
||||
x = event.getX(i) / mWidth;
|
||||
y = event.getY(i) / mHeight;
|
||||
p = event.getPressure(i);
|
||||
if (p > 1.0f) {
|
||||
// may be larger than 1.0f on some devices
|
||||
// see the documentation of getPressure(i)
|
||||
p = 1.0f;
|
||||
}
|
||||
SDLActivity.onNativeTouch(touchDevId, pointerFingerId, MotionEvent.ACTION_UP, x, y, p);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Sensor events
|
||||
public void enableSensor(int sensortype, boolean enabled) {
|
||||
// TODO: This uses getDefaultSensor - what if we have >1 accels?
|
||||
if (enabled) {
|
||||
mSensorManager.registerListener(this,
|
||||
mSensorManager.getDefaultSensor(sensortype),
|
||||
SensorManager.SENSOR_DELAY_GAME, null);
|
||||
} else {
|
||||
mSensorManager.unregisterListener(this,
|
||||
mSensorManager.getDefaultSensor(sensortype));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAccuracyChanged(Sensor sensor, int accuracy) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSensorChanged(SensorEvent event) {
|
||||
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
|
||||
|
||||
// Since we may have an orientation set, we won't receive onConfigurationChanged events.
|
||||
// We thus should check here.
|
||||
int newOrientation;
|
||||
|
||||
float x, y;
|
||||
switch (mDisplay.getRotation()) {
|
||||
case Surface.ROTATION_90:
|
||||
x = -event.values[1];
|
||||
y = event.values[0];
|
||||
newOrientation = SDLActivity.SDL_ORIENTATION_LANDSCAPE;
|
||||
break;
|
||||
case Surface.ROTATION_270:
|
||||
x = event.values[1];
|
||||
y = -event.values[0];
|
||||
newOrientation = SDLActivity.SDL_ORIENTATION_LANDSCAPE_FLIPPED;
|
||||
break;
|
||||
case Surface.ROTATION_180:
|
||||
x = -event.values[0];
|
||||
y = -event.values[1];
|
||||
newOrientation = SDLActivity.SDL_ORIENTATION_PORTRAIT_FLIPPED;
|
||||
break;
|
||||
case Surface.ROTATION_0:
|
||||
default:
|
||||
x = event.values[0];
|
||||
y = event.values[1];
|
||||
newOrientation = SDLActivity.SDL_ORIENTATION_PORTRAIT;
|
||||
break;
|
||||
}
|
||||
|
||||
if (newOrientation != SDLActivity.mCurrentOrientation) {
|
||||
SDLActivity.mCurrentOrientation = newOrientation;
|
||||
SDLActivity.onNativeOrientationChanged(newOrientation);
|
||||
}
|
||||
|
||||
SDLActivity.onNativeAccel(-x / SensorManager.GRAVITY_EARTH,
|
||||
y / SensorManager.GRAVITY_EARTH,
|
||||
event.values[2] / SensorManager.GRAVITY_EARTH);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Captured pointer events for API 26.
|
||||
public boolean onCapturedPointerEvent(MotionEvent event)
|
||||
{
|
||||
int action = event.getActionMasked();
|
||||
|
||||
float x, y;
|
||||
switch (action) {
|
||||
case MotionEvent.ACTION_SCROLL:
|
||||
x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);
|
||||
y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);
|
||||
SDLActivity.onNativeMouse(0, action, x, y, false);
|
||||
return true;
|
||||
|
||||
case MotionEvent.ACTION_HOVER_MOVE:
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
x = event.getX(0);
|
||||
y = event.getY(0);
|
||||
SDLActivity.onNativeMouse(0, action, x, y, true);
|
||||
return true;
|
||||
|
||||
case MotionEvent.ACTION_BUTTON_PRESS:
|
||||
case MotionEvent.ACTION_BUTTON_RELEASE:
|
||||
|
||||
// Change our action value to what SDL's code expects.
|
||||
if (action == MotionEvent.ACTION_BUTTON_PRESS) {
|
||||
action = MotionEvent.ACTION_DOWN;
|
||||
} else { /* MotionEvent.ACTION_BUTTON_RELEASE */
|
||||
action = MotionEvent.ACTION_UP;
|
||||
}
|
||||
|
||||
x = event.getX(0);
|
||||
y = event.getY(0);
|
||||
int button = event.getButtonState();
|
||||
|
||||
SDLActivity.onNativeMouse(button, action, x, y, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="label">Luanti</string>
|
||||
<string name="loading">Lädt…</string>
|
||||
<string name="unzip_notification_title">Luanti lädt</string>
|
||||
<string name="unzip_notification_description">Weniger als 1 Minute…</string>
|
||||
<string name="ime_dialog_done">Fertig</string>
|
||||
<string name="no_web_browser">Kein Web-Browser gefunden</string>
|
||||
<string name="notification_channel_name">Allgemeine Benachrichtigung</string>
|
||||
<string name="notification_channel_description">Benachrichtigungen von Luanti</string>
|
||||
</resources>
|
|
@ -1,11 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="unzip_notification_description">Menos de 1 minuto…</string>
|
||||
<string name="ime_dialog_done">Hecho</string>
|
||||
<string name="no_web_browser">No se encontró ningún navegador web</string>
|
||||
<string name="loading">Cargando…</string>
|
||||
<string name="notification_channel_name">Notificación General</string>
|
||||
<string name="label">Luanti</string>
|
||||
<string name="notification_channel_description">Notificaciones de Luanti</string>
|
||||
<string name="unzip_notification_title">Cargando Luanti</string>
|
||||
</resources>
|
|
@ -1,11 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="no_web_browser">No se encontró ningún navegador web</string>
|
||||
<string name="loading">Cargando…</string>
|
||||
<string name="notification_channel_description">Notificaciones de Luanti</string>
|
||||
<string name="unzip_notification_description">Menos de 1 minuto…</string>
|
||||
<string name="ime_dialog_done">Hecho</string>
|
||||
<string name="label">Luanti</string>
|
||||
<string name="unzip_notification_title">Cargando Luanti</string>
|
||||
<string name="notification_channel_name">Notificación General</string>
|
||||
</resources>
|
|
@ -1,11 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="unzip_notification_description">کمتر از 1 دقیقه…</string>
|
||||
<string name="loading">در حال بارگذاری…</string>
|
||||
<string name="ime_dialog_done">انجام شد</string>
|
||||
<string name="label">لوآنتی</string>
|
||||
<string name="unzip_notification_title">در حال بارگذاری لوآنتی</string>
|
||||
<string name="notification_channel_name">نوتیفیکیشن عمومی</string>
|
||||
<string name="notification_channel_description">نوتیفیکیشن از لوآنتی</string>
|
||||
<string name="no_web_browser">هیچ مرورگری یافت نشد</string>
|
||||
</resources>
|
|
@ -1,11 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="label">Luanti</string>
|
||||
<string name="loading">Chargement…</string>
|
||||
<string name="notification_channel_name">Notification générale</string>
|
||||
<string name="notification_channel_description">Notifications de Luanti</string>
|
||||
<string name="unzip_notification_title">Chargement de Luanti</string>
|
||||
<string name="unzip_notification_description">Moins d\'une minute…</string>
|
||||
<string name="ime_dialog_done">Terminé</string>
|
||||
<string name="no_web_browser">Aucun navigateur web trouvé</string>
|
||||
</resources>
|
|
@ -1,11 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="label">Luanti</string>
|
||||
<string name="loading">Cargando…</string>
|
||||
<string name="notification_channel_name">Notificación xeral</string>
|
||||
<string name="notification_channel_description">Notificacións de Luanti</string>
|
||||
<string name="unzip_notification_title">Cargando Luanti</string>
|
||||
<string name="unzip_notification_description">Menos de 1 minuto…</string>
|
||||
<string name="ime_dialog_done">Feito</string>
|
||||
<string name="no_web_browser">Non se atopou ningún navegador web</string>
|
||||
</resources>
|
|
@ -1,11 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="loading">Betöltés…</string>
|
||||
<string name="notification_channel_name">Általános értesítés</string>
|
||||
<string name="notification_channel_description">Értesítések a Luanti-től</string>
|
||||
<string name="unzip_notification_title">Luanti betöltése…</string>
|
||||
<string name="ime_dialog_done">Kész</string>
|
||||
<string name="no_web_browser">Nem található webböngésző</string>
|
||||
<string name="label">Luanti</string>
|
||||
<string name="unzip_notification_description">Kevesebb, mint 1 perc…</string>
|
||||
</resources>
|
|
@ -1,11 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="no_web_browser">Tidak ditemukan peramban web</string>
|
||||
<string name="ime_dialog_done">Selesai</string>
|
||||
<string name="label">Luanti</string>
|
||||
<string name="loading">Memuat…</string>
|
||||
<string name="notification_channel_name">Pemberitahuan umum</string>
|
||||
<string name="unzip_notification_description">Kurang dari 1 menit…</string>
|
||||
<string name="notification_channel_description">Pemberitahuan dari Luanti</string>
|
||||
<string name="unzip_notification_title">Memuat Luanti…</string>
|
||||
</resources>
|
|
@ -1,11 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="label">Luanti</string>
|
||||
<string name="notification_channel_description">Pemberitahuan dari Luanti</string>
|
||||
<string name="unzip_notification_title">Memuatkan Luanti…</string>
|
||||
<string name="unzip_notification_description">Kurang dari 1 minit…</string>
|
||||
<string name="no_web_browser">Tiada pelayar sesawang dijumpai</string>
|
||||
<string name="loading">Memuatkan…</string>
|
||||
<string name="notification_channel_name">Pemberitahuan umum</string>
|
||||
<string name="ime_dialog_done">Selesai</string>
|
||||
</resources>
|
|
@ -1,11 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="label">Luanti</string>
|
||||
<string name="loading">Laster inn …</string>
|
||||
<string name="notification_channel_name">Generell merknad</string>
|
||||
<string name="notification_channel_description">Merknader fra Luanti</string>
|
||||
<string name="unzip_notification_description">Mindre enn ett minutt …</string>
|
||||
<string name="ime_dialog_done">Ferdig</string>
|
||||
<string name="no_web_browser">Fant ingen nettleser</string>
|
||||
<string name="unzip_notification_title">Laster inn Luanti …</string>
|
||||
</resources>
|
|
@ -1,11 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="unzip_notification_title">Загрузка Luanti</string>
|
||||
<string name="unzip_notification_description">Меньше чам за 1 минуту…</string>
|
||||
<string name="ime_dialog_done">Готово</string>
|
||||
<string name="label">Luаnti</string>
|
||||
<string name="notification_channel_description">Уведомления от Luanti</string>
|
||||
<string name="notification_channel_name">Основные уведомления</string>
|
||||
<string name="loading">Загрузка…</string>
|
||||
<string name="no_web_browser">Не найдено веб-браузера</string>
|
||||
</resources>
|
|
@ -1,11 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="label">Luanti</string>
|
||||
<string name="loading">Laddar…</string>
|
||||
<string name="unzip_notification_description">Mindre än 1 minut…</string>
|
||||
<string name="ime_dialog_done">Färdig</string>
|
||||
<string name="no_web_browser">Ingen webbläsare kunde hittas</string>
|
||||
<string name="notification_channel_name">Generell notis</string>
|
||||
<string name="notification_channel_description">Notiser från Luanti</string>
|
||||
<string name="unzip_notification_title">Laddar Luanti</string>
|
||||
</resources>
|
|
@ -1,2 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
|
@ -1,11 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="label">Luanti</string>
|
||||
<string name="no_web_browser">Браузерів не знайдено</string>
|
||||
<string name="loading">Завантаження…</string>
|
||||
<string name="notification_channel_name">Загальні сповіщення</string>
|
||||
<string name="unzip_notification_title">Luanti завантажується</string>
|
||||
<string name="unzip_notification_description">Менше за 1 хвилину…</string>
|
||||
<string name="ime_dialog_done">Готово</string>
|
||||
<string name="notification_channel_description">Сповіщення від Luanti</string>
|
||||
</resources>
|
|
@ -1,11 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="label">Luanti</string>
|
||||
<string name="loading">加载中…</string>
|
||||
<string name="notification_channel_name">一般通知</string>
|
||||
<string name="notification_channel_description">Luanti 的通知</string>
|
||||
<string name="unzip_notification_title">加载 Luanti 中</string>
|
||||
<string name="unzip_notification_description">不到1分钟…</string>
|
||||
<string name="ime_dialog_done">完成</string>
|
||||
<string name="no_web_browser">未找到网页浏览器</string>
|
||||
</resources>
|
|
@ -1,11 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="loading">載入中…</string>
|
||||
<string name="notification_channel_name">一般通知</string>
|
||||
<string name="notification_channel_description">Luanti 的通知</string>
|
||||
<string name="unzip_notification_description">不到1分鐘…</string>
|
||||
<string name="ime_dialog_done">完畢</string>
|
||||
<string name="no_web_browser">未找到任何網頁瀏覽器</string>
|
||||
<string name="label">Luanti</string>
|
||||
<string name="unzip_notification_title">載入Luanti中</string>
|
||||
</resources>
|
|
@ -1,11 +1,13 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="label">Luanti</string>
|
||||
|
||||
<string name="label">Minetest</string>
|
||||
<string name="loading">Loading…</string>
|
||||
<string name="notification_channel_name">General notification</string>
|
||||
<string name="notification_channel_description">Notifications from Luanti</string>
|
||||
<string name="unzip_notification_title">Loading Luanti</string>
|
||||
<string name="unzip_notification_description">Less than 1 minute…</string>
|
||||
<string name="migrating">Migrating save data from old install… (this may take a while)</string>
|
||||
<string name="not_granted">Required permission wasn\'t granted, Minetest can\'t run without it</string>
|
||||
<string name="notification_title">Loading Minetest</string>
|
||||
<string name="notification_description">Less than 1 minute…</string>
|
||||
<string name="ime_dialog_done">Done</string>
|
||||
<string name="no_web_browser">No web browser found</string>
|
||||
<string name="no_external_storage">External storage isn\'t available. If you use an SDCard, please reinsert it. Otherwise, try restarting your phone or contacting the Minetest developers</string>
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -1,21 +1,23 @@
|
|||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
|
||||
project.ext.set("versionMajor", 5) // Version Major
|
||||
project.ext.set("versionMinor", 12) // Version Minor
|
||||
project.ext.set("versionMinor", 6) // Version Minor
|
||||
project.ext.set("versionPatch", 0) // Version Patch
|
||||
// ^ keep in sync with cmake
|
||||
|
||||
project.ext.set("versionBuild", 0) // Version Build
|
||||
// ^ fourth version number to allow releasing Android-only fixes and beta versions
|
||||
project.ext.set("versionExtra", "") // Version Extra
|
||||
project.ext.set("versionCode", 42) // Android Version Code
|
||||
project.ext.set("developmentBuild", 0) // Whether it is a development build, or a release
|
||||
// NOTE: +2 after each release!
|
||||
// +1 for ARM and +1 for ARM64 APK's, because
|
||||
// each APK must have a larger `versionCode` than the previous
|
||||
|
||||
buildscript {
|
||||
ext.ndk_version = '27.2.12479018'
|
||||
ext.ndk_version = '23.2.8568313'
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:8.5.1'
|
||||
classpath 'com.android.tools.build:gradle:7.0.3'
|
||||
classpath 'de.undercouch:gradle-download-task:4.1.1'
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
|
@ -25,10 +27,11 @@ buildscript {
|
|||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
jcenter()
|
||||
}
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
delete rootProject.buildDir
|
||||
delete 'native/deps'
|
||||
}
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
<#if isLowMemory>
|
||||
org.gradle.jvmargs=-Xmx4G -XX:+HeapDumpOnOutOfMemoryError
|
||||
org.gradle.jvmargs=-Xmx4G -XX:MaxPermSize=2G -XX:+HeapDumpOnOutOfMemoryError
|
||||
<#else>
|
||||
org.gradle.jvmargs=-Xmx16G -XX:+HeapDumpOnOutOfMemoryError
|
||||
org.gradle.jvmargs=-Xmx16G -XX:MaxPermSize=8G -XX:+HeapDumpOnOutOfMemoryError
|
||||
</#if>
|
||||
org.gradle.daemon=true
|
||||
org.gradle.parallel=true
|
||||
org.gradle.parallel.threads=8
|
||||
org.gradle.configureondemand=true
|
||||
android.enableJetifier=false
|
||||
android.enableJetifier=true
|
||||
android.useAndroidX=true
|
||||
android.nonTransitiveRClass=false
|
||||
android.nonFinalResIds=false
|
||||
|
|
BIN
android/gradle/wrapper/gradle-wrapper.jar
vendored
BIN
android/gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
|
@ -1,7 +1,5 @@
|
|||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
|
||||
|
|
285
android/gradlew
vendored
285
android/gradlew
vendored
|
@ -1,7 +1,7 @@
|
|||
#!/bin/sh
|
||||
#!/usr/bin/env sh
|
||||
|
||||
#
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
# Copyright 2015 the original author or authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
|
@ -17,111 +17,78 @@
|
|||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||
# noncompliant, but you have some other compliant shell such as ksh or
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh Gradle
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
# * functions;
|
||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||
# * compound commands having a testable exit status, especially «case»;
|
||||
# * various built-in commands including «command», «set», and «ulimit».
|
||||
#
|
||||
# Important for patching:
|
||||
#
|
||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||
#
|
||||
# The "traditional" practice of packing multiple parameters into a
|
||||
# space-separated string is a well documented source of bugs and security
|
||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||
# options in "$@", and eventually passing that to Java.
|
||||
#
|
||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||
# see the in-line comments for details.
|
||||
#
|
||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
#
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
|
||||
# Resolve links: $0 may be a link
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
} >&2
|
||||
}
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
} >&2
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "$( uname )" in #(
|
||||
CYGWIN* ) cygwin=true ;; #(
|
||||
Darwin* ) darwin=true ;; #(
|
||||
MSYS* | MINGW* ) msys=true ;; #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD=$JAVA_HOME/bin/java
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
@ -130,120 +97,92 @@ Please set the JAVA_HOME variable in your environment to match the
|
|||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD=java
|
||||
if ! command -v java >/dev/null 2>&1
|
||||
then
|
||||
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
JAVACMD="java"
|
||||
command -v java >/dev/null || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --module-path (only if needed)
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
for arg do
|
||||
if
|
||||
case $arg in #(
|
||||
-*) false ;; # don't mess with options #(
|
||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||
[ -e "$t" ] ;; #(
|
||||
*) false ;;
|
||||
esac
|
||||
then
|
||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||
fi
|
||||
# Roll the args list around exactly as many times as the number of
|
||||
# args, so each arg winds up back in the position where it started, but
|
||||
# possibly modified.
|
||||
#
|
||||
# NB: a `for` loop captures its iteration list before it begins, so
|
||||
# changing the positional parameters here affects neither the number of
|
||||
# iterations, nor the values presented in `arg`.
|
||||
shift # remove old arg
|
||||
set -- "$@" "$arg" # push replacement arg
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=$((i+1))
|
||||
done
|
||||
case $i in
|
||||
(0) set -- ;;
|
||||
(1) set -- "$args0" ;;
|
||||
(2) set -- "$args0" "$args1" ;;
|
||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=$(save "$@")
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
if ! command -v xargs >/dev/null 2>&1
|
||||
then
|
||||
die "xargs is not available"
|
||||
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
||||
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
||||
cd "$(dirname "$0")"
|
||||
fi
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
#
|
||||
# In Bash we could simply go:
|
||||
#
|
||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||
# set -- "${ARGS[@]}" "$@"
|
||||
#
|
||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||
# character that might be a shell metacharacter, then use eval to reverse
|
||||
# that process (while maintaining the separation between arguments), and wrap
|
||||
# the whole thing up as a single "set" statement.
|
||||
#
|
||||
# This will of course break if any of these variables contains a newline or
|
||||
# an unmatched quote.
|
||||
#
|
||||
|
||||
eval "set -- $(
|
||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||
xargs -n1 |
|
||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
|
|
100
android/gradlew.bat
vendored
Normal file
100
android/gradlew.bat
vendored
Normal file
|
@ -0,0 +1,100 @@
|
|||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windows variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
|
@ -1,111 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="512"
|
||||
height="512"
|
||||
viewBox="0 0 135.46666 135.46667"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
|
||||
sodipodi:docname="exit_btn.svg"
|
||||
inkscape:export-filename="../../textures/base/pack/exit_btn.png"
|
||||
inkscape:export-xdpi="24.000002"
|
||||
inkscape:export-ydpi="24.000002"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<defs
|
||||
id="defs10" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#404040"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="0.84958349"
|
||||
inkscape:cx="-94.752312"
|
||||
inkscape:cy="291.31922"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer2"
|
||||
showgrid="true"
|
||||
units="px"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1001"
|
||||
inkscape:window-x="-9"
|
||||
inkscape:window-y="-9"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:pagecheckerboard="false"
|
||||
inkscape:snap-grids="true"
|
||||
inkscape:snap-page="true"
|
||||
showguides="false"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:deskcolor="#404040">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid16"
|
||||
spacingx="0.26458333"
|
||||
spacingy="0.26458333"
|
||||
empspacing="4"
|
||||
color="#40ff40"
|
||||
opacity="0.1254902"
|
||||
empcolor="#40ff40"
|
||||
empopacity="0.25098039" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<cc:license
|
||||
rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/" />
|
||||
</cc:Work>
|
||||
<cc:License
|
||||
rdf:about="http://creativecommons.org/licenses/by-sa/4.0/">
|
||||
<cc:permits
|
||||
rdf:resource="http://creativecommons.org/ns#Reproduction" />
|
||||
<cc:permits
|
||||
rdf:resource="http://creativecommons.org/ns#Distribution" />
|
||||
<cc:requires
|
||||
rdf:resource="http://creativecommons.org/ns#Notice" />
|
||||
<cc:requires
|
||||
rdf:resource="http://creativecommons.org/ns#Attribution" />
|
||||
<cc:permits
|
||||
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
|
||||
<cc:requires
|
||||
rdf:resource="http://creativecommons.org/ns#ShareAlike" />
|
||||
</cc:License>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer2"
|
||||
inkscape:label="Layer 2"
|
||||
style="display:inline">
|
||||
<path
|
||||
id="rect5028"
|
||||
style="display:inline;fill:none;stroke:#ffffff;stroke-width:5.99996;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none"
|
||||
d="m 78.052082,90.48746 v 17.4625 l -50.535415,4e-5 V 27.516667 l 50.535415,3.7e-5 v 17.462423"
|
||||
sodipodi:nodetypes="cccccc" />
|
||||
<path
|
||||
style="display:inline;fill:none;stroke:#ffffff;stroke-width:5.99996;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 101.49853,55.033202 12.69966,12.700052 -12.69966,12.699942"
|
||||
id="path4737"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="ccc" />
|
||||
<path
|
||||
style="display:inline;fill:none;stroke:#ffffff;stroke-width:6;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 113.36416,67.733332 H 59.484405"
|
||||
id="path4729"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cc" />
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 3.9 KiB |
194
android/icons/gear_icon.svg
Normal file
194
android/icons/gear_icon.svg
Normal file
|
@ -0,0 +1,194 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="512"
|
||||
height="512"
|
||||
viewBox="0 0 135.46666 135.46667"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
inkscape:version="0.92.1 r15371"
|
||||
sodipodi:docname="gear_icon.svg"
|
||||
inkscape:export-filename="/home/stu/Desktop/icons/png/gear_icon.png"
|
||||
inkscape:export-xdpi="24"
|
||||
inkscape:export-ydpi="24">
|
||||
<defs
|
||||
id="defs2">
|
||||
<marker
|
||||
style="overflow:visible"
|
||||
refY="0.0"
|
||||
refX="0.0"
|
||||
orient="auto"
|
||||
id="DistanceX">
|
||||
<path
|
||||
id="path4687"
|
||||
style="stroke:#000000; stroke-width:0.5"
|
||||
d="M 3,-3 L -3,3 M 0,-5 L 0,5" />
|
||||
</marker>
|
||||
<pattern
|
||||
y="0"
|
||||
x="0"
|
||||
width="8"
|
||||
patternUnits="userSpaceOnUse"
|
||||
id="Hatch"
|
||||
height="8">
|
||||
<path
|
||||
id="path4690"
|
||||
stroke-width="0.25"
|
||||
stroke="#000000"
|
||||
linecap="square"
|
||||
d="M8 4 l-4,4" />
|
||||
<path
|
||||
id="path4692"
|
||||
stroke-width="0.25"
|
||||
stroke="#000000"
|
||||
linecap="square"
|
||||
d="M6 2 l-4,4" />
|
||||
<path
|
||||
id="path4694"
|
||||
stroke-width="0.25"
|
||||
stroke="#000000"
|
||||
linecap="square"
|
||||
d="M4 0 l-4,4" />
|
||||
</pattern>
|
||||
<marker
|
||||
style="overflow:visible"
|
||||
refY="0.0"
|
||||
refX="0.0"
|
||||
orient="auto"
|
||||
id="DistanceX-3">
|
||||
<path
|
||||
id="path4756"
|
||||
style="stroke:#000000; stroke-width:0.5"
|
||||
d="M 3,-3 L -3,3 M 0,-5 L 0,5" />
|
||||
</marker>
|
||||
<pattern
|
||||
y="0"
|
||||
x="0"
|
||||
width="8"
|
||||
patternUnits="userSpaceOnUse"
|
||||
id="Hatch-6"
|
||||
height="8">
|
||||
<path
|
||||
id="path4759"
|
||||
stroke-width="0.25"
|
||||
stroke="#000000"
|
||||
linecap="square"
|
||||
d="M8 4 l-4,4" />
|
||||
<path
|
||||
id="path4761"
|
||||
stroke-width="0.25"
|
||||
stroke="#000000"
|
||||
linecap="square"
|
||||
d="M6 2 l-4,4" />
|
||||
<path
|
||||
id="path4763"
|
||||
stroke-width="0.25"
|
||||
stroke="#000000"
|
||||
linecap="square"
|
||||
d="M4 0 l-4,4" />
|
||||
</pattern>
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#404040"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="0.5"
|
||||
inkscape:cx="-308.644"
|
||||
inkscape:cy="171.10144"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="true"
|
||||
units="px"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1023"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="34"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:pagecheckerboard="false"
|
||||
inkscape:snap-page="true"
|
||||
inkscape:snap-grids="true"
|
||||
inkscape:snap-bbox="true"
|
||||
inkscape:snap-bbox-midpoints="false"
|
||||
inkscape:snap-object-midpoints="true"
|
||||
inkscape:snap-to-guides="false"
|
||||
inkscape:showpageshadow="false"
|
||||
inkscape:snap-smooth-nodes="true"
|
||||
inkscape:object-nodes="false">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid16"
|
||||
spacingx="2.1166666"
|
||||
spacingy="2.1166666"
|
||||
empspacing="2"
|
||||
color="#40ff40"
|
||||
opacity="0.1254902"
|
||||
empcolor="#40ff40"
|
||||
empopacity="0.25098039" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
<cc:license
|
||||
rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/" />
|
||||
</cc:Work>
|
||||
<cc:License
|
||||
rdf:about="http://creativecommons.org/licenses/by-sa/4.0/">
|
||||
<cc:permits
|
||||
rdf:resource="http://creativecommons.org/ns#Reproduction" />
|
||||
<cc:permits
|
||||
rdf:resource="http://creativecommons.org/ns#Distribution" />
|
||||
<cc:requires
|
||||
rdf:resource="http://creativecommons.org/ns#Notice" />
|
||||
<cc:requires
|
||||
rdf:resource="http://creativecommons.org/ns#Attribution" />
|
||||
<cc:permits
|
||||
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
|
||||
<cc:requires
|
||||
rdf:resource="http://creativecommons.org/ns#ShareAlike" />
|
||||
</cc:License>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-161.53332)">
|
||||
<g
|
||||
id="g4792"
|
||||
transform="matrix(0.68725287,0,0,0.65623884,67.477909,-509.24679)"
|
||||
style="fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:0.74041259;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1">
|
||||
<g
|
||||
id="g4772"
|
||||
inkscape:label="OpenJsCad"
|
||||
style="fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:0.74041259;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1">
|
||||
<path
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 256,80.943359 -8.28125,0.72461 -3.63477,5.410156 -5.61328,12.685547 -2.28906,9.259768 -0.35156,5.1875 0.0937,0.86133 0.70703,2.44726 0.60547,9.80664 -2.66602,5.47461 -21.5957,5.78711 -5.04492,-3.40625 -4.37696,-8.79687 -0.61132,-2.47071 -0.35157,-0.79297 -2.89648,-4.31836 -6.60938,-6.87304 -11.20507,-8.17969 -5.84961,-2.86719 -7.53516,3.51367 -6.80859,4.76954 -0.44336,6.50195 1.48047,13.79297 2.64453,9.16406 2.28906,4.66992 0.51172,0.69922 1.83398,1.76563 5.42774,8.18945 0.42773,6.07422 -15.81054,15.81445 -6.07032,-0.42773 -8.18945,-5.42969 -1.76367,-1.83399 -0.69922,-0.51171 -4.66992,-2.28907 -9.15821,-2.64843 -13.79297,-1.47852 -6.5,0.44141 -4.76757,6.8125 -3.51367,7.53515 2.86914,5.85157 8.17382,11.20703 6.87305,6.61132 4.31641,2.90039 0.79297,0.34961 2.4707,0.61133 8.79492,4.37696 3.4043,5.04687 -5.78516,21.60156 -5.47265,2.66602 -9.80469,-0.60547 -2.44727,-0.70703 -0.85937,-0.0918 -5.1875,0.35156 -9.257816,2.28907 -12.68164,5.61523 -5.408203,3.63281 -0.72461,8.28516 0.72461,8.28516 5.408203,3.63281 12.68164,5.61523 9.257816,2.28907 5.1875,0.35156 0.85937,-0.0918 2.44727,-0.70703 9.80469,-0.60547 5.47265,2.66602 5.78516,21.60156 -3.4043,5.04687 -8.79492,4.37696 -2.4707,0.61133 -0.79297,0.34961 -4.31641,2.90039 -6.87305,6.61132 -8.17382,11.20703 -2.86914,5.85157 3.51367,7.53515 4.76757,6.8125 6.5,0.44141 13.79297,-1.47852 9.15821,-2.64843 4.66992,-2.28907 0.69922,-0.50976 1.76367,-1.83594 8.18945,-5.42969 6.07032,-0.42773 15.81054,15.81445 -0.42773,6.07422 -5.42774,8.18945 -1.83398,1.76563 -0.51172,0.69922 -2.28906,4.66992 -2.64453,9.16406 -1.48047,13.79297 0.44336,6.50195 6.80859,4.76758 7.53516,3.51758 5.84961,-2.86914 11.20507,-8.17969 6.60938,-6.87304 2.89648,-4.31836 0.35157,-0.79297 0.61132,-2.47071 4.37696,-8.79687 5.04492,-3.40625 21.5957,5.78711 2.66602,5.47461 -0.60547,9.80664 -0.70703,2.44726 -0.0937,0.85938 0.35156,5.18945 2.28906,9.25977 5.61328,12.68555 3.63477,5.41015 8.28125,0.72461 8.28125,-0.72461 3.63477,-5.41015 5.61328,-12.68555 2.28906,-9.25977 0.35156,-5.18945 -0.0937,-0.85938 -0.70703,-2.44726 -0.60547,-9.80664 2.66602,-5.47461 21.5957,-5.78711 5.04492,3.40625 4.37696,8.79687 0.61132,2.47071 0.35157,0.79297 2.89648,4.31836 6.60938,6.87304 11.20507,8.17969 5.84961,2.86914 7.53516,-3.51758 6.80859,-4.76758 0.44336,-6.50195 -1.48047,-13.79297 -2.64453,-9.16406 -2.28906,-4.66992 -0.51172,-0.69922 -1.83398,-1.76563 -5.42774,-8.18945 -0.42773,-6.07422 15.81054,-15.81445 6.07032,0.42773 8.18945,5.42969 1.76367,1.83594 0.69922,0.50976 4.66992,2.28907 9.15821,2.64843 13.79297,1.47852 6.5,-0.44141 v -0.002 l 4.76757,-6.81055 3.51367,-7.53711 -2.86914,-5.85156 -8.17382,-11.20508 -6.87305,-6.61328 -4.31641,-2.89843 -0.79297,-0.34961 -2.4707,-0.61133 -8.79492,-4.37891 -3.4043,-5.04492 5.78516,-21.60352 5.47265,-2.66797 9.80469,0.60938 2.44727,0.70703 0.85937,0.0918 5.1875,-0.35156 9.25782,-2.28907 12.68164,-5.61718 5.4082,-3.63282 0.72461,-8.28515 -0.72461,-8.28321 -5.4082,-3.63476 -12.68164,-5.61524 -9.25782,-2.28711 -5.1875,-0.35351 -0.85937,0.0937 -2.44727,0.70508 -9.80469,0.60937 -5.47265,-2.66797 -5.78516,-21.59961 3.4043,-5.04882 8.79492,-4.37696 2.4707,-0.61133 0.79297,-0.35156 4.31641,-2.89844 6.87305,-6.61132 8.17382,-11.20703 2.86914,-5.85157 -3.51367,-7.53711 -4.76757,-6.81054 -6.5,-0.44336 -13.79297,1.48047 -9.15821,2.64648 -4.66992,2.28906 -0.69922,0.51172 -1.76367,1.83594 -8.18945,5.42773 -6.07032,0.42774 -15.81054,-15.81446 0.42773,-6.07226 5.42774,-8.18945 1.83398,-1.76563 0.51172,-0.69922 2.28906,-4.67187 2.64453,-9.16016 1.48047,-13.79492 -0.44336,-6.50195 -6.80859,-4.76954 -7.53516,-3.51562 -5.84961,2.87109 -11.20507,8.17578 -6.60938,6.875 -2.89648,4.31836 -0.35157,0.79102 -0.61132,2.47266 -4.37696,8.79687 -5.04492,3.4082 -21.5957,-5.79101 -2.66602,-5.47266 0.60547,-9.80664 0.70703,-2.44726 0.0937,-0.85938 -0.35156,-5.19141 -2.28906,-9.259761 -5.61328,-12.683594 -3.63477,-5.412109 z m 0,97.111331 A 77.946197,77.946197 0 0 1 333.94531,256 77.946197,77.946197 0 0 1 256,333.94531 77.946197,77.946197 0 0 1 178.05469,256 77.946197,77.946197 0 0 1 256,178.05469 Z"
|
||||
transform="matrix(0.38495268,0,0,0.40318156,-98.176247,1022.1341)"
|
||||
id="path4768"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
<g
|
||||
id="g4774"
|
||||
inkscape:label="0"
|
||||
style="fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:0.74041259;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 9.7 KiB |
|
@ -2,23 +2,23 @@
|
|||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="512"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="256"
|
||||
height="512"
|
||||
viewBox="0 0 135.46666 135.46667"
|
||||
viewBox="0 0 67.73333 135.46667"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25)"
|
||||
sodipodi:docname="overflow_btn.svg"
|
||||
inkscape:export-filename="../../textures/base/pack/overflow_btn.png"
|
||||
inkscape:version="0.92.1 r15371"
|
||||
sodipodi:docname="rare_controls.svg"
|
||||
inkscape:export-filename="/home/stu/Desktop/icons/png/rare_controls.png"
|
||||
inkscape:export-xdpi="24.000002"
|
||||
inkscape:export-ydpi="24.000002"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
inkscape:export-ydpi="24.000002">
|
||||
<defs
|
||||
id="defs2">
|
||||
<filter
|
||||
|
@ -397,24 +397,22 @@
|
|||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="0.98994949"
|
||||
inkscape:cx="41.921331"
|
||||
inkscape:cy="163.13964"
|
||||
inkscape:zoom="0.7"
|
||||
inkscape:cx="-59.862018"
|
||||
inkscape:cy="260.34663"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:current-layer="layer2"
|
||||
showgrid="true"
|
||||
units="px"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1011"
|
||||
inkscape:window-height="1023"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="32"
|
||||
inkscape:window-y="34"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:pagecheckerboard="false"
|
||||
inkscape:snap-grids="true"
|
||||
inkscape:snap-page="true"
|
||||
showguides="false"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:deskcolor="#d1d1d1">
|
||||
showguides="false">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid16"
|
||||
|
@ -424,11 +422,7 @@
|
|||
color="#40ff40"
|
||||
opacity="0.1254902"
|
||||
empcolor="#40ff40"
|
||||
empopacity="0.25098039"
|
||||
originx="0"
|
||||
originy="0"
|
||||
units="px"
|
||||
visible="true" />
|
||||
empopacity="0.25098039" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata5">
|
||||
|
@ -438,6 +432,7 @@
|
|||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
<cc:license
|
||||
rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/" />
|
||||
</cc:Work>
|
||||
|
@ -501,27 +496,26 @@
|
|||
x="264.65997"
|
||||
y="124.10143"
|
||||
style="fill:none;fill-opacity:1;stroke:#ffffff;stroke-opacity:1" /></flowRegion><flowPara
|
||||
id="flowPara4724" /></flowRoot>
|
||||
id="flowPara4724" /></flowRoot> <rect
|
||||
style="display:inline;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect5231-9"
|
||||
width="25.4"
|
||||
height="25.400003"
|
||||
x="21.166666"
|
||||
y="101.6" />
|
||||
<rect
|
||||
style="display:inline;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:5.38756;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
style="display:inline;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect5231-9-7"
|
||||
width="25.4"
|
||||
height="25.400003"
|
||||
x="21.166666"
|
||||
y="55.033333" />
|
||||
<rect
|
||||
style="display:inline;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect5231-9-5"
|
||||
width="96.212502"
|
||||
height="0.61243516"
|
||||
x="19.62678"
|
||||
y="33.575779" />
|
||||
<rect
|
||||
style="display:inline;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:5.38755;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect5231-9-5-7"
|
||||
width="96.212448"
|
||||
height="0.6124543"
|
||||
x="19.627108"
|
||||
y="67.442772" />
|
||||
<rect
|
||||
style="display:inline;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:5.38755;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect5231-9-5-7-5"
|
||||
width="96.212448"
|
||||
height="0.6124543"
|
||||
x="19.627108"
|
||||
y="101.30978" />
|
||||
width="25.4"
|
||||
height="25.400003"
|
||||
x="21.166664"
|
||||
y="8.4666681" />
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
@ -2,24 +2,27 @@ apply plugin: 'com.android.library'
|
|||
apply plugin: 'de.undercouch.download'
|
||||
|
||||
android {
|
||||
compileSdkVersion 30
|
||||
buildToolsVersion '30.0.3'
|
||||
ndkVersion "$ndk_version"
|
||||
defaultConfig {
|
||||
minSdkVersion 21
|
||||
compileSdk 34
|
||||
targetSdkVersion 34
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 30
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
arguments "-DANDROID_STL=c++_shared",
|
||||
"-DENABLE_CURL=1", "-DENABLE_SOUND=1",
|
||||
"-DENABLE_GETTEXT=1",
|
||||
"-DBUILD_UNITTESTS=0", "-DENABLE_UPDATE_CHECKER=0"
|
||||
ndkBuild {
|
||||
arguments '-j' + Runtime.getRuntime().availableProcessors(),
|
||||
"versionMajor=${versionMajor}",
|
||||
"versionMinor=${versionMinor}",
|
||||
"versionPatch=${versionPatch}",
|
||||
"versionExtra=${versionExtra}",
|
||||
"developmentBuild=${developmentBuild}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
path file("../../CMakeLists.txt")
|
||||
ndkBuild {
|
||||
path file('jni/Android.mk')
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -34,39 +37,33 @@ android {
|
|||
|
||||
buildTypes {
|
||||
release {
|
||||
externalNativeBuild {
|
||||
ndkBuild {
|
||||
arguments 'NDEBUG=1'
|
||||
}
|
||||
}
|
||||
|
||||
ndk {
|
||||
debugSymbolLevel 'FULL'
|
||||
debugSymbolLevel 'SYMBOL_TABLE'
|
||||
}
|
||||
}
|
||||
}
|
||||
namespace 'net.minetest'
|
||||
}
|
||||
|
||||
// get precompiled deps
|
||||
def depsDir = new File(buildDir.parent, 'deps')
|
||||
if (new File(depsDir, 'armeabi-v7a').exists()) {
|
||||
task getDeps {
|
||||
doLast { logger.lifecycle('Using existing deps from {}', depsDir) }
|
||||
}
|
||||
} else {
|
||||
task downloadDeps(type: Download) {
|
||||
def depsZip = new File(buildDir, 'deps.zip')
|
||||
task downloadDeps(type: Download) {
|
||||
src 'https://github.com/minetest/minetest_android_deps/releases/download/latest/deps.zip'
|
||||
dest new File(buildDir, 'deps.zip')
|
||||
overwrite false
|
||||
}
|
||||
|
||||
src 'https://github.com/luanti-org/luanti_android_deps/releases/download/latest/deps-lite.zip'
|
||||
dest depsZip
|
||||
overwrite false
|
||||
|
||||
task getDeps(dependsOn: downloadDeps, type: Copy) {
|
||||
depsDir.mkdir()
|
||||
from zipTree(depsZip)
|
||||
into depsDir
|
||||
doFirst { logger.lifecycle('Extracting to {}', depsDir) }
|
||||
}
|
||||
task getDeps(dependsOn: downloadDeps, type: Copy) {
|
||||
def deps = new File(buildDir.parent, 'deps')
|
||||
if (!deps.exists()) {
|
||||
deps.mkdir()
|
||||
from zipTree(downloadDeps.dest)
|
||||
into deps
|
||||
}
|
||||
}
|
||||
|
||||
preBuild.dependsOn getDeps
|
||||
|
||||
clean {
|
||||
delete new File(buildDir.parent, 'deps')
|
||||
}
|
||||
|
|
301
android/native/jni/Android.mk
Normal file
301
android/native/jni/Android.mk
Normal file
|
@ -0,0 +1,301 @@
|
|||
LOCAL_PATH := $(call my-dir)/..
|
||||
|
||||
#LOCAL_ADDRESS_SANITIZER:=true
|
||||
#USE_BUILTIN_LUA:=true
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := Curl
|
||||
LOCAL_SRC_FILES := deps/$(APP_ABI)/Curl/libcurl.a
|
||||
include $(PREBUILT_STATIC_LIBRARY)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := libmbedcrypto
|
||||
LOCAL_SRC_FILES := deps/$(APP_ABI)/Curl/libmbedcrypto.a
|
||||
include $(PREBUILT_STATIC_LIBRARY)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := libmbedtls
|
||||
LOCAL_SRC_FILES := deps/$(APP_ABI)/Curl/libmbedtls.a
|
||||
include $(PREBUILT_STATIC_LIBRARY)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := libmbedx509
|
||||
LOCAL_SRC_FILES := deps/$(APP_ABI)/Curl/libmbedx509.a
|
||||
include $(PREBUILT_STATIC_LIBRARY)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := Freetype
|
||||
LOCAL_SRC_FILES := deps/$(APP_ABI)/Freetype/libfreetype.a
|
||||
include $(PREBUILT_STATIC_LIBRARY)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := Iconv
|
||||
LOCAL_SRC_FILES := deps/$(APP_ABI)/Iconv/libiconv.a
|
||||
include $(PREBUILT_STATIC_LIBRARY)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := libcharset
|
||||
LOCAL_SRC_FILES := deps/$(APP_ABI)/Iconv/libcharset.a
|
||||
include $(PREBUILT_STATIC_LIBRARY)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := Irrlicht
|
||||
LOCAL_SRC_FILES := deps/$(APP_ABI)/Irrlicht/libIrrlichtMt.a
|
||||
include $(PREBUILT_STATIC_LIBRARY)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := Irrlicht-libpng
|
||||
LOCAL_SRC_FILES := deps/$(APP_ABI)/Irrlicht/libpng.a
|
||||
include $(PREBUILT_STATIC_LIBRARY)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := Irrlicht-libjpeg
|
||||
LOCAL_SRC_FILES := deps/$(APP_ABI)/Irrlicht/libjpeg.a
|
||||
include $(PREBUILT_STATIC_LIBRARY)
|
||||
|
||||
ifndef USE_BUILTIN_LUA
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := LuaJIT
|
||||
LOCAL_SRC_FILES := deps/$(APP_ABI)/LuaJIT/libluajit.a
|
||||
include $(PREBUILT_STATIC_LIBRARY)
|
||||
|
||||
endif
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := OpenAL
|
||||
LOCAL_SRC_FILES := deps/$(APP_ABI)/OpenAL-Soft/libopenal.a
|
||||
include $(PREBUILT_STATIC_LIBRARY)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := Gettext
|
||||
LOCAL_SRC_FILES := deps/$(APP_ABI)/Gettext/libintl.a
|
||||
include $(PREBUILT_STATIC_LIBRARY)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := SQLite3
|
||||
LOCAL_SRC_FILES := deps/$(APP_ABI)/SQLite/libsqlite3.a
|
||||
include $(PREBUILT_STATIC_LIBRARY)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := Vorbis
|
||||
LOCAL_SRC_FILES := deps/$(APP_ABI)/Vorbis/libvorbis.a
|
||||
include $(PREBUILT_STATIC_LIBRARY)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := libvorbisfile
|
||||
LOCAL_SRC_FILES := deps/$(APP_ABI)/Vorbis/libvorbisfile.a
|
||||
include $(PREBUILT_STATIC_LIBRARY)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := libogg
|
||||
LOCAL_SRC_FILES := deps/$(APP_ABI)/Vorbis/libogg.a
|
||||
include $(PREBUILT_STATIC_LIBRARY)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := Zstd
|
||||
LOCAL_SRC_FILES := deps/$(APP_ABI)/Zstd/libzstd.a
|
||||
include $(PREBUILT_STATIC_LIBRARY)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := Minetest
|
||||
|
||||
LOCAL_CFLAGS += \
|
||||
-DJSONCPP_NO_LOCALE_SUPPORT \
|
||||
-DHAVE_TOUCHSCREENGUI \
|
||||
-DENABLE_GLES=1 \
|
||||
-DUSE_CURL=1 \
|
||||
-DUSE_SOUND=1 \
|
||||
-DUSE_LEVELDB=0 \
|
||||
-DUSE_GETTEXT=1 \
|
||||
-DVERSION_MAJOR=${versionMajor} \
|
||||
-DVERSION_MINOR=${versionMinor} \
|
||||
-DVERSION_PATCH=${versionPatch} \
|
||||
-DVERSION_EXTRA=${versionExtra} \
|
||||
-DDEVELOPMENT_BUILD=${developmentBuild} \
|
||||
$(GPROF_DEF)
|
||||
|
||||
ifdef USE_BUILTIN_LUA
|
||||
LOCAL_CFLAGS += -DUSE_LUAJIT=0
|
||||
else
|
||||
LOCAL_CFLAGS += -DUSE_LUAJIT=1
|
||||
endif
|
||||
|
||||
ifdef NDEBUG
|
||||
LOCAL_CFLAGS += -DNDEBUG=1
|
||||
endif
|
||||
|
||||
ifdef GPROF
|
||||
GPROF_DEF := -DGPROF
|
||||
PROFILER_LIBS := android-ndk-profiler
|
||||
LOCAL_CFLAGS += -pg
|
||||
endif
|
||||
|
||||
LOCAL_C_INCLUDES := \
|
||||
../../src \
|
||||
../../src/script \
|
||||
../../lib/gmp \
|
||||
../../lib/jsoncpp \
|
||||
deps/$(APP_ABI)/Curl/include \
|
||||
deps/$(APP_ABI)/Freetype/include/freetype2 \
|
||||
deps/$(APP_ABI)/Irrlicht/include \
|
||||
deps/$(APP_ABI)/Gettext/include \
|
||||
deps/$(APP_ABI)/Iconv/include \
|
||||
deps/$(APP_ABI)/OpenAL-Soft/include \
|
||||
deps/$(APP_ABI)/SQLite/include \
|
||||
deps/$(APP_ABI)/Vorbis/include \
|
||||
deps/$(APP_ABI)/Zstd/include
|
||||
|
||||
ifdef USE_BUILTIN_LUA
|
||||
LOCAL_C_INCLUDES += \
|
||||
../../lib/lua/src \
|
||||
../../lib/bitop
|
||||
else
|
||||
LOCAL_C_INCLUDES += deps/$(APP_ABI)/LuaJIT/include
|
||||
endif
|
||||
|
||||
LOCAL_SRC_FILES := \
|
||||
$(wildcard ../../src/client/*.cpp) \
|
||||
$(wildcard ../../src/client/*/*.cpp) \
|
||||
$(wildcard ../../src/content/*.cpp) \
|
||||
../../src/database/database.cpp \
|
||||
../../src/database/database-dummy.cpp \
|
||||
../../src/database/database-files.cpp \
|
||||
../../src/database/database-sqlite3.cpp \
|
||||
$(wildcard ../../src/gui/*.cpp) \
|
||||
$(wildcard ../../src/irrlicht_changes/*.cpp) \
|
||||
$(wildcard ../../src/mapgen/*.cpp) \
|
||||
$(wildcard ../../src/network/*.cpp) \
|
||||
$(wildcard ../../src/script/*.cpp) \
|
||||
$(wildcard ../../src/script/*/*.cpp) \
|
||||
$(wildcard ../../src/server/*.cpp) \
|
||||
$(wildcard ../../src/threading/*.cpp) \
|
||||
$(wildcard ../../src/util/*.c) \
|
||||
$(wildcard ../../src/util/*.cpp) \
|
||||
../../src/ban.cpp \
|
||||
../../src/chat.cpp \
|
||||
../../src/clientiface.cpp \
|
||||
../../src/collision.cpp \
|
||||
../../src/content_mapnode.cpp \
|
||||
../../src/content_nodemeta.cpp \
|
||||
../../src/convert_json.cpp \
|
||||
../../src/craftdef.cpp \
|
||||
../../src/debug.cpp \
|
||||
../../src/defaultsettings.cpp \
|
||||
../../src/emerge.cpp \
|
||||
../../src/environment.cpp \
|
||||
../../src/face_position_cache.cpp \
|
||||
../../src/filesys.cpp \
|
||||
../../src/gettext.cpp \
|
||||
../../src/httpfetch.cpp \
|
||||
../../src/hud.cpp \
|
||||
../../src/inventory.cpp \
|
||||
../../src/inventorymanager.cpp \
|
||||
../../src/itemdef.cpp \
|
||||
../../src/itemstackmetadata.cpp \
|
||||
../../src/light.cpp \
|
||||
../../src/log.cpp \
|
||||
../../src/main.cpp \
|
||||
../../src/map.cpp \
|
||||
../../src/map_settings_manager.cpp \
|
||||
../../src/mapblock.cpp \
|
||||
../../src/mapnode.cpp \
|
||||
../../src/mapsector.cpp \
|
||||
../../src/metadata.cpp \
|
||||
../../src/modchannels.cpp \
|
||||
../../src/nameidmapping.cpp \
|
||||
../../src/nodedef.cpp \
|
||||
../../src/nodemetadata.cpp \
|
||||
../../src/nodetimer.cpp \
|
||||
../../src/noise.cpp \
|
||||
../../src/objdef.cpp \
|
||||
../../src/object_properties.cpp \
|
||||
../../src/particles.cpp \
|
||||
../../src/pathfinder.cpp \
|
||||
../../src/player.cpp \
|
||||
../../src/porting.cpp \
|
||||
../../src/porting_android.cpp \
|
||||
../../src/profiler.cpp \
|
||||
../../src/raycast.cpp \
|
||||
../../src/reflowscan.cpp \
|
||||
../../src/remoteplayer.cpp \
|
||||
../../src/rollback.cpp \
|
||||
../../src/rollback_interface.cpp \
|
||||
../../src/serialization.cpp \
|
||||
../../src/server.cpp \
|
||||
../../src/serverenvironment.cpp \
|
||||
../../src/serverlist.cpp \
|
||||
../../src/settings.cpp \
|
||||
../../src/staticobject.cpp \
|
||||
../../src/texture_override.cpp \
|
||||
../../src/tileanimation.cpp \
|
||||
../../src/tool.cpp \
|
||||
../../src/translation.cpp \
|
||||
../../src/version.cpp \
|
||||
../../src/voxel.cpp \
|
||||
../../src/voxelalgorithms.cpp
|
||||
|
||||
# Built-in Lua
|
||||
ifdef USE_BUILTIN_LUA
|
||||
LOCAL_SRC_FILES += \
|
||||
../../lib/lua/src/lapi.c \
|
||||
../../lib/lua/src/lauxlib.c \
|
||||
../../lib/lua/src/lbaselib.c \
|
||||
../../lib/lua/src/lcode.c \
|
||||
../../lib/lua/src/ldblib.c \
|
||||
../../lib/lua/src/ldebug.c \
|
||||
../../lib/lua/src/ldo.c \
|
||||
../../lib/lua/src/ldump.c \
|
||||
../../lib/lua/src/lfunc.c \
|
||||
../../lib/lua/src/lgc.c \
|
||||
../../lib/lua/src/linit.c \
|
||||
../../lib/lua/src/liolib.c \
|
||||
../../lib/lua/src/llex.c \
|
||||
../../lib/lua/src/lmathlib.c \
|
||||
../../lib/lua/src/lmem.c \
|
||||
../../lib/lua/src/loadlib.c \
|
||||
../../lib/lua/src/lobject.c \
|
||||
../../lib/lua/src/lopcodes.c \
|
||||
../../lib/lua/src/loslib.c \
|
||||
../../lib/lua/src/lparser.c \
|
||||
../../lib/lua/src/lstate.c \
|
||||
../../lib/lua/src/lstring.c \
|
||||
../../lib/lua/src/lstrlib.c \
|
||||
../../lib/lua/src/ltable.c \
|
||||
../../lib/lua/src/ltablib.c \
|
||||
../../lib/lua/src/ltm.c \
|
||||
../../lib/lua/src/lundump.c \
|
||||
../../lib/lua/src/lvm.c \
|
||||
../../lib/lua/src/lzio.c \
|
||||
../../lib/bitop/bit.c
|
||||
endif
|
||||
|
||||
# GMP
|
||||
LOCAL_SRC_FILES += ../../lib/gmp/mini-gmp.c
|
||||
|
||||
# JSONCPP
|
||||
LOCAL_SRC_FILES += ../../lib/jsoncpp/jsoncpp.cpp
|
||||
|
||||
LOCAL_STATIC_LIBRARIES += \
|
||||
Curl libmbedcrypto libmbedtls libmbedx509 \
|
||||
Freetype \
|
||||
Iconv libcharset \
|
||||
Irrlicht Irrlicht-libpng Irrlicht-libjpeg \
|
||||
OpenAL \
|
||||
Gettext \
|
||||
SQLite3 \
|
||||
Vorbis libvorbisfile libogg \
|
||||
Zstd
|
||||
ifndef USE_BUILTIN_LUA
|
||||
LOCAL_STATIC_LIBRARIES += LuaJIT
|
||||
endif
|
||||
LOCAL_STATIC_LIBRARIES += android_native_app_glue $(PROFILER_LIBS)
|
||||
|
||||
LOCAL_LDLIBS := -lEGL -lGLESv1_CM -lGLESv2 -landroid -lOpenSLES -lz
|
||||
|
||||
include $(BUILD_SHARED_LIBRARY)
|
||||
|
||||
ifdef GPROF
|
||||
$(call import-module,android-ndk-profiler)
|
||||
endif
|
||||
$(call import-module,android/native_app_glue)
|
32
android/native/jni/Application.mk
Normal file
32
android/native/jni/Application.mk
Normal file
|
@ -0,0 +1,32 @@
|
|||
APP_PLATFORM := ${APP_PLATFORM}
|
||||
APP_ABI := ${TARGET_ABI}
|
||||
APP_STL := c++_shared
|
||||
NDK_TOOLCHAIN_VERSION := clang
|
||||
APP_SHORT_COMMANDS := true
|
||||
APP_MODULES := Minetest
|
||||
|
||||
APP_CPPFLAGS := -O2 -fvisibility=hidden
|
||||
|
||||
ifeq ($(APP_ABI),armeabi-v7a)
|
||||
APP_CPPFLAGS += -mfloat-abi=softfp -mfpu=vfpv3-d16 -mthumb
|
||||
endif
|
||||
|
||||
ifeq ($(APP_ABI),x86)
|
||||
APP_CPPFLAGS += -mssse3 -mfpmath=sse -funroll-loops
|
||||
endif
|
||||
|
||||
ifndef NDEBUG
|
||||
APP_CPPFLAGS := -g -Og -fno-omit-frame-pointer
|
||||
endif
|
||||
|
||||
APP_CFLAGS := $(APP_CPPFLAGS) -Wno-inconsistent-missing-override -Wno-parentheses-equality
|
||||
APP_CXXFLAGS := $(APP_CPPFLAGS) -fexceptions -frtti -std=gnu++14
|
||||
APP_LDFLAGS := -Wl,--no-warn-mismatch,--gc-sections,--icf=safe
|
||||
|
||||
ifeq ($(APP_ABI),arm64-v8a)
|
||||
APP_LDFLAGS := -Wl,--no-warn-mismatch,--gc-sections
|
||||
endif
|
||||
|
||||
ifndef NDEBUG
|
||||
APP_LDFLAGS :=
|
||||
endif
|
|
@ -1 +1 @@
|
|||
<manifest />
|
||||
<manifest package="net.minetest" />
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
rootProject.name = "Luanti"
|
||||
rootProject.name = "Minetest"
|
||||
include ':app', ':native'
|
||||
|
|
|
@ -13,12 +13,9 @@ end
|
|||
|
||||
-- Import a bunch of individual files from builtin/game/
|
||||
local gamepath = core.get_builtin_path() .. "game" .. DIR_DELIM
|
||||
local commonpath = core.get_builtin_path() .. "common" .. DIR_DELIM
|
||||
|
||||
local builtin_shared = {}
|
||||
|
||||
dofile(gamepath .. "constants.lua")
|
||||
assert(loadfile(commonpath .. "item_s.lua"))(builtin_shared)
|
||||
dofile(gamepath .. "item_s.lua")
|
||||
dofile(gamepath .. "misc_s.lua")
|
||||
dofile(gamepath .. "features.lua")
|
||||
dofile(gamepath .. "voxelarea.lua")
|
||||
|
@ -36,16 +33,11 @@ do
|
|||
setmetatable(v, {__newindex = {}})
|
||||
-- Reassemble the other tables
|
||||
if v.type == "node" then
|
||||
getmetatable(v).__index = all.nodedef_default
|
||||
all.registered_nodes[k] = v
|
||||
elseif v.type == "craft" then
|
||||
getmetatable(v).__index = all.craftitemdef_default
|
||||
elseif v.type == "craftitem" then
|
||||
all.registered_craftitems[k] = v
|
||||
elseif v.type == "tool" then
|
||||
getmetatable(v).__index = all.tooldef_default
|
||||
all.registered_tools[k] = v
|
||||
else
|
||||
getmetatable(v).__index = all.noneitemdef_default
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -65,5 +57,3 @@ setmetatable(core.registered_items, alias_metatable)
|
|||
setmetatable(core.registered_nodes, alias_metatable)
|
||||
setmetatable(core.registered_craftitems, alias_metatable)
|
||||
setmetatable(core.registered_tools, alias_metatable)
|
||||
|
||||
builtin_shared.cache_content_ids()
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
-- Minetest: builtin/client/chatcommands.lua
|
||||
|
||||
core.register_on_sending_chat_message(function(message)
|
||||
if message:sub(1,2) == ".." then
|
||||
return false
|
||||
|
|
15
builtin/client/death_formspec.lua
Normal file
15
builtin/client/death_formspec.lua
Normal file
|
@ -0,0 +1,15 @@
|
|||
-- CSM death formspec. Only used when clientside modding is enabled, otherwise
|
||||
-- handled by the engine.
|
||||
|
||||
core.register_on_death(function()
|
||||
local formspec = "size[11,5.5]bgcolor[#320000b4;true]" ..
|
||||
"label[4.85,1.35;" .. fgettext("You died") ..
|
||||
"]button_exit[4,3;3,0.5;btn_respawn;".. fgettext("Respawn") .."]"
|
||||
core.show_formspec("bultin:death", formspec)
|
||||
end)
|
||||
|
||||
core.register_on_formspec_input(function(formname, fields)
|
||||
if formname == "bultin:death" then
|
||||
core.send_respawn()
|
||||
end
|
||||
end)
|
|
@ -1,15 +1,12 @@
|
|||
-- Minetest: builtin/client/init.lua
|
||||
local scriptpath = core.get_builtin_path()
|
||||
local clientpath = scriptpath.."client"..DIR_DELIM
|
||||
local commonpath = scriptpath.."common"..DIR_DELIM
|
||||
|
||||
local builtin_shared = {}
|
||||
|
||||
assert(loadfile(commonpath .. "register.lua"))(builtin_shared)
|
||||
assert(loadfile(clientpath .. "register.lua"))(builtin_shared)
|
||||
dofile(clientpath .. "register.lua")
|
||||
dofile(commonpath .. "after.lua")
|
||||
dofile(commonpath .. "mod_storage.lua")
|
||||
dofile(commonpath .. "chatcommands.lua")
|
||||
dofile(commonpath .. "information_formspecs.lua")
|
||||
dofile(clientpath .. "chatcommands.lua")
|
||||
dofile(clientpath .. "death_formspec.lua")
|
||||
dofile(clientpath .. "misc.lua")
|
||||
assert(loadfile(commonpath .. "item_s.lua"))({}) -- Just for push/read node functions
|
||||
|
|
|
@ -5,14 +5,3 @@ function core.setting_get_pos(name)
|
|||
end
|
||||
return core.string_to_pos(value)
|
||||
end
|
||||
|
||||
|
||||
-- old non-method sound functions
|
||||
|
||||
function core.sound_stop(handle, ...)
|
||||
return handle:stop(...)
|
||||
end
|
||||
|
||||
function core.sound_fade(handle, ...)
|
||||
return handle:fade(...)
|
||||
end
|
||||
|
|
|
@ -1,6 +1,68 @@
|
|||
local builtin_shared = ...
|
||||
core.callback_origins = {}
|
||||
|
||||
local make_registration = builtin_shared.make_registration
|
||||
local getinfo = debug.getinfo
|
||||
debug.getinfo = nil
|
||||
|
||||
--- Runs given callbacks.
|
||||
--
|
||||
-- Note: this function is also called from C++
|
||||
-- @tparam table callbacks a table with registered callbacks, like `core.registered_on_*`
|
||||
-- @tparam number mode a RunCallbacksMode, as defined in src/script/common/c_internal.h
|
||||
-- @param ... arguments for the callback
|
||||
-- @return depends on mode
|
||||
function core.run_callbacks(callbacks, mode, ...)
|
||||
assert(type(callbacks) == "table")
|
||||
local cb_len = #callbacks
|
||||
if cb_len == 0 then
|
||||
if mode == 2 or mode == 3 then
|
||||
return true
|
||||
elseif mode == 4 or mode == 5 then
|
||||
return false
|
||||
end
|
||||
end
|
||||
local ret
|
||||
for i = 1, cb_len do
|
||||
local cb_ret = callbacks[i](...)
|
||||
|
||||
if mode == 0 and i == 1 or mode == 1 and i == cb_len then
|
||||
ret = cb_ret
|
||||
elseif mode == 2 then
|
||||
if not cb_ret or i == 1 then
|
||||
ret = cb_ret
|
||||
end
|
||||
elseif mode == 3 then
|
||||
if cb_ret then
|
||||
return cb_ret
|
||||
end
|
||||
ret = cb_ret
|
||||
elseif mode == 4 then
|
||||
if (cb_ret and not ret) or i == 1 then
|
||||
ret = cb_ret
|
||||
end
|
||||
elseif mode == 5 and cb_ret then
|
||||
return cb_ret
|
||||
end
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
--
|
||||
-- Callback registration
|
||||
--
|
||||
|
||||
local function make_registration()
|
||||
local t = {}
|
||||
local registerfunc = function(func)
|
||||
t[#t + 1] = func
|
||||
core.callback_origins[func] = {
|
||||
mod = core.get_current_modname() or "??",
|
||||
name = getinfo(1, "n").name or "??"
|
||||
}
|
||||
--local origin = core.callback_origins[func]
|
||||
--print(origin.name .. ": " .. origin.mod .. " registering cbk " .. tostring(func))
|
||||
end
|
||||
return t, registerfunc
|
||||
end
|
||||
|
||||
core.registered_globalsteps, core.register_globalstep = make_registration()
|
||||
core.registered_on_mods_loaded, core.register_on_mods_loaded = make_registration()
|
||||
|
|
|
@ -1,116 +1,4 @@
|
|||
-- This is an implementation of a job sheduling mechanism. It guarantees that
|
||||
-- coexisting jobs will execute primarily in order of least expiry, and
|
||||
-- secondarily in order of first registration.
|
||||
|
||||
-- These functions implement an intrusive singly linked list of one or more
|
||||
-- elements where the first element has a pointer to the last. The next pointer
|
||||
-- is stored with key list_next. The pointer to the last is with key list_end.
|
||||
|
||||
local function list_init(first)
|
||||
first.list_end = first
|
||||
end
|
||||
|
||||
local function list_append(first, append)
|
||||
first.list_end.list_next = append
|
||||
first.list_end = append
|
||||
end
|
||||
|
||||
local function list_append_list(first, first_append)
|
||||
first.list_end.list_next = first_append
|
||||
first.list_end = first_append.list_end
|
||||
end
|
||||
|
||||
-- The jobs are stored in a map from expiration times to linked lists of jobs
|
||||
-- as above. The expiration times are also stored in an array representing a
|
||||
-- binary min heap, which is a particular arrangement of binary tree. A parent
|
||||
-- at index i has children at indices i*2 and i*2+1. Out-of-bounds indices
|
||||
-- represent nonexistent children. A parent is never greater than its children.
|
||||
-- This structure means that, if there is at least one job, the next expiration
|
||||
-- time is the first item in the array.
|
||||
|
||||
-- Push element on a binary min-heap,
|
||||
-- "bubbling up" the element by swapping with larger parents.
|
||||
local function heap_push(heap, element)
|
||||
local index = #heap + 1
|
||||
while index > 1 do
|
||||
local parent_index = math.floor(index / 2)
|
||||
local parent = heap[parent_index]
|
||||
if element < parent then
|
||||
heap[index] = parent
|
||||
index = parent_index
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
heap[index] = element
|
||||
end
|
||||
|
||||
-- Pop smallest element from the heap,
|
||||
-- "sinking down" the last leaf on the last layer of the heap
|
||||
-- by swapping with the smaller child.
|
||||
local function heap_pop(heap)
|
||||
local removed_element = heap[1]
|
||||
local length = #heap
|
||||
local element = heap[length]
|
||||
heap[length] = nil
|
||||
length = length - 1
|
||||
if length > 0 then
|
||||
local index = 1
|
||||
while true do
|
||||
local old_index = index
|
||||
local smaller_element = element
|
||||
local left_index = index * 2
|
||||
local right_index = index * 2 + 1
|
||||
if left_index <= length then
|
||||
local left_element = heap[left_index]
|
||||
if left_element < smaller_element then
|
||||
index = left_index
|
||||
smaller_element = left_element
|
||||
end
|
||||
end
|
||||
if right_index <= length then
|
||||
if heap[right_index] < smaller_element then
|
||||
index = right_index
|
||||
end
|
||||
end
|
||||
if old_index ~= index then
|
||||
heap[old_index] = heap[index]
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
heap[index] = element
|
||||
end
|
||||
return removed_element
|
||||
end
|
||||
|
||||
local job_map = {}
|
||||
local expiries = {}
|
||||
|
||||
-- Adds an individual job with the given expiry.
|
||||
-- The worst-case complexity is O(log n), where n is the number of distinct
|
||||
-- expiration times.
|
||||
local function add_job(expiry, job)
|
||||
local list = job_map[expiry]
|
||||
if list then
|
||||
list_append(list, job)
|
||||
else
|
||||
list_init(job)
|
||||
job_map[expiry] = job
|
||||
heap_push(expiries, expiry)
|
||||
end
|
||||
end
|
||||
|
||||
-- Removes the next expiring jobs and returns the linked list of them.
|
||||
-- The worst-case complexity is O(log n), where n is the number of distinct
|
||||
-- expiration times.
|
||||
local function remove_first_jobs()
|
||||
local removed_expiry = heap_pop(expiries)
|
||||
local removed = job_map[removed_expiry]
|
||||
job_map[removed_expiry] = nil
|
||||
return removed
|
||||
end
|
||||
|
||||
local jobs = {}
|
||||
local time = 0.0
|
||||
local time_next = math.huge
|
||||
|
||||
|
@ -121,54 +9,42 @@ core.register_globalstep(function(dtime)
|
|||
return
|
||||
end
|
||||
|
||||
-- Remove the expired jobs.
|
||||
local expired = remove_first_jobs()
|
||||
time_next = math.huge
|
||||
|
||||
-- Remove other expired jobs and append them to the list.
|
||||
while true do
|
||||
time_next = expiries[1] or math.huge
|
||||
if time_next > time then
|
||||
break
|
||||
-- Iterate backwards so that we miss any new timers added by
|
||||
-- a timer callback.
|
||||
for i = #jobs, 1, -1 do
|
||||
local job = jobs[i]
|
||||
if time >= job.expire then
|
||||
core.set_last_run_mod(job.mod_origin)
|
||||
job.func(unpack(job.arg))
|
||||
local jobs_l = #jobs
|
||||
jobs[i] = jobs[jobs_l]
|
||||
jobs[jobs_l] = nil
|
||||
elseif job.expire < time_next then
|
||||
time_next = job.expire
|
||||
end
|
||||
list_append_list(expired, remove_first_jobs())
|
||||
end
|
||||
|
||||
-- Run the callbacks afterward to prevent infinite loops with core.after(0, ...).
|
||||
local last_expired = expired.list_end
|
||||
while true do
|
||||
core.set_last_run_mod(expired.mod_origin)
|
||||
expired.func(unpack(expired.args, 1, expired.args.n))
|
||||
if expired == last_expired then
|
||||
break
|
||||
end
|
||||
expired = expired.list_next
|
||||
end
|
||||
end)
|
||||
|
||||
local job_metatable = {__index = {}}
|
||||
|
||||
local function dummy_func() end
|
||||
function job_metatable.__index:cancel()
|
||||
self.func = dummy_func
|
||||
self.args = {n = 0}
|
||||
end
|
||||
|
||||
function core.after(after, func, ...)
|
||||
assert(tonumber(after) and not core.is_nan(after) and type(func) == "function",
|
||||
"Invalid core.after invocation")
|
||||
|
||||
assert(tonumber(after) and type(func) == "function",
|
||||
"Invalid minetest.after invocation")
|
||||
local expire = time + after
|
||||
local new_job = {
|
||||
mod_origin = core.get_last_run_mod(),
|
||||
func = func,
|
||||
args = {
|
||||
n = select("#", ...),
|
||||
...
|
||||
},
|
||||
expire = expire,
|
||||
arg = {...},
|
||||
mod_origin = core.get_last_run_mod(),
|
||||
}
|
||||
|
||||
local expiry = time + after
|
||||
add_job(expiry, new_job)
|
||||
time_next = math.min(time_next, expiry)
|
||||
jobs[#jobs + 1] = new_job
|
||||
time_next = math.min(time_next, expire)
|
||||
|
||||
return setmetatable(new_job, job_metatable)
|
||||
return {
|
||||
cancel = function()
|
||||
new_job.func = function() end
|
||||
new_job.args = {}
|
||||
end
|
||||
}
|
||||
end
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
-- Minetest: builtin/common/chatcommands.lua
|
||||
|
||||
-- For server-side translations (if INIT == "game")
|
||||
-- Otherwise, use core.gettext
|
||||
local S = core.get_translator("__builtin")
|
||||
|
@ -87,7 +89,7 @@ local function do_help_cmd(name, param)
|
|||
if #args > 1 then
|
||||
return false, S("Too many arguments, try using just /help <command>")
|
||||
end
|
||||
local use_gui = INIT == "client" or core.get_player_by_name(name)
|
||||
local use_gui = INIT ~= "client" and core.get_player_by_name(name)
|
||||
use_gui = use_gui and not opts:find("t")
|
||||
|
||||
if #args == 0 and not use_gui then
|
||||
|
@ -161,8 +163,8 @@ end
|
|||
|
||||
if INIT == "client" then
|
||||
core.register_chatcommand("help", {
|
||||
params = core.gettext("[all | <cmd>] [-t]"),
|
||||
description = core.gettext("Get help for commands (-t: output in chat)"),
|
||||
params = core.gettext("[all | <cmd>]"),
|
||||
description = core.gettext("Get help for commands"),
|
||||
func = function(param)
|
||||
return do_help_cmd(nil, param)
|
||||
end,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
--Luanti
|
||||
--Minetest
|
||||
--Copyright (C) 2013 sapier
|
||||
--
|
||||
--This program is free software; you can redistribute it and/or modify
|
||||
|
@ -20,8 +20,8 @@
|
|||
-- TODO code cleanup --
|
||||
-- Generic implementation of a filter/sortable list --
|
||||
-- Usage: --
|
||||
-- Filterlist needs to be initialized on creation. To achieve this you need --
|
||||
-- to pass following functions: --
|
||||
-- Filterlist needs to be initialized on creation. To achieve this you need to --
|
||||
-- pass following functions: --
|
||||
-- raw_fct() (mandatory): --
|
||||
-- function returning a table containing the elements to be filtered --
|
||||
-- compare_fct(element1,element2) (mandatory): --
|
||||
|
@ -31,7 +31,7 @@
|
|||
-- filter_fct(element,filtercriteria) (optional) --
|
||||
-- function returning true/false if filtercriteria met to element --
|
||||
-- fetch_param (optional) --
|
||||
-- parameter passed to raw_fct to acquire correct raw data --
|
||||
-- parameter passed to raw_fct to aquire correct raw data --
|
||||
-- --
|
||||
--------------------------------------------------------------------------------
|
||||
filterlist = {}
|
||||
|
|
|
@ -22,7 +22,6 @@ local LIST_FORMSPEC_DESCRIPTION = [[
|
|||
|
||||
local F = core.formspec_escape
|
||||
local S = core.get_translator("__builtin")
|
||||
local check_player_privs = core.check_player_privs
|
||||
|
||||
|
||||
-- CHAT COMMANDS FORMSPEC
|
||||
|
@ -58,23 +57,19 @@ local function build_chatcommands_formspec(name, sel, copy)
|
|||
.. "any entry in the list.").. "\n" ..
|
||||
S("Double-click to copy the entry to the chat history.")
|
||||
|
||||
local privs = core.get_player_privs(name)
|
||||
for i, data in ipairs(mod_cmds) do
|
||||
rows[#rows + 1] = COLOR_BLUE .. ",0," .. F(data[1]) .. ","
|
||||
for j, cmds in ipairs(data[2]) do
|
||||
local has_priv = INIT == "client" or check_player_privs(name, cmds[2].privs)
|
||||
local has_priv = privs[cmds[2].privs]
|
||||
rows[#rows + 1] = ("%s,1,%s,%s"):format(
|
||||
has_priv and COLOR_GREEN or COLOR_GRAY,
|
||||
cmds[1], F(cmds[2].params))
|
||||
if sel == #rows then
|
||||
description = cmds[2].description
|
||||
if copy then
|
||||
local msg = S("Command: @1 @2",
|
||||
core.colorize("#0FF", (INIT == "client" and "." or "/") .. cmds[1]), cmds[2].params)
|
||||
if INIT == "client" then
|
||||
core.display_chat_message(msg)
|
||||
else
|
||||
core.chat_send_player(name, msg)
|
||||
end
|
||||
core.chat_send_player(name, S("Command: @1 @2",
|
||||
core.colorize("#0FF", "/" .. cmds[1]), cmds[2].params))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -116,46 +111,26 @@ end
|
|||
|
||||
|
||||
-- DETAILED CHAT COMMAND INFORMATION
|
||||
if INIT == "client" then
|
||||
core.register_on_formspec_input(function(formname, fields)
|
||||
if formname ~= "__builtin:help_cmds" or fields.quit then
|
||||
return
|
||||
end
|
||||
|
||||
local event = core.explode_table_event(fields.list)
|
||||
if event.type ~= "INV" then
|
||||
core.show_formspec("__builtin:help_cmds",
|
||||
build_chatcommands_formspec(nil, event.row, event.type == "DCL"))
|
||||
end
|
||||
end)
|
||||
else
|
||||
core.register_on_player_receive_fields(function(player, formname, fields)
|
||||
if formname ~= "__builtin:help_cmds" or fields.quit then
|
||||
return
|
||||
end
|
||||
core.register_on_player_receive_fields(function(player, formname, fields)
|
||||
if formname ~= "__builtin:help_cmds" or fields.quit then
|
||||
return
|
||||
end
|
||||
|
||||
local event = core.explode_table_event(fields.list)
|
||||
if event.type ~= "INV" then
|
||||
local name = player:get_player_name()
|
||||
core.show_formspec(name, "__builtin:help_cmds",
|
||||
build_chatcommands_formspec(name, event.row, event.type == "DCL"))
|
||||
end
|
||||
end)
|
||||
end
|
||||
local event = core.explode_table_event(fields.list)
|
||||
if event.type ~= "INV" then
|
||||
local name = player:get_player_name()
|
||||
core.show_formspec(name, "__builtin:help_cmds",
|
||||
build_chatcommands_formspec(name, event.row, event.type == "DCL"))
|
||||
end
|
||||
end)
|
||||
|
||||
function core.show_general_help_formspec(name)
|
||||
if INIT == "client" then
|
||||
core.show_formspec("__builtin:help_cmds",
|
||||
build_chatcommands_formspec(name))
|
||||
else
|
||||
core.show_formspec(name, "__builtin:help_cmds",
|
||||
build_chatcommands_formspec(name))
|
||||
end
|
||||
core.show_formspec(name, "__builtin:help_cmds",
|
||||
build_chatcommands_formspec(name))
|
||||
end
|
||||
|
||||
if INIT ~= "client" then
|
||||
function core.show_privs_help_formspec(name)
|
||||
core.show_formspec(name, "__builtin:help_privs",
|
||||
build_privs_formspec(name))
|
||||
end
|
||||
function core.show_privs_help_formspec(name)
|
||||
core.show_formspec(name, "__builtin:help_privs",
|
||||
build_privs_formspec(name))
|
||||
end
|
||||
|
|
|
@ -1,41 +0,0 @@
|
|||
--[[
|
||||
Math utils.
|
||||
--]]
|
||||
|
||||
function math.hypot(x, y)
|
||||
return math.sqrt(x * x + y * y)
|
||||
end
|
||||
|
||||
function math.sign(x, tolerance)
|
||||
tolerance = tolerance or 0
|
||||
if x > tolerance then
|
||||
return 1
|
||||
elseif x < -tolerance then
|
||||
return -1
|
||||
end
|
||||
return 0
|
||||
end
|
||||
|
||||
function math.factorial(x)
|
||||
assert(x % 1 == 0 and x >= 0, "factorial expects a non-negative integer")
|
||||
if x >= 171 then
|
||||
-- 171! is greater than the biggest double, no need to calculate
|
||||
return math.huge
|
||||
end
|
||||
local v = 1
|
||||
for k = 2, x do
|
||||
v = v * k
|
||||
end
|
||||
return v
|
||||
end
|
||||
|
||||
function math.round(x)
|
||||
if x < 0 then
|
||||
local int = math.ceil(x)
|
||||
local frac = x - int
|
||||
return int - ((frac <= -0.5) and 1 or 0)
|
||||
end
|
||||
local int = math.floor(x)
|
||||
local frac = x - int
|
||||
return int + ((frac >= 0.5) and 1 or 0)
|
||||
end
|
|
@ -1,19 +0,0 @@
|
|||
-- Registered metatables, used by the C++ packer
|
||||
local known_metatables = {}
|
||||
function core.register_portable_metatable(name, mt)
|
||||
assert(type(name) == "string", ("attempt to use %s value as metatable name"):format(type(name)))
|
||||
assert(type(mt) == "table", ("attempt to register a %s value as metatable"):format(type(mt)))
|
||||
assert(known_metatables[name] == nil or known_metatables[name] == mt,
|
||||
("attempt to override metatable %s"):format(name))
|
||||
known_metatables[name] = mt
|
||||
known_metatables[mt] = name
|
||||
end
|
||||
core.known_metatables = known_metatables
|
||||
|
||||
function core.register_async_metatable(...)
|
||||
core.log("deprecated", "core.register_async_metatable is deprecated. " ..
|
||||
"Use core.register_portable_metatable instead.")
|
||||
return core.register_portable_metatable(...)
|
||||
end
|
||||
|
||||
core.register_portable_metatable("__builtin:vector", vector.metatable)
|
|
@ -1,7 +1,8 @@
|
|||
-- Minetest: builtin/misc_helpers.lua
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Localize functions to avoid table lookups (better performance).
|
||||
local string_sub, string_find = string.sub, string.find
|
||||
local math = math
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
local function basic_dump(o)
|
||||
|
@ -169,9 +170,6 @@ end
|
|||
--------------------------------------------------------------------------------
|
||||
function string.split(str, delim, include_empty, max_splits, sep_is_pattern)
|
||||
delim = delim or ","
|
||||
if delim == "" then
|
||||
error("string.split separator is empty", 2)
|
||||
end
|
||||
max_splits = max_splits or -2
|
||||
local items = {}
|
||||
local pos, len = 1, #str
|
||||
|
@ -205,18 +203,46 @@ function table.indexof(list, val)
|
|||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
function table.keyof(tb, val)
|
||||
for k, v in pairs(tb) do
|
||||
if v == val then
|
||||
return k
|
||||
end
|
||||
end
|
||||
return nil
|
||||
function string:trim()
|
||||
return self:match("^%s*(.-)%s*$")
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
function string:trim()
|
||||
return self:match("^%s*(.-)%s*$")
|
||||
function math.hypot(x, y)
|
||||
return math.sqrt(x * x + y * y)
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
function math.sign(x, tolerance)
|
||||
tolerance = tolerance or 0
|
||||
if x > tolerance then
|
||||
return 1
|
||||
elseif x < -tolerance then
|
||||
return -1
|
||||
end
|
||||
return 0
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
function math.factorial(x)
|
||||
assert(x % 1 == 0 and x >= 0, "factorial expects a non-negative integer")
|
||||
if x >= 171 then
|
||||
-- 171! is greater than the biggest double, no need to calculate
|
||||
return math.huge
|
||||
end
|
||||
local v = 1
|
||||
for k = 2, x do
|
||||
v = v * k
|
||||
end
|
||||
return v
|
||||
end
|
||||
|
||||
|
||||
function math.round(x)
|
||||
if x >= 0 then
|
||||
return math.floor(x + 0.5)
|
||||
end
|
||||
return math.ceil(x - 0.5)
|
||||
end
|
||||
|
||||
local formspec_escapes = {
|
||||
|
@ -224,22 +250,11 @@ local formspec_escapes = {
|
|||
["["] = "\\[",
|
||||
["]"] = "\\]",
|
||||
[";"] = "\\;",
|
||||
[","] = "\\,",
|
||||
["$"] = "\\$",
|
||||
[","] = "\\,"
|
||||
}
|
||||
function core.formspec_escape(text)
|
||||
-- Use explicit character set instead of dot here because it doubles the performance
|
||||
return text and string.gsub(text, "[\\%[%];,$]", formspec_escapes)
|
||||
end
|
||||
|
||||
|
||||
local hypertext_escapes = {
|
||||
["\\"] = "\\\\",
|
||||
["<"] = "\\<",
|
||||
[">"] = "\\>",
|
||||
}
|
||||
function core.hypertext_escape(text)
|
||||
return text and text:gsub("[\\<>]", hypertext_escapes)
|
||||
return text and string.gsub(text, "[\\%[%];,]", formspec_escapes)
|
||||
end
|
||||
|
||||
|
||||
|
@ -471,9 +486,6 @@ end
|
|||
|
||||
|
||||
function table.insert_all(t, other)
|
||||
if table.move then -- LuaJIT
|
||||
return table.move(other, 1, #other, #t + 1, t)
|
||||
end
|
||||
for i=1, #other do
|
||||
t[#t + 1] = other[i]
|
||||
end
|
||||
|
@ -507,6 +519,18 @@ end
|
|||
--------------------------------------------------------------------------------
|
||||
-- mainmenu only functions
|
||||
--------------------------------------------------------------------------------
|
||||
if INIT == "mainmenu" then
|
||||
function core.get_game(index)
|
||||
local games = core.get_games()
|
||||
|
||||
if index > 0 and index <= #games then
|
||||
return games[index]
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
if core.gettext then -- for client and mainmenu
|
||||
function fgettext_ne(text, ...)
|
||||
text = core.gettext(text)
|
||||
|
@ -572,14 +596,12 @@ function core.strip_colors(str)
|
|||
return (str:gsub(ESCAPE_CHAR .. "%([bc]@[^)]+%)", ""))
|
||||
end
|
||||
|
||||
local function translate(textdomain, str, num, ...)
|
||||
function core.translate(textdomain, str, ...)
|
||||
local start_seq
|
||||
if textdomain == "" and num == "" then
|
||||
if textdomain == "" then
|
||||
start_seq = ESCAPE_CHAR .. "T"
|
||||
elseif num == "" then
|
||||
start_seq = ESCAPE_CHAR .. "(T@" .. textdomain .. ")"
|
||||
else
|
||||
start_seq = ESCAPE_CHAR .. "(T@" .. textdomain .. "@" .. num .. ")"
|
||||
start_seq = ESCAPE_CHAR .. "(T@" .. textdomain .. ")"
|
||||
end
|
||||
local arg = {n=select('#', ...), ...}
|
||||
local end_seq = ESCAPE_CHAR .. "E"
|
||||
|
@ -610,31 +632,8 @@ local function translate(textdomain, str, num, ...)
|
|||
return start_seq .. translated .. end_seq
|
||||
end
|
||||
|
||||
function core.translate(textdomain, str, ...)
|
||||
return translate(textdomain, str, "", ...)
|
||||
end
|
||||
|
||||
function core.translate_n(textdomain, str, str_plural, n, ...)
|
||||
assert (type(n) == "number")
|
||||
assert (n >= 0)
|
||||
assert (math.floor(n) == n)
|
||||
|
||||
-- Truncate n if too large
|
||||
local max = 1000000
|
||||
if n >= 2 * max then
|
||||
n = n % max + max
|
||||
end
|
||||
if n == 1 then
|
||||
return translate(textdomain, str, "1", ...)
|
||||
else
|
||||
return translate(textdomain, str_plural, tostring(n), ...)
|
||||
end
|
||||
end
|
||||
|
||||
function core.get_translator(textdomain)
|
||||
return
|
||||
(function(str, ...) return core.translate(textdomain or "", str, ...) end),
|
||||
(function(str, str_plural, n, ...) return core.translate_n(textdomain or "", str, str_plural, n, ...) end)
|
||||
return function(str, ...) return core.translate(textdomain or "", str, ...) end
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
@ -695,7 +694,6 @@ function core.privs_to_string(privs, delim)
|
|||
list[#list + 1] = priv
|
||||
end
|
||||
end
|
||||
table.sort(list)
|
||||
return table.concat(list, delim)
|
||||
end
|
||||
|
||||
|
|
|
@ -1,76 +0,0 @@
|
|||
local builtin_shared = ...
|
||||
|
||||
do
|
||||
local default = {mod = "??", name = "??"}
|
||||
core.callback_origins = setmetatable({}, {
|
||||
__index = function()
|
||||
return default
|
||||
end
|
||||
})
|
||||
end
|
||||
|
||||
function core.run_callbacks(callbacks, mode, ...)
|
||||
assert(type(callbacks) == "table")
|
||||
local cb_len = #callbacks
|
||||
if cb_len == 0 then
|
||||
if mode == 2 or mode == 3 then
|
||||
return true
|
||||
elseif mode == 4 or mode == 5 then
|
||||
return false
|
||||
end
|
||||
end
|
||||
local ret = nil
|
||||
for i = 1, cb_len do
|
||||
local origin = core.callback_origins[callbacks[i]]
|
||||
core.set_last_run_mod(origin.mod)
|
||||
local cb_ret = callbacks[i](...)
|
||||
|
||||
if mode == 0 and i == 1 then
|
||||
ret = cb_ret
|
||||
elseif mode == 1 and i == cb_len then
|
||||
ret = cb_ret
|
||||
elseif mode == 2 then
|
||||
if not cb_ret or i == 1 then
|
||||
ret = cb_ret
|
||||
end
|
||||
elseif mode == 3 then
|
||||
if cb_ret then
|
||||
return cb_ret
|
||||
end
|
||||
ret = cb_ret
|
||||
elseif mode == 4 then
|
||||
if (cb_ret and not ret) or i == 1 then
|
||||
ret = cb_ret
|
||||
end
|
||||
elseif mode == 5 and cb_ret then
|
||||
return cb_ret
|
||||
end
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
function builtin_shared.make_registration()
|
||||
local t = {}
|
||||
local registerfunc = function(func)
|
||||
t[#t + 1] = func
|
||||
core.callback_origins[func] = {
|
||||
-- may be nil or return nil
|
||||
mod = core.get_current_modname and core.get_current_modname() or "??",
|
||||
name = debug.getinfo(1, "n").name or "??"
|
||||
}
|
||||
end
|
||||
return t, registerfunc
|
||||
end
|
||||
|
||||
function builtin_shared.make_registration_reverse()
|
||||
local t = {}
|
||||
local registerfunc = function(func)
|
||||
table.insert(t, 1, func)
|
||||
core.callback_origins[func] = {
|
||||
-- may be nil or return nil
|
||||
mod = core.get_current_modname and core.get_current_modname() or "??",
|
||||
name = debug.getinfo(1, "n").name or "??"
|
||||
}
|
||||
end
|
||||
return t, registerfunc
|
||||
end
|
|
@ -8,7 +8,7 @@ local next, rawget, pairs, pcall, error, type, setfenv, loadstring
|
|||
local table_concat, string_dump, string_format, string_match, math_huge
|
||||
= table.concat, string.dump, string.format, string.match, math.huge
|
||||
|
||||
-- Recursively counts occurrences of objects (non-primitives including strings) in a table.
|
||||
-- Recursively counts occurences of objects (non-primitives including strings) in a table.
|
||||
local function count_objects(value)
|
||||
local counts = {}
|
||||
if value == nil then
|
||||
|
@ -204,18 +204,18 @@ local function dummy_func() end
|
|||
function core.deserialize(str, safe)
|
||||
-- Backwards compatibility
|
||||
if str == nil then
|
||||
core.log("deprecated", "core.deserialize called with nil (expected string).")
|
||||
core.log("deprecated", "minetest.deserialize called with nil (expected string).")
|
||||
return nil, "Invalid type: Expected a string, got nil"
|
||||
end
|
||||
local t = type(str)
|
||||
if t ~= "string" then
|
||||
error(("core.deserialize called with %s (expected string)."):format(t))
|
||||
error(("minetest.deserialize called with %s (expected string)."):format(t))
|
||||
end
|
||||
|
||||
local func, err = loadstring(str)
|
||||
if not func then return nil, err end
|
||||
|
||||
-- math.huge was serialized to inf and NaNs to nan by Lua in engine version 5.6, so we have to support this here
|
||||
-- math.huge was serialized to inf and NaNs to nan by Lua in Minetest 5.6, so we have to support this here
|
||||
local env = {inf = math_huge, nan = 0/0}
|
||||
if safe then
|
||||
env.loadstring = dummy_func
|
||||
|
|
|
@ -1,463 +0,0 @@
|
|||
--Luanti
|
||||
--Copyright (C) 2022 rubenwardy
|
||||
--
|
||||
--This program is free software; you can redistribute it and/or modify
|
||||
--it under the terms of the GNU Lesser General Public License as published by
|
||||
--the Free Software Foundation; either version 2.1 of the License, or
|
||||
--(at your option) any later version.
|
||||
--
|
||||
--This program is distributed in the hope that it will be useful,
|
||||
--but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
--GNU Lesser General Public License for more details.
|
||||
--
|
||||
--You should have received a copy of the GNU Lesser General Public License along
|
||||
--with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
local make = {}
|
||||
|
||||
|
||||
-- This file defines various component constructors, of the form:
|
||||
--
|
||||
-- make.component(setting)
|
||||
--
|
||||
-- `setting` is a table representing the settingtype.
|
||||
--
|
||||
-- A component is a table with the following:
|
||||
--
|
||||
-- * `full_width`: (Optional) true if the component shouldn't reserve space for info / reset.
|
||||
-- * `info_text`: (Optional) string, informational text shown in an info icon.
|
||||
-- * `setting`: (Optional) the setting.
|
||||
-- * `max_w`: (Optional) maximum width, `avail_w` will never exceed this.
|
||||
-- * `resettable`: (Optional) if this is true, a reset button is shown.
|
||||
-- * `get_formspec = function(self, avail_w)`:
|
||||
-- * `avail_w` is the available width for the component.
|
||||
-- * Returns `fs, used_height`.
|
||||
-- * `fs` is a string for the formspec.
|
||||
-- Components should be relative to `0,0`, and not exceed `avail_w` or the returned `used_height`.
|
||||
-- * `used_height` is the space used by components in `fs`.
|
||||
-- * `on_submit = function(self, fields, parent)`:
|
||||
-- * `fields`: submitted formspec fields
|
||||
-- * `parent`: the fstk element for the settings UI, use to show dialogs
|
||||
-- * Return true if the event was handled, to prevent future components receiving it.
|
||||
|
||||
|
||||
local function get_label(setting)
|
||||
local show_technical_names = core.settings:get_bool("show_technical_names")
|
||||
if not show_technical_names and setting.readable_name then
|
||||
return fgettext(setting.readable_name)
|
||||
end
|
||||
return setting.name
|
||||
end
|
||||
|
||||
|
||||
local function is_valid_number(value)
|
||||
return type(value) == "number" and not (value ~= value or value >= math.huge or value <= -math.huge)
|
||||
end
|
||||
|
||||
|
||||
function make.heading(text)
|
||||
return {
|
||||
full_width = true,
|
||||
get_formspec = function(self, avail_w)
|
||||
return ("label[0,0.6;%s]box[0,0.9;%f,0.05;#ccc6]"):format(core.formspec_escape(text), avail_w), 1.2
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
function make.note(text)
|
||||
return {
|
||||
full_width = true,
|
||||
get_formspec = function(self, avail_w)
|
||||
-- Assuming label height 0.4:
|
||||
-- Position at y=0 to eat 0.2 of the padding above, leave 0.05.
|
||||
-- The returned used_height doesn't include padding.
|
||||
return ("label[0,0;%s]"):format(core.colorize("#bbb", core.formspec_escape(text))), 0.2
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
--- Used for string and numeric style fields
|
||||
---
|
||||
--- @param converter Function to coerce values from strings.
|
||||
--- @param validator Validator function, optional. Returns true when valid.
|
||||
--- @param stringifier Function to convert values to strings, optional.
|
||||
local function make_field(converter, validator, stringifier)
|
||||
return function(setting)
|
||||
return {
|
||||
info_text = setting.comment,
|
||||
setting = setting,
|
||||
|
||||
get_formspec = function(self, avail_w)
|
||||
local value = core.settings:get(setting.name) or setting.default
|
||||
self.resettable = core.settings:has(setting.name)
|
||||
|
||||
local fs = ("field[0,0.3;%f,0.8;%s;%s;%s]"):format(
|
||||
avail_w - 1.5, setting.name, get_label(setting), core.formspec_escape(value))
|
||||
fs = fs .. ("field_enter_after_edit[%s;true]"):format(setting.name)
|
||||
fs = fs .. ("field_close_on_enter[%s;false]"):format(setting.name) -- for pause menu env
|
||||
fs = fs .. ("button[%f,0.3;1.5,0.8;%s;%s]"):format(avail_w - 1.5, "set_" .. setting.name, fgettext("Set"))
|
||||
|
||||
return fs, 1.1
|
||||
end,
|
||||
|
||||
on_submit = function(self, fields)
|
||||
if fields["set_" .. setting.name] or fields.key_enter_field == setting.name then
|
||||
local value = converter(fields[setting.name])
|
||||
if value == nil or (validator and not validator(value)) then
|
||||
return true
|
||||
end
|
||||
|
||||
if setting.min then
|
||||
value = math.max(value, setting.min)
|
||||
end
|
||||
if setting.max then
|
||||
value = math.min(value, setting.max)
|
||||
end
|
||||
core.settings:set(setting.name, (stringifier or tostring)(value))
|
||||
return true
|
||||
end
|
||||
end,
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
make.float = make_field(tonumber, is_valid_number, function(x)
|
||||
local str = tostring(x)
|
||||
if str:match("^[+-]?%d+$") then
|
||||
str = str .. ".0"
|
||||
end
|
||||
return str
|
||||
end)
|
||||
make.int = make_field(function(x)
|
||||
local value = tonumber(x)
|
||||
return value and math.floor(value)
|
||||
end, is_valid_number)
|
||||
make.string = make_field(tostring, nil)
|
||||
|
||||
|
||||
function make.bool(setting)
|
||||
return {
|
||||
info_text = setting.comment,
|
||||
setting = setting,
|
||||
|
||||
get_formspec = function(self, avail_w)
|
||||
local value = core.settings:get_bool(setting.name, core.is_yes(setting.default))
|
||||
self.resettable = core.settings:has(setting.name)
|
||||
|
||||
local fs = ("checkbox[0,0.25;%s;%s;%s]"):format(
|
||||
setting.name, get_label(setting), tostring(value))
|
||||
return fs, 0.5
|
||||
end,
|
||||
|
||||
on_submit = function(self, fields)
|
||||
if fields[setting.name] == nil then
|
||||
return false
|
||||
end
|
||||
|
||||
core.settings:set_bool(setting.name, core.is_yes(fields[setting.name]))
|
||||
return true
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
function make.enum(setting)
|
||||
return {
|
||||
info_text = setting.comment,
|
||||
setting = setting,
|
||||
max_w = 4.5,
|
||||
|
||||
get_formspec = function(self, avail_w)
|
||||
local value = core.settings:get(setting.name) or setting.default
|
||||
self.resettable = core.settings:has(setting.name)
|
||||
|
||||
local labels = setting.option_labels or {}
|
||||
|
||||
local items = {}
|
||||
for i, option in ipairs(setting.values) do
|
||||
items[i] = core.formspec_escape(labels[option] or option)
|
||||
end
|
||||
|
||||
local selected_idx = table.indexof(setting.values, value)
|
||||
local fs = "label[0,0.1;" .. get_label(setting) .. "]"
|
||||
|
||||
fs = fs .. ("dropdown[0,0.3;%f,0.8;%s;%s;%d;true]"):format(
|
||||
avail_w, setting.name, table.concat(items, ","), selected_idx, value)
|
||||
|
||||
return fs, 1.1
|
||||
end,
|
||||
|
||||
on_submit = function(self, fields)
|
||||
local old_value = core.settings:get(setting.name) or setting.default
|
||||
local idx = tonumber(fields[setting.name]) or 0
|
||||
local value = setting.values[idx]
|
||||
if value == nil or value == old_value then
|
||||
return false
|
||||
end
|
||||
|
||||
core.settings:set(setting.name, value)
|
||||
return true
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
local function make_path(setting)
|
||||
return {
|
||||
info_text = setting.comment,
|
||||
setting = setting,
|
||||
|
||||
get_formspec = function(self, avail_w)
|
||||
local value = core.settings:get(setting.name) or setting.default
|
||||
self.resettable = core.settings:has(setting.name)
|
||||
|
||||
local fs = ("field[0,0.3;%f,0.8;%s;%s;%s]"):format(
|
||||
avail_w - 3, setting.name, get_label(setting), core.formspec_escape(value))
|
||||
fs = fs .. ("field_enter_after_edit[%s;true]"):format(setting.name)
|
||||
fs = fs .. ("field_close_on_enter[%s;false]"):format(setting.name) -- for pause menu env
|
||||
fs = fs .. ("button[%f,0.3;1.5,0.8;%s;%s]"):format(avail_w - 3, "pick_" .. setting.name, fgettext("Browse"))
|
||||
fs = fs .. ("button[%f,0.3;1.5,0.8;%s;%s]"):format(avail_w - 1.5, "set_" .. setting.name, fgettext("Set"))
|
||||
|
||||
return fs, 1.1
|
||||
end,
|
||||
|
||||
on_submit = function(self, fields)
|
||||
local dialog_name = "dlg_path_" .. setting.name
|
||||
if fields["pick_" .. setting.name] then
|
||||
local is_file = setting.type ~= "path"
|
||||
core.show_path_select_dialog(dialog_name,
|
||||
is_file and fgettext_ne("Select file") or fgettext_ne("Select directory"), is_file)
|
||||
return true
|
||||
end
|
||||
if fields[dialog_name .. "_accepted"] then
|
||||
local value = fields[dialog_name .. "_accepted"]
|
||||
if value ~= nil then
|
||||
core.settings:set(setting.name, value)
|
||||
end
|
||||
return true
|
||||
end
|
||||
if fields["set_" .. setting.name] or fields.key_enter_field == setting.name then
|
||||
local value = fields[setting.name]
|
||||
if value ~= nil then
|
||||
core.settings:set(setting.name, value)
|
||||
end
|
||||
return true
|
||||
end
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
if PLATFORM == "Android" or INIT == "pause_menu" then
|
||||
-- The Irrlicht file picker doesn't work on Android.
|
||||
-- Access to the Irrlicht file picker isn't implemented in the pause menu.
|
||||
-- We want to delete the Irrlicht file picker anyway, so any time spent on
|
||||
-- that would be wasted.
|
||||
make.path = make.string
|
||||
make.filepath = make.string
|
||||
else
|
||||
make.path = make_path
|
||||
make.filepath = make_path
|
||||
end
|
||||
|
||||
|
||||
function make.v3f(setting)
|
||||
return {
|
||||
info_text = setting.comment,
|
||||
setting = setting,
|
||||
|
||||
get_formspec = function(self, avail_w)
|
||||
local value = vector.from_string(core.settings:get(setting.name) or setting.default)
|
||||
self.resettable = core.settings:has(setting.name)
|
||||
|
||||
-- Allocate space for "Set" button
|
||||
avail_w = avail_w - 1
|
||||
|
||||
local fs = "label[0,0.1;" .. get_label(setting) .. "]"
|
||||
|
||||
local field_width = (avail_w - 3*0.25) / 3
|
||||
|
||||
fs = fs .. ("field[%f,0.6;%f,0.8;%s;%s;%s]"):format(
|
||||
0, field_width, setting.name .. "_x", "X", value.x)
|
||||
fs = fs .. ("field[%f,0.6;%f,0.8;%s;%s;%s]"):format(
|
||||
field_width + 0.25, field_width, setting.name .. "_y", "Y", value.y)
|
||||
fs = fs .. ("field[%f,0.6;%f,0.8;%s;%s;%s]"):format(
|
||||
2 * (field_width + 0.25), field_width, setting.name .. "_z", "Z", value.z)
|
||||
|
||||
fs = fs .. ("field_enter_after_edit[%s;true]"):format(setting.name .. "_x")
|
||||
fs = fs .. ("field_enter_after_edit[%s;true]"):format(setting.name .. "_y")
|
||||
fs = fs .. ("field_enter_after_edit[%s;true]"):format(setting.name .. "_z")
|
||||
-- for pause menu env
|
||||
fs = fs .. ("field_close_on_enter[%s;false]"):format(setting.name .. "_x")
|
||||
fs = fs .. ("field_close_on_enter[%s;false]"):format(setting.name .. "_y")
|
||||
fs = fs .. ("field_close_on_enter[%s;false]"):format(setting.name .. "_z")
|
||||
|
||||
fs = fs .. ("button[%f,0.6;1,0.8;%s;%s]"):format(avail_w, "set_" .. setting.name, fgettext("Set"))
|
||||
|
||||
return fs, 1.4
|
||||
end,
|
||||
|
||||
on_submit = function(self, fields)
|
||||
if fields["set_" .. setting.name] or
|
||||
fields.key_enter_field == setting.name .. "_x" or
|
||||
fields.key_enter_field == setting.name .. "_y" or
|
||||
fields.key_enter_field == setting.name .. "_z" then
|
||||
local x = tonumber(fields[setting.name .. "_x"])
|
||||
local y = tonumber(fields[setting.name .. "_y"])
|
||||
local z = tonumber(fields[setting.name .. "_z"])
|
||||
if is_valid_number(x) and is_valid_number(y) and is_valid_number(z) then
|
||||
core.settings:set(setting.name, vector.new(x, y, z):to_string())
|
||||
else
|
||||
core.log("error", "Invalid vector: " .. dump({x, y, z}))
|
||||
end
|
||||
return true
|
||||
end
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
function make.flags(setting)
|
||||
local checkboxes = {}
|
||||
|
||||
return {
|
||||
info_text = setting.comment,
|
||||
setting = setting,
|
||||
|
||||
get_formspec = function(self, avail_w)
|
||||
local fs = {
|
||||
"label[0,0.1;" .. get_label(setting) .. "]",
|
||||
}
|
||||
|
||||
self.resettable = core.settings:has(setting.name)
|
||||
|
||||
checkboxes = {}
|
||||
for _, name in ipairs(setting.possible) do
|
||||
checkboxes[name] = false
|
||||
end
|
||||
local function apply_flags(flag_string, what)
|
||||
local prefixed_flags = {}
|
||||
for _, name in ipairs(flag_string:split(",")) do
|
||||
prefixed_flags[name:trim()] = true
|
||||
end
|
||||
for _, name in ipairs(setting.possible) do
|
||||
local enabled = prefixed_flags[name]
|
||||
local disabled = prefixed_flags["no" .. name]
|
||||
if enabled and disabled then
|
||||
core.log("warning", "Flag " .. name .. " in " .. what .. " " ..
|
||||
setting.name .. " both enabled and disabled, ignoring")
|
||||
elseif enabled then
|
||||
checkboxes[name] = true
|
||||
elseif disabled then
|
||||
checkboxes[name] = false
|
||||
end
|
||||
end
|
||||
end
|
||||
-- First apply the default, which is necessary since flags
|
||||
-- which are not overridden may be missing from the value.
|
||||
apply_flags(setting.default, "default for setting")
|
||||
local value = core.settings:get(setting.name)
|
||||
if value then
|
||||
apply_flags(value, "setting")
|
||||
end
|
||||
|
||||
local columns = math.max(math.floor(avail_w / 2.5), 1)
|
||||
local column_width = avail_w / columns
|
||||
local x = 0
|
||||
local y = 0.55
|
||||
|
||||
for _, possible in ipairs(setting.possible) do
|
||||
if x >= avail_w then
|
||||
x = 0
|
||||
y = y + 0.5
|
||||
end
|
||||
|
||||
local is_checked = checkboxes[possible]
|
||||
fs[#fs + 1] = ("checkbox[%f,%f;%s;%s;%s]"):format(
|
||||
x, y, setting.name .. "_" .. possible,
|
||||
core.formspec_escape(possible), tostring(is_checked))
|
||||
x = x + column_width
|
||||
end
|
||||
|
||||
return table.concat(fs, ""), y + 0.25
|
||||
end,
|
||||
|
||||
on_submit = function(self, fields)
|
||||
local changed = false
|
||||
for name, _ in pairs(checkboxes) do
|
||||
local value = fields[setting.name .. "_" .. name]
|
||||
if value ~= nil then
|
||||
checkboxes[name] = core.is_yes(value)
|
||||
changed = true
|
||||
end
|
||||
end
|
||||
|
||||
if changed then
|
||||
local values = {}
|
||||
for _, name in ipairs(setting.possible) do
|
||||
if checkboxes[name] then
|
||||
table.insert(values, name)
|
||||
else
|
||||
table.insert(values, "no" .. name)
|
||||
end
|
||||
end
|
||||
|
||||
core.settings:set(setting.name, table.concat(values, ","))
|
||||
end
|
||||
return changed
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
local function make_noise_params(setting)
|
||||
return {
|
||||
info_text = setting.comment,
|
||||
setting = setting,
|
||||
|
||||
get_formspec = function(self, avail_w)
|
||||
-- The "defaults" noise parameter flag doesn't reset a noise
|
||||
-- setting to its default value, so we offer a regular reset button.
|
||||
self.resettable = core.settings:has(setting.name)
|
||||
|
||||
local fs = "label[0,0.4;" .. get_label(setting) .. "]" ..
|
||||
("button[%f,0;2.5,0.8;%s;%s]"):format(avail_w - 2.5, "edit_" .. setting.name, fgettext("Edit"))
|
||||
return fs, 0.8
|
||||
end,
|
||||
|
||||
on_submit = function(self, fields, tabview)
|
||||
if fields["edit_" .. setting.name] then
|
||||
local dlg = create_change_mapgen_flags_dlg(setting)
|
||||
dlg:set_parent(tabview)
|
||||
tabview:hide()
|
||||
dlg:show()
|
||||
|
||||
return true
|
||||
end
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
if INIT == "pause_menu" then
|
||||
-- Making the noise parameter dialog work in the pause menu settings would
|
||||
-- require porting "FSTK" (at least the dialog API) from the mainmenu formspec
|
||||
-- API to the in-game formspec API.
|
||||
-- There's no reason you'd want to adjust mapgen noise parameter settings
|
||||
-- in-game (they only apply to new worlds), so there's no reason to implement
|
||||
-- this.
|
||||
local empty = function()
|
||||
return { get_formspec = function() return "", 0 end }
|
||||
end
|
||||
make.noise_params_2d = empty
|
||||
make.noise_params_3d = empty
|
||||
else
|
||||
make.noise_params_2d = make_noise_params
|
||||
make.noise_params_3d = make_noise_params
|
||||
end
|
||||
|
||||
|
||||
return make
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue