mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-09-08 10:53:05 +00:00
Compare commits
266 Commits
Author | SHA1 | Date | |
---|---|---|---|
a42f68d3cf | |||
b6f3f6120b | |||
96d3f4f78b | |||
6756203aec | |||
adb78679c5 | |||
b60731607d | |||
0058bfcd23 | |||
9f31b479e1 | |||
eb161f8e1c | |||
80f8a27094 | |||
341c480e13 | |||
971ad04299 | |||
025b72e2f2 | |||
cf538afb84 | |||
7ff91337ad | |||
63e5867727 | |||
c38779f1fd | |||
14bae72dd5 | |||
f35b43613d | |||
3d0fa10437 | |||
76e5ea385b | |||
5a351d3caf | |||
bb93d4f8de | |||
66818e178a | |||
f782a478c4 | |||
c06b4830d1 | |||
6d64ef8d74 | |||
c5498bb3fa | |||
59334d652f | |||
5c81da9b1e | |||
b08affc4c6 | |||
cb49f1e835 | |||
f6a0f4aa82 | |||
de09c8c082 | |||
0ac87989dc | |||
13ea984b12 | |||
3878f58847 | |||
d7a35a5302 | |||
d850a84d0d | |||
22d9260a3b | |||
3c90ed13b9 | |||
396efbac7e | |||
00644dd529 | |||
c2c210e25a | |||
86c4e936cb | |||
3b103dcd62 | |||
48dfc5b232 | |||
39360f127a | |||
25890e76e2 | |||
810bdeb965 | |||
b6b3dcc1aa | |||
e0818e7e52 | |||
d0c4463773 | |||
7521160f53 | |||
4f5190e3f8 | |||
3c941dd992 | |||
89f457b397 | |||
2bdbb9794c | |||
898009a91b | |||
793f93afdb | |||
64506ea3ae | |||
4a35516441 | |||
74b9922a28 | |||
088fbf65b1 | |||
fa70127241 | |||
fc76d04dcb | |||
15ae323bcb | |||
353bae87a7 | |||
dc7be3fd52 | |||
f28a0740a0 | |||
df990fb47a | |||
98ca28d96e | |||
db896b9faf | |||
76c234e4e6 | |||
2ef3962028 | |||
1e5941c98c | |||
9ca7c0c883 | |||
b4a8f8391b | |||
142d750b9f | |||
948b0b4cbc | |||
d6b596a8ac | |||
eab2d4d704 | |||
8114551600 | |||
8e1d1993c5 | |||
82bf5f6193 | |||
76a86e51f7 | |||
d1e803685a | |||
0a884aa5fb | |||
c410e676b3 | |||
8f1f5fde47 | |||
0f268df2e0 | |||
86108e7010 | |||
5ea448ef36 | |||
389e7767d1 | |||
4c268acc00 | |||
e34a4f6b98 | |||
3925e598d6 | |||
383ec8a8e3 | |||
95313e0a90 | |||
e9a87978a6 | |||
612e9e162c | |||
fdc3faadc9 | |||
9644e72acf | |||
2c678dcf0d | |||
7bd9a2b2e0 | |||
5c26deb517 | |||
8fcb44de7d | |||
bd2e7db3b9 | |||
f4480c07ee | |||
c09e2301c8 | |||
8e9f787d33 | |||
89833b3b68 | |||
a10a656a5d | |||
2659ed8d91 | |||
9c01ecbe7e | |||
6a55021779 | |||
6ada261b04 | |||
14a6779e08 | |||
cce99b07af | |||
c3d80d711d | |||
1a8b33dafe | |||
baa094a2d1 | |||
f2ff510597 | |||
562179bdd6 | |||
5c12bee874 | |||
99606bbe23 | |||
a1d50de12e | |||
4252c5914b | |||
0659d2fbef | |||
10612acace | |||
1d810f8aeb | |||
414104851a | |||
c0bed03a2a | |||
d25c84acff | |||
55994e08db | |||
6f5d4d6b80 | |||
df1ef7fe0c | |||
20a25a69df | |||
faca610594 | |||
91603dc2d6 | |||
af90e18b18 | |||
ab5b4d112b | |||
a30b1fb6d5 | |||
20b4723728 | |||
d1ced0ffc6 | |||
2164dbae67 | |||
6c92a2e88b | |||
97deadc59f | |||
0c3b136a8d | |||
79b7e08e60 | |||
2540dacdd7 | |||
f1078e3909 | |||
2f43b054de | |||
23b5d64535 | |||
9afa0e5483 | |||
4eaea54b0e | |||
6b51bf4a80 | |||
cba8d86c4f | |||
2e834c8f5c | |||
f9873e9108 | |||
074baf7e1c | |||
2e0dd574e0 | |||
e16d8e31af | |||
3c93a57397 | |||
e2e927b328 | |||
a8dab25201 | |||
3de2b7969e | |||
8f486ea65d | |||
6b971b1761 | |||
6f36fa504b | |||
8e73842a93 | |||
e71e18fc88 | |||
e1bacb5c6d | |||
44697e784a | |||
65529ff2ce | |||
c346c45d42 | |||
c433fad0a7 | |||
8fad5a6e30 | |||
7a6f279825 | |||
10b72c895d | |||
d520928888 | |||
27767e7ddb | |||
243c12de7c | |||
372545e47e | |||
8913b48700 | |||
6ee4a0e090 | |||
8bd8da4bc6 | |||
9ba4144a71 | |||
9da7c6af27 | |||
109312284c | |||
51934614bc | |||
9e89f65094 | |||
9562711b84 | |||
30b49e0d22 | |||
a975868fc3 | |||
b38b932845 | |||
43cb19ebca | |||
769cc91543 | |||
278f37d3e0 | |||
02a6ca84a9 | |||
b8703d5dff | |||
37c2d78731 | |||
b7663e5815 | |||
1d0ffa06f8 | |||
768cfe3953 | |||
2822465f33 | |||
5da48f429f | |||
dbd0d04549 | |||
0f92ec6d2a | |||
791b4d8ef3 | |||
0b7ff6f2e7 | |||
af092b01e1 | |||
d811217755 | |||
d7f86f0240 | |||
5fe1d2e396 | |||
ddbb5363ef | |||
d3704bfae4 | |||
07f034d2da | |||
331ae5498f | |||
eebd90ec42 | |||
8e47a40b4c | |||
0f9fdf6442 | |||
5364e4de68 | |||
1099e2044b | |||
80d48161d3 | |||
fbe8485696 | |||
a150f39b02 | |||
71d17c50d6 | |||
3a18bdd6a0 | |||
96857c65b6 | |||
9826abd83e | |||
48c8c2a8c3 | |||
c83b7d9b69 | |||
c66dc7b273 | |||
5bf7350ee5 | |||
636cc1c199 | |||
62b6405371 | |||
9ecf23d3ee | |||
187f36fe38 | |||
dc89b48354 | |||
e6d1c1dfbc | |||
5a8812b1dc | |||
4b8e4123af | |||
45a4252c26 | |||
feaaa925a7 | |||
5221db1178 | |||
47321114eb | |||
a27c9409f1 | |||
854f851525 | |||
9003b38be3 | |||
a6a93f822f | |||
d4851a8f1f | |||
480a513f30 | |||
4fd3bee360 | |||
41fd7545e3 | |||
82dddde159 | |||
bc709efb77 | |||
cd98e6a23e | |||
cb591a98f4 | |||
e9d1af0aee | |||
0f545c410a | |||
1c2ed0836f | |||
6cf30dc813 | |||
f7d9247d39 | |||
3380aa3ac2 | |||
6a9cad8fb7 |
@ -4,3 +4,7 @@ root = yes
|
||||
[*]
|
||||
indent_size = 4
|
||||
indent_style = tab
|
||||
|
||||
[*.yml]
|
||||
indent_size = 2
|
||||
indent_style = space
|
||||
|
8
.github/FUNDING.yml
vendored
Normal file
8
.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
patreon: pocketminemp
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
custom: https://github.com/pmmp/PocketMine-MP#donate
|
48
.github/ISSUE_TEMPLATE.md
vendored
48
.github/ISSUE_TEMPLATE.md
vendored
@ -1,48 +0,0 @@
|
||||
### Issue description
|
||||
<!---
|
||||
THIS ISSUE TRACKER IS FOR BUG REPORTING, NOT FOR HELP & SUPPORT. If you need help, use the links below.
|
||||
- http://pmmp.readthedocs.io/en/rtfd/ - Documentation
|
||||
- https://forums.pmmp.io - PMMP Forums
|
||||
|
||||
Any issues requesting updates to new versions of MCPE will be treated as spam.
|
||||
Please do not create issues for missing/un-implemented gameplay features - they will be closed.
|
||||
-->
|
||||
|
||||
<!--- Write a short description about the issue -->
|
||||
|
||||
<!--- If you are reporting a regression or unexpected behaviour, please include the below information: -->
|
||||
- Expected result: What were you expecting to happen?
|
||||
- Actual result: What actually happened?
|
||||
|
||||
### Steps to reproduce the issue
|
||||
<!--- help us find the problem by adding steps to reproduce the issue -->
|
||||
1. ...
|
||||
2. ...
|
||||
|
||||
### OS and versions
|
||||
<!--- use the 'version' command in PocketMine-MP
|
||||
|
||||
NOTE: LATEST is not a valid version. PocketMine-MP version should include Jenkins build number and/or git commit hash.
|
||||
|
||||
NO support whatsoever will be provided for third-party modified variants of PocketMine-MP. Issues relating to third-party modifications will be closed as spam.
|
||||
|
||||
Note that 32-bit platforms are no longer supported by PocketMine-MP and issues concerning 32-bit platforms will be closed.
|
||||
-->
|
||||
* PocketMine-MP: <!-- LATEST IS NOT A VALID VERSION -->
|
||||
* PHP:
|
||||
* Server OS:
|
||||
* Game version: PE/Win10 (delete as appropriate)
|
||||
|
||||
### Plugins
|
||||
- Test on a clean server without plugins: is the issue reproducible without any plugins loaded?
|
||||
|
||||
If the issue is **not** reproducible without plugins:
|
||||
- Have you asked for help on our forums before creating an issue?
|
||||
- Can you provide sample, *minimal* reproducing code for the issue? If so, paste it in the bottom section
|
||||
- Paste your list of plugins here (use the 'plugins' command in PocketMine-MP)
|
||||
|
||||
### Crashdump, backtrace or other files
|
||||
- Do not paste crashdumps into an issue - please use our Crash Archive at https://crash.pmmp.io for submitting crash reports to not spam the issue tracker. Add links to your reports in the Crash Archive here.
|
||||
- Please use gist or anything else to add other files and add links here
|
||||
|
||||
* ...
|
19
.github/ISSUE_TEMPLATE/api-change-request.md
vendored
Normal file
19
.github/ISSUE_TEMPLATE/api-change-request.md
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
---
|
||||
name: API change request
|
||||
about: Suggest a change, addition or removal to the plugin API
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!--- tell us what you want -->
|
||||
## Description
|
||||
|
||||
|
||||
<!--- explain why you want this and why it's a good idea -->
|
||||
## Justification
|
||||
|
||||
|
||||
<!--- (optional) describe alternative methods you've explored to achieve your goal -->
|
||||
## Alternative methods
|
36
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
36
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Unexpected non-crash behaviour (except missing gameplay features)
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
### Issue description
|
||||
|
||||
- Expected result: What were you expecting to happen?
|
||||
- Actual result: What actually happened?
|
||||
|
||||
### Steps to reproduce the issue
|
||||
1. ...
|
||||
2. ...
|
||||
|
||||
### OS and versions
|
||||
<!-- try the `version` command | LATEST IS NOT A VALID VERSION -->
|
||||
* PocketMine-MP:
|
||||
* PHP:
|
||||
* Server OS:
|
||||
* Game version: PE/Win10 (delete as appropriate)
|
||||
|
||||
### Plugins
|
||||
<!--- use the `plugins` command and paste the output below -->
|
||||
|
||||
- If you remove all plugins, does the issue still occur?
|
||||
- If the issue is **not** reproducible without plugins:
|
||||
- Have you asked for help on our forums before creating an issue?
|
||||
- Can you provide sample, *minimal* reproducing code for the issue? If so, paste it in the bottom section
|
||||
|
||||
### Crashdump, backtrace or other files
|
||||
<!--- Submit crashdumps at https://crash.pmmp.io and paste a link -->
|
||||
<!--- Use gist or anything else to add other files and add links here -->
|
15
.github/ISSUE_TEMPLATE/crash.md
vendored
Normal file
15
.github/ISSUE_TEMPLATE/crash.md
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
---
|
||||
name: Crash
|
||||
about: Report a crash in PocketMine-MP (not plugins)
|
||||
title: Server crashed
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!--- submit crashdump files to https://crash.pmmp.io -->
|
||||
<!--- or, copy the data between ===BEGIN CRASH DUMP=== and ===END CRASH DUMP and paste it on a site like https://pastebin.com -->
|
||||
Link to crashdump:
|
||||
|
||||
<!--- write additional information about the crash to help us find the problem -->
|
||||
### Additional comments (optional)
|
14
.github/ISSUE_TEMPLATE/help---support.md
vendored
Normal file
14
.github/ISSUE_TEMPLATE/help---support.md
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
---
|
||||
name: Help & support
|
||||
about: We don't accept support requests here. Try the links on the README.
|
||||
title: ''
|
||||
labels: Support request
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
We don't accept support requests on the issue tracker. Please try the following links instead:
|
||||
|
||||
Documentation: http://pmmp.rtfd.io
|
||||
Forums: https://forums.pmmp.io
|
||||
Discord: https://discord.gg/bge7dYQ
|
12
.github/ISSUE_TEMPLATE/security-dos-vulnerability.md
vendored
Normal file
12
.github/ISSUE_TEMPLATE/security-dos-vulnerability.md
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
---
|
||||
name: Security/DoS vulnerability
|
||||
about: 'Bug or exploit that can be used to attack servers (hint: don''t report it
|
||||
on a public issue tracker)'
|
||||
title: ''
|
||||
labels: 'Auto: Spam'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
Please DO NOT report security vulnerabilities here.
|
||||
Instead, send an email to team@pmmp.io or contact a developer directly, IN PRIVATE.
|
6
.github/support.yml
vendored
6
.github/support.yml
vendored
@ -5,7 +5,11 @@ supportLabel: "Support request"
|
||||
# Comment to post on issues marked as support requests. Add a link
|
||||
# to a support page, or set to `false` to disable
|
||||
supportComment: >
|
||||
This issue tracker is not a support forum. Please use the [forums](https://forums.pmmp.io) for support.
|
||||
Thanks, but this issue tracker not intended for support requests. Please read the guidelines on [submitting an issue](https://github.com/pmmp/PocketMine-MP/blob/master/CONTRIBUTING.md#creating-an-issue).
|
||||
|
||||
|
||||
[Docs](https://pmmp.rtfd.io) | [Discord](https://discord.gg/bge7dYQ) | [Forums](https://forums.pmmp.io)
|
||||
|
||||
# Whether to close issues marked as support requests
|
||||
close: true
|
||||
# Whether to lock issues marked as support requests
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -12,6 +12,7 @@ server.properties
|
||||
/pocketmine.yml
|
||||
memory_dumps/*
|
||||
resource_packs/
|
||||
server.lock
|
||||
|
||||
# Common IDEs
|
||||
.idea/
|
||||
|
@ -2,13 +2,14 @@ language: php
|
||||
|
||||
php:
|
||||
- 7.2
|
||||
- 7.3
|
||||
|
||||
before_script:
|
||||
# - pecl install channel://pecl.php.net/pthreads-3.1.6
|
||||
- echo | pecl install channel://pecl.php.net/yaml-2.0.2
|
||||
- echo | pecl install channel://pecl.php.net/yaml-2.0.4
|
||||
- git clone https://github.com/pmmp/pthreads.git
|
||||
- cd pthreads
|
||||
- git checkout c8cfacda84f21032d6014b53e72bf345ac901dac
|
||||
- git checkout 6ca019c58b4fa09ee2ff490f2444e34bef0773d0
|
||||
- phpize
|
||||
- ./configure
|
||||
- make
|
||||
|
13
README.md
13
README.md
@ -8,15 +8,20 @@ __A highly customisable, open source server software for Minecraft: Bedrock Edit
|
||||
Head over to the [documentation site](http://pmmp.readthedocs.org/).
|
||||
If you don't find what you're looking for there, [talk to a human](#discussion). Please do not use our issue tracker for support requests.
|
||||
|
||||
### Docker
|
||||
We provide an official docker image on Docker Hub: [`pmmp/pocketmine-mp`](https://hub.docker.com/r/pmmp/pocketmine-mp).
|
||||
|
||||
### Discussion
|
||||
- [Forums](https://forums.pmmp.io/)
|
||||
- [Community Discord](https://discord.gg/bge7dYQ)
|
||||
|
||||
### Plugins
|
||||
There are a very wide range of already-written plugins available which you can use to customise your server. Check out [Poggit](https://poggit.pmmp.io), or just search GitHub.
|
||||
|
||||
### For developers
|
||||
* [Latest API documentation](https://jenkins.pmmp.io/job/PocketMine-MP-doc/doxygen/) - Doxygen documentation generated from development
|
||||
* [DevTools](https://github.com/pmmp/PocketMine-DevTools/) - A development tools plugin for creating plugins.
|
||||
* [DevTools](https://github.com/pmmp/PocketMine-DevTools/) - Development tools plugin for creating plugins
|
||||
* [ExamplePlugin](https://github.com/pmmp/ExamplePlugin/) - Example plugin demonstrating some basic API features
|
||||
|
||||
### Can I contribute?
|
||||
Yes you can! Contributions are welcomed provided that they comply with our [Contributing Guidelines](CONTRIBUTING.md). Please ensure you read the relevant sections of the guidelines carefully before making a Pull Request or opening an Issue.
|
||||
@ -27,6 +32,12 @@ Yes you can! Contributions are welcomed provided that they comply with our [Cont
|
||||
|
||||
**Note: Please avoid development builds unless there is no other alternative for what you need.** Development builds are subject to changes at any time without notice, and it is likely that your server or plugins might break without warning.
|
||||
|
||||
### Donate
|
||||
Donations help support the development of the project and pay for our expenses.
|
||||
- Bitcoin Cash (BCH): `qz9p8dqkv0r7aahdatu5uewqfkvstrglv58f8yle07`
|
||||
- Bitcoin (BTC): `1PVAyDJ2g7kcjCxAC3C89oxpV2ZYcLad8T`
|
||||
- [Patreon](https://www.patreon.com/pocketminemp)
|
||||
|
||||
## Licensing information
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
|
@ -27,7 +27,7 @@
|
||||
"pocketmine/raklib": "^0.12.0",
|
||||
"pocketmine/spl": "^0.3.0",
|
||||
"pocketmine/binaryutils": "^0.1.0",
|
||||
"pocketmine/nbt": "^0.2.1",
|
||||
"pocketmine/nbt": "^0.2.6",
|
||||
"pocketmine/math": "^0.2.0",
|
||||
"pocketmine/snooze": "^0.1.0",
|
||||
"daverandom/callback-validator": "dev-master",
|
||||
|
40
composer.lock
generated
40
composer.lock
generated
@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "d3fb809caf4d5a5c99054f47f28ff271",
|
||||
"content-hash": "2f5313e4ebd7b62c785cf683b27464b4",
|
||||
"packages": [
|
||||
{
|
||||
"name": "adhocore/json-comment",
|
||||
@ -92,16 +92,16 @@
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/binaryutils",
|
||||
"version": "0.1.5",
|
||||
"version": "0.1.8",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/pmmp/BinaryUtils.git",
|
||||
"reference": "03361b0d78ef2b400a99e96406aa594a5bc1c4ed"
|
||||
"reference": "33f511715d22418c03368b49b45a6c25d6b33806"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/pmmp/BinaryUtils/zipball/03361b0d78ef2b400a99e96406aa594a5bc1c4ed",
|
||||
"reference": "03361b0d78ef2b400a99e96406aa594a5bc1c4ed",
|
||||
"url": "https://api.github.com/repos/pmmp/BinaryUtils/zipball/33f511715d22418c03368b49b45a6c25d6b33806",
|
||||
"reference": "33f511715d22418c03368b49b45a6c25d6b33806",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -119,10 +119,10 @@
|
||||
],
|
||||
"description": "Classes and methods for conveniently handling binary data",
|
||||
"support": {
|
||||
"source": "https://github.com/pmmp/BinaryUtils/tree/0.1.5",
|
||||
"source": "https://github.com/pmmp/BinaryUtils/tree/0.1.8",
|
||||
"issues": "https://github.com/pmmp/BinaryUtils/issues"
|
||||
},
|
||||
"time": "2019-01-04T13:32:11+00:00"
|
||||
"time": "2019-01-16T17:31:44+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/math",
|
||||
@ -160,16 +160,16 @@
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/nbt",
|
||||
"version": "0.2.4",
|
||||
"version": "0.2.7",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/pmmp/NBT.git",
|
||||
"reference": "05dddb51830fd8f3b6c93e553abe07643ec96fc5"
|
||||
"reference": "2f176c9f2fd9b31db8bc2ada2f38990157ec8f1a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/pmmp/NBT/zipball/05dddb51830fd8f3b6c93e553abe07643ec96fc5",
|
||||
"reference": "05dddb51830fd8f3b6c93e553abe07643ec96fc5",
|
||||
"url": "https://api.github.com/repos/pmmp/NBT/zipball/2f176c9f2fd9b31db8bc2ada2f38990157ec8f1a",
|
||||
"reference": "2f176c9f2fd9b31db8bc2ada2f38990157ec8f1a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -194,23 +194,23 @@
|
||||
],
|
||||
"description": "PHP library for working with Named Binary Tags",
|
||||
"support": {
|
||||
"source": "https://github.com/pmmp/NBT/tree/0.2.4",
|
||||
"source": "https://github.com/pmmp/NBT/tree/0.2.7",
|
||||
"issues": "https://github.com/pmmp/NBT/issues"
|
||||
},
|
||||
"time": "2019-01-04T15:28:44+00:00"
|
||||
"time": "2019-03-29T19:39:42+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/raklib",
|
||||
"version": "0.12.1",
|
||||
"version": "0.12.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/pmmp/RakLib.git",
|
||||
"reference": "334b469f2d0f070f17902d7ac584bea7c6149dc6"
|
||||
"reference": "fc1ccc8e61b9033e5372436b2e28a7a95388373f"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/pmmp/RakLib/zipball/334b469f2d0f070f17902d7ac584bea7c6149dc6",
|
||||
"reference": "334b469f2d0f070f17902d7ac584bea7c6149dc6",
|
||||
"url": "https://api.github.com/repos/pmmp/RakLib/zipball/fc1ccc8e61b9033e5372436b2e28a7a95388373f",
|
||||
"reference": "fc1ccc8e61b9033e5372436b2e28a7a95388373f",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -235,10 +235,10 @@
|
||||
],
|
||||
"description": "A RakNet server implementation written in PHP",
|
||||
"support": {
|
||||
"source": "https://github.com/pmmp/RakLib/tree/0.12.1",
|
||||
"source": "https://github.com/pmmp/RakLib/tree/0.12.4",
|
||||
"issues": "https://github.com/pmmp/RakLib/issues"
|
||||
},
|
||||
"time": "2019-01-04T14:23:37+00:00"
|
||||
"time": "2019-05-02T14:53:51+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/snooze",
|
||||
@ -302,7 +302,7 @@
|
||||
],
|
||||
"description": "Standard library files required by PocketMine-MP and related projects",
|
||||
"support": {
|
||||
"source": "https://github.com/pmmp/SPL/tree/master"
|
||||
"source": "https://github.com/pmmp/SPL/tree/0.3.2"
|
||||
},
|
||||
"time": "2018-08-12T15:17:39+00:00"
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
10
doxygen/index.md
Normal file
10
doxygen/index.md
Normal file
@ -0,0 +1,10 @@
|
||||
## PocketMine-MP API Documentation
|
||||
|
||||
This site contains auto-generated API documentation for PocketMine-MP (and dependencies which are not outsourced). Documentation here is regenerated for every release.
|
||||
|
||||
This site can be accessed via https://apidoc.pmmp.io.
|
||||
|
||||
### Additional developer resources
|
||||
- [DevTools](https://github.com/pmmp/PocketMine-DevTools/) - Development tools plugin for creating plugins
|
||||
- [ExamplePlugin](https://github.com/pmmp/ExamplePlugin/) - Example plugin demonstrating some basic API features
|
||||
- [DeveloperDocs](https://github.com/pmmp/DeveloperDocs/) - Reference, guides and specifications for the PocketMine-MP API
|
@ -44,6 +44,7 @@ use function implode;
|
||||
use function is_dir;
|
||||
use function is_resource;
|
||||
use function json_encode;
|
||||
use function json_last_error_msg;
|
||||
use function max;
|
||||
use function mkdir;
|
||||
use function ob_end_clean;
|
||||
@ -146,7 +147,11 @@ class CrashDump{
|
||||
$this->addLine("----------------------REPORT THE DATA BELOW THIS LINE-----------------------");
|
||||
$this->addLine();
|
||||
$this->addLine("===BEGIN CRASH DUMP===");
|
||||
$this->encodedData = zlib_encode(json_encode($this->data, JSON_UNESCAPED_SLASHES), ZLIB_ENCODING_DEFLATE, 9);
|
||||
$json = json_encode($this->data, JSON_UNESCAPED_SLASHES);
|
||||
if($json === false){
|
||||
throw new \RuntimeException("Failed to encode crashdump JSON: " . json_last_error_msg());
|
||||
}
|
||||
$this->encodedData = zlib_encode($json, ZLIB_ENCODING_DEFLATE, 9);
|
||||
foreach(str_split(base64_encode($this->encodedData), 76) as $line){
|
||||
$this->addLine($line);
|
||||
}
|
||||
@ -238,6 +243,9 @@ class CrashDump{
|
||||
}
|
||||
|
||||
if(isset($lastError)){
|
||||
if(isset($lastError["trace"])){
|
||||
$lastError["trace"] = Utils::printableTrace($lastError["trace"]);
|
||||
}
|
||||
$this->data["lastError"] = $lastError;
|
||||
}
|
||||
|
||||
|
@ -26,6 +26,7 @@ namespace pocketmine;
|
||||
use pocketmine\block\Bed;
|
||||
use pocketmine\block\Block;
|
||||
use pocketmine\block\BlockFactory;
|
||||
use pocketmine\block\UnknownBlock;
|
||||
use pocketmine\command\Command;
|
||||
use pocketmine\command\CommandSender;
|
||||
use pocketmine\entity\Effect;
|
||||
@ -103,6 +104,7 @@ use pocketmine\network\mcpe\protocol\AnimatePacket;
|
||||
use pocketmine\network\mcpe\protocol\AvailableCommandsPacket;
|
||||
use pocketmine\network\mcpe\protocol\AvailableEntityIdentifiersPacket;
|
||||
use pocketmine\network\mcpe\protocol\BatchPacket;
|
||||
use pocketmine\network\mcpe\protocol\BiomeDefinitionListPacket;
|
||||
use pocketmine\network\mcpe\protocol\BlockEntityDataPacket;
|
||||
use pocketmine\network\mcpe\protocol\BlockPickRequestPacket;
|
||||
use pocketmine\network\mcpe\protocol\BookEditPacket;
|
||||
@ -185,6 +187,7 @@ use function min;
|
||||
use function preg_match;
|
||||
use function round;
|
||||
use function spl_object_hash;
|
||||
use function sqrt;
|
||||
use function strlen;
|
||||
use function strpos;
|
||||
use function strtolower;
|
||||
@ -390,7 +393,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
}
|
||||
|
||||
public function isBanned() : bool{
|
||||
return $this->server->getNameBans()->isBanned($this->iusername);
|
||||
return $this->server->getNameBans()->isBanned($this->username);
|
||||
}
|
||||
|
||||
public function setBanned(bool $value){
|
||||
@ -403,14 +406,14 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
}
|
||||
|
||||
public function isWhitelisted() : bool{
|
||||
return $this->server->isWhitelisted($this->iusername);
|
||||
return $this->server->isWhitelisted($this->username);
|
||||
}
|
||||
|
||||
public function setWhitelisted(bool $value){
|
||||
if($value){
|
||||
$this->server->addWhitelist($this->iusername);
|
||||
$this->server->addWhitelist($this->username);
|
||||
}else{
|
||||
$this->server->removeWhitelist($this->iusername);
|
||||
$this->server->removeWhitelist($this->username);
|
||||
}
|
||||
}
|
||||
|
||||
@ -475,8 +478,11 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
}
|
||||
|
||||
public function setFlying(bool $value){
|
||||
$this->flying = $value;
|
||||
$this->sendSettings();
|
||||
if($this->flying !== $value){
|
||||
$this->flying = $value;
|
||||
$this->resetFallDistance();
|
||||
$this->sendSettings();
|
||||
}
|
||||
}
|
||||
|
||||
public function isFlying() : bool{
|
||||
@ -688,14 +694,14 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
|
||||
$this->perm->recalculatePermissions();
|
||||
|
||||
if($this->hasPermission(Server::BROADCAST_CHANNEL_USERS)){
|
||||
$permManager->subscribeToPermission(Server::BROADCAST_CHANNEL_USERS, $this);
|
||||
}
|
||||
if($this->hasPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE)){
|
||||
$permManager->subscribeToPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE, $this);
|
||||
}
|
||||
|
||||
if($this->spawned){
|
||||
if($this->hasPermission(Server::BROADCAST_CHANNEL_USERS)){
|
||||
$permManager->subscribeToPermission(Server::BROADCAST_CHANNEL_USERS, $this);
|
||||
}
|
||||
if($this->hasPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE)){
|
||||
$permManager->subscribeToPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE, $this);
|
||||
}
|
||||
|
||||
$this->sendCommandData();
|
||||
}
|
||||
}
|
||||
@ -715,7 +721,8 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
}
|
||||
|
||||
$data = new CommandData();
|
||||
$data->commandName = $command->getName();
|
||||
//TODO: commands containing uppercase letters in the name crash 1.9.0 client
|
||||
$data->commandName = strtolower($command->getName());
|
||||
$data->commandDescription = $this->server->getLanguage()->translateString($command->getDescription());
|
||||
$data->flags = 0;
|
||||
$data->permission = 0;
|
||||
@ -1058,6 +1065,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
return; //avoid player spawning twice (this can only happen on 3.x with a custom malicious client)
|
||||
}
|
||||
$this->spawned = true;
|
||||
$this->setImmobile(false);
|
||||
|
||||
if($this->hasPermission(Server::BROADCAST_CHANNEL_USERS)){
|
||||
PermissionManager::getInstance()->subscribeToPermission(Server::BROADCAST_CHANNEL_USERS, $this);
|
||||
@ -1076,10 +1084,12 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
$this->server->broadcastMessage($ev->getJoinMessage());
|
||||
}
|
||||
|
||||
$this->setImmobile(false);
|
||||
$this->noDamageTicks = 60;
|
||||
|
||||
foreach($this->usedChunks as $index => $c){
|
||||
foreach($this->usedChunks as $index => $hasSent){
|
||||
if(!$hasSent){
|
||||
continue; //this will happen when the chunk is ready to send
|
||||
}
|
||||
Level::getXZ($index, $chunkX, $chunkZ);
|
||||
foreach($this->level->getChunkEntities($chunkX, $chunkZ) as $entity){
|
||||
if($entity !== $this and !$entity->isClosed() and $entity->isAlive() and !$entity->isFlaggedForDespawn()){
|
||||
@ -1409,19 +1419,17 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
|
||||
$this->allowFlight = $this->isCreative();
|
||||
if($this->isSpectator()){
|
||||
$this->flying = true;
|
||||
$this->setFlying(true);
|
||||
$this->keepMovement = true;
|
||||
$this->despawnFromAll();
|
||||
}else{
|
||||
$this->keepMovement = $this->allowMovementCheats;
|
||||
if($this->isSurvival()){
|
||||
$this->flying = false;
|
||||
$this->setFlying(false);
|
||||
}
|
||||
$this->spawnToAll();
|
||||
}
|
||||
|
||||
$this->resetFallDistance();
|
||||
|
||||
$this->namedtag->setInt("playerGameType", $this->gamemode);
|
||||
if(!$client){ //Gamemode changed by server, do not send for client changes
|
||||
$this->sendGamemode();
|
||||
@ -1647,7 +1655,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
}else{
|
||||
$this->broadcastMovement();
|
||||
|
||||
$distance = $from->distance($to);
|
||||
$distance = sqrt((($from->x - $to->x) ** 2) + (($from->z - $to->z) ** 2));
|
||||
//TODO: check swimming (adds 0.015 exhaustion in MCPE)
|
||||
if($this->isSprinting()){
|
||||
$this->exhaust(0.1 * $distance, PlayerExhaustEvent::CAUSE_SPRINTING);
|
||||
@ -1678,6 +1686,12 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
$this->newPosition = null;
|
||||
}
|
||||
|
||||
public function fall(float $fallDistance) : void{
|
||||
if(!$this->flying){
|
||||
parent::fall($fallDistance);
|
||||
}
|
||||
}
|
||||
|
||||
public function jump() : void{
|
||||
(new PlayerJumpEvent($this))->call();
|
||||
parent::jump();
|
||||
@ -1920,7 +1934,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
return true;
|
||||
}
|
||||
|
||||
if(!$this->server->isWhitelisted($this->iusername) and $this->kick("Server is white-listed", false)){
|
||||
if(!$this->server->isWhitelisted($this->username) and $this->kick("Server is white-listed", false)){
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -2050,7 +2064,13 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
case ResourcePackClientResponsePacket::STATUS_SEND_PACKS:
|
||||
$manager = $this->server->getResourcePackManager();
|
||||
foreach($packet->packIds as $uuid){
|
||||
$pack = $manager->getPackById(substr($uuid, 0, strpos($uuid, "_"))); //dirty hack for mojang's dirty hack for versions
|
||||
//dirty hack for mojang's dirty hack for versions
|
||||
$splitPos = strpos($uuid, "_");
|
||||
if($splitPos !== false){
|
||||
$uuid = substr($uuid, 0, $splitPos);
|
||||
}
|
||||
|
||||
$pack = $manager->getPackById($uuid);
|
||||
if(!($pack instanceof ResourcePack)){
|
||||
//Client requested a resource pack but we don't have it available on the server
|
||||
$this->close("", "disconnectionScreen.resourcePack", true);
|
||||
@ -2090,6 +2110,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
/** @var float[] $pos */
|
||||
$pos = $this->namedtag->getListTag("Pos")->getAllValues();
|
||||
$this->level->registerChunkLoader($this, ((int) floor($pos[0])) >> 4, ((int) floor($pos[2])) >> 4, true);
|
||||
$this->usedChunks[Level::chunkHash(((int) floor($pos[0])) >> 4, ((int) floor($pos[2])) >> 4)] = false;
|
||||
|
||||
parent::__construct($this->level, $this->namedtag);
|
||||
$ev = new PlayerLoginEvent($this, "Plugin reason");
|
||||
@ -2137,6 +2158,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
$this->dataPacket($pk);
|
||||
|
||||
$this->sendDataPacket(new AvailableEntityIdentifiersPacket());
|
||||
$this->sendDataPacket(new BiomeDefinitionListPacket());
|
||||
|
||||
$this->level->sendTime($this);
|
||||
|
||||
@ -2714,6 +2736,9 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
|
||||
public function handleBlockPickRequest(BlockPickRequestPacket $packet) : bool{
|
||||
$block = $this->level->getBlockAt($packet->blockX, $packet->blockY, $packet->blockZ);
|
||||
if($block instanceof UnknownBlock){
|
||||
return true;
|
||||
}
|
||||
|
||||
$item = $block->getPickedItem();
|
||||
if($packet->addUserData){
|
||||
@ -2938,8 +2963,9 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
$ev->call();
|
||||
if($ev->isCancelled()){
|
||||
$this->sendSettings();
|
||||
}else{
|
||||
}else{ //don't use setFlying() here, to avoid feedback loops
|
||||
$this->flying = $ev->isFlying();
|
||||
$this->resetFallDistance();
|
||||
}
|
||||
|
||||
$handled = true;
|
||||
@ -2969,7 +2995,8 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
$t = $this->level->getTile($pos);
|
||||
if($t instanceof Spawnable){
|
||||
$nbt = new NetworkLittleEndianNBTStream();
|
||||
$compound = $nbt->read($packet->namedtag);
|
||||
$_ = 0;
|
||||
$compound = $nbt->read($packet->namedtag, false, $_, 512);
|
||||
|
||||
if(!($compound instanceof CompoundTag)){
|
||||
throw new \InvalidArgumentException("Expected " . CompoundTag::class . " in block entity NBT, got " . (is_object($compound) ? get_class($compound) : gettype($compound)));
|
||||
@ -3285,7 +3312,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the title duration settings.
|
||||
* Resets the title duration settings to defaults and removes any existing titles.
|
||||
*/
|
||||
public function resetTitles(){
|
||||
$pk = new SetTitlePacket();
|
||||
@ -3613,7 +3640,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
}
|
||||
|
||||
if($this->inventory !== null){
|
||||
$this->inventory->setHeldItemIndex(0, false); //This is already handled when sending contents, don't send it twice
|
||||
$this->inventory->setHeldItemIndex(0);
|
||||
$this->inventory->clearAll();
|
||||
}
|
||||
if($this->armorInventory !== null){
|
||||
@ -3621,8 +3648,12 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: allow this number to be manipulated during PlayerDeathEvent
|
||||
$this->level->dropExperience($this, $this->getXpDropAmount());
|
||||
$this->setXpAndProgress(0, 0);
|
||||
|
||||
if($ev->getDeathMessage() != ""){
|
||||
$this->server->broadcast($ev->getDeathMessage(), Server::BROADCAST_CHANNEL_USERS);
|
||||
$this->server->broadcastMessage($ev->getDeathMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@ -3881,17 +3912,17 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
* @param Inventory $inventory
|
||||
* @param bool $force Forces removal of permanent windows such as normal inventory, cursor
|
||||
*
|
||||
* @throws \BadMethodCallException if trying to remove a fixed inventory window without the `force` parameter as true
|
||||
* @throws \InvalidArgumentException if trying to remove a fixed inventory window without the `force` parameter as true
|
||||
*/
|
||||
public function removeWindow(Inventory $inventory, bool $force = false){
|
||||
$id = $this->windows[$hash = spl_object_hash($inventory)] ?? null;
|
||||
|
||||
if($id !== null and !$force and isset($this->permanentWindows[$id])){
|
||||
throw new \BadMethodCallException("Cannot remove fixed window $id (" . get_class($inventory) . ") from " . $this->getName());
|
||||
throw new \InvalidArgumentException("Cannot remove fixed window $id (" . get_class($inventory) . ") from " . $this->getName());
|
||||
}
|
||||
|
||||
$inventory->close($this);
|
||||
if($id !== null){
|
||||
$inventory->close($this);
|
||||
unset($this->windows[$hash], $this->windowIndex[$id], $this->permanentWindows[$id]);
|
||||
}
|
||||
}
|
||||
|
@ -36,10 +36,7 @@ namespace pocketmine {
|
||||
use pocketmine\utils\VersionString;
|
||||
use pocketmine\wizard\SetupWizard;
|
||||
|
||||
const NAME = "PocketMine-MP";
|
||||
const BASE_VERSION = "3.5.5";
|
||||
const IS_DEVELOPMENT_BUILD = false;
|
||||
const BUILD_NUMBER = 0;
|
||||
require_once __DIR__ . '/VersionInfo.php';
|
||||
|
||||
const MIN_PHP_VERSION = "7.2.0";
|
||||
|
||||
@ -177,11 +174,10 @@ namespace pocketmine {
|
||||
ini_set("default_charset", "utf-8");
|
||||
|
||||
ini_set("memory_limit", '-1');
|
||||
define('pocketmine\START_TIME', microtime(true));
|
||||
|
||||
define('pocketmine\RESOURCE_PATH', \pocketmine\PATH . 'src' . DIRECTORY_SEPARATOR . 'pocketmine' . DIRECTORY_SEPARATOR . 'resources' . DIRECTORY_SEPARATOR);
|
||||
|
||||
$opts = getopt("", ["data:", "plugins:", "no-wizard"]);
|
||||
$opts = getopt("", ["data:", "plugins:", "no-wizard", "enable-ansi", "disable-ansi"]);
|
||||
|
||||
define('pocketmine\DATA', isset($opts["data"]) ? $opts["data"] . DIRECTORY_SEPARATOR : realpath(getcwd()) . DIRECTORY_SEPARATOR);
|
||||
define('pocketmine\PLUGIN_PATH', isset($opts["plugins"]) ? $opts["plugins"] . DIRECTORY_SEPARATOR : realpath(getcwd()) . DIRECTORY_SEPARATOR . "plugins" . DIRECTORY_SEPARATOR);
|
||||
@ -190,9 +186,25 @@ namespace pocketmine {
|
||||
mkdir(\pocketmine\DATA, 0777, true);
|
||||
}
|
||||
|
||||
define('pocketmine\LOCK_FILE_PATH', \pocketmine\DATA . 'server.lock');
|
||||
define('pocketmine\LOCK_FILE', fopen(\pocketmine\LOCK_FILE_PATH, "cb"));
|
||||
if(!flock(\pocketmine\LOCK_FILE, LOCK_EX | LOCK_NB)){
|
||||
critical_error("Another " . \pocketmine\NAME . " instance is already using this folder (" . realpath(\pocketmine\DATA) . ").");
|
||||
critical_error("Please stop the other server first before running a new one.");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
//Logger has a dependency on timezone
|
||||
$tzError = Timezone::init();
|
||||
|
||||
if(isset($opts["enable-ansi"])){
|
||||
Terminal::init(true);
|
||||
}elseif(isset($opts["disable-ansi"])){
|
||||
Terminal::init(false);
|
||||
}else{
|
||||
Terminal::init();
|
||||
}
|
||||
|
||||
$logger = new MainLogger(\pocketmine\DATA . "server.log");
|
||||
$logger->registerStatic();
|
||||
|
||||
@ -204,6 +216,9 @@ namespace pocketmine {
|
||||
if(extension_loaded("xdebug")){
|
||||
$logger->warning(PHP_EOL . PHP_EOL . PHP_EOL . "\tYou are running " . \pocketmine\NAME . " with xdebug enabled. This has a major impact on performance." . PHP_EOL . PHP_EOL);
|
||||
}
|
||||
if(!extension_loaded("pocketmine_chunkutils")){
|
||||
$logger->warning("ChunkUtils extension is missing. Anvil-format worlds will experience degraded performance.");
|
||||
}
|
||||
|
||||
if(\Phar::running(true) === ""){
|
||||
$logger->warning("Non-packaged " . \pocketmine\NAME . " installation detected. Consider using a phar in production for better performance.");
|
||||
@ -245,6 +260,8 @@ namespace pocketmine {
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: move this to a Server field
|
||||
define('pocketmine\START_TIME', microtime(true));
|
||||
ThreadManager::init();
|
||||
new Server($autoloader, $logger, \pocketmine\DATA, \pocketmine\PLUGIN_PATH);
|
||||
|
||||
|
@ -112,7 +112,6 @@ use function array_sum;
|
||||
use function asort;
|
||||
use function assert;
|
||||
use function base64_encode;
|
||||
use function bin2hex;
|
||||
use function class_exists;
|
||||
use function count;
|
||||
use function define;
|
||||
@ -122,9 +121,7 @@ use function file_exists;
|
||||
use function file_get_contents;
|
||||
use function file_put_contents;
|
||||
use function filemtime;
|
||||
use function floor;
|
||||
use function function_exists;
|
||||
use function gc_collect_cycles;
|
||||
use function get_class;
|
||||
use function getmypid;
|
||||
use function getopt;
|
||||
@ -143,6 +140,7 @@ use function max;
|
||||
use function microtime;
|
||||
use function min;
|
||||
use function mkdir;
|
||||
use function ob_end_flush;
|
||||
use function pcntl_signal;
|
||||
use function pcntl_signal_dispatch;
|
||||
use function preg_replace;
|
||||
@ -295,15 +293,6 @@ class Server{
|
||||
/** @var int */
|
||||
public $networkCompressionLevel = 7;
|
||||
|
||||
/** @var bool */
|
||||
private $autoTickRate = true;
|
||||
/** @var int */
|
||||
private $autoTickRateLimit = 20;
|
||||
/** @var bool */
|
||||
private $alwaysTickPlayers = false;
|
||||
/** @var int */
|
||||
private $baseTickRate = 1;
|
||||
|
||||
/** @var int */
|
||||
private $autoSaveTicker = 0;
|
||||
/** @var int */
|
||||
@ -809,6 +798,7 @@ class Server{
|
||||
* @return bool
|
||||
*/
|
||||
public function hasOfflinePlayerData(string $name) : bool{
|
||||
$name = strtolower($name);
|
||||
return file_exists($this->getDataPath() . "players/$name.dat");
|
||||
}
|
||||
|
||||
@ -1056,7 +1046,7 @@ class Server{
|
||||
*/
|
||||
public function unloadLevel(Level $level, bool $forceUnload = false) : bool{
|
||||
if($level === $this->getDefaultLevel() and !$forceUnload){
|
||||
throw new \InvalidStateException("The default level cannot be unloaded while running, please switch levels.");
|
||||
throw new \InvalidStateException("The default world cannot be unloaded while running, please switch worlds.");
|
||||
}
|
||||
|
||||
return $level->unload($forceUnload);
|
||||
@ -1082,7 +1072,7 @@ class Server{
|
||||
*/
|
||||
public function loadLevel(string $name) : bool{
|
||||
if(trim($name) === ""){
|
||||
throw new LevelException("Invalid empty level name");
|
||||
throw new LevelException("Invalid empty world name");
|
||||
}
|
||||
if($this->isLevelLoaded($name)){
|
||||
return true;
|
||||
@ -1101,16 +1091,24 @@ class Server{
|
||||
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* @var LevelProvider $provider
|
||||
* @see LevelProvider::__construct()
|
||||
*/
|
||||
$provider = new $providerClass($path);
|
||||
try{
|
||||
GeneratorManager::getGenerator($provider->getGenerator(), true);
|
||||
}catch(\InvalidArgumentException $e){
|
||||
$this->logger->error($this->getLanguage()->translateString("pocketmine.level.loadError", [$name, "Unknown generator \"" . $provider->getGenerator() . "\""]));
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @see LevelProvider::__construct() */
|
||||
$level = new Level($this, $name, new $providerClass($path));
|
||||
$level = new Level($this, $name, $provider);
|
||||
|
||||
$this->levels[$level->getId()] = $level;
|
||||
|
||||
(new LevelLoadEvent($level))->call();
|
||||
|
||||
$level->setTickRate($this->baseTickRate);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -1142,7 +1140,7 @@ class Server{
|
||||
if(($providerClass = LevelProviderManager::getProviderByName($this->getProperty("level-settings.default-format", "pmanvil"))) === null){
|
||||
$providerClass = LevelProviderManager::getProviderByName("pmanvil");
|
||||
if($providerClass === null){
|
||||
throw new \InvalidStateException("Default level provider has not been registered");
|
||||
throw new \InvalidStateException("Default world provider has not been registered");
|
||||
}
|
||||
}
|
||||
|
||||
@ -1154,8 +1152,6 @@ class Server{
|
||||
$level = new Level($this, $name, new $providerClass($path));
|
||||
$this->levels[$level->getId()] = $level;
|
||||
|
||||
$level->setTickRate($this->baseTickRate);
|
||||
|
||||
(new LevelInitEvent($level))->call();
|
||||
|
||||
(new LevelLoadEvent($level))->call();
|
||||
@ -1593,11 +1589,6 @@ class Server{
|
||||
}
|
||||
$this->networkCompressionAsync = (bool) $this->getProperty("network.async-compression", true);
|
||||
|
||||
$this->autoTickRate = (bool) $this->getProperty("level-settings.auto-tick-rate", true);
|
||||
$this->autoTickRateLimit = (int) $this->getProperty("level-settings.auto-tick-rate-limit", 20);
|
||||
$this->alwaysTickPlayers = (bool) $this->getProperty("level-settings.always-tick-players", false);
|
||||
$this->baseTickRate = (int) $this->getProperty("level-settings.base-tick-rate", 1);
|
||||
|
||||
$this->doTitleTick = ((bool) $this->getProperty("console.title-tick", true)) && Terminal::hasFormattingCodes();
|
||||
|
||||
|
||||
@ -1696,12 +1687,17 @@ class Server{
|
||||
Entity::init();
|
||||
Tile::init();
|
||||
BlockFactory::init();
|
||||
BlockFactory::registerStaticRuntimeIdMappings();
|
||||
Enchantment::init();
|
||||
ItemFactory::init();
|
||||
Item::initCreativeItems();
|
||||
Biome::init();
|
||||
|
||||
LevelProviderManager::init();
|
||||
if(extension_loaded("leveldb")){
|
||||
$this->logger->debug($this->getLanguage()->translateString("pocketmine.debug.enable"));
|
||||
}
|
||||
GeneratorManager::registerDefaultGenerators();
|
||||
|
||||
$this->craftingManager = new CraftingManager();
|
||||
|
||||
$this->resourceManager = new ResourcePackManager($this->getDataPath() . "resource_packs" . DIRECTORY_SEPARATOR, $this->logger);
|
||||
@ -1723,15 +1719,10 @@ class Server{
|
||||
|
||||
$this->network->registerInterface(new RakLibInterface($this));
|
||||
|
||||
LevelProviderManager::init();
|
||||
if(extension_loaded("leveldb")){
|
||||
$this->logger->debug($this->getLanguage()->translateString("pocketmine.debug.enable"));
|
||||
}
|
||||
|
||||
GeneratorManager::registerDefaultGenerators();
|
||||
|
||||
foreach((array) $this->getProperty("worlds", []) as $name => $options){
|
||||
if(!is_array($options)){
|
||||
if($options === null){
|
||||
$options = [];
|
||||
}elseif(!is_array($options)){
|
||||
continue;
|
||||
}
|
||||
if(!$this->loadLevel($name)){
|
||||
@ -2036,7 +2027,7 @@ class Server{
|
||||
}
|
||||
|
||||
public function reload(){
|
||||
$this->logger->info("Saving levels...");
|
||||
$this->logger->info("Saving worlds...");
|
||||
|
||||
foreach($this->levels as $level){
|
||||
$level->save();
|
||||
@ -2114,7 +2105,7 @@ class Server{
|
||||
$player->close($player->getLeaveMessage(), $this->getProperty("settings.shutdown-message", "Server closed"));
|
||||
}
|
||||
|
||||
$this->getLogger()->debug("Unloading all levels");
|
||||
$this->getLogger()->debug("Unloading all worlds");
|
||||
foreach($this->getLevels() as $level){
|
||||
$this->unloadLevel($level, true);
|
||||
}
|
||||
@ -2146,9 +2137,6 @@ class Server{
|
||||
$this->network->unregisterInterface($interface);
|
||||
}
|
||||
}
|
||||
|
||||
$this->getLogger()->debug("Collecting cycles");
|
||||
gc_collect_cycles();
|
||||
}catch(\Throwable $e){
|
||||
$this->logger->logException($e);
|
||||
$this->logger->emergency("Crashed while crashing, killing process");
|
||||
@ -2219,6 +2207,7 @@ class Server{
|
||||
* @param array|null $trace
|
||||
*/
|
||||
public function exceptionHandler(\Throwable $e, $trace = null){
|
||||
while(@ob_end_flush()){}
|
||||
global $lastError;
|
||||
|
||||
if($trace === null){
|
||||
@ -2250,6 +2239,7 @@ class Server{
|
||||
}
|
||||
|
||||
public function crashDump(){
|
||||
while(@ob_end_flush()){}
|
||||
if(!$this->isRunning){
|
||||
return;
|
||||
}
|
||||
@ -2281,6 +2271,7 @@ class Server{
|
||||
$p = $this->pluginManager->getPlugin($plugin);
|
||||
if($p instanceof Plugin and !($p->getPluginLoader() instanceof PharPluginLoader)){
|
||||
$this->logger->debug("Not sending crashdump due to caused by non-phar plugin");
|
||||
$report = false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -2430,8 +2421,6 @@ class Server{
|
||||
foreach($this->players as $p){
|
||||
if(!$p->loggedIn and ($tickTime - $p->creationTime) >= 10){
|
||||
$p->close("", "Login timeout");
|
||||
}elseif($this->alwaysTickPlayers and $p->spawned){
|
||||
$p->onUpdate($currentTick);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2441,32 +2430,13 @@ class Server{
|
||||
// Level unloaded during the tick of a level earlier in this loop, perhaps by plugin
|
||||
continue;
|
||||
}
|
||||
if($level->getTickRate() > $this->baseTickRate and --$level->tickRateCounter > 0){
|
||||
continue;
|
||||
}
|
||||
|
||||
$levelTime = microtime(true);
|
||||
$level->doTick($currentTick);
|
||||
$tickMs = (microtime(true) - $levelTime) * 1000;
|
||||
$level->tickRateTime = $tickMs;
|
||||
|
||||
if($this->autoTickRate){
|
||||
if($tickMs < 50 and $level->getTickRate() > $this->baseTickRate){
|
||||
$level->setTickRate($r = $level->getTickRate() - 1);
|
||||
if($r > $this->baseTickRate){
|
||||
$level->tickRateCounter = $level->getTickRate();
|
||||
}
|
||||
$this->getLogger()->debug("Raising level \"{$level->getName()}\" tick rate to {$level->getTickRate()} ticks");
|
||||
}elseif($tickMs >= 50){
|
||||
if($level->getTickRate() === $this->baseTickRate){
|
||||
$level->setTickRate(max($this->baseTickRate + 1, min($this->autoTickRateLimit, (int) floor($tickMs / 50))));
|
||||
$this->getLogger()->debug(sprintf("Level \"%s\" took %gms, setting tick rate to %d ticks", $level->getName(), (int) round($tickMs, 2), $level->getTickRate()));
|
||||
}elseif(($tickMs / $level->getTickRate()) >= 50 and $level->getTickRate() < $this->autoTickRateLimit){
|
||||
$level->setTickRate($level->getTickRate() + 1);
|
||||
$this->getLogger()->debug(sprintf("Level \"%s\" took %gms, setting tick rate to %d ticks", $level->getName(), (int) round($tickMs, 2), $level->getTickRate()));
|
||||
}
|
||||
$level->tickRateCounter = $level->getTickRate();
|
||||
}
|
||||
if($tickMs >= 50){
|
||||
$this->getLogger()->debug(sprintf("World \"%s\" took too long to tick: %gms (%g ticks)", $level->getName(), $tickMs, round($tickMs / 50, 2)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2557,7 +2527,7 @@ class Server{
|
||||
if(strlen($payload) > 2 and substr($payload, 0, 2) === "\xfe\xfd" and $this->queryHandler instanceof QueryHandler){
|
||||
$this->queryHandler->handle($interface, $address, $port, $payload);
|
||||
}else{
|
||||
$this->logger->debug("Unhandled raw packet from $address $port: " . bin2hex($payload));
|
||||
$this->logger->debug("Unhandled raw packet from $address $port: " . base64_encode($payload));
|
||||
}
|
||||
}catch(\Throwable $e){
|
||||
$this->logger->logException($e);
|
||||
@ -2619,7 +2589,11 @@ class Server{
|
||||
|
||||
if($this->autoSave and ++$this->autoSaveTicker >= $this->autoSaveTicks){
|
||||
$this->autoSaveTicker = 0;
|
||||
$this->getLogger()->debug("[Auto Save] Saving worlds...");
|
||||
$start = microtime(true);
|
||||
$this->doAutoSave();
|
||||
$time = (microtime(true) - $start);
|
||||
$this->getLogger()->debug("[Auto Save] Save completed in " . ($time >= 1 ? round($time, 3) . "s" : round($time * 1000) . "ms"));
|
||||
}
|
||||
|
||||
if($this->sendUsageTicker > 0 and --$this->sendUsageTicker === 0){
|
||||
|
@ -28,7 +28,7 @@ namespace pocketmine;
|
||||
*/
|
||||
abstract class Thread extends \Thread{
|
||||
|
||||
/** @var \ClassLoader */
|
||||
/** @var \ClassLoader|null */
|
||||
protected $classLoader;
|
||||
/** @var string|null */
|
||||
protected $composerAutoloaderPath;
|
||||
|
27
src/pocketmine/VersionInfo.php
Normal file
27
src/pocketmine/VersionInfo.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
namespace pocketmine;
|
||||
|
||||
const NAME = "PocketMine-MP";
|
||||
const BASE_VERSION = "3.8.4";
|
||||
const IS_DEVELOPMENT_BUILD = false;
|
||||
const BUILD_NUMBER = 0;
|
@ -28,7 +28,7 @@ namespace pocketmine;
|
||||
*/
|
||||
abstract class Worker extends \Worker{
|
||||
|
||||
/** @var \ClassLoader */
|
||||
/** @var \ClassLoader|null */
|
||||
protected $classLoader;
|
||||
/** @var string|null */
|
||||
protected $composerAutoloaderPath;
|
||||
|
@ -36,9 +36,11 @@ use pocketmine\math\RayTraceResult;
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\metadata\Metadatable;
|
||||
use pocketmine\metadata\MetadataValue;
|
||||
use pocketmine\network\mcpe\protocol\types\RuntimeBlockMapping;
|
||||
use pocketmine\Player;
|
||||
use pocketmine\plugin\Plugin;
|
||||
use function array_merge;
|
||||
use function get_class;
|
||||
use const PHP_INT_MAX;
|
||||
|
||||
class Block extends Position implements BlockIds, Metadatable{
|
||||
@ -116,7 +118,7 @@ class Block extends Position implements BlockIds, Metadatable{
|
||||
* @return int
|
||||
*/
|
||||
public function getRuntimeId() : int{
|
||||
return BlockFactory::toStaticRuntimeId($this->getId(), $this->getDamage());
|
||||
return RuntimeBlockMapping::toStaticRuntimeId($this->getId(), $this->getDamage());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -268,6 +270,7 @@ class Block extends Position implements BlockIds, Metadatable{
|
||||
* @param Item $item
|
||||
*
|
||||
* @return float
|
||||
* @throws \InvalidArgumentException if the item efficiency is not a positive number
|
||||
*/
|
||||
public function getBreakTime(Item $item) : float{
|
||||
$base = $this->getHardness();
|
||||
@ -279,7 +282,7 @@ class Block extends Position implements BlockIds, Metadatable{
|
||||
|
||||
$efficiency = $item->getMiningEfficiency($this);
|
||||
if($efficiency <= 0){
|
||||
throw new \RuntimeException("Item efficiency is invalid");
|
||||
throw new \InvalidArgumentException(get_class($item) . " has invalid mining efficiency: expected >= 0, got $efficiency");
|
||||
}
|
||||
|
||||
$base /= $efficiency;
|
||||
|
@ -25,9 +25,7 @@ namespace pocketmine\block;
|
||||
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\level\Position;
|
||||
use function file_get_contents;
|
||||
use function json_decode;
|
||||
use function max;
|
||||
use pocketmine\network\mcpe\protocol\types\RuntimeBlockMapping;
|
||||
use function min;
|
||||
|
||||
/**
|
||||
@ -52,15 +50,6 @@ class BlockFactory{
|
||||
/** @var \SplFixedArray<float> */
|
||||
public static $blastResistance = null;
|
||||
|
||||
/** @var int[] */
|
||||
public static $staticRuntimeIdMap = [];
|
||||
|
||||
/** @var int[] */
|
||||
public static $legacyIdMap = [];
|
||||
|
||||
/** @var int */
|
||||
private static $lastRuntimeId = 0;
|
||||
|
||||
/**
|
||||
* Initializes the block factory. By default this is called only once on server start, however you may wish to use
|
||||
* this if you need to reset the block factory back to its original defaults for whatever reason.
|
||||
@ -171,7 +160,7 @@ class BlockFactory{
|
||||
self::registerBlock(new Cake());
|
||||
//TODO: REPEATER_BLOCK
|
||||
//TODO: POWERED_REPEATER
|
||||
//TODO: INVISIBLEBEDROCK
|
||||
self::registerBlock(new InvisibleBedrock());
|
||||
self::registerBlock(new Trapdoor());
|
||||
//TODO: MONSTER_EGG
|
||||
self::registerBlock(new StoneBricks());
|
||||
@ -319,13 +308,13 @@ class BlockFactory{
|
||||
self::registerBlock(new Stonecutter());
|
||||
self::registerBlock(new GlowingObsidian());
|
||||
self::registerBlock(new NetherReactor());
|
||||
//TODO: INFO_UPDATE
|
||||
//TODO: INFO_UPDATE2
|
||||
self::registerBlock(new InfoUpdate(Block::INFO_UPDATE, 0, "update!"));
|
||||
self::registerBlock(new InfoUpdate(Block::INFO_UPDATE2, 0, "ate!upd"));
|
||||
//TODO: MOVINGBLOCK
|
||||
//TODO: OBSERVER
|
||||
//TODO: STRUCTURE_BLOCK
|
||||
|
||||
//TODO: RESERVED6
|
||||
self::registerBlock(new Reserved6(Block::RESERVED6, 0, "reserved6"));
|
||||
|
||||
for($id = 0, $size = self::$fullList->getSize() >> 4; $id < $size; ++$id){
|
||||
if(self::$fullList[$id << 4] === null){
|
||||
@ -427,16 +416,9 @@ class BlockFactory{
|
||||
return $b !== null and !($b instanceof UnknownBlock);
|
||||
}
|
||||
|
||||
public static function registerStaticRuntimeIdMappings() : void{
|
||||
/** @var mixed[] $runtimeIdMap */
|
||||
$runtimeIdMap = json_decode(file_get_contents(\pocketmine\RESOURCE_PATH . "runtimeid_table.json"), true);
|
||||
foreach($runtimeIdMap as $k => $obj){
|
||||
self::registerMapping($k, $obj["id"], $obj["data"]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @deprecated
|
||||
*
|
||||
* @param int $id
|
||||
* @param int $meta
|
||||
@ -444,15 +426,11 @@ class BlockFactory{
|
||||
* @return int
|
||||
*/
|
||||
public static function toStaticRuntimeId(int $id, int $meta = 0) : int{
|
||||
/*
|
||||
* try id+meta first
|
||||
* if not found, try id+0 (strip meta)
|
||||
* if still not found, return update! block
|
||||
*/
|
||||
return self::$staticRuntimeIdMap[($id << 4) | $meta] ?? self::$staticRuntimeIdMap[$id << 4] ?? self::$staticRuntimeIdMap[BlockIds::INFO_UPDATE << 4];
|
||||
return RuntimeBlockMapping::toStaticRuntimeId($id, $meta);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @internal
|
||||
*
|
||||
* @param int $runtimeId
|
||||
@ -460,13 +438,6 @@ class BlockFactory{
|
||||
* @return int[] [id, meta]
|
||||
*/
|
||||
public static function fromStaticRuntimeId(int $runtimeId) : array{
|
||||
$v = self::$legacyIdMap[$runtimeId];
|
||||
return [$v >> 4, $v & 0xf];
|
||||
}
|
||||
|
||||
private static function registerMapping(int $staticRuntimeId, int $legacyId, int $legacyMeta) : void{
|
||||
self::$staticRuntimeIdMap[($legacyId << 4) | $legacyMeta] = $staticRuntimeId;
|
||||
self::$legacyIdMap[$staticRuntimeId] = ($legacyId << 4) | $legacyMeta;
|
||||
self::$lastRuntimeId = max(self::$lastRuntimeId, $staticRuntimeId);
|
||||
return RuntimeBlockMapping::fromStaticRuntimeId($runtimeId);
|
||||
}
|
||||
}
|
||||
|
@ -96,9 +96,12 @@ class Cactus extends Transparent{
|
||||
if($b->getId() === self::AIR){
|
||||
$ev = new BlockGrowEvent($b, BlockFactory::get(Block::CACTUS));
|
||||
$ev->call();
|
||||
if(!$ev->isCancelled()){
|
||||
$this->getLevel()->setBlock($b, $ev->getNewState(), true);
|
||||
if($ev->isCancelled()){
|
||||
break;
|
||||
}
|
||||
$this->getLevel()->setBlock($b, $ev->getNewState(), true);
|
||||
}else{
|
||||
break;
|
||||
}
|
||||
}
|
||||
$this->meta = 0;
|
||||
|
@ -46,7 +46,7 @@ class Carpet extends Flowable{
|
||||
}
|
||||
|
||||
public function getName() : string{
|
||||
return ColorBlockMetaHelper::getColorFromMeta($this->meta) . " Carpet";
|
||||
return ColorBlockMetaHelper::getColorFromMeta($this->getVariant()) . " Carpet";
|
||||
}
|
||||
|
||||
protected function recalculateBoundingBox() : ?AxisAlignedBB{
|
||||
|
@ -30,6 +30,18 @@ use pocketmine\math\Vector3;
|
||||
class CobblestoneWall extends Transparent{
|
||||
public const NONE_MOSSY_WALL = 0;
|
||||
public const MOSSY_WALL = 1;
|
||||
public const GRANITE_WALL = 2;
|
||||
public const DIORITE_WALL = 3;
|
||||
public const ANDESITE_WALL = 4;
|
||||
public const SANDSTONE_WALL = 5;
|
||||
public const BRICK_WALL = 6;
|
||||
public const STONE_BRICK_WALL = 7;
|
||||
public const MOSSY_STONE_BRICK_WALL = 8;
|
||||
public const NETHER_BRICK_WALL = 9;
|
||||
public const END_STONE_BRICK_WALL = 10;
|
||||
public const PRISMARINE_WALL = 11;
|
||||
public const RED_SANDSTONE_WALL = 12;
|
||||
public const RED_NETHER_BRICK_WALL = 13;
|
||||
|
||||
protected $id = self::COBBLESTONE_WALL;
|
||||
|
||||
@ -50,11 +62,23 @@ class CobblestoneWall extends Transparent{
|
||||
}
|
||||
|
||||
public function getName() : string{
|
||||
if($this->meta === 0x01){
|
||||
return "Mossy Cobblestone Wall";
|
||||
}
|
||||
|
||||
return "Cobblestone Wall";
|
||||
static $names = [
|
||||
self::NONE_MOSSY_WALL => "Cobblestone",
|
||||
self::MOSSY_WALL => "Mossy Cobblestone",
|
||||
self::GRANITE_WALL => "Granite",
|
||||
self::DIORITE_WALL => "Diorite",
|
||||
self::ANDESITE_WALL => "Andesite",
|
||||
self::SANDSTONE_WALL => "Sandstone",
|
||||
self::BRICK_WALL => "Brick",
|
||||
self::STONE_BRICK_WALL => "Stone Brick",
|
||||
self::MOSSY_STONE_BRICK_WALL => "Mossy Stone Brick",
|
||||
self::NETHER_BRICK_WALL => "Nether Brick",
|
||||
self::END_STONE_BRICK_WALL => "End Stone Brick",
|
||||
self::PRISMARINE_WALL => "Prismarine",
|
||||
self::RED_SANDSTONE_WALL => "Red Sandstone",
|
||||
self::RED_NETHER_BRICK_WALL => "Red Nether Brick"
|
||||
];
|
||||
return ($names[$this->getVariant()] ?? "Unknown") . " Wall";
|
||||
}
|
||||
|
||||
protected function recalculateBoundingBox() : ?AxisAlignedBB{
|
||||
|
@ -35,7 +35,7 @@ class Concrete extends Solid{
|
||||
}
|
||||
|
||||
public function getName() : string{
|
||||
return ColorBlockMetaHelper::getColorFromMeta($this->meta) . " Concrete";
|
||||
return ColorBlockMetaHelper::getColorFromMeta($this->getVariant()) . " Concrete";
|
||||
}
|
||||
|
||||
public function getHardness() : float{
|
||||
|
@ -34,7 +34,7 @@ class ConcretePowder extends Fallable{
|
||||
}
|
||||
|
||||
public function getName() : string{
|
||||
return ColorBlockMetaHelper::getColorFromMeta($this->meta) . " Concrete Powder";
|
||||
return ColorBlockMetaHelper::getColorFromMeta($this->getVariant()) . " Concrete Powder";
|
||||
}
|
||||
|
||||
public function getHardness() : float{
|
||||
|
@ -43,7 +43,7 @@ abstract class Crops extends Flowable{
|
||||
|
||||
|
||||
public function onActivate(Item $item, Player $player = null) : bool{
|
||||
if($item->getId() === Item::DYE and $item->getDamage() === 0x0F){ //Bonemeal
|
||||
if($this->meta < 7 and $item->getId() === Item::DYE and $item->getDamage() === 0x0F){ //Bonemeal
|
||||
$block = clone $this;
|
||||
$block->meta += mt_rand(2, 5);
|
||||
if($block->meta > 7){
|
||||
|
@ -39,7 +39,7 @@ class DoublePlant extends Flowable{
|
||||
}
|
||||
|
||||
public function canBeReplaced() : bool{
|
||||
return $this->meta === 2 or $this->meta === 3; //grass or fern
|
||||
return $this->getVariant() === 2 or $this->getVariant() === 3; //grass or fern
|
||||
}
|
||||
|
||||
public function getName() : string{
|
||||
@ -95,11 +95,11 @@ class DoublePlant extends Flowable{
|
||||
}
|
||||
|
||||
public function getToolType() : int{
|
||||
return ($this->meta === 2 or $this->meta === 3) ? BlockToolType::TYPE_SHEARS : BlockToolType::TYPE_NONE;
|
||||
return ($this->getVariant() === 2 or $this->getVariant() === 3) ? BlockToolType::TYPE_SHEARS : BlockToolType::TYPE_NONE;
|
||||
}
|
||||
|
||||
public function getToolHarvestLevel() : int{
|
||||
return ($this->meta === 2 or $this->meta === 3) ? 1 : 0; //only grass or fern require shears
|
||||
return ($this->getVariant() === 2 or $this->getVariant() === 3) ? 1 : 0; //only grass or fern require shears
|
||||
}
|
||||
|
||||
public function getDrops(Item $item) : array{
|
||||
|
@ -56,7 +56,7 @@ class Flower extends Flowable{
|
||||
self::TYPE_PINK_TULIP => "Pink Tulip",
|
||||
self::TYPE_OXEYE_DAISY => "Oxeye Daisy"
|
||||
];
|
||||
return $names[$this->meta] ?? "Unknown";
|
||||
return $names[$this->getVariant()] ?? "Unknown";
|
||||
}
|
||||
|
||||
public function place(Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, Player $player = null) : bool{
|
||||
|
31
src/pocketmine/block/InfoUpdate.php
Normal file
31
src/pocketmine/block/InfoUpdate.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\block;
|
||||
|
||||
class InfoUpdate extends Solid{
|
||||
|
||||
public function getHardness() : float{
|
||||
return 1;
|
||||
}
|
||||
}
|
51
src/pocketmine/block/InvisibleBedrock.php
Normal file
51
src/pocketmine/block/InvisibleBedrock.php
Normal file
@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\block;
|
||||
|
||||
use pocketmine\item\Item;
|
||||
|
||||
class InvisibleBedrock extends Transparent{
|
||||
|
||||
protected $id = self::INVISIBLE_BEDROCK;
|
||||
|
||||
public function __construct(){
|
||||
|
||||
}
|
||||
|
||||
public function getName() : string{
|
||||
return "Invisible Bedrock";
|
||||
}
|
||||
|
||||
public function getHardness() : float{
|
||||
return -1;
|
||||
}
|
||||
|
||||
public function getBlastResistance() : float{
|
||||
return 18000000;
|
||||
}
|
||||
|
||||
public function isBreakable(Item $item) : bool{
|
||||
return false;
|
||||
}
|
||||
}
|
@ -44,4 +44,8 @@ class IronTrapdoor extends Trapdoor{
|
||||
public function getToolHarvestLevel() : int{
|
||||
return TieredTool::TIER_WOODEN;
|
||||
}
|
||||
|
||||
public function getFuelTime() : int{
|
||||
return 0; //TODO: remove this hack on 4.0
|
||||
}
|
||||
}
|
||||
|
@ -65,7 +65,7 @@ class ItemFrame extends Flowable{
|
||||
2 => Vector3::SIDE_NORTH,
|
||||
3 => Vector3::SIDE_SOUTH
|
||||
];
|
||||
if(!$this->getSide($sides[$this->meta])->isSolid()){
|
||||
if(isset($sides[$this->meta]) and !$this->getSide($sides[$this->meta])->isSolid()){
|
||||
$this->level->useBreakOn($this);
|
||||
}
|
||||
}
|
||||
@ -112,4 +112,8 @@ class ItemFrame extends Flowable{
|
||||
public function isAffectedBySilkTouch() : bool{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getHardness() : float{
|
||||
return 0.25;
|
||||
}
|
||||
}
|
||||
|
@ -58,8 +58,10 @@ class Ladder extends Transparent{
|
||||
}
|
||||
|
||||
public function onEntityCollide(Entity $entity) : void{
|
||||
$entity->resetFallDistance();
|
||||
$entity->onGround = true;
|
||||
if($entity->asVector3()->floor()->distanceSquared($this) < 1){ //entity coordinates must be inside block
|
||||
$entity->resetFallDistance();
|
||||
$entity->onGround = true;
|
||||
}
|
||||
}
|
||||
|
||||
protected function recalculateBoundingBox() : ?AxisAlignedBB{
|
||||
|
@ -188,7 +188,7 @@ class Leaves extends Transparent{
|
||||
}
|
||||
|
||||
public function canDropApples() : bool{
|
||||
return $this->meta === self::OAK;
|
||||
return $this->getVariant() === self::OAK;
|
||||
}
|
||||
|
||||
public function getFlameEncouragement() : int{
|
||||
|
@ -44,6 +44,6 @@ class Leaves2 extends Leaves{
|
||||
}
|
||||
|
||||
public function canDropApples() : bool{
|
||||
return $this->meta === self::DARK_OAK;
|
||||
return $this->getVariant() === self::DARK_OAK;
|
||||
}
|
||||
}
|
||||
|
@ -46,4 +46,6 @@ class NoteBlock extends Solid{
|
||||
public function getToolType() : int{
|
||||
return BlockToolType::TYPE_AXE;
|
||||
}
|
||||
|
||||
//TODO
|
||||
}
|
||||
|
@ -23,6 +23,8 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\block;
|
||||
|
||||
use pocketmine\item\Item;
|
||||
|
||||
class PackedIce extends Solid{
|
||||
|
||||
protected $id = self::PACKED_ICE;
|
||||
@ -46,4 +48,8 @@ class PackedIce extends Solid{
|
||||
public function getToolType() : int{
|
||||
return BlockToolType::TYPE_PICKAXE;
|
||||
}
|
||||
|
||||
public function getDropsForCompatibleTool(Item $item) : array{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
@ -55,7 +55,7 @@ class Quartz extends Solid{
|
||||
}
|
||||
|
||||
public function place(Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, Player $player = null) : bool{
|
||||
if($this->meta !== self::NORMAL){
|
||||
if($this->getVariant() !== self::NORMAL){
|
||||
$this->meta = PillarRotationHelper::getMetaFromFace($this->meta, $face);
|
||||
}
|
||||
return $this->getLevel()->setBlock($blockReplace, $this, true, true);
|
||||
|
31
src/pocketmine/block/Reserved6.php
Normal file
31
src/pocketmine/block/Reserved6.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\block;
|
||||
|
||||
class Reserved6 extends Solid{
|
||||
|
||||
public function getHardness() : float{
|
||||
return 0;
|
||||
}
|
||||
}
|
@ -40,7 +40,7 @@ class Sand extends Fallable{
|
||||
}
|
||||
|
||||
public function getName() : string{
|
||||
if($this->meta === 0x01){
|
||||
if($this->getVariant() === 0x01){
|
||||
return "Red Sand";
|
||||
}
|
||||
|
||||
|
@ -78,7 +78,7 @@ abstract class Slab extends Transparent{
|
||||
}
|
||||
}else{ //TODO: collision
|
||||
if($blockReplace->getId() === $this->id){
|
||||
if($blockReplace->getVariant() === $this->meta){
|
||||
if($blockReplace->getVariant() === $this->getVariant()){
|
||||
$this->getLevel()->setBlock($blockReplace, BlockFactory::get($this->getDoubleSlabId(), $this->getVariant()), true);
|
||||
|
||||
return true;
|
||||
|
@ -30,6 +30,6 @@ class StainedClay extends HardenedClay{
|
||||
protected $id = self::STAINED_CLAY;
|
||||
|
||||
public function getName() : string{
|
||||
return ColorBlockMetaHelper::getColorFromMeta($this->meta) . " Stained Clay";
|
||||
return ColorBlockMetaHelper::getColorFromMeta($this->getVariant()) . " Stained Clay";
|
||||
}
|
||||
}
|
||||
|
@ -30,6 +30,6 @@ class StainedGlass extends Glass{
|
||||
protected $id = self::STAINED_GLASS;
|
||||
|
||||
public function getName() : string{
|
||||
return ColorBlockMetaHelper::getColorFromMeta($this->meta) . " Stained Glass";
|
||||
return ColorBlockMetaHelper::getColorFromMeta($this->getVariant()) . " Stained Glass";
|
||||
}
|
||||
}
|
||||
|
@ -30,6 +30,6 @@ class StainedGlassPane extends GlassPane{
|
||||
protected $id = self::STAINED_GLASS_PANE;
|
||||
|
||||
public function getName() : string{
|
||||
return ColorBlockMetaHelper::getColorFromMeta($this->meta) . " Stained Glass Pane";
|
||||
return ColorBlockMetaHelper::getColorFromMeta($this->getVariant()) . " Stained Glass Pane";
|
||||
}
|
||||
}
|
||||
|
@ -89,7 +89,7 @@ abstract class Stair extends Transparent{
|
||||
2 => 1,
|
||||
3 => 3
|
||||
];
|
||||
$this->meta = $faces[$player->getDirection()] & 0x03;
|
||||
$this->meta = $player !== null ? $faces[$player->getDirection()] & 0x03 : 0;
|
||||
if(($clickVector->y > 0.5 and $face !== Vector3::SIDE_UP) or $face === Vector3::SIDE_DOWN){
|
||||
$this->meta |= 0x04; //Upside-down stairs
|
||||
}
|
||||
|
@ -26,6 +26,12 @@ namespace pocketmine\block;
|
||||
class StoneSlab2 extends StoneSlab{
|
||||
public const TYPE_RED_SANDSTONE = 0;
|
||||
public const TYPE_PURPUR = 1;
|
||||
public const TYPE_PRISMARINE = 2;
|
||||
public const TYPE_DARK_PRISMARINE = 3;
|
||||
public const TYPE_PRISMARINE_BRICKS = 4;
|
||||
public const TYPE_MOSSY_COBBLESTONE = 5;
|
||||
public const TYPE_SMOOTH_SANDSTONE = 6;
|
||||
public const TYPE_RED_NETHER_BRICK = 7;
|
||||
|
||||
protected $id = self::STONE_SLAB2;
|
||||
|
||||
@ -36,7 +42,13 @@ class StoneSlab2 extends StoneSlab{
|
||||
public function getName() : string{
|
||||
static $names = [
|
||||
self::TYPE_RED_SANDSTONE => "Red Sandstone",
|
||||
self::TYPE_PURPUR => "Purpur"
|
||||
self::TYPE_PURPUR => "Purpur",
|
||||
self::TYPE_PRISMARINE => "Prismarine",
|
||||
self::TYPE_DARK_PRISMARINE => "Dark Prismarine",
|
||||
self::TYPE_PRISMARINE_BRICKS => "Prismarine Bricks",
|
||||
self::TYPE_MOSSY_COBBLESTONE => "Mossy Cobblestone",
|
||||
self::TYPE_SMOOTH_SANDSTONE => "Smooth Sandstone",
|
||||
self::TYPE_RED_NETHER_BRICK => "Red Nether Brick"
|
||||
];
|
||||
|
||||
return (($this->meta & 0x08) > 0 ? "Upper " : "") . ($names[$this->getVariant()] ?? "") . " Slab";
|
||||
|
@ -37,6 +37,10 @@ class Stonecutter extends Solid{
|
||||
return "Stonecutter";
|
||||
}
|
||||
|
||||
public function getHardness() : float{
|
||||
return 3.5;
|
||||
}
|
||||
|
||||
public function getToolType() : int{
|
||||
return BlockToolType::TYPE_PICKAXE;
|
||||
}
|
||||
|
@ -50,9 +50,11 @@ class Sugarcane extends Flowable{
|
||||
if($b->getId() === self::AIR){
|
||||
$ev = new BlockGrowEvent($b, BlockFactory::get(Block::SUGARCANE_BLOCK));
|
||||
$ev->call();
|
||||
if(!$ev->isCancelled()){
|
||||
$this->getLevel()->setBlock($b, $ev->getNewState(), true);
|
||||
if($ev->isCancelled()){
|
||||
break;
|
||||
}
|
||||
$this->getLevel()->setBlock($b, $ev->getNewState(), true);
|
||||
}else{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -25,6 +25,8 @@ namespace pocketmine\block;
|
||||
|
||||
use pocketmine\entity\Entity;
|
||||
use pocketmine\entity\projectile\Arrow;
|
||||
use pocketmine\item\Durable;
|
||||
use pocketmine\item\enchantment\Enchantment;
|
||||
use pocketmine\item\FlintSteel;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\math\Vector3;
|
||||
@ -51,8 +53,10 @@ class TNT extends Solid{
|
||||
}
|
||||
|
||||
public function onActivate(Item $item, Player $player = null) : bool{
|
||||
if($item instanceof FlintSteel){
|
||||
$item->applyDamage(1);
|
||||
if($item instanceof FlintSteel or $item->hasEnchantment(Enchantment::FIRE_ASPECT)){
|
||||
if($item instanceof Durable){
|
||||
$item->applyDamage(1);
|
||||
}
|
||||
$this->ignite();
|
||||
return true;
|
||||
}
|
||||
|
@ -51,8 +51,8 @@ class TallGrass extends Flowable{
|
||||
}
|
||||
|
||||
public function place(Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, Player $player = null) : bool{
|
||||
$down = $this->getSide(Vector3::SIDE_DOWN);
|
||||
if($down->getId() === self::GRASS){
|
||||
$down = $this->getSide(Vector3::SIDE_DOWN)->getId();
|
||||
if($down === self::GRASS or $down === self::DIRT){
|
||||
$this->getLevel()->setBlock($blockReplace, $this, true);
|
||||
|
||||
return true;
|
||||
|
@ -67,7 +67,7 @@ class Water extends Liquid{
|
||||
|
||||
public function onEntityCollide(Entity $entity) : void{
|
||||
$entity->resetFallDistance();
|
||||
if($entity->fireTicks > 0){
|
||||
if($entity->isOnFire()){
|
||||
$entity->extinguish();
|
||||
}
|
||||
|
||||
|
@ -43,7 +43,7 @@ class Wool extends Solid{
|
||||
}
|
||||
|
||||
public function getName() : string{
|
||||
return ColorBlockMetaHelper::getColorFromMeta($this->meta) . " Wool";
|
||||
return ColorBlockMetaHelper::getColorFromMeta($this->getVariant()) . " Wool";
|
||||
}
|
||||
|
||||
public function getBreakTime(Item $item) : float{
|
||||
|
@ -26,6 +26,7 @@ declare(strict_types=1);
|
||||
*/
|
||||
namespace pocketmine\command;
|
||||
|
||||
use pocketmine\command\utils\CommandException;
|
||||
use pocketmine\lang\TextContainer;
|
||||
use pocketmine\lang\TranslationContainer;
|
||||
use pocketmine\permission\PermissionManager;
|
||||
@ -92,6 +93,7 @@ abstract class Command{
|
||||
* @param string[] $args
|
||||
*
|
||||
* @return mixed
|
||||
* @throws CommandException
|
||||
*/
|
||||
abstract public function execute(CommandSender $sender, string $commandLabel, array $args);
|
||||
|
||||
|
@ -66,13 +66,13 @@ use pocketmine\command\defaults\VersionCommand;
|
||||
use pocketmine\command\defaults\WhitelistCommand;
|
||||
use pocketmine\command\utils\InvalidCommandSyntaxException;
|
||||
use pocketmine\Server;
|
||||
use function array_map;
|
||||
use function array_shift;
|
||||
use function count;
|
||||
use function explode;
|
||||
use function implode;
|
||||
use function min;
|
||||
use function str_getcsv;
|
||||
use function preg_match_all;
|
||||
use function stripslashes;
|
||||
use function strpos;
|
||||
use function strtolower;
|
||||
use function trim;
|
||||
@ -247,7 +247,16 @@ class SimpleCommandMap implements CommandMap{
|
||||
}
|
||||
|
||||
public function dispatch(CommandSender $sender, string $commandLine) : bool{
|
||||
$args = array_map("\stripslashes", str_getcsv($commandLine, " "));
|
||||
$args = [];
|
||||
preg_match_all('/"((?:\\\\.|[^\\\\"])*)"|(\S+)/u', $commandLine, $matches);
|
||||
foreach($matches[0] as $k => $_){
|
||||
for($i = 1; $i <= 2; ++$i){
|
||||
if($matches[$i][$k] !== ""){
|
||||
$args[$k] = stripslashes($matches[$i][$k]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
$sentCommandLabel = "";
|
||||
$target = $this->matchCommand($sentCommandLabel, $args);
|
||||
|
||||
|
@ -53,6 +53,9 @@ class DeopCommand extends VanillaCommand{
|
||||
}
|
||||
|
||||
$name = array_shift($args);
|
||||
if(!Player::isValidUserName($name)){
|
||||
throw new InvalidCommandSyntaxException();
|
||||
}
|
||||
|
||||
$player = $sender->getServer()->getOfflinePlayer($name);
|
||||
$player->setOp(false);
|
||||
|
@ -83,7 +83,7 @@ class EffectCommand extends VanillaCommand{
|
||||
$amplification = 0;
|
||||
|
||||
if(count($args) >= 3){
|
||||
if(($d = $this->getBoundedInt($sender, $args[2], 0, INT32_MAX)) === null){
|
||||
if(($d = $this->getBoundedInt($sender, $args[2], 0, (int) (INT32_MAX / 20))) === null){
|
||||
return false;
|
||||
}
|
||||
$duration = $d * 20; //ticks
|
||||
|
@ -77,7 +77,15 @@ class EnchantCommand extends VanillaCommand{
|
||||
return true;
|
||||
}
|
||||
|
||||
$item->addEnchantment(new EnchantmentInstance($enchantment, (int) ($args[2] ?? 1)));
|
||||
$level = 1;
|
||||
if(isset($args[2])){
|
||||
$level = $this->getBoundedInt($sender, $args[2], 1, $enchantment->getMaxLevel());
|
||||
if($level === null){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$item->addEnchantment(new EnchantmentInstance($enchantment, $level));
|
||||
$player->getInventory()->setItemInHand($item);
|
||||
|
||||
|
||||
|
@ -55,7 +55,7 @@ class KillCommand extends VanillaCommand{
|
||||
|
||||
if(count($args) === 1){
|
||||
if(!$sender->hasPermission("pocketmine.command.kill.other")){
|
||||
$sender->sendMessage(new TranslationContainer(TextFormat::RED . "%commands.generic.permission"));
|
||||
$sender->sendMessage($sender->getServer()->getLanguage()->translateString(TextFormat::RED . "%commands.generic.permission"));
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -74,7 +74,7 @@ class KillCommand extends VanillaCommand{
|
||||
|
||||
if($sender instanceof Player){
|
||||
if(!$sender->hasPermission("pocketmine.command.kill.self")){
|
||||
$sender->sendMessage(new TranslationContainer(TextFormat::RED . "%commands.generic.permission"));
|
||||
$sender->sendMessage($sender->getServer()->getLanguage()->translateString(TextFormat::RED . "%commands.generic.permission"));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -53,6 +53,9 @@ class OpCommand extends VanillaCommand{
|
||||
}
|
||||
|
||||
$name = array_shift($args);
|
||||
if(!Player::isValidUserName($name)){
|
||||
throw new InvalidCommandSyntaxException();
|
||||
}
|
||||
|
||||
$player = $sender->getServer()->getOfflinePlayer($name);
|
||||
Command::broadcastCommandMessage($sender, new TranslationContainer("commands.op.success", [$player->getName()]));
|
||||
|
@ -26,6 +26,8 @@ namespace pocketmine\command\defaults;
|
||||
use pocketmine\command\Command;
|
||||
use pocketmine\command\CommandSender;
|
||||
use pocketmine\lang\TranslationContainer;
|
||||
use function microtime;
|
||||
use function round;
|
||||
|
||||
class SaveCommand extends VanillaCommand{
|
||||
|
||||
@ -43,7 +45,8 @@ class SaveCommand extends VanillaCommand{
|
||||
return true;
|
||||
}
|
||||
|
||||
Command::broadcastCommandMessage($sender, new TranslationContainer("commands.save.start"));
|
||||
Command::broadcastCommandMessage($sender, new TranslationContainer("pocketmine.save.start"));
|
||||
$start = microtime(true);
|
||||
|
||||
foreach($sender->getServer()->getOnlinePlayers() as $player){
|
||||
$player->save();
|
||||
@ -53,7 +56,7 @@ class SaveCommand extends VanillaCommand{
|
||||
$level->save(true);
|
||||
}
|
||||
|
||||
Command::broadcastCommandMessage($sender, new TranslationContainer("commands.save.success"));
|
||||
Command::broadcastCommandMessage($sender, new TranslationContainer("pocketmine.save.success", [round(microtime(true) - $start, 3)]));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -108,13 +108,11 @@ class StatusCommand extends VanillaCommand{
|
||||
|
||||
foreach($server->getLevels() as $level){
|
||||
$levelName = $level->getFolderName() !== $level->getName() ? " (" . $level->getName() . ")" : "";
|
||||
$timeColor = ($level->getTickRate() > 1 or $level->getTickRateTime() > 40) ? TextFormat::RED : TextFormat::YELLOW;
|
||||
$tickRate = $level->getTickRate() > 1 ? " (tick rate " . $level->getTickRate() . ")" : "";
|
||||
$timeColor = $level->getTickRateTime() > 40 ? TextFormat::RED : TextFormat::YELLOW;
|
||||
$sender->sendMessage(TextFormat::GOLD . "World \"{$level->getFolderName()}\"$levelName: " .
|
||||
TextFormat::RED . number_format(count($level->getChunks())) . TextFormat::GREEN . " chunks, " .
|
||||
TextFormat::RED . number_format(count($level->getEntities())) . TextFormat::GREEN . " entities, " .
|
||||
TextFormat::RED . number_format(count($level->getTiles())) . TextFormat::GREEN . " tiles. " .
|
||||
"Time $timeColor" . round($level->getTickRateTime(), 2) . "ms" . $tickRate
|
||||
TextFormat::RED . number_format(count($level->getEntities())) . TextFormat::GREEN . " entities. " .
|
||||
"Time $timeColor" . round($level->getTickRateTime(), 2) . "ms"
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -50,33 +50,29 @@ class TimeCommand extends VanillaCommand{
|
||||
|
||||
if($args[0] === "start"){
|
||||
if(!$sender->hasPermission("pocketmine.command.time.start")){
|
||||
$sender->sendMessage(new TranslationContainer(TextFormat::RED . "%commands.generic.permission"));
|
||||
$sender->sendMessage($sender->getServer()->getLanguage()->translateString(TextFormat::RED . "%commands.generic.permission"));
|
||||
|
||||
return true;
|
||||
}
|
||||
foreach($sender->getServer()->getLevels() as $level){
|
||||
$level->checkTime();
|
||||
$level->startTime();
|
||||
$level->checkTime();
|
||||
}
|
||||
Command::broadcastCommandMessage($sender, "Restarted the time");
|
||||
return true;
|
||||
}elseif($args[0] === "stop"){
|
||||
if(!$sender->hasPermission("pocketmine.command.time.stop")){
|
||||
$sender->sendMessage(new TranslationContainer(TextFormat::RED . "%commands.generic.permission"));
|
||||
$sender->sendMessage($sender->getServer()->getLanguage()->translateString(TextFormat::RED . "%commands.generic.permission"));
|
||||
|
||||
return true;
|
||||
}
|
||||
foreach($sender->getServer()->getLevels() as $level){
|
||||
$level->checkTime();
|
||||
$level->stopTime();
|
||||
$level->checkTime();
|
||||
}
|
||||
Command::broadcastCommandMessage($sender, "Stopped the time");
|
||||
return true;
|
||||
}elseif($args[0] === "query"){
|
||||
if(!$sender->hasPermission("pocketmine.command.time.query")){
|
||||
$sender->sendMessage(new TranslationContainer(TextFormat::RED . "%commands.generic.permission"));
|
||||
$sender->sendMessage($sender->getServer()->getLanguage()->translateString(TextFormat::RED . "%commands.generic.permission"));
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -85,7 +81,7 @@ class TimeCommand extends VanillaCommand{
|
||||
}else{
|
||||
$level = $sender->getServer()->getDefaultLevel();
|
||||
}
|
||||
$sender->sendMessage(new TranslationContainer("commands.time.query", [$level->getTime()]));
|
||||
$sender->sendMessage($sender->getServer()->getLanguage()->translateString("commands.time.query", [$level->getTime()]));
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -96,7 +92,7 @@ class TimeCommand extends VanillaCommand{
|
||||
|
||||
if($args[0] === "set"){
|
||||
if(!$sender->hasPermission("pocketmine.command.time.set")){
|
||||
$sender->sendMessage(new TranslationContainer(TextFormat::RED . "%commands.generic.permission"));
|
||||
$sender->sendMessage($sender->getServer()->getLanguage()->translateString(TextFormat::RED . "%commands.generic.permission"));
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -110,23 +106,19 @@ class TimeCommand extends VanillaCommand{
|
||||
}
|
||||
|
||||
foreach($sender->getServer()->getLevels() as $level){
|
||||
$level->checkTime();
|
||||
$level->setTime($value);
|
||||
$level->checkTime();
|
||||
}
|
||||
Command::broadcastCommandMessage($sender, new TranslationContainer("commands.time.set", [$value]));
|
||||
}elseif($args[0] === "add"){
|
||||
if(!$sender->hasPermission("pocketmine.command.time.add")){
|
||||
$sender->sendMessage(new TranslationContainer(TextFormat::RED . "%commands.generic.permission"));
|
||||
$sender->sendMessage($sender->getServer()->getLanguage()->translateString(TextFormat::RED . "%commands.generic.permission"));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$value = $this->getInteger($sender, $args[1], 0);
|
||||
foreach($sender->getServer()->getLevels() as $level){
|
||||
$level->checkTime();
|
||||
$level->setTime($level->getTime() + $value);
|
||||
$level->checkTime();
|
||||
}
|
||||
Command::broadcastCommandMessage($sender, new TranslationContainer("commands.time.added", [$value]));
|
||||
}else{
|
||||
|
@ -27,6 +27,7 @@ use pocketmine\command\Command;
|
||||
use pocketmine\command\CommandSender;
|
||||
use pocketmine\command\utils\InvalidCommandSyntaxException;
|
||||
use pocketmine\lang\TranslationContainer;
|
||||
use pocketmine\Player;
|
||||
use pocketmine\utils\TextFormat;
|
||||
use function count;
|
||||
use function implode;
|
||||
@ -94,6 +95,9 @@ class WhitelistCommand extends VanillaCommand{
|
||||
if($this->badPerm($sender, strtolower($args[0]))){
|
||||
return false;
|
||||
}
|
||||
if(!Player::isValidUserName($args[1])){
|
||||
throw new InvalidCommandSyntaxException();
|
||||
}
|
||||
switch(strtolower($args[0])){
|
||||
case "add":
|
||||
$sender->getServer()->getOfflinePlayer($args[1])->setWhitelisted(true);
|
||||
@ -111,9 +115,13 @@ class WhitelistCommand extends VanillaCommand{
|
||||
return true;
|
||||
}
|
||||
|
||||
private function badPerm(CommandSender $sender, string $perm) : bool{
|
||||
if(!$sender->hasPermission("pocketmine.command.whitelist.$perm")){
|
||||
$sender->sendMessage(new TranslationContainer(TextFormat::RED . "%commands.generic.permission"));
|
||||
private function badPerm(CommandSender $sender, string $subcommand) : bool{
|
||||
static $map = [
|
||||
"on" => "enable",
|
||||
"off" => "disable"
|
||||
];
|
||||
if(!$sender->hasPermission("pocketmine.command.whitelist." . ($map[$subcommand] ?? $subcommand))){
|
||||
$sender->sendMessage($sender->getServer()->getLanguage()->translateString(TextFormat::RED . "%commands.generic.permission"));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -40,6 +40,11 @@ class Attribute{
|
||||
public const ATTACK_DAMAGE = 8;
|
||||
public const EXPERIENCE_LEVEL = 9;
|
||||
public const EXPERIENCE = 10;
|
||||
public const UNDERWATER_MOVEMENT = 11;
|
||||
public const LUCK = 12;
|
||||
public const FALL_DAMAGE = 13;
|
||||
public const HORSE_JUMP_STRENGTH = 14;
|
||||
public const ZOMBIE_SPAWN_REINFORCEMENTS = 15;
|
||||
|
||||
private $id;
|
||||
protected $minValue;
|
||||
@ -66,8 +71,11 @@ class Attribute{
|
||||
self::addAttribute(self::ATTACK_DAMAGE, "minecraft:attack_damage", 0.00, 340282346638528859811704183484516925440.00, 1.00, false);
|
||||
self::addAttribute(self::EXPERIENCE_LEVEL, "minecraft:player.level", 0.00, 24791.00, 0.00);
|
||||
self::addAttribute(self::EXPERIENCE, "minecraft:player.experience", 0.00, 1.00, 0.00);
|
||||
//TODO: minecraft:luck (for fishing?)
|
||||
//TODO: minecraft:fall_damage
|
||||
self::addAttribute(self::UNDERWATER_MOVEMENT, "minecraft:underwater_movement", 0.0, 340282346638528859811704183484516925440.0, 0.02);
|
||||
self::addAttribute(self::LUCK, "minecraft:luck", -1024.0, 1024.0, 0.0);
|
||||
self::addAttribute(self::FALL_DAMAGE, "minecraft:fall_damage", 0.0, 340282346638528859811704183484516925440.0, 1.0);
|
||||
self::addAttribute(self::HORSE_JUMP_STRENGTH, "minecraft:horse.jump_strength", 0.0, 2.0, 0.7);
|
||||
self::addAttribute(self::ZOMBIE_SPAWN_REINFORCEMENTS, "minecraft:zombie.spawn_reinforcements", 0.0, 1.0, 0.0);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -107,6 +107,10 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
|
||||
public const DATA_TYPE_LONG = 7;
|
||||
public const DATA_TYPE_VECTOR3F = 8;
|
||||
|
||||
/*
|
||||
* Readers beware: this isn't a nice list. Some of the properties have different types for different entities, and
|
||||
* are used for entirely different things.
|
||||
*/
|
||||
public const DATA_FLAGS = 0;
|
||||
public const DATA_HEALTH = 1; //int (minecart/boat)
|
||||
public const DATA_VARIANT = 2; //int
|
||||
@ -124,17 +128,21 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
|
||||
public const DATA_PADDLE_TIME_RIGHT = 14; //float
|
||||
public const DATA_EXPERIENCE_VALUE = 15; //int (xp orb)
|
||||
public const DATA_MINECART_DISPLAY_BLOCK = 16; //int (id | (data << 16))
|
||||
public const DATA_HORSE_FLAGS = 16; //int
|
||||
/* 16 (byte) used by wither skull */
|
||||
public const DATA_MINECART_DISPLAY_OFFSET = 17; //int
|
||||
public const DATA_SHOOTER_ID = 17; //long (used by arrows)
|
||||
public const DATA_MINECART_HAS_DISPLAY = 18; //byte (must be 1 for minecart to show block inside)
|
||||
|
||||
//TODO: add more properties
|
||||
|
||||
public const DATA_HORSE_TYPE = 19; //byte
|
||||
/* 20 (unknown)
|
||||
* 21 (unknown) */
|
||||
public const DATA_CHARGE_AMOUNT = 22; //int8, used for ghasts and also crossbow charging
|
||||
public const DATA_ENDERMAN_HELD_ITEM_ID = 23; //short
|
||||
public const DATA_ENTITY_AGE = 24; //short
|
||||
|
||||
/* 26 (byte) player-specific flags
|
||||
* 27 (int) player "index"?
|
||||
* 28 (block coords) bed position */
|
||||
/* 25 (int) used by horse, (byte) used by witch */
|
||||
public const DATA_PLAYER_FLAGS = 26; //byte
|
||||
public const DATA_PLAYER_INDEX = 27; //int, used for marker colours and agent nametag colours
|
||||
public const DATA_PLAYER_BED_POSITION = 28; //blockpos
|
||||
public const DATA_FIREBALL_POWER_X = 29; //float
|
||||
public const DATA_FIREBALL_POWER_Y = 30;
|
||||
public const DATA_FIREBALL_POWER_Z = 31;
|
||||
@ -145,62 +153,71 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
|
||||
public const DATA_POTION_AUX_VALUE = 36; //short
|
||||
public const DATA_LEAD_HOLDER_EID = 37; //long
|
||||
public const DATA_SCALE = 38; //float
|
||||
public const DATA_INTERACTIVE_TAG = 39; //string (button text)
|
||||
public const DATA_NPC_SKIN_ID = 40; //string
|
||||
public const DATA_URL_TAG = 41; //string
|
||||
public const DATA_MAX_AIR = 42; //short
|
||||
public const DATA_MARK_VARIANT = 43; //int
|
||||
public const DATA_CONTAINER_TYPE = 44; //byte (ContainerComponent)
|
||||
public const DATA_CONTAINER_BASE_SIZE = 45; //int (ContainerComponent)
|
||||
public const DATA_CONTAINER_EXTRA_SLOTS_PER_STRENGTH = 46; //int (used for llamas, inventory size is baseSize + thisProp * strength)
|
||||
public const DATA_BLOCK_TARGET = 47; //block coords (ender crystal)
|
||||
public const DATA_WITHER_INVULNERABLE_TICKS = 48; //int
|
||||
public const DATA_WITHER_TARGET_1 = 49; //long
|
||||
public const DATA_WITHER_TARGET_2 = 50; //long
|
||||
public const DATA_WITHER_TARGET_3 = 51; //long
|
||||
/* 52 (short) */
|
||||
public const DATA_BOUNDING_BOX_WIDTH = 53; //float
|
||||
public const DATA_BOUNDING_BOX_HEIGHT = 54; //float
|
||||
public const DATA_FUSE_LENGTH = 55; //int
|
||||
public const DATA_RIDER_SEAT_POSITION = 56; //vector3f
|
||||
public const DATA_RIDER_ROTATION_LOCKED = 57; //byte
|
||||
public const DATA_RIDER_MAX_ROTATION = 58; //float
|
||||
public const DATA_RIDER_MIN_ROTATION = 59; //float
|
||||
public const DATA_AREA_EFFECT_CLOUD_RADIUS = 60; //float
|
||||
public const DATA_AREA_EFFECT_CLOUD_WAITING = 61; //int
|
||||
public const DATA_AREA_EFFECT_CLOUD_PARTICLE_ID = 62; //int
|
||||
/* 63 (int) shulker-related */
|
||||
public const DATA_SHULKER_ATTACH_FACE = 64; //byte
|
||||
/* 65 (short) shulker-related */
|
||||
public const DATA_SHULKER_ATTACH_POS = 66; //block coords
|
||||
public const DATA_TRADING_PLAYER_EID = 67; //long
|
||||
|
||||
/* 69 (byte) command-block */
|
||||
public const DATA_COMMAND_BLOCK_COMMAND = 70; //string
|
||||
public const DATA_COMMAND_BLOCK_LAST_OUTPUT = 71; //string
|
||||
public const DATA_COMMAND_BLOCK_TRACK_OUTPUT = 72; //byte
|
||||
public const DATA_CONTROLLING_RIDER_SEAT_NUMBER = 73; //byte
|
||||
public const DATA_STRENGTH = 74; //int
|
||||
public const DATA_MAX_STRENGTH = 75; //int
|
||||
/* 76 (int) */
|
||||
public const DATA_LIMITED_LIFE = 77;
|
||||
public const DATA_ARMOR_STAND_POSE_INDEX = 78; //int
|
||||
public const DATA_ENDER_CRYSTAL_TIME_OFFSET = 79; //int
|
||||
public const DATA_ALWAYS_SHOW_NAMETAG = 80; //byte: -1 = default, 0 = only when looked at, 1 = always
|
||||
public const DATA_COLOR_2 = 81; //byte
|
||||
/* 82 (unknown) */
|
||||
public const DATA_SCORE_TAG = 83; //string
|
||||
public const DATA_BALLOON_ATTACHED_ENTITY = 84; //int64, entity unique ID of owner
|
||||
public const DATA_PUFFERFISH_SIZE = 85; //byte
|
||||
public const DATA_BOAT_BUBBLE_TIME = 86; //int (time in bubble column)
|
||||
public const DATA_PLAYER_AGENT_EID = 87; //long
|
||||
/* 88 (float) related to panda sitting
|
||||
* 89 (float) related to panda sitting
|
||||
* 90 (unknown) */
|
||||
public const DATA_FLAGS2 = 91; //long (extended data flags)
|
||||
/* 92 (float) related to panda lying down
|
||||
* 93 (float) related to panda lying down */
|
||||
public const DATA_HAS_NPC_COMPONENT = 39; //byte (???)
|
||||
public const DATA_SKIN_ID = 40; //string
|
||||
public const DATA_NPC_SKIN_ID = 41; //string
|
||||
public const DATA_URL_TAG = 42; //string
|
||||
public const DATA_MAX_AIR = 43; //short
|
||||
public const DATA_MARK_VARIANT = 44; //int
|
||||
public const DATA_CONTAINER_TYPE = 45; //byte (ContainerComponent)
|
||||
public const DATA_CONTAINER_BASE_SIZE = 46; //int (ContainerComponent)
|
||||
public const DATA_CONTAINER_EXTRA_SLOTS_PER_STRENGTH = 47; //int (used for llamas, inventory size is baseSize + thisProp * strength)
|
||||
public const DATA_BLOCK_TARGET = 48; //block coords (ender crystal)
|
||||
public const DATA_WITHER_INVULNERABLE_TICKS = 49; //int
|
||||
public const DATA_WITHER_TARGET_1 = 50; //long
|
||||
public const DATA_WITHER_TARGET_2 = 51; //long
|
||||
public const DATA_WITHER_TARGET_3 = 52; //long
|
||||
/* 53 (short) */
|
||||
public const DATA_BOUNDING_BOX_WIDTH = 54; //float
|
||||
public const DATA_BOUNDING_BOX_HEIGHT = 55; //float
|
||||
public const DATA_FUSE_LENGTH = 56; //int
|
||||
public const DATA_RIDER_SEAT_POSITION = 57; //vector3f
|
||||
public const DATA_RIDER_ROTATION_LOCKED = 58; //byte
|
||||
public const DATA_RIDER_MAX_ROTATION = 59; //float
|
||||
public const DATA_RIDER_MIN_ROTATION = 60; //float
|
||||
public const DATA_AREA_EFFECT_CLOUD_RADIUS = 61; //float
|
||||
public const DATA_AREA_EFFECT_CLOUD_WAITING = 62; //int
|
||||
public const DATA_AREA_EFFECT_CLOUD_PARTICLE_ID = 63; //int
|
||||
/* 64 (int) shulker-related */
|
||||
public const DATA_SHULKER_ATTACH_FACE = 65; //byte
|
||||
/* 66 (short) shulker-related */
|
||||
public const DATA_SHULKER_ATTACH_POS = 67; //block coords
|
||||
public const DATA_TRADING_PLAYER_EID = 68; //long
|
||||
|
||||
/* 70 (byte) command-block */
|
||||
public const DATA_COMMAND_BLOCK_COMMAND = 71; //string
|
||||
public const DATA_COMMAND_BLOCK_LAST_OUTPUT = 72; //string
|
||||
public const DATA_COMMAND_BLOCK_TRACK_OUTPUT = 73; //byte
|
||||
public const DATA_CONTROLLING_RIDER_SEAT_NUMBER = 74; //byte
|
||||
public const DATA_STRENGTH = 75; //int
|
||||
public const DATA_MAX_STRENGTH = 76; //int
|
||||
/* 77 (int) */
|
||||
public const DATA_LIMITED_LIFE = 78;
|
||||
public const DATA_ARMOR_STAND_POSE_INDEX = 79; //int
|
||||
public const DATA_ENDER_CRYSTAL_TIME_OFFSET = 80; //int
|
||||
public const DATA_ALWAYS_SHOW_NAMETAG = 81; //byte: -1 = default, 0 = only when looked at, 1 = always
|
||||
public const DATA_COLOR_2 = 82; //byte
|
||||
/* 83 (unknown) */
|
||||
public const DATA_SCORE_TAG = 84; //string
|
||||
public const DATA_BALLOON_ATTACHED_ENTITY = 85; //int64, entity unique ID of owner
|
||||
public const DATA_PUFFERFISH_SIZE = 86; //byte
|
||||
public const DATA_BOAT_BUBBLE_TIME = 87; //int (time in bubble column)
|
||||
public const DATA_PLAYER_AGENT_EID = 88; //long
|
||||
/* 89 (float) related to panda sitting
|
||||
* 90 (float) related to panda sitting */
|
||||
public const DATA_EAT_COUNTER = 91; //int (used by pandas)
|
||||
public const DATA_FLAGS2 = 92; //long (extended data flags)
|
||||
/* 93 (float) related to panda lying down
|
||||
* 94 (float) related to panda lying down */
|
||||
public const DATA_AREA_EFFECT_CLOUD_DURATION = 95; //int
|
||||
public const DATA_AREA_EFFECT_CLOUD_SPAWN_TIME = 96; //int
|
||||
public const DATA_AREA_EFFECT_CLOUD_RADIUS_PER_TICK = 97; //float, usually negative
|
||||
public const DATA_AREA_EFFECT_CLOUD_RADIUS_CHANGE_ON_PICKUP = 98; //float
|
||||
public const DATA_AREA_EFFECT_CLOUD_PICKUP_COUNT = 99; //int
|
||||
public const DATA_INTERACTIVE_TAG = 100; //string (button text)
|
||||
public const DATA_TRADE_TIER = 101; //int
|
||||
public const DATA_MAX_TRADE_TIER = 102; //int
|
||||
public const DATA_TRADE_XP = 103; //int
|
||||
|
||||
public const DATA_FLAG_ONFIRE = 0;
|
||||
public const DATA_FLAG_SNEAKING = 1;
|
||||
@ -273,6 +290,26 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
|
||||
public const DATA_FLAG_IN_SCAFFOLDING = 68;
|
||||
public const DATA_FLAG_OVER_SCAFFOLDING = 69;
|
||||
public const DATA_FLAG_FALL_THROUGH_SCAFFOLDING = 70;
|
||||
public const DATA_FLAG_BLOCKING = 71; //shield
|
||||
public const DATA_FLAG_DISABLE_BLOCKING = 72;
|
||||
//73 is set when a player is attacked while using shield, unclear on purpose
|
||||
//74 related to shield usage, needs further investigation
|
||||
public const DATA_FLAG_SLEEPING = 75;
|
||||
//76 related to sleeping, unclear usage
|
||||
public const DATA_FLAG_TRADE_INTEREST = 77;
|
||||
public const DATA_FLAG_DOOR_BREAKER = 78; //...
|
||||
public const DATA_FLAG_BREAKING_OBSTRUCTION = 79;
|
||||
public const DATA_FLAG_DOOR_OPENER = 80; //...
|
||||
public const DATA_FLAG_ILLAGER_CAPTAIN = 81;
|
||||
public const DATA_FLAG_STUNNED = 82;
|
||||
public const DATA_FLAG_ROARING = 83;
|
||||
public const DATA_FLAG_DELAYED_ATTACKING = 84;
|
||||
public const DATA_FLAG_AVOIDING_MOBS = 85;
|
||||
//86 used by RangedAttackGoal
|
||||
//87 used by NearestAttackableTargetGoal
|
||||
|
||||
public const DATA_PLAYER_FLAG_SLEEP = 1;
|
||||
public const DATA_PLAYER_FLAG_DEAD = 2; //TODO: CHECK
|
||||
|
||||
public static $entityCount = 1;
|
||||
/** @var Entity[] */
|
||||
@ -477,7 +514,7 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
|
||||
/** @var int */
|
||||
public $lastUpdate;
|
||||
/** @var int */
|
||||
public $fireTicks = 0;
|
||||
protected $fireTicks = 0;
|
||||
/** @var CompoundTag */
|
||||
public $namedtag;
|
||||
/** @var bool */
|
||||
@ -525,6 +562,8 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
|
||||
/** @var bool */
|
||||
protected $constructed = false;
|
||||
|
||||
/** @var bool */
|
||||
private $closeInFlight = false;
|
||||
|
||||
public function __construct(Level $level, CompoundTag $nbt){
|
||||
$this->constructed = true;
|
||||
@ -556,6 +595,7 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
|
||||
throw new \InvalidStateException("Cannot create entities in unloaded chunks");
|
||||
}
|
||||
|
||||
$this->motion = new Vector3(0, 0, 0);
|
||||
if($this->namedtag->hasTag("Motion", ListTag::class)){
|
||||
/** @var float[] $motion */
|
||||
$motion = $this->namedtag->getListTag("Motion")->getAllValues();
|
||||
@ -1093,8 +1133,8 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
|
||||
|
||||
public function setOnFire(int $seconds) : void{
|
||||
$ticks = $seconds * 20;
|
||||
if($ticks > $this->fireTicks){
|
||||
$this->fireTicks = $ticks;
|
||||
if($ticks > $this->getFireTicks()){
|
||||
$this->setFireTicks($ticks);
|
||||
}
|
||||
|
||||
$this->setGenericFlag(self::DATA_FLAG_ONFIRE, true);
|
||||
@ -1109,8 +1149,12 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
|
||||
|
||||
/**
|
||||
* @param int $fireTicks
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function setFireTicks(int $fireTicks) : void{
|
||||
if($fireTicks < 0 or $fireTicks > 0x7fff){
|
||||
throw new \InvalidArgumentException("Fire ticks must be in range 0 ... " . 0x7fff . ", got $fireTicks");
|
||||
}
|
||||
$this->fireTicks = $fireTicks;
|
||||
}
|
||||
|
||||
@ -1160,6 +1204,9 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
|
||||
}
|
||||
|
||||
protected function updateMovement(bool $teleport = false) : void{
|
||||
//TODO: hack for client-side AI interference: prevent client sided movement when motion is 0
|
||||
$this->setImmobile($this->motion->x == 0 and $this->motion->y == 0 and $this->motion->z == 0);
|
||||
|
||||
$diffPosition = ($this->x - $this->lastX) ** 2 + ($this->y - $this->lastY) ** 2 + ($this->z - $this->lastZ) ** 2;
|
||||
$diffRotation = ($this->yaw - $this->lastYaw) ** 2 + ($this->pitch - $this->lastPitch) ** 2;
|
||||
|
||||
@ -1628,7 +1675,8 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
|
||||
|
||||
assert(abs($dx) <= 20 and abs($dy) <= 20 and abs($dz) <= 20, "Movement distance is excessive: dx=$dx, dy=$dy, dz=$dz");
|
||||
|
||||
$list = $this->level->getCollisionCubes($this, $this->level->getTickRate() > 1 ? $this->boundingBox->offsetCopy($dx, $dy, $dz) : $this->boundingBox->addCoord($dx, $dy, $dz), false);
|
||||
//TODO: bad hack here will cause unexpected behaviour under heavy lag
|
||||
$list = $this->level->getCollisionCubes($this, $this->level->getTickRateTime() > 50 ? $this->boundingBox->offsetCopy($dx, $dy, $dz) : $this->boundingBox->addCoord($dx, $dy, $dz), false);
|
||||
|
||||
foreach($list as $bb){
|
||||
$dy = $bb->calculateYOffset($this->boundingBox, $dy);
|
||||
@ -2063,7 +2111,12 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
|
||||
* WARNING: Entities are unusable after this has been executed!
|
||||
*/
|
||||
public function close() : void{
|
||||
if($this->closeInFlight){
|
||||
return;
|
||||
}
|
||||
|
||||
if(!$this->closed){
|
||||
$this->closeInFlight = true;
|
||||
(new EntityDespawnEvent($this))->call();
|
||||
$this->closed = true;
|
||||
|
||||
@ -2082,6 +2135,7 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
|
||||
|
||||
$this->namedtag = null;
|
||||
$this->lastDamageCause = null;
|
||||
$this->closeInFlight = false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -2187,4 +2241,47 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
|
||||
public function __toString(){
|
||||
return (new \ReflectionClass($this))->getShortName() . "(" . $this->getId() . ")";
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: remove this BC hack in 4.0
|
||||
*
|
||||
* @param string $name
|
||||
*
|
||||
* @return mixed
|
||||
* @throws \ErrorException
|
||||
*/
|
||||
public function __get($name){
|
||||
if($name === "fireTicks"){
|
||||
return $this->fireTicks;
|
||||
}
|
||||
throw new \ErrorException("Undefined property: " . get_class($this) . "::\$" . $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: remove this BC hack in 4.0
|
||||
*
|
||||
* @param string $name
|
||||
* @param mixed $value
|
||||
*
|
||||
* @throws \ErrorException
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function __set($name, $value){
|
||||
if($name === "fireTicks"){
|
||||
$this->setFireTicks($value);
|
||||
}else{
|
||||
throw new \ErrorException("Undefined property: " . get_class($this) . "::\$" . $name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: remove this BC hack in 4.0
|
||||
*
|
||||
* @param string $name
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function __isset($name){
|
||||
return $name === "fireTicks";
|
||||
}
|
||||
}
|
||||
|
@ -60,6 +60,7 @@ use function array_merge;
|
||||
use function array_rand;
|
||||
use function array_values;
|
||||
use function ceil;
|
||||
use function in_array;
|
||||
use function max;
|
||||
use function min;
|
||||
use function mt_rand;
|
||||
@ -70,13 +71,6 @@ use const INT32_MIN;
|
||||
|
||||
class Human extends Creature implements ProjectileSource, InventoryHolder{
|
||||
|
||||
public const DATA_PLAYER_FLAG_SLEEP = 1;
|
||||
public const DATA_PLAYER_FLAG_DEAD = 2; //TODO: CHECK
|
||||
|
||||
public const DATA_PLAYER_FLAGS = 26;
|
||||
|
||||
public const DATA_PLAYER_BED_POSITION = 28;
|
||||
|
||||
/** @var PlayerInventory */
|
||||
protected $inventory;
|
||||
|
||||
@ -105,18 +99,36 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{
|
||||
public function __construct(Level $level, CompoundTag $nbt){
|
||||
if($this->skin === null){
|
||||
$skinTag = $nbt->getCompoundTag("Skin");
|
||||
if($skinTag === null or !self::isValidSkin($skinTag->hasTag("Data", ByteArrayTag::class) ?
|
||||
$skinTag->getByteArray("Data") :
|
||||
$skinTag->getString("Data", "")
|
||||
)){
|
||||
if($skinTag === null){
|
||||
throw new \InvalidStateException((new \ReflectionClass($this))->getShortName() . " must have a valid skin set");
|
||||
}
|
||||
$this->skin = self::deserializeSkinNBT($skinTag); //this throws if the skin is invalid
|
||||
}
|
||||
|
||||
parent::__construct($level, $nbt);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param CompoundTag $skinTag
|
||||
*
|
||||
* @return Skin
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
protected static function deserializeSkinNBT(CompoundTag $skinTag) : Skin{
|
||||
$skin = new Skin(
|
||||
$skinTag->getString("Name"),
|
||||
$skinTag->hasTag("Data", StringTag::class) ? $skinTag->getString("Data") : $skinTag->getByteArray("Data"), //old data (this used to be saved as a StringTag in older versions of PM)
|
||||
$skinTag->getByteArray("CapeData", ""),
|
||||
$skinTag->getString("GeometryName", ""),
|
||||
$skinTag->getByteArray("GeometryData", "")
|
||||
);
|
||||
$skin->validate();
|
||||
return $skin;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*
|
||||
* Checks the length of a supplied skin bitmap and returns whether the length is valid.
|
||||
*
|
||||
* @param string $skin
|
||||
@ -124,7 +136,7 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{
|
||||
* @return bool
|
||||
*/
|
||||
public static function isValidSkin(string $skin) : bool{
|
||||
return strlen($skin) === 64 * 64 * 4 or strlen($skin) === 64 * 32 * 4 or strlen($skin) === 128 * 128 * 4;
|
||||
return in_array(strlen($skin), Skin::ACCEPTED_SKIN_SIZES, true);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -156,10 +168,7 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{
|
||||
* @param Skin $skin
|
||||
*/
|
||||
public function setSkin(Skin $skin) : void{
|
||||
if(!$skin->isValid()){
|
||||
throw new \InvalidStateException("Specified skin is not valid, must be 8KiB or 16KiB");
|
||||
}
|
||||
|
||||
$skin->validate();
|
||||
$this->skin = $skin;
|
||||
$this->skin->debloatGeometryData();
|
||||
}
|
||||
@ -203,18 +212,13 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{
|
||||
$old = $attr->getValue();
|
||||
$attr->setValue($new);
|
||||
|
||||
$reset = false;
|
||||
// ranges: 18-20 (regen), 7-17 (none), 1-6 (no sprint), 0 (health depletion)
|
||||
foreach([17, 6, 0] as $bound){
|
||||
if(($old > $bound) !== ($new > $bound)){
|
||||
$reset = true;
|
||||
$this->foodTickTimer = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if($reset){
|
||||
$this->foodTickTimer = 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function getMaxFood() : float{
|
||||
@ -301,7 +305,7 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{
|
||||
$food = $this->getFood();
|
||||
if($food > 0){
|
||||
$food--;
|
||||
$this->setFood($food);
|
||||
$this->setFood(max($food, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -578,7 +582,9 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{
|
||||
}
|
||||
|
||||
public function getXpDropAmount() : int{
|
||||
return (int) min(100, $this->getCurrentTotalXp());
|
||||
//this causes some XP to be lost on death when above level 1 (by design), dropping at most enough points for
|
||||
//about 7.5 levels of XP.
|
||||
return (int) min(100, 7 * $this->getXpLevel());
|
||||
}
|
||||
|
||||
public function getInventory(){
|
||||
@ -597,17 +603,6 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{
|
||||
$this->setNameTag($this->namedtag->getString("NameTag"));
|
||||
}
|
||||
|
||||
$skin = $this->namedtag->getCompoundTag("Skin");
|
||||
if($skin !== null){
|
||||
$this->setSkin(new Skin(
|
||||
$skin->getString("Name"),
|
||||
$skin->hasTag("Data", StringTag::class) ? $skin->getString("Data") : $skin->getByteArray("Data"), //old data (this used to be saved as a StringTag in older versions of PM)
|
||||
$skin->getByteArray("CapeData", ""),
|
||||
$skin->getString("GeometryName", ""),
|
||||
$skin->getByteArray("GeometryData", "")
|
||||
));
|
||||
}
|
||||
|
||||
$this->uuid = UUID::fromData((string) $this->getId(), $this->skin->getSkinData(), $this->getNameTag());
|
||||
}
|
||||
|
||||
@ -846,9 +841,7 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{
|
||||
}
|
||||
|
||||
protected function sendSpawnPacket(Player $player) : void{
|
||||
if(!$this->skin->isValid()){
|
||||
throw new \InvalidStateException((new \ReflectionClass($this))->getShortName() . " must have a valid skin set");
|
||||
}
|
||||
$this->skin->validate();
|
||||
|
||||
if(!($this instanceof Player)){
|
||||
/* we don't use Server->updatePlayerListData() because that uses batches, which could cause race conditions in async compression mode */
|
||||
|
@ -152,7 +152,7 @@ abstract class Living extends Entity implements Damageable{
|
||||
}
|
||||
|
||||
public function setMaxHealth(int $amount) : void{
|
||||
$this->attributeMap->getAttribute(Attribute::HEALTH)->setMaxValue($amount);
|
||||
$this->attributeMap->getAttribute(Attribute::HEALTH)->setMaxValue($amount)->setDefaultValue($amount);
|
||||
}
|
||||
|
||||
public function getAbsorption() : float{
|
||||
@ -644,6 +644,10 @@ abstract class Living extends Entity implements Damageable{
|
||||
foreach($ev->getDrops() as $item){
|
||||
$this->getLevel()->dropItem($this, $item);
|
||||
}
|
||||
|
||||
//TODO: check death conditions (must have been damaged by player < 5 seconds from death)
|
||||
//TODO: allow this number to be manipulated during EntityDeathEvent
|
||||
$this->level->dropExperience($this, $this->getXpDropAmount());
|
||||
}
|
||||
|
||||
protected function onDeathUpdate(int $tickDiff) : bool{
|
||||
@ -651,9 +655,6 @@ abstract class Living extends Entity implements Damageable{
|
||||
$this->deadTicks += $tickDiff;
|
||||
if($this->deadTicks >= $this->maxDeadTicks){
|
||||
$this->endDeathAnimation();
|
||||
|
||||
//TODO: check death conditions (must have been damaged by player < 5 seconds from death)
|
||||
$this->level->dropExperience($this, $this->getXpDropAmount());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -23,11 +23,18 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\entity;
|
||||
|
||||
use function implode;
|
||||
use function in_array;
|
||||
use function json_decode;
|
||||
use function json_encode;
|
||||
use function strlen;
|
||||
|
||||
class Skin{
|
||||
public const ACCEPTED_SKIN_SIZES = [
|
||||
64 * 32 * 4,
|
||||
64 * 64 * 4,
|
||||
128 * 128 * 4
|
||||
];
|
||||
|
||||
/** @var string */
|
||||
private $skinId;
|
||||
@ -48,12 +55,34 @@ class Skin{
|
||||
$this->geometryData = $geometryData;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @return bool
|
||||
*/
|
||||
public function isValid() : bool{
|
||||
return (
|
||||
$this->skinId !== "" and
|
||||
(($s = strlen($this->skinData)) === 16384 or $s === 8192 or $s === 65536) and
|
||||
($this->capeData === "" or strlen($this->capeData) === 8192)
|
||||
);
|
||||
try{
|
||||
$this->validate();
|
||||
return true;
|
||||
}catch(\InvalidArgumentException $e){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function validate() : void{
|
||||
if($this->skinId === ""){
|
||||
throw new \InvalidArgumentException("Skin ID must not be empty");
|
||||
}
|
||||
$len = strlen($this->skinData);
|
||||
if(!in_array($len, self::ACCEPTED_SKIN_SIZES, true)){
|
||||
throw new \InvalidArgumentException("Invalid skin data size $len bytes (allowed sizes: " . implode(", ", self::ACCEPTED_SKIN_SIZES) . ")");
|
||||
}
|
||||
if($this->capeData !== "" and strlen($this->capeData) !== 8192){
|
||||
throw new \InvalidArgumentException("Invalid cape data size " . strlen($this->capeData) . " bytes (must be exactly 8192 bytes)");
|
||||
}
|
||||
//TODO: validate geometry
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -28,6 +28,7 @@ use pocketmine\entity\Human;
|
||||
use pocketmine\nbt\tag\IntTag;
|
||||
use pocketmine\nbt\tag\ShortTag;
|
||||
use pocketmine\Player;
|
||||
use function sqrt;
|
||||
|
||||
class ExperienceOrb extends Entity{
|
||||
public const NETWORK_ID = self::XP_ORB;
|
||||
@ -147,7 +148,7 @@ class ExperienceOrb extends Entity{
|
||||
return null;
|
||||
}
|
||||
|
||||
$entity = $this->server->findEntity($this->targetPlayerRuntimeId);
|
||||
$entity = $this->level->getEntity($this->targetPlayerRuntimeId);
|
||||
if($entity instanceof Human){
|
||||
return $entity;
|
||||
}
|
||||
@ -156,7 +157,7 @@ class ExperienceOrb extends Entity{
|
||||
}
|
||||
|
||||
public function setTargetPlayer(?Human $player) : void{
|
||||
$this->targetPlayerRuntimeId = $player ? $player->getId() : null;
|
||||
$this->targetPlayerRuntimeId = $player !== null ? $player->getId() : null;
|
||||
}
|
||||
|
||||
public function entityBaseTick(int $tickDiff = 1) : bool{
|
||||
@ -190,15 +191,15 @@ class ExperienceOrb extends Entity{
|
||||
$this->setTargetPlayer($currentTarget);
|
||||
|
||||
if($currentTarget !== null){
|
||||
$vector = $currentTarget->subtract($this)->add(0, $currentTarget->getEyeHeight() / 2, 0)->divide(self::MAX_TARGET_DISTANCE);
|
||||
$vector = $currentTarget->add(0, $currentTarget->getEyeHeight() / 2, 0)->subtract($this)->divide(self::MAX_TARGET_DISTANCE);
|
||||
|
||||
$distance = $vector->length();
|
||||
$oneMinusDistance = (1 - $distance) ** 2;
|
||||
$distance = $vector->lengthSquared();
|
||||
if($distance < 1){
|
||||
$diff = $vector->normalize()->multiply(0.2 * (1 - sqrt($distance)) ** 2);
|
||||
|
||||
if($oneMinusDistance > 0){
|
||||
$this->motion->x += $vector->x / $distance * $oneMinusDistance * 0.2;
|
||||
$this->motion->y += $vector->y / $distance * $oneMinusDistance * 0.2;
|
||||
$this->motion->z += $vector->z / $distance * $oneMinusDistance * 0.2;
|
||||
$this->motion->x += $diff->x;
|
||||
$this->motion->y += $diff->y;
|
||||
$this->motion->z += $diff->z;
|
||||
}
|
||||
|
||||
if($currentTarget->canPickupXp() and $this->boundingBox->intersectsWith($currentTarget->getBoundingBox())){
|
||||
|
@ -123,7 +123,7 @@ class Arrow extends Projectile{
|
||||
|
||||
$hasUpdate = parent::entityBaseTick($tickDiff);
|
||||
|
||||
if($this->isCollided){
|
||||
if($this->blockHit !== null){
|
||||
$this->collideTicks += $tickDiff;
|
||||
if($this->collideTicks > 1200){
|
||||
$this->flagForDespawn();
|
||||
|
@ -50,8 +50,6 @@ use const PHP_INT_MAX;
|
||||
|
||||
abstract class Projectile extends Entity{
|
||||
|
||||
public const DATA_SHOOTER_ID = 17;
|
||||
|
||||
/** @var float */
|
||||
protected $damage = 0.0;
|
||||
|
||||
@ -319,7 +317,7 @@ abstract class Projectile extends Entity{
|
||||
|
||||
$entityHit->attack($ev);
|
||||
|
||||
if($this->fireTicks > 0){
|
||||
if($this->isOnFire()){
|
||||
$ev = new EntityCombustByEntityEvent($this, $entityHit, 5);
|
||||
$ev->call();
|
||||
if(!$ev->isCancelled()){
|
||||
|
@ -85,7 +85,7 @@ class SplashPotion extends Throwable{
|
||||
if(!$this->willLinger()){
|
||||
foreach($this->level->getNearbyEntities($this->boundingBox->expandedCopy(4.125, 2.125, 4.125), $this) as $entity){
|
||||
if($entity instanceof Living and $entity->isAlive()){
|
||||
$distanceSquared = $entity->distanceSquared($this);
|
||||
$distanceSquared = $entity->add(0, $entity->getEyeHeight(), 0)->distanceSquared($this);
|
||||
if($distanceSquared > 16){ //4 blocks
|
||||
continue;
|
||||
}
|
||||
|
@ -67,10 +67,7 @@ class PlayerChangeSkinEvent extends PlayerEvent implements Cancellable{
|
||||
* @throws \InvalidArgumentException if the specified skin is not valid
|
||||
*/
|
||||
public function setNewSkin(Skin $skin) : void{
|
||||
if(!$skin->isValid()){
|
||||
throw new \InvalidArgumentException("Skin format is invalid");
|
||||
}
|
||||
|
||||
$skin->validate();
|
||||
$this->newSkin = $skin;
|
||||
}
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ use pocketmine\level\Position;
|
||||
use pocketmine\Player;
|
||||
|
||||
/**
|
||||
* Called when a player is respawned (or first time spawned)
|
||||
* Called when a player is respawned
|
||||
*/
|
||||
class PlayerRespawnEvent extends PlayerEvent{
|
||||
/** @var Position */
|
||||
|
@ -59,6 +59,9 @@ class AnvilInventory extends ContainerInventory{
|
||||
public function onClose(Player $who) : void{
|
||||
parent::onClose($who);
|
||||
|
||||
$this->dropContents($this->holder->getLevel(), $this->holder->add(0.5, 0.5, 0.5));
|
||||
foreach($this->getContents() as $item){
|
||||
$who->dropItem($item);
|
||||
}
|
||||
$this->clearAll();
|
||||
}
|
||||
}
|
||||
|
@ -94,11 +94,12 @@ class ArmorInventory extends BaseInventory{
|
||||
$target = [$target];
|
||||
}
|
||||
|
||||
$armor = $this->getContents(true);
|
||||
|
||||
$pk = new MobArmorEquipmentPacket();
|
||||
$pk->entityRuntimeId = $this->getHolder()->getId();
|
||||
$pk->slots = $armor;
|
||||
$pk->head = $this->getHelmet();
|
||||
$pk->chest = $this->getChestplate();
|
||||
$pk->legs = $this->getLeggings();
|
||||
$pk->feet = $this->getBoots();
|
||||
$pk->encode();
|
||||
|
||||
foreach($target as $player){
|
||||
@ -121,18 +122,19 @@ class ArmorInventory extends BaseInventory{
|
||||
$target = [$target];
|
||||
}
|
||||
|
||||
$armor = $this->getContents(true);
|
||||
|
||||
$pk = new MobArmorEquipmentPacket();
|
||||
$pk->entityRuntimeId = $this->getHolder()->getId();
|
||||
$pk->slots = $armor;
|
||||
$pk->head = $this->getHelmet();
|
||||
$pk->chest = $this->getChestplate();
|
||||
$pk->legs = $this->getLeggings();
|
||||
$pk->feet = $this->getBoots();
|
||||
$pk->encode();
|
||||
|
||||
foreach($target as $player){
|
||||
if($player === $this->getHolder()){
|
||||
$pk2 = new InventoryContentPacket();
|
||||
$pk2->windowId = $player->getWindowId($this);
|
||||
$pk2->items = $armor;
|
||||
$pk2->items = $this->getContents(true);
|
||||
$player->dataPacket($pk2);
|
||||
}else{
|
||||
$player->dataPacket($pk);
|
||||
|
@ -253,11 +253,9 @@ abstract class BaseInventory implements Inventory{
|
||||
|
||||
public function canAddItem(Item $item) : bool{
|
||||
$item = clone $item;
|
||||
$checkDamage = !$item->hasAnyDamageValue();
|
||||
$checkTags = $item->hasCompoundTag();
|
||||
for($i = 0, $size = $this->getSize(); $i < $size; ++$i){
|
||||
$slot = $this->getItem($i);
|
||||
if($item->equals($slot, $checkDamage, $checkTags)){
|
||||
if($item->equals($slot)){
|
||||
if(($diff = $slot->getMaxStackSize() - $slot->getCount()) > 0){
|
||||
$item->setCount($item->getCount() - $diff);
|
||||
}
|
||||
|
@ -24,7 +24,6 @@ declare(strict_types=1);
|
||||
namespace pocketmine\inventory;
|
||||
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\item\ItemFactory;
|
||||
use pocketmine\network\mcpe\protocol\BatchPacket;
|
||||
use pocketmine\network\mcpe\protocol\CraftingDataPacket;
|
||||
use pocketmine\Server;
|
||||
@ -56,24 +55,33 @@ class CraftingManager{
|
||||
|
||||
foreach($recipes as $recipe){
|
||||
switch($recipe["type"]){
|
||||
case 0:
|
||||
case "shapeless":
|
||||
if($recipe["block"] !== "crafting_table"){ //TODO: filter others out for now to avoid breaking economics
|
||||
break;
|
||||
}
|
||||
$this->registerRecipe(new ShapelessRecipe(
|
||||
array_map(function(array $data) : Item{ return Item::jsonDeserialize($data); }, $recipe["input"]),
|
||||
array_map(function(array $data) : Item{ return Item::jsonDeserialize($data); }, $recipe["output"])
|
||||
));
|
||||
break;
|
||||
case 1:
|
||||
case "shaped":
|
||||
if($recipe["block"] !== "crafting_table"){ //TODO: filter others out for now to avoid breaking economics
|
||||
break;
|
||||
}
|
||||
$this->registerRecipe(new ShapedRecipe(
|
||||
$recipe["shape"],
|
||||
array_map(function(array $data) : Item{ return Item::jsonDeserialize($data); }, $recipe["input"]),
|
||||
array_map(function(array $data) : Item{ return Item::jsonDeserialize($data); }, $recipe["output"])
|
||||
));
|
||||
break;
|
||||
case 2:
|
||||
case 3:
|
||||
$result = $recipe["output"];
|
||||
$resultItem = Item::jsonDeserialize($result);
|
||||
$this->registerRecipe(new FurnaceRecipe($resultItem, ItemFactory::get($recipe["inputId"], $recipe["inputDamage"] ?? -1, 1)));
|
||||
case "smelting":
|
||||
if($recipe["block"] !== "furnace"){ //TODO: filter others out for now to avoid breaking economics
|
||||
break;
|
||||
}
|
||||
$this->registerRecipe(new FurnaceRecipe(
|
||||
Item::jsonDeserialize($recipe["output"]),
|
||||
Item::jsonDeserialize($recipe["input"]))
|
||||
);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
@ -59,6 +59,9 @@ class EnchantInventory extends ContainerInventory{
|
||||
public function onClose(Player $who) : void{
|
||||
parent::onClose($who);
|
||||
|
||||
$this->dropContents($this->holder->getLevel(), $this->holder->add(0.5, 0.5, 0.5));
|
||||
foreach($this->getContents() as $item){
|
||||
$who->dropItem($item);
|
||||
}
|
||||
$this->clearAll();
|
||||
}
|
||||
}
|
||||
|
@ -32,6 +32,21 @@ use function array_pop;
|
||||
use function count;
|
||||
use function intdiv;
|
||||
|
||||
/**
|
||||
* This transaction type is specialized for crafting validation. It shares most of the same semantics of the base
|
||||
* inventory transaction type, but the requirement for validity is slightly different.
|
||||
*
|
||||
* It is expected that the actions in this transaction type will produce an **unbalanced result**, i.e. some inputs won't
|
||||
* have corresponding outputs, and vice versa. The reason why is because the unmatched inputs are recipe inputs, and
|
||||
* the unmatched outputs are recipe results.
|
||||
*
|
||||
* Therefore, the validity requirement is that the imbalance of the transaction should match the expected inputs and
|
||||
* outputs of a registered crafting recipe.
|
||||
*
|
||||
* This transaction allows multiple repetitions of the same recipe to be crafted in a single batch. In the case of batch
|
||||
* crafting, the number of unmatched inputs and outputs must be exactly divisible by the expected recipe ingredients and
|
||||
* results, with no remainder. Any leftovers are expected to be emitted back to the crafting grid.
|
||||
*/
|
||||
class CraftingTransaction extends InventoryTransaction{
|
||||
/** @var CraftingRecipe|null */
|
||||
protected $recipe;
|
||||
|
@ -29,14 +29,28 @@ use pocketmine\inventory\transaction\action\InventoryAction;
|
||||
use pocketmine\inventory\transaction\action\SlotChangeAction;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\Player;
|
||||
use function array_keys;
|
||||
use function assert;
|
||||
use function count;
|
||||
use function get_class;
|
||||
use function min;
|
||||
use function shuffle;
|
||||
use function spl_object_hash;
|
||||
|
||||
/**
|
||||
* This InventoryTransaction only allows doing Transaction between one / two inventories
|
||||
* This is the basic type for an inventory transaction. This is used for moving items between inventories, dropping
|
||||
* items and more. It allows transactions with multiple inputs and outputs.
|
||||
*
|
||||
* Validation **does not** depend on ordering. This means that the actions can appear in any order and still be valid.
|
||||
* The only validity requirement for this transaction type is that the balance of items must add up to zero. This means:
|
||||
* - No new outputs without matching input amounts
|
||||
* - No inputs without matching output amounts
|
||||
* - No userdata changes (item state, NBT, etc)
|
||||
*
|
||||
* A transaction is composed of "actions", which are pairs of inputs and outputs which target a specific itemstack in
|
||||
* a specific location. There are multiple types of inventory actions which might be involved in a transaction.
|
||||
*
|
||||
* @see InventoryAction
|
||||
*/
|
||||
class InventoryTransaction{
|
||||
protected $hasExecuted = false;
|
||||
@ -75,6 +89,11 @@ class InventoryTransaction{
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an **unordered** set of actions involved in this transaction.
|
||||
*
|
||||
* WARNING: This system is **explicitly designed NOT to care about ordering**. Any order seen in this set has NO
|
||||
* significance and should not be relied on.
|
||||
*
|
||||
* @return InventoryAction[]
|
||||
*/
|
||||
public function getActions() : array{
|
||||
@ -93,6 +112,19 @@ class InventoryTransaction{
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shuffles actions in the transaction to prevent external things relying on any implicit ordering.
|
||||
*/
|
||||
private function shuffleActions() : void{
|
||||
$keys = array_keys($this->actions);
|
||||
shuffle($keys);
|
||||
$actions = [];
|
||||
foreach($keys as $key){
|
||||
$actions[$key] = $this->actions[$key];
|
||||
}
|
||||
$this->actions = $actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal This method should not be used by plugins, it's used to add tracked inventories for InventoryActions
|
||||
* involving inventories.
|
||||
@ -271,6 +303,8 @@ class InventoryTransaction{
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->shuffleActions();
|
||||
|
||||
$this->validate();
|
||||
|
||||
if(!$this->callExecuteEvent()){
|
||||
|
@ -63,10 +63,10 @@ class Bow extends Tool{
|
||||
|
||||
$diff = $player->getItemUseDuration();
|
||||
$p = $diff / 20;
|
||||
$force = min((($p ** 2) + $p * 2) / 3, 1) * 2;
|
||||
$baseForce = min((($p ** 2) + $p * 2) / 3, 1);
|
||||
|
||||
|
||||
$entity = Entity::createEntity("Arrow", $player->getLevel(), $nbt, $player, $force == 2);
|
||||
$entity = Entity::createEntity("Arrow", $player->getLevel(), $nbt, $player, $baseForce >= 1);
|
||||
if($entity instanceof Projectile){
|
||||
$infinity = $this->hasEnchantment(Enchantment::INFINITY);
|
||||
if($entity instanceof ArrowEntity){
|
||||
@ -83,9 +83,9 @@ class Bow extends Tool{
|
||||
if($this->hasEnchantment(Enchantment::FLAME)){
|
||||
$entity->setOnFire(intdiv($entity->getFireTicks(), 20) + 100);
|
||||
}
|
||||
$ev = new EntityShootBowEvent($player, $this, $entity, $force);
|
||||
$ev = new EntityShootBowEvent($player, $this, $entity, $baseForce * 3);
|
||||
|
||||
if($force < 0.1 or $diff < 5){
|
||||
if($baseForce < 0.1 or $diff < 5){
|
||||
$ev->setCancelled();
|
||||
}
|
||||
|
||||
|
@ -46,7 +46,6 @@ use pocketmine\utils\Binary;
|
||||
use function array_map;
|
||||
use function base64_decode;
|
||||
use function base64_encode;
|
||||
use function bin2hex;
|
||||
use function file_get_contents;
|
||||
use function get_class;
|
||||
use function hex2bin;
|
||||
@ -216,7 +215,7 @@ class Item implements ItemIds, \JsonSerializable{
|
||||
/**
|
||||
* Sets the Item's NBT
|
||||
*
|
||||
* @param CompoundTag|string $tags
|
||||
* @param CompoundTag|string|null $tags
|
||||
*
|
||||
* @return Item
|
||||
*/
|
||||
@ -224,7 +223,7 @@ class Item implements ItemIds, \JsonSerializable{
|
||||
if($tags instanceof CompoundTag){
|
||||
$this->setNamedTag($tags);
|
||||
}else{
|
||||
$this->tags = (string) $tags;
|
||||
$this->tags = $tags === null ? "" : (string) $tags;
|
||||
$this->cachedNBT = null;
|
||||
}
|
||||
|
||||
@ -232,6 +231,9 @@ class Item implements ItemIds, \JsonSerializable{
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated This method returns NBT serialized in a network-dependent format. Prefer use of getNamedTag() instead.
|
||||
* @see Item::getNamedTag()
|
||||
*
|
||||
* Returns the serialized NBT of the Item
|
||||
* @return string
|
||||
*/
|
||||
@ -871,7 +873,7 @@ class Item implements ItemIds, \JsonSerializable{
|
||||
* @return string
|
||||
*/
|
||||
final public function __toString() : string{
|
||||
return "Item " . $this->name . " (" . $this->id . ":" . ($this->hasAnyDamageValue() ? "?" : $this->meta) . ")x" . $this->count . ($this->hasCompoundTag() ? " tags:0x" . bin2hex($this->getCompoundTag()) : "");
|
||||
return "Item " . $this->name . " (" . $this->id . ":" . ($this->hasAnyDamageValue() ? "?" : $this->meta) . ")x" . $this->count . ($this->hasCompoundTag() ? " tags:" . base64_encode($this->getCompoundTag()) : "");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -300,16 +300,16 @@ class ItemFactory{
|
||||
/**
|
||||
* Returns an instance of the Item with the specified id, meta, count and NBT.
|
||||
*
|
||||
* @param int $id
|
||||
* @param int $meta
|
||||
* @param int $count
|
||||
* @param CompoundTag|string $tags
|
||||
* @param int $id
|
||||
* @param int $meta
|
||||
* @param int $count
|
||||
* @param CompoundTag|string|null $tags
|
||||
*
|
||||
* @return Item
|
||||
* @throws \TypeError
|
||||
*/
|
||||
public static function get(int $id, int $meta = 0, int $count = 1, $tags = "") : Item{
|
||||
if(!is_string($tags) and !($tags instanceof CompoundTag)){
|
||||
public static function get(int $id, int $meta = 0, int $count = 1, $tags = null) : Item{
|
||||
if(!is_string($tags) and !($tags instanceof CompoundTag) and $tags !== null){
|
||||
throw new \TypeError("`tags` argument must be a string or CompoundTag instance, " . (is_object($tags) ? "instance of " . get_class($tags) : gettype($tags)) . " given");
|
||||
}
|
||||
|
||||
@ -318,7 +318,7 @@ class ItemFactory{
|
||||
$listed = self::$list[self::getListOffset($id)];
|
||||
if($listed !== null){
|
||||
$item = clone $listed;
|
||||
}elseif($id < 256){ //intentionally includes negatives, for extended block IDs
|
||||
}elseif($id >= 0 and $id < 256){ //intentionally excludes negatives because extended blocks aren't supported yet
|
||||
/* Blocks must have a damage value 0-15, but items can have damage value -1 to indicate that they are
|
||||
* crafting ingredients with any-damage. */
|
||||
$item = new ItemBlock($id, $meta);
|
||||
|
@ -204,6 +204,7 @@ interface ItemIds extends BlockIds{
|
||||
public const DARK_OAK_DOOR = 431;
|
||||
public const CHORUS_FRUIT = 432;
|
||||
public const CHORUS_FRUIT_POPPED = 433;
|
||||
public const BANNER_PATTERN = 434;
|
||||
|
||||
public const DRAGON_BREATH = 437;
|
||||
public const SPLASH_POTION = 438;
|
||||
@ -237,6 +238,14 @@ interface ItemIds extends BlockIds{
|
||||
public const HEART_OF_THE_SEA = 467;
|
||||
public const TURTLE_SHELL_PIECE = 468;
|
||||
public const TURTLE_HELMET = 469;
|
||||
public const PHANTOM_MEMBRANE = 470;
|
||||
public const CROSSBOW = 471;
|
||||
public const SPRUCE_SIGN = 472;
|
||||
public const BIRCH_SIGN = 473;
|
||||
public const JUNGLE_SIGN = 474;
|
||||
public const ACACIA_SIGN = 475;
|
||||
public const DARKOAK_SIGN = 476;
|
||||
public const SWEET_BERRIES = 477;
|
||||
|
||||
public const COMPOUND = 499;
|
||||
public const RECORD_13 = 500;
|
||||
@ -252,4 +261,6 @@ interface ItemIds extends BlockIds{
|
||||
public const RECORD_11 = 510;
|
||||
public const RECORD_WAIT = 511;
|
||||
|
||||
public const SHIELD = 513;
|
||||
|
||||
}
|
||||
|
Submodule src/pocketmine/lang/locale updated: 1d525a7c1c...73ed1ab3e1
@ -74,7 +74,7 @@ class Explosion{
|
||||
*/
|
||||
public function __construct(Position $center, float $size, $what = null){
|
||||
if(!$center->isValid()){
|
||||
throw new \InvalidArgumentException("Position does not have a valid level");
|
||||
throw new \InvalidArgumentException("Position does not have a valid world");
|
||||
}
|
||||
$this->source = $center;
|
||||
$this->level = $center->getLevel();
|
||||
@ -219,11 +219,10 @@ class Explosion{
|
||||
|
||||
$t = $this->level->getTileAt($block->x, $block->y, $block->z);
|
||||
if($t instanceof Tile){
|
||||
if($t instanceof Chest){
|
||||
$t->unpair();
|
||||
}
|
||||
if($yieldDrops and $t instanceof Container){
|
||||
if($t instanceof Chest){
|
||||
$t->unpair();
|
||||
}
|
||||
|
||||
$t->getInventory()->dropContents($this->level, $t->add(0.5, 0.5, 0.5));
|
||||
}
|
||||
|
||||
|
@ -26,6 +26,7 @@ declare(strict_types=1);
|
||||
*/
|
||||
namespace pocketmine\level;
|
||||
|
||||
use pocketmine\block\Air;
|
||||
use pocketmine\block\Block;
|
||||
use pocketmine\block\BlockFactory;
|
||||
use pocketmine\entity\Entity;
|
||||
@ -78,6 +79,7 @@ use pocketmine\network\mcpe\protocol\LevelEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\LevelSoundEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\SetDifficultyPacket;
|
||||
use pocketmine\network\mcpe\protocol\SetTimePacket;
|
||||
use pocketmine\network\mcpe\protocol\types\RuntimeBlockMapping;
|
||||
use pocketmine\network\mcpe\protocol\UpdateBlockPacket;
|
||||
use pocketmine\Player;
|
||||
use pocketmine\plugin\Plugin;
|
||||
@ -111,6 +113,8 @@ use function trim;
|
||||
use const INT32_MAX;
|
||||
use const INT32_MIN;
|
||||
use const M_PI;
|
||||
use const PHP_INT_MAX;
|
||||
use const PHP_INT_MIN;
|
||||
|
||||
#include <rules/Level.h>
|
||||
|
||||
@ -261,11 +265,12 @@ class Level implements ChunkManager, Metadatable{
|
||||
/** @var LevelTimings */
|
||||
public $timings;
|
||||
|
||||
/** @var int */
|
||||
private $tickRate;
|
||||
/** @var int */
|
||||
public $tickRateTime = 0;
|
||||
/** @var int */
|
||||
/**
|
||||
* @deprecated
|
||||
* @var int
|
||||
*/
|
||||
public $tickRateCounter = 0;
|
||||
|
||||
/** @var bool */
|
||||
@ -293,6 +298,19 @@ class Level implements ChunkManager, Metadatable{
|
||||
return (($x & 0xFFFFFFF) << 36) | (($y & Level::Y_MASK) << 28) | ($z & 0xFFFFFFF);
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes a small index relative to chunk base from the given coordinates.
|
||||
*
|
||||
* @param int $x
|
||||
* @param int $y
|
||||
* @param int $z
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public static function chunkBlockHash(int $x, int $y, int $z) : int{
|
||||
return ($y << 8) | (($z & 0xf) << 4) | ($x & 0xf);
|
||||
}
|
||||
|
||||
public static function getBlockXYZ(int $hash, ?int &$x, ?int &$y, ?int &$z) : void{
|
||||
$x = $hash >> 36;
|
||||
$y = ($hash >> 28) & Level::Y_MASK; //it's always positive
|
||||
@ -368,7 +386,8 @@ class Level implements ChunkManager, Metadatable{
|
||||
$this->worldHeight = $this->provider->getWorldHeight();
|
||||
|
||||
$this->server->getLogger()->info($this->server->getLanguage()->translateString("pocketmine.level.preparing", [$this->displayName]));
|
||||
$this->generator = GeneratorManager::getGenerator($this->provider->getGenerator());
|
||||
$this->generator = GeneratorManager::getGenerator($this->provider->getGenerator(), true);
|
||||
//TODO: validate generator options
|
||||
|
||||
$this->folderName = $name;
|
||||
|
||||
@ -397,19 +416,26 @@ class Level implements ChunkManager, Metadatable{
|
||||
$this->timings = new LevelTimings($this);
|
||||
$this->temporalPosition = new Position(0, 0, 0, $this);
|
||||
$this->temporalVector = new Vector3(0, 0, 0);
|
||||
$this->tickRate = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @return int
|
||||
*/
|
||||
public function getTickRate() : int{
|
||||
return $this->tickRate;
|
||||
return 1;
|
||||
}
|
||||
|
||||
public function getTickRateTime() : float{
|
||||
return $this->tickRateTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated does nothing
|
||||
* @param int $tickRate
|
||||
*/
|
||||
public function setTickRate(int $tickRate){
|
||||
$this->tickRate = $tickRate;
|
||||
|
||||
}
|
||||
|
||||
public function registerGeneratorToWorker(int $worker) : void{
|
||||
@ -452,7 +478,7 @@ class Level implements ChunkManager, Metadatable{
|
||||
|
||||
public function close(){
|
||||
if($this->closed){
|
||||
throw new \InvalidStateException("Tried to close a level which is already closed");
|
||||
throw new \InvalidStateException("Tried to close a world which is already closed");
|
||||
}
|
||||
|
||||
foreach($this->chunks as $chunk){
|
||||
@ -554,7 +580,8 @@ class Level implements ChunkManager, Metadatable{
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal DO NOT use this from plugins, it's for internal use only. Use Server->unloadLevel() instead.
|
||||
* @internal
|
||||
* @see Server::unloadLevel()
|
||||
*
|
||||
* Unloads the current level from memory safely
|
||||
*
|
||||
@ -565,7 +592,7 @@ class Level implements ChunkManager, Metadatable{
|
||||
*/
|
||||
public function unload(bool $force = false) : bool{
|
||||
if($this->doingTick and !$force){
|
||||
throw new \InvalidStateException("Cannot unload a level during level tick");
|
||||
throw new \InvalidStateException("Cannot unload a world during world tick");
|
||||
}
|
||||
|
||||
$ev = new LevelUnloadEvent($this);
|
||||
@ -584,7 +611,7 @@ class Level implements ChunkManager, Metadatable{
|
||||
$defaultLevel = $this->server->getDefaultLevel();
|
||||
foreach($this->getPlayers() as $player){
|
||||
if($this === $defaultLevel or $defaultLevel === null){
|
||||
$player->close($player->getLeaveMessage(), "Forced default level unload");
|
||||
$player->close($player->getLeaveMessage(), "Forced default world unload");
|
||||
}elseif($defaultLevel instanceof Level){
|
||||
$player->teleport($this->server->getDefaultLevel()->getSafeSpawn());
|
||||
}
|
||||
@ -602,7 +629,10 @@ class Level implements ChunkManager, Metadatable{
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the players being used in a specific chunk
|
||||
* @deprecated WARNING: This function has a misleading name. Contrary to what the name might imply, this function
|
||||
* DOES NOT return players who are IN a chunk, rather, it returns players who can SEE the chunk.
|
||||
*
|
||||
* Returns a list of players who have the target chunk within their view distance.
|
||||
*
|
||||
* @param int $chunkX
|
||||
* @param int $chunkZ
|
||||
@ -681,25 +711,25 @@ class Level implements ChunkManager, Metadatable{
|
||||
}
|
||||
|
||||
public function registerChunkLoader(ChunkLoader $loader, int $chunkX, int $chunkZ, bool $autoLoad = true){
|
||||
$hash = $loader->getLoaderId();
|
||||
$loaderId = $loader->getLoaderId();
|
||||
|
||||
if(!isset($this->chunkLoaders[$index = Level::chunkHash($chunkX, $chunkZ)])){
|
||||
$this->chunkLoaders[$index] = [];
|
||||
$this->playerLoaders[$index] = [];
|
||||
}elseif(isset($this->chunkLoaders[$index][$hash])){
|
||||
if(!isset($this->chunkLoaders[$chunkHash = Level::chunkHash($chunkX, $chunkZ)])){
|
||||
$this->chunkLoaders[$chunkHash] = [];
|
||||
$this->playerLoaders[$chunkHash] = [];
|
||||
}elseif(isset($this->chunkLoaders[$chunkHash][$loaderId])){
|
||||
return;
|
||||
}
|
||||
|
||||
$this->chunkLoaders[$index][$hash] = $loader;
|
||||
$this->chunkLoaders[$chunkHash][$loaderId] = $loader;
|
||||
if($loader instanceof Player){
|
||||
$this->playerLoaders[$index][$hash] = $loader;
|
||||
$this->playerLoaders[$chunkHash][$loaderId] = $loader;
|
||||
}
|
||||
|
||||
if(!isset($this->loaders[$hash])){
|
||||
$this->loaderCounter[$hash] = 1;
|
||||
$this->loaders[$hash] = $loader;
|
||||
if(!isset($this->loaders[$loaderId])){
|
||||
$this->loaderCounter[$loaderId] = 1;
|
||||
$this->loaders[$loaderId] = $loader;
|
||||
}else{
|
||||
++$this->loaderCounter[$hash];
|
||||
++$this->loaderCounter[$loaderId];
|
||||
}
|
||||
|
||||
$this->cancelUnloadChunkRequest($chunkX, $chunkZ);
|
||||
@ -710,59 +740,45 @@ class Level implements ChunkManager, Metadatable{
|
||||
}
|
||||
|
||||
public function unregisterChunkLoader(ChunkLoader $loader, int $chunkX, int $chunkZ){
|
||||
$index = Level::chunkHash($chunkX, $chunkZ);
|
||||
$hash = $loader->getLoaderId();
|
||||
if(isset($this->chunkLoaders[$index][$hash])){
|
||||
unset($this->chunkLoaders[$index][$hash]);
|
||||
unset($this->playerLoaders[$index][$hash]);
|
||||
if(count($this->chunkLoaders[$index]) === 0){
|
||||
unset($this->chunkLoaders[$index]);
|
||||
unset($this->playerLoaders[$index]);
|
||||
$chunkHash = Level::chunkHash($chunkX, $chunkZ);
|
||||
$loaderId = $loader->getLoaderId();
|
||||
if(isset($this->chunkLoaders[$chunkHash][$loaderId])){
|
||||
unset($this->chunkLoaders[$chunkHash][$loaderId]);
|
||||
unset($this->playerLoaders[$chunkHash][$loaderId]);
|
||||
if(count($this->chunkLoaders[$chunkHash]) === 0){
|
||||
unset($this->chunkLoaders[$chunkHash]);
|
||||
unset($this->playerLoaders[$chunkHash]);
|
||||
$this->unloadChunkRequest($chunkX, $chunkZ, true);
|
||||
}
|
||||
|
||||
if(--$this->loaderCounter[$hash] === 0){
|
||||
unset($this->loaderCounter[$hash]);
|
||||
unset($this->loaders[$hash]);
|
||||
if(--$this->loaderCounter[$loaderId] === 0){
|
||||
unset($this->loaderCounter[$loaderId]);
|
||||
unset($this->loaders[$loaderId]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WARNING: Do not use this, it's only for internal use.
|
||||
* Changes to this function won't be recorded on the version.
|
||||
*/
|
||||
public function checkTime(){
|
||||
if($this->stopTime){
|
||||
return;
|
||||
}else{
|
||||
++$this->time;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WARNING: Do not use this, it's only for internal use.
|
||||
* Changes to this function won't be recorded on the version.
|
||||
* @internal
|
||||
*
|
||||
* @param Player ...$targets If empty, will send to all players in the level.
|
||||
*/
|
||||
public function sendTime(Player ...$targets){
|
||||
$pk = new SetTimePacket();
|
||||
$pk->time = $this->time;
|
||||
$pk->time = $this->time & 0xffffffff; //avoid overflowing the field, since the packet uses an int32
|
||||
|
||||
$this->server->broadcastPacket(count($targets) > 0 ? $targets : $this->players, $pk);
|
||||
}
|
||||
|
||||
/**
|
||||
* WARNING: Do not use this, it's only for internal use.
|
||||
* Changes to this function won't be recorded on the version.
|
||||
* @internal
|
||||
*
|
||||
* @param int $currentTick
|
||||
*
|
||||
*/
|
||||
public function doTick(int $currentTick){
|
||||
if($this->closed){
|
||||
throw new \InvalidStateException("Attempted to tick a Level which has been closed");
|
||||
throw new \InvalidStateException("Attempted to tick a world which has been closed");
|
||||
}
|
||||
|
||||
$this->timings->doTick->startTiming();
|
||||
@ -776,7 +792,14 @@ class Level implements ChunkManager, Metadatable{
|
||||
}
|
||||
|
||||
protected function actuallyDoTick(int $currentTick) : void{
|
||||
$this->checkTime();
|
||||
if(!$this->stopTime){
|
||||
//this simulates an overflow, as would happen in any language which doesn't do stupid things to var types
|
||||
if($this->time === PHP_INT_MAX){
|
||||
$this->time = PHP_INT_MIN;
|
||||
}else{
|
||||
$this->time++;
|
||||
}
|
||||
}
|
||||
|
||||
$this->sunAnglePercentage = $this->computeSunAnglePercentage(); //Sun angle depends on the current time
|
||||
$this->skyLightReduction = $this->computeSkyLightReduction(); //Sky light reduction depends on the sun angle
|
||||
@ -959,7 +982,7 @@ class Level implements ChunkManager, Metadatable{
|
||||
$pk->blockRuntimeId = $b->getRuntimeId();
|
||||
}else{
|
||||
$fullBlock = $this->getFullBlock($b->x, $b->y, $b->z);
|
||||
$pk->blockRuntimeId = BlockFactory::toStaticRuntimeId($fullBlock >> 4, $fullBlock & 0xf);
|
||||
$pk->blockRuntimeId = RuntimeBlockMapping::toStaticRuntimeId($fullBlock >> 4, $fullBlock & 0xf);
|
||||
}
|
||||
|
||||
$pk->flags = $first ? $flags : UpdateBlockPacket::FLAG_NONE;
|
||||
@ -981,7 +1004,7 @@ class Level implements ChunkManager, Metadatable{
|
||||
$pk->blockRuntimeId = $b->getRuntimeId();
|
||||
}else{
|
||||
$fullBlock = $this->getFullBlock($b->x, $b->y, $b->z);
|
||||
$pk->blockRuntimeId = BlockFactory::toStaticRuntimeId($fullBlock >> 4, $fullBlock & 0xf);
|
||||
$pk->blockRuntimeId = RuntimeBlockMapping::toStaticRuntimeId($fullBlock >> 4, $fullBlock & 0xf);
|
||||
}
|
||||
|
||||
$pk->flags = $flags;
|
||||
@ -1076,11 +1099,12 @@ class Level implements ChunkManager, Metadatable{
|
||||
|
||||
foreach($chunk->getSubChunks() as $Y => $subChunk){
|
||||
if(!($subChunk instanceof EmptySubChunk)){
|
||||
$k = mt_rand(0, 0xfffffffff); //36 bits
|
||||
for($i = 0; $i < 3; ++$i){
|
||||
$k = mt_rand(0, 0xfff);
|
||||
$x = $k & 0x0f;
|
||||
$y = ($k >> 4) & 0x0f;
|
||||
$z = ($k >> 8) & 0x0f;
|
||||
$k >>= 12;
|
||||
|
||||
$blockId = $subChunk->getBlockId($x, $y, $z);
|
||||
if($this->randomTickBlocks[$blockId] !== null){
|
||||
@ -1133,11 +1157,16 @@ class Level implements ChunkManager, Metadatable{
|
||||
}
|
||||
|
||||
public function saveChunks(){
|
||||
foreach($this->chunks as $chunk){
|
||||
if(($chunk->hasChanged() or count($chunk->getTiles()) > 0 or count($chunk->getSavableEntities()) > 0) and $chunk->isGenerated()){
|
||||
$this->provider->saveChunk($chunk);
|
||||
$chunk->setChanged(false);
|
||||
$this->timings->syncChunkSaveTimer->startTiming();
|
||||
try{
|
||||
foreach($this->chunks as $chunk){
|
||||
if(($chunk->hasChanged() or count($chunk->getTiles()) > 0 or count($chunk->getSavableEntities()) > 0) and $chunk->isGenerated()){
|
||||
$this->provider->saveChunk($chunk);
|
||||
$chunk->setChanged(false);
|
||||
}
|
||||
}
|
||||
}finally{
|
||||
$this->timings->syncChunkSaveTimer->stopTiming();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1423,14 +1452,14 @@ class Level implements ChunkManager, Metadatable{
|
||||
*/
|
||||
public function getBlockAt(int $x, int $y, int $z, bool $cached = true, bool $addToCache = true) : Block{
|
||||
$fullState = 0;
|
||||
$blockHash = null;
|
||||
$relativeBlockHash = null;
|
||||
$chunkHash = Level::chunkHash($x >> 4, $z >> 4);
|
||||
|
||||
if($this->isInWorld($x, $y, $z)){
|
||||
$blockHash = Level::blockHash($x, $y, $z);
|
||||
$relativeBlockHash = Level::chunkBlockHash($x, $y, $z);
|
||||
|
||||
if($cached and isset($this->blockCache[$chunkHash][$blockHash])){
|
||||
return $this->blockCache[$chunkHash][$blockHash];
|
||||
if($cached and isset($this->blockCache[$chunkHash][$relativeBlockHash])){
|
||||
return $this->blockCache[$chunkHash][$relativeBlockHash];
|
||||
}
|
||||
|
||||
$chunk = $this->chunks[$chunkHash] ?? null;
|
||||
@ -1448,8 +1477,8 @@ class Level implements ChunkManager, Metadatable{
|
||||
$block->z = $z;
|
||||
$block->level = $this;
|
||||
|
||||
if($addToCache and $blockHash !== null){
|
||||
$this->blockCache[$chunkHash][$blockHash] = $block;
|
||||
if($addToCache and $relativeBlockHash !== null){
|
||||
$this->blockCache[$chunkHash][$relativeBlockHash] = $block;
|
||||
}
|
||||
|
||||
return $block;
|
||||
@ -1607,19 +1636,19 @@ class Level implements ChunkManager, Metadatable{
|
||||
$block->clearCaches();
|
||||
|
||||
$chunkHash = Level::chunkHash($pos->x >> 4, $pos->z >> 4);
|
||||
$blockHash = Level::blockHash($pos->x, $pos->y, $pos->z);
|
||||
$relativeBlockHash = Level::chunkBlockHash($pos->x, $pos->y, $pos->z);
|
||||
|
||||
unset($this->blockCache[$chunkHash][$blockHash]);
|
||||
unset($this->blockCache[$chunkHash][$relativeBlockHash]);
|
||||
|
||||
if($direct){
|
||||
$this->sendBlocks($this->getChunkPlayers($pos->x >> 4, $pos->z >> 4), [$block], UpdateBlockPacket::FLAG_ALL_PRIORITY);
|
||||
unset($this->chunkCache[$chunkHash], $this->changedBlocks[$chunkHash][$blockHash]);
|
||||
unset($this->chunkCache[$chunkHash], $this->changedBlocks[$chunkHash][$relativeBlockHash]);
|
||||
}else{
|
||||
if(!isset($this->changedBlocks[$chunkHash])){
|
||||
$this->changedBlocks[$chunkHash] = [];
|
||||
}
|
||||
|
||||
$this->changedBlocks[$chunkHash][$blockHash] = $block;
|
||||
$this->changedBlocks[$chunkHash][$relativeBlockHash] = $block;
|
||||
}
|
||||
|
||||
foreach($this->getChunkLoaders($pos->x >> 4, $pos->z >> 4) as $loader){
|
||||
@ -1769,7 +1798,7 @@ class Level implements ChunkManager, Metadatable{
|
||||
if($player !== null){
|
||||
$ev = new BlockBreakEvent($player, $target, $item, $player->isCreative(), $drops, $xpDrop);
|
||||
|
||||
if(($player->isSurvival() and !$target->isBreakable($item)) or $player->isSpectator()){
|
||||
if($target instanceof Air or ($player->isSurvival() and !$target->isBreakable($item)) or $player->isSpectator()){
|
||||
$ev->setCancelled();
|
||||
}elseif($this->checkSpawnProtection($player, $target)){
|
||||
$ev->setCancelled(); //set it to cancelled so plugins can bypass this
|
||||
@ -1868,7 +1897,7 @@ class Level implements ChunkManager, Metadatable{
|
||||
$clickVector = new Vector3(0.0, 0.0, 0.0);
|
||||
}
|
||||
|
||||
if($blockReplace->y >= $this->worldHeight or $blockReplace->y < 0){
|
||||
if(!$this->isInWorld($blockReplace->x, $blockReplace->y, $blockReplace->z)){
|
||||
//TODO: build height limit messages for custom world heights and mcregion cap
|
||||
return false;
|
||||
}
|
||||
@ -2212,14 +2241,14 @@ class Level implements ChunkManager, Metadatable{
|
||||
return;
|
||||
}
|
||||
$chunkHash = Level::chunkHash($x >> 4, $z >> 4);
|
||||
$blockHash = Level::blockHash($x, $y, $z);
|
||||
unset($this->blockCache[$chunkHash][$blockHash]);
|
||||
$relativeBlockHash = Level::chunkBlockHash($x, $y, $z);
|
||||
unset($this->blockCache[$chunkHash][$relativeBlockHash]);
|
||||
$this->getChunk($x >> 4, $z >> 4, true)->setBlockId($x & 0x0f, $y, $z & 0x0f, $id & 0xff);
|
||||
|
||||
if(!isset($this->changedBlocks[$chunkHash])){
|
||||
$this->changedBlocks[$chunkHash] = [];
|
||||
}
|
||||
$this->changedBlocks[$chunkHash][$blockHash] = $v = new Vector3($x, $y, $z);
|
||||
$this->changedBlocks[$chunkHash][$relativeBlockHash] = $v = new Vector3($x, $y, $z);
|
||||
foreach($this->getChunkLoaders($x >> 4, $z >> 4) as $loader){
|
||||
$loader->onBlockChanged($v);
|
||||
}
|
||||
@ -2251,15 +2280,15 @@ class Level implements ChunkManager, Metadatable{
|
||||
return;
|
||||
}
|
||||
$chunkHash = Level::chunkHash($x >> 4, $z >> 4);
|
||||
$blockHash = Level::blockHash($x, $y, $z);
|
||||
unset($this->blockCache[$chunkHash][$blockHash]);
|
||||
$relativeBlockHash = Level::chunkBlockHash($x, $y, $z);
|
||||
unset($this->blockCache[$chunkHash][$relativeBlockHash]);
|
||||
|
||||
$this->getChunk($x >> 4, $z >> 4, true)->setBlockData($x & 0x0f, $y, $z & 0x0f, $data & 0x0f);
|
||||
|
||||
if(!isset($this->changedBlocks[$chunkHash])){
|
||||
$this->changedBlocks[$chunkHash] = [];
|
||||
}
|
||||
$this->changedBlocks[$chunkHash][$blockHash] = $v = new Vector3($x, $y, $z);
|
||||
$this->changedBlocks[$chunkHash][$relativeBlockHash] = $v = new Vector3($x, $y, $z);
|
||||
foreach($this->getChunkLoaders($x >> 4, $z >> 4) as $loader){
|
||||
$loader->onBlockChanged($v);
|
||||
}
|
||||
@ -2461,9 +2490,9 @@ class Level implements ChunkManager, Metadatable{
|
||||
* @param int $chunkX
|
||||
* @param int $chunkZ
|
||||
* @param Chunk|null $chunk
|
||||
* @param bool $unload
|
||||
* @param bool $deleteEntitiesAndTiles Whether to delete entities and tiles on the old chunk, or transfer them to the new one
|
||||
*/
|
||||
public function setChunk(int $chunkX, int $chunkZ, Chunk $chunk = null, bool $unload = true){
|
||||
public function setChunk(int $chunkX, int $chunkZ, Chunk $chunk = null, bool $deleteEntitiesAndTiles = true){
|
||||
if($chunk === null){
|
||||
return;
|
||||
}
|
||||
@ -2474,7 +2503,16 @@ class Level implements ChunkManager, Metadatable{
|
||||
$chunkHash = Level::chunkHash($chunkX, $chunkZ);
|
||||
$oldChunk = $this->getChunk($chunkX, $chunkZ, false);
|
||||
if($oldChunk !== null and $oldChunk !== $chunk){
|
||||
if($unload){
|
||||
if($deleteEntitiesAndTiles){
|
||||
foreach($oldChunk->getEntities() as $player){
|
||||
if(!($player instanceof Player)){
|
||||
continue;
|
||||
}
|
||||
$chunk->addEntity($player);
|
||||
$oldChunk->removeEntity($player);
|
||||
$player->chunk = $chunk;
|
||||
}
|
||||
//TODO: this causes chunkloaders to receive false "unloaded" notifications
|
||||
$this->unloadChunk($chunkX, $chunkZ, false, false);
|
||||
}else{
|
||||
foreach($oldChunk->getEntities() as $entity){
|
||||
@ -2666,10 +2704,10 @@ class Level implements ChunkManager, Metadatable{
|
||||
*/
|
||||
public function addEntity(Entity $entity){
|
||||
if($entity->isClosed()){
|
||||
throw new \InvalidArgumentException("Attempted to add a garbage closed Entity to Level");
|
||||
throw new \InvalidArgumentException("Attempted to add a garbage closed Entity to world");
|
||||
}
|
||||
if($entity->getLevel() !== $this){
|
||||
throw new LevelException("Invalid Entity level");
|
||||
throw new LevelException("Invalid Entity world");
|
||||
}
|
||||
|
||||
if($entity instanceof Player){
|
||||
@ -2687,7 +2725,7 @@ class Level implements ChunkManager, Metadatable{
|
||||
*/
|
||||
public function removeEntity(Entity $entity){
|
||||
if($entity->getLevel() !== $this){
|
||||
throw new LevelException("Invalid Entity level");
|
||||
throw new LevelException("Invalid Entity world");
|
||||
}
|
||||
|
||||
if($entity instanceof Player){
|
||||
@ -2706,10 +2744,10 @@ class Level implements ChunkManager, Metadatable{
|
||||
*/
|
||||
public function addTile(Tile $tile){
|
||||
if($tile->isClosed()){
|
||||
throw new \InvalidArgumentException("Attempted to add a garbage closed Tile to Level");
|
||||
throw new \InvalidArgumentException("Attempted to add a garbage closed Tile to world");
|
||||
}
|
||||
if($tile->getLevel() !== $this){
|
||||
throw new LevelException("Invalid Tile level");
|
||||
throw new LevelException("Invalid Tile world");
|
||||
}
|
||||
|
||||
$chunkX = $tile->getFloorX() >> 4;
|
||||
@ -2732,7 +2770,7 @@ class Level implements ChunkManager, Metadatable{
|
||||
*/
|
||||
public function removeTile(Tile $tile){
|
||||
if($tile->getLevel() !== $this){
|
||||
throw new LevelException("Invalid Tile level");
|
||||
throw new LevelException("Invalid Tile world");
|
||||
}
|
||||
|
||||
unset($this->tiles[$tile->getId()], $this->updateTiles[$tile->getId()]);
|
||||
@ -2814,6 +2852,7 @@ class Level implements ChunkManager, Metadatable{
|
||||
$loader->onChunkLoaded($chunk);
|
||||
}
|
||||
}else{
|
||||
$this->server->getLogger()->debug("Newly loaded chunk $x $z has no loaders registered, will be unloaded at next available opportunity");
|
||||
$this->unloadChunkRequest($x, $z);
|
||||
}
|
||||
|
||||
@ -2867,7 +2906,12 @@ class Level implements ChunkManager, Metadatable{
|
||||
|
||||
if($trySave and $this->getAutoSave() and $chunk->isGenerated()){
|
||||
if($chunk->hasChanged() or count($chunk->getTiles()) > 0 or count($chunk->getSavableEntities()) > 0){
|
||||
$this->provider->saveChunk($chunk);
|
||||
$this->timings->syncChunkSaveTimer->startTiming();
|
||||
try{
|
||||
$this->provider->saveChunk($chunk);
|
||||
}finally{
|
||||
$this->timings->syncChunkSaveTimer->stopTiming();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -3063,35 +3107,31 @@ class Level implements ChunkManager, Metadatable{
|
||||
if(isset($this->chunkPopulationQueue[$index = Level::chunkHash($x, $z)]) or (count($this->chunkPopulationQueue) >= $this->chunkPopulationQueueSize and !$force)){
|
||||
return false;
|
||||
}
|
||||
for($xx = -1; $xx <= 1; ++$xx){
|
||||
for($zz = -1; $zz <= 1; ++$zz){
|
||||
if(isset($this->chunkPopulationLock[Level::chunkHash($x + $xx, $z + $zz)])){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$chunk = $this->getChunk($x, $z, true);
|
||||
if(!$chunk->isPopulated()){
|
||||
Timings::$populationTimer->startTiming();
|
||||
$populate = true;
|
||||
|
||||
$this->chunkPopulationQueue[$index] = true;
|
||||
for($xx = -1; $xx <= 1; ++$xx){
|
||||
for($zz = -1; $zz <= 1; ++$zz){
|
||||
if(isset($this->chunkPopulationLock[Level::chunkHash($x + $xx, $z + $zz)])){
|
||||
$populate = false;
|
||||
break;
|
||||
}
|
||||
$this->chunkPopulationLock[Level::chunkHash($x + $xx, $z + $zz)] = true;
|
||||
}
|
||||
}
|
||||
|
||||
if($populate){
|
||||
$this->chunkPopulationQueue[$index] = true;
|
||||
for($xx = -1; $xx <= 1; ++$xx){
|
||||
for($zz = -1; $zz <= 1; ++$zz){
|
||||
$this->chunkPopulationLock[Level::chunkHash($x + $xx, $z + $zz)] = true;
|
||||
}
|
||||
}
|
||||
|
||||
$task = new PopulationTask($this, $chunk);
|
||||
$workerId = $this->server->getAsyncPool()->selectWorker();
|
||||
if(!isset($this->generatorRegisteredWorkers[$workerId])){
|
||||
$this->registerGeneratorToWorker($workerId);
|
||||
}
|
||||
$this->server->getAsyncPool()->submitTaskToWorker($task, $workerId);
|
||||
$task = new PopulationTask($this, $chunk);
|
||||
$workerId = $this->server->getAsyncPool()->selectWorker();
|
||||
if(!isset($this->generatorRegisteredWorkers[$workerId])){
|
||||
$this->registerGeneratorToWorker($workerId);
|
||||
}
|
||||
$this->server->getAsyncPool()->submitTaskToWorker($task, $workerId);
|
||||
|
||||
Timings::$populationTimer->stopTiming();
|
||||
return false;
|
||||
|
@ -23,6 +23,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\level;
|
||||
|
||||
use pocketmine\timings\Timings;
|
||||
use pocketmine\timings\TimingsHandler;
|
||||
|
||||
class LevelTimings{
|
||||
@ -62,6 +63,8 @@ class LevelTimings{
|
||||
public $syncChunkLoadEntitiesTimer;
|
||||
/** @var TimingsHandler */
|
||||
public $syncChunkLoadTileEntitiesTimer;
|
||||
/** @var TimingsHandler */
|
||||
public $syncChunkSaveTimer;
|
||||
|
||||
public function __construct(Level $level){
|
||||
$name = $level->getFolderName() . " - ";
|
||||
@ -85,6 +88,9 @@ class LevelTimings{
|
||||
$this->syncChunkLoadEntitiesTimer = new TimingsHandler("** " . $name . "syncChunkLoad - Entities");
|
||||
$this->syncChunkLoadTileEntitiesTimer = new TimingsHandler("** " . $name . "syncChunkLoad - TileEntities");
|
||||
|
||||
Timings::init(); //make sure the timer we want is available
|
||||
$this->syncChunkSaveTimer = new TimingsHandler("** " . $name . "syncChunkSave", Timings::$worldSaveTimer);
|
||||
|
||||
$this->doTick = new TimingsHandler($name . "doTick");
|
||||
}
|
||||
}
|
||||
|
@ -64,7 +64,7 @@ class Position extends Vector3{
|
||||
*/
|
||||
public function getLevel(){
|
||||
if($this->level !== null and $this->level->isClosed()){
|
||||
MainLogger::getLogger()->debug("Position was holding a reference to an unloaded Level");
|
||||
MainLogger::getLogger()->debug("Position was holding a reference to an unloaded world");
|
||||
$this->level = null;
|
||||
}
|
||||
|
||||
@ -82,7 +82,7 @@ class Position extends Vector3{
|
||||
*/
|
||||
public function setLevel(Level $level = null){
|
||||
if($level !== null and $level->isClosed()){
|
||||
throw new \InvalidArgumentException("Specified level has been unloaded and cannot be used");
|
||||
throw new \InvalidArgumentException("Specified world has been unloaded and cannot be used");
|
||||
}
|
||||
|
||||
$this->level = $level;
|
||||
|
@ -74,10 +74,10 @@ class ChunkRequestTask extends AsyncTask{
|
||||
$batch->isEncoded = true;
|
||||
$level->chunkRequestCallback($this->chunkX, $this->chunkZ, $batch);
|
||||
}else{
|
||||
$server->getLogger()->error("Chunk request for level #" . $this->levelId . ", x=" . $this->chunkX . ", z=" . $this->chunkZ . " doesn't have any result data");
|
||||
$server->getLogger()->error("Chunk request for world #" . $this->levelId . ", x=" . $this->chunkX . ", z=" . $this->chunkZ . " doesn't have any result data");
|
||||
}
|
||||
}else{
|
||||
$server->getLogger()->debug("Dropped chunk task due to level not loaded");
|
||||
$server->getLogger()->debug("Dropped chunk task due to world not loaded");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -311,6 +311,7 @@ class LevelDB extends BaseLevelProvider{
|
||||
$lightPopulated = true;
|
||||
|
||||
$chunkVersion = ord($this->db->get($index . self::TAG_VERSION));
|
||||
$hasBeenUpgraded = $chunkVersion < self::CURRENT_LEVEL_CHUNK_VERSION;
|
||||
|
||||
$binaryStream = new BinaryStream();
|
||||
|
||||
@ -326,6 +327,9 @@ class LevelDB extends BaseLevelProvider{
|
||||
|
||||
$binaryStream->setBuffer($data, 0);
|
||||
$subChunkVersion = $binaryStream->getByte();
|
||||
if($subChunkVersion < self::CURRENT_LEVEL_SUBCHUNK_VERSION){
|
||||
$hasBeenUpgraded = true;
|
||||
}
|
||||
|
||||
switch($subChunkVersion){
|
||||
case 0:
|
||||
@ -334,6 +338,7 @@ class LevelDB extends BaseLevelProvider{
|
||||
if($chunkVersion < 4){
|
||||
$blockSkyLight = $binaryStream->get(2048);
|
||||
$blockLight = $binaryStream->get(2048);
|
||||
$hasBeenUpgraded = true; //drop saved light
|
||||
}else{
|
||||
//Mojang didn't bother changing the subchunk version when they stopped saving sky light -_-
|
||||
$blockSkyLight = "";
|
||||
@ -453,6 +458,7 @@ class LevelDB extends BaseLevelProvider{
|
||||
$chunk->setGenerated(true);
|
||||
$chunk->setPopulated(true);
|
||||
$chunk->setLightPopulated($lightPopulated);
|
||||
$chunk->setChanged($hasBeenUpgraded); //trigger rewriting chunk to disk if it was converted from an older format
|
||||
|
||||
return $chunk;
|
||||
}
|
||||
|
@ -46,6 +46,7 @@ use function str_pad;
|
||||
use function stream_set_read_buffer;
|
||||
use function stream_set_write_buffer;
|
||||
use function strlen;
|
||||
use function substr;
|
||||
use function time;
|
||||
use function touch;
|
||||
use function unpack;
|
||||
@ -123,9 +124,6 @@ class RegionLoader{
|
||||
*/
|
||||
public function readChunk(int $x, int $z) : ?string{
|
||||
$index = self::getChunkOffset($x, $z);
|
||||
if($index < 0 or $index >= 4096){
|
||||
throw new \InvalidArgumentException("Invalid chunk position in region, expected x/z in range 0-31, got x=$x, z=$z");
|
||||
}
|
||||
|
||||
$this->lastUsed = time();
|
||||
|
||||
@ -134,39 +132,57 @@ class RegionLoader{
|
||||
}
|
||||
|
||||
fseek($this->filePointer, $this->locationTable[$index][0] << 12);
|
||||
$length = Binary::readInt(fread($this->filePointer, 4));
|
||||
$compression = ord(fgetc($this->filePointer));
|
||||
$prefix = fread($this->filePointer, 4);
|
||||
if($prefix === false or strlen($prefix) !== 4){
|
||||
throw new CorruptedChunkException("Corrupted chunk header detected (unexpected end of file reading length prefix)");
|
||||
}
|
||||
$length = Binary::readInt($prefix);
|
||||
|
||||
if($length <= 0 or $length > self::MAX_SECTOR_LENGTH){ //Not yet generated / corrupted
|
||||
if($length >= self::MAX_SECTOR_LENGTH){
|
||||
$this->locationTable[$index][0] = ++$this->lastSector;
|
||||
$this->locationTable[$index][1] = 1;
|
||||
throw new CorruptedChunkException("Corrupted chunk header detected (sector count larger than max)");
|
||||
throw new CorruptedChunkException("Corrupted chunk header detected (sector count $length larger than max " . self::MAX_SECTOR_LENGTH . ")");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
if($length > ($this->locationTable[$index][1] << 12)){ //Invalid chunk, bigger than defined number of sectors
|
||||
MainLogger::getLogger()->error("Corrupted bigger chunk detected (bigger than number of sectors given in header)");
|
||||
MainLogger::getLogger()->error("Chunk x=$x,z=$z length mismatch (expected " . ($this->locationTable[$index][1] << 12) . " sectors, got $length sectors)");
|
||||
$this->locationTable[$index][1] = $length >> 12;
|
||||
$this->writeLocationIndex($index);
|
||||
}elseif($compression !== self::COMPRESSION_ZLIB and $compression !== self::COMPRESSION_GZIP){
|
||||
}
|
||||
|
||||
$chunkData = fread($this->filePointer, $length);
|
||||
if($chunkData === false or strlen($chunkData) !== $length){
|
||||
throw new CorruptedChunkException("Corrupted chunk detected (unexpected end of file reading chunk data)");
|
||||
}
|
||||
|
||||
$compression = ord($chunkData[0]);
|
||||
if($compression !== self::COMPRESSION_ZLIB and $compression !== self::COMPRESSION_GZIP){
|
||||
throw new CorruptedChunkException("Invalid compression type (got $compression, expected " . self::COMPRESSION_ZLIB . " or " . self::COMPRESSION_GZIP . ")");
|
||||
}
|
||||
|
||||
$chunkData = fread($this->filePointer, $length - 1);
|
||||
if($chunkData === false){
|
||||
throw new CorruptedChunkException("Corrupted chunk detected (failed to read chunk data from disk)");
|
||||
|
||||
}
|
||||
|
||||
return $chunkData;
|
||||
return substr($chunkData, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $x
|
||||
* @param int $z
|
||||
*
|
||||
* @return bool
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function chunkExists(int $x, int $z) : bool{
|
||||
return $this->isChunkGenerated(self::getChunkOffset($x, $z));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $x
|
||||
* @param int $z
|
||||
* @param string $chunkData
|
||||
*
|
||||
* @throws ChunkException
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function writeChunk(int $x, int $z, string $chunkData){
|
||||
$this->lastUsed = time();
|
||||
|
||||
@ -196,14 +212,40 @@ class RegionLoader{
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $x
|
||||
* @param int $z
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function removeChunk(int $x, int $z){
|
||||
$index = self::getChunkOffset($x, $z);
|
||||
$this->locationTable[$index][0] = 0;
|
||||
$this->locationTable[$index][1] = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $x
|
||||
* @param int $z
|
||||
*
|
||||
* @return int
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
protected static function getChunkOffset(int $x, int $z) : int{
|
||||
return $x + ($z << 5);
|
||||
if($x < 0 or $x > 31 or $z < 0 or $z > 31){
|
||||
throw new \InvalidArgumentException("Invalid chunk position in region, expected x/z in range 0-31, got x=$x, z=$z");
|
||||
}
|
||||
return $x | ($z << 5);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $offset
|
||||
* @param int &$x
|
||||
* @param int &$z
|
||||
*/
|
||||
protected static function getChunkCoords(int $offset, ?int &$x, ?int &$z) : void{
|
||||
$x = $offset & 0x1f;
|
||||
$z = ($offset >> 5) & 0x1f;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -231,18 +273,23 @@ class RegionLoader{
|
||||
}
|
||||
|
||||
$data = unpack("N*", $headerRaw);
|
||||
/** @var int[] $usedOffsets */
|
||||
$usedOffsets = [];
|
||||
for($i = 0; $i < 1024; ++$i){
|
||||
$index = $data[$i + 1];
|
||||
$offset = $index >> 8;
|
||||
if($offset !== 0){
|
||||
fseek($this->filePointer, $offset << 12);
|
||||
self::getChunkCoords($i, $x, $z);
|
||||
$fileOffset = $offset << 12;
|
||||
|
||||
fseek($this->filePointer, $fileOffset);
|
||||
if(fgetc($this->filePointer) === false){ //Try and read from the location
|
||||
throw new CorruptedRegionException("Region file location offset points to invalid location");
|
||||
throw new CorruptedRegionException("Region file location offset x=$x,z=$z points to invalid file location $fileOffset");
|
||||
}elseif(isset($usedOffsets[$offset])){
|
||||
throw new CorruptedRegionException("Found two chunk offsets pointing to the same location");
|
||||
self::getChunkCoords($usedOffsets[$offset], $existingX, $existingZ);
|
||||
throw new CorruptedRegionException("Found two chunk offsets (chunk1: x=$existingX,z=$existingZ, chunk2: x=$x,z=$z) pointing to the file location $fileOffset");
|
||||
}else{
|
||||
$usedOffsets[$offset] = true;
|
||||
$usedOffsets[$offset] = $i;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -33,10 +33,11 @@ use pocketmine\level\generator\populator\Ore;
|
||||
use pocketmine\level\generator\populator\Populator;
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\utils\Random;
|
||||
use function array_map;
|
||||
use function count;
|
||||
use function explode;
|
||||
use function preg_match;
|
||||
use function preg_match_all;
|
||||
use function str_replace;
|
||||
|
||||
class Flat extends Generator{
|
||||
/** @var Chunk */
|
||||
@ -62,6 +63,11 @@ class Flat extends Generator{
|
||||
return "flat";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $options
|
||||
*
|
||||
* @throws InvalidGeneratorOptionsException
|
||||
*/
|
||||
public function __construct(array $options = []){
|
||||
$this->options = $options;
|
||||
if(isset($this->options["preset"]) and $this->options["preset"] != ""){
|
||||
@ -89,13 +95,28 @@ class Flat extends Generator{
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $layers
|
||||
*
|
||||
* @return int[][]
|
||||
* @throws InvalidGeneratorOptionsException
|
||||
*/
|
||||
public static function parseLayers(string $layers) : array{
|
||||
$result = [];
|
||||
preg_match_all('#^(([0-9]*x|)([0-9]{1,3})(|:[0-9]{0,2}))$#m', str_replace(",", "\n", $layers), $matches);
|
||||
$split = array_map('\trim', explode(',', $layers));
|
||||
$y = 0;
|
||||
foreach($matches[3] as $i => $b){
|
||||
$b = ItemFactory::fromString($b . $matches[4][$i]);
|
||||
$cnt = $matches[2][$i] === "" ? 1 : (int) $matches[2][$i];
|
||||
foreach($split as $line){
|
||||
preg_match('#^(?:(\d+)[x|*])?(.+)$#', $line, $matches);
|
||||
if(count($matches) !== 3){
|
||||
throw new InvalidGeneratorOptionsException("Invalid preset layer \"$line\"");
|
||||
}
|
||||
|
||||
$cnt = $matches[1] !== "" ? (int) $matches[1] : 1;
|
||||
try{
|
||||
$b = ItemFactory::fromString($matches[2])->getBlock();
|
||||
}catch(\InvalidArgumentException $e){
|
||||
throw new InvalidGeneratorOptionsException("Invalid preset layer \"$line\": " . $e->getMessage(), 0, $e);
|
||||
}
|
||||
for($cY = $y, $y += $cnt; $cY < $y; ++$cY){
|
||||
$result[$cY] = [$b->getId(), $b->getDamage()];
|
||||
}
|
||||
@ -113,6 +134,7 @@ class Flat extends Generator{
|
||||
|
||||
$this->floorLevel = count($this->structure);
|
||||
|
||||
//TODO: more error checking
|
||||
preg_match_all('#(([0-9a-z_]{1,})\(?([0-9a-z_ =:]{0,})\)?),?#', $options, $matches);
|
||||
foreach($matches[2] as $i => $option){
|
||||
$params = true;
|
||||
|
@ -58,6 +58,11 @@ abstract class Generator{
|
||||
/** @var Random */
|
||||
protected $random;
|
||||
|
||||
/**
|
||||
* @param array $settings
|
||||
*
|
||||
* @throws InvalidGeneratorOptionsException
|
||||
*/
|
||||
abstract public function __construct(array $settings = []);
|
||||
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user