├── plugin.yml
├── .poggit.yml
├── README.md
├── src
└── muqsit
│ └── aggressiveoptz
│ ├── component
│ ├── defaults
│ │ ├── utils
│ │ │ └── FallingBlockChunkInfo.php
│ │ ├── LiquidFallingOptimizationComponent.php
│ │ ├── BreakVerticallySupportedBlockOptimizationComponent.php
│ │ └── FallingBlockOptimizationComponent.php
│ ├── OptimizationComponent.php
│ ├── OptimizationComponentFactory.php
│ └── OptimizationComponentManager.php
│ ├── helper
│ ├── world
│ │ ├── AggressiveOptzChunkCache.php
│ │ ├── AggressiveOptzWorldCache.php
│ │ └── AggressiveOptzWorldCacheManager.php
│ └── AggressiveOptzHelper.php
│ ├── Loader.php
│ └── AggressiveOptzApi.php
└── resources
└── components.json
/plugin.yml:
--------------------------------------------------------------------------------
1 | name: AggressiveOptz
2 | main: muqsit\aggressiveoptz\Loader
3 | api: 5.0.0
4 | version: 0.0.11
--------------------------------------------------------------------------------
/.poggit.yml:
--------------------------------------------------------------------------------
1 | --- # Poggit-CI Manifest. Open the CI at https://poggit.pmmp.io/ci/Muqsit/AggressiveOptz
2 | build-by-default: true
3 | branches:
4 | - master
5 | projects:
6 | AggressiveOptz:
7 | path: ""
8 |
9 | ...
10 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # AggressiveOptz
2 | Aggressive optimizations that may tamper with vanilla behaviour in an attempt to reduce overall CPU cost.
3 | List of built-in optimizations can be found [here](https://github.com/Muqsit/AggressiveOptz/wiki/Built-in-Optimization-Components).
4 |
--------------------------------------------------------------------------------
/src/muqsit/aggressiveoptz/component/defaults/utils/FallingBlockChunkInfo.php:
--------------------------------------------------------------------------------
1 | */
12 | public array $queued = [];
13 |
14 | public function __construct(){
15 | }
16 | }
--------------------------------------------------------------------------------
/src/muqsit/aggressiveoptz/component/OptimizationComponent.php:
--------------------------------------------------------------------------------
1 | $config
13 | * @return static
14 | */
15 | public static function fromConfig(array $config);
16 |
17 | public function enable(AggressiveOptzApi $api) : void;
18 |
19 | public function disable(AggressiveOptzApi $api) : void;
20 | }
--------------------------------------------------------------------------------
/resources/components.json:
--------------------------------------------------------------------------------
1 | {
2 | "aggressiveoptz:break_vertically_supported_block": {
3 | "enabled": true,
4 | "configuration": {
5 | "blocks": ["carpet"]
6 | }
7 | },
8 | "aggressiveoptz:falling_block": {
9 | "enabled": true,
10 | "configuration": {
11 | "falling_block_queue_size": 16,
12 | "falling_block_max_height": 16,
13 | "falling_block_max_count": 16
14 | }
15 | },
16 | "aggressiveoptz:liquid_falling": {
17 | "enabled": true,
18 | "configuration": {}
19 | }
20 | }
--------------------------------------------------------------------------------
/src/muqsit/aggressiveoptz/helper/world/AggressiveOptzChunkCache.php:
--------------------------------------------------------------------------------
1 | */
10 | private array $cache = [];
11 |
12 | public function set(string $key, mixed $value) : void{
13 | $this->cache[$key] = $value;
14 | }
15 |
16 | public function remove(string $key) : void{
17 | unset($this->cache[$key]);
18 | }
19 |
20 | public function get(string $key, mixed $default = null) : mixed{
21 | return $this->cache[$key] ?? $default;
22 | }
23 | }
--------------------------------------------------------------------------------
/src/muqsit/aggressiveoptz/helper/AggressiveOptzHelper.php:
--------------------------------------------------------------------------------
1 | world_cache_manager = new AggressiveOptzWorldCacheManager();
16 | }
17 |
18 | public function init(AggressiveOptzApi $api) : void{
19 | $this->world_cache_manager->init($api);
20 | }
21 |
22 | public function getWorldCacheManager() : AggressiveOptzWorldCacheManager{
23 | return $this->world_cache_manager;
24 | }
25 | }
--------------------------------------------------------------------------------
/src/muqsit/aggressiveoptz/component/OptimizationComponentFactory.php:
--------------------------------------------------------------------------------
1 | > */
13 | private array $registered = [];
14 |
15 | public function exists(string $identifier) : bool{
16 | return array_key_exists($identifier, $this->registered);
17 | }
18 |
19 | /**
20 | * @param string $identifier
21 | * @param class-string $component
22 | */
23 | public function register(string $identifier, string $component) : void{
24 | if($this->exists($identifier)){
25 | throw new InvalidArgumentException("Tried to override an already existing component with the identifier \"{$identifier}\" ({$this->registered[$identifier]})");
26 | }
27 |
28 | $this->registered[$identifier] = $component;
29 | }
30 |
31 | /**
32 | * @param string $identifier
33 | * @param array $config
34 | * @return OptimizationComponent
35 | */
36 | public function build(string $identifier, array $config) : OptimizationComponent{
37 | return $this->registered[$identifier]::fromConfig($config);
38 | }
39 | }
--------------------------------------------------------------------------------
/src/muqsit/aggressiveoptz/helper/world/AggressiveOptzWorldCache.php:
--------------------------------------------------------------------------------
1 | */
12 | private array $chunks = [];
13 |
14 | /** @var array */
15 | private array $cache = [];
16 |
17 | public function __construct(World $world){
18 | foreach($world->getLoadedChunks() as $chunk_hash => $_){
19 | World::getXZ($chunk_hash, $chunkX, $chunkZ);
20 | $this->onChunkLoad($chunkX, $chunkZ);
21 | }
22 | }
23 |
24 | public function onChunkLoad(int $x, int $z) : void{
25 | $this->chunks[World::chunkHash($x, $z)] = new AggressiveOptzChunkCache();
26 | }
27 |
28 | public function onChunkUnload(int $x, int $z) : void{
29 | unset($this->chunks[World::chunkHash($x, $z)]);
30 | }
31 |
32 | public function getChunk(int $x, int $z) : ?AggressiveOptzChunkCache{
33 | return $this->chunks[World::chunkHash($x, $z)] ?? null;
34 | }
35 |
36 | public function set(string $key, mixed $value) : void{
37 | $this->cache[$key] = $value;
38 | }
39 |
40 | public function remove(string $key) : void{
41 | unset($this->cache[$key]);
42 | }
43 |
44 | public function get(string $key, mixed $default = null) : mixed{
45 | return $this->cache[$key] ?? $default;
46 | }
47 | }
--------------------------------------------------------------------------------
/src/muqsit/aggressiveoptz/Loader.php:
--------------------------------------------------------------------------------
1 | saveResource(self::COMPONENTS_CONFIG_FILE);
18 | $this->api = new AggressiveOptzApi($this);
19 | }
20 |
21 | protected function onEnable() : void{
22 | $this->api->load();
23 |
24 | $contents = Filesystem::fileGetContents($this->getDataFolder() . self::COMPONENTS_CONFIG_FILE);
25 | $this->loadComponentsFromConfig(json_decode($contents, true, 512, JSON_THROW_ON_ERROR));
26 |
27 | $this->api->init();
28 | }
29 |
30 | protected function onDisable() : void{
31 | }
32 |
33 | public function getApi() : AggressiveOptzApi{
34 | return $this->api;
35 | }
36 |
37 | /**
38 | * @param array}> $config
39 | */
40 | public function loadComponentsFromConfig(array $config) : void{
41 | $component_manager = $this->api->getComponentManager();
42 | foreach($config as $identifier => $data){
43 | if(!$this->api->getComponentFactory()->exists($identifier)){
44 | $this->getLogger()->warning("Component {$identifier} is not registered. This configuration entry will be skipped.");
45 | continue;
46 | }
47 | if($data["enabled"]){
48 | $component_manager->enable($identifier, $data["configuration"]);
49 | }
50 | }
51 | }
52 | }
--------------------------------------------------------------------------------
/src/muqsit/aggressiveoptz/component/OptimizationComponentManager.php:
--------------------------------------------------------------------------------
1 | */
19 | private array $enabled = [];
20 |
21 | public function __construct(AggressiveOptzApi $api){
22 | $this->api = $api;
23 | $this->logger = new PrefixedLogger($api->getLogger(), "OC-Manager");
24 | }
25 |
26 | public function getLogger() : Logger{
27 | return $this->logger;
28 | }
29 |
30 | public function isEnabled(string $identifier) : bool{
31 | return array_key_exists($identifier, $this->enabled);
32 | }
33 |
34 | /**
35 | * @param string $identifier
36 | * @param array $config
37 | */
38 | public function enable(string $identifier, array $config) : void{
39 | if($this->isEnabled($identifier)){
40 | throw new InvalidArgumentException("Tried to enable an already enabled component: {$identifier}");
41 | }
42 |
43 | $this->enabled[$identifier] = $this->api->getComponentFactory()->build($identifier, $config);
44 | $this->enabled[$identifier]->enable($this->api);
45 | $this->logger->debug("Enabled component: {$identifier}");
46 | }
47 |
48 | public function disable(string $identifier) : void{
49 | if(!$this->isEnabled($identifier)){
50 | throw new InvalidArgumentException("Tried to disable an already disabled component: {$identifier}");
51 | }
52 |
53 | $this->enabled[$identifier]->disable($this->api);
54 | unset($this->enabled[$identifier]);
55 | $this->logger->debug("Disabled component: {$identifier}");
56 | }
57 | }
--------------------------------------------------------------------------------
/src/muqsit/aggressiveoptz/component/defaults/LiquidFallingOptimizationComponent.php:
--------------------------------------------------------------------------------
1 | unregister !== null){
32 | throw new LogicException("Tried to register event handler twice");
33 | }
34 |
35 | $liquids = [];
36 | foreach(RuntimeBlockStateRegistry::getInstance()->getAllKnownStates() as $block){
37 | if($block instanceof Liquid && $block->isFalling() && $block->getDecay() === 0){
38 | $liquids[$block->getStateId()] = true;
39 | }
40 | }
41 |
42 | $air_id = VanillaBlocks::AIR()->getStateId();
43 | $this->unregister = $api->registerEvent(function(BlockSpreadEvent $event) use($liquids, $air_id) : void{
44 | $new_state = $event->getNewState();
45 | if(!array_key_exists($new_state->getStateId(), $liquids)){
46 | return;
47 | }
48 |
49 | $pos = $new_state->getPosition();
50 | $world = $pos->getWorld();
51 |
52 | /** @var int $x */
53 | $x = $pos->x;
54 | /** @var int $y */
55 | $y = $pos->y;
56 | /** @var int $z */
57 | $z = $pos->z;
58 |
59 | $chunk = $world->getChunk($x >> Chunk::COORD_BIT_SIZE, $z >> Chunk::COORD_BIT_SIZE);
60 | if($chunk === null){
61 | return;
62 | }
63 |
64 | $xc = $x & Chunk::COORD_MASK;
65 | $zc = $z & Chunk::COORD_MASK;
66 | $last_y = null;
67 | while(--$y >= 0){
68 | if($chunk->getBlockStateId($xc, $y, $zc) !== $air_id){
69 | break;
70 | }
71 | $world->setBlockAt($x, $y, $z, $new_state, false);
72 | $last_y = $y;
73 | }
74 |
75 | if($last_y !== null){
76 | $source = $event->getSource();
77 | if($source instanceof Liquid){
78 | $world->scheduleDelayedBlockUpdate(new Vector3($x, $last_y, $z), max(1, $source->tickRate()));
79 | }
80 | }
81 | });
82 | }
83 |
84 | public function disable(AggressiveOptzApi $api) : void{
85 | if($this->unregister === null){
86 | throw new LogicException("Tried to unregister an unregistered event handler");
87 | }
88 |
89 | ($this->unregister)();
90 | $this->unregister = null;
91 | }
92 | }
--------------------------------------------------------------------------------
/src/muqsit/aggressiveoptz/helper/world/AggressiveOptzWorldCacheManager.php:
--------------------------------------------------------------------------------
1 | */
21 | private array $worlds = [];
22 |
23 | /** @var array */
24 | private array $unload_listeners = [];
25 |
26 | public function __construct(){
27 | }
28 |
29 | public function init(AggressiveOptzApi $api) : void{
30 | $api->registerEvent(function(WorldLoadEvent $event) : void{
31 | $this->onWorldLoad($event->getWorld());
32 | }, EventPriority::LOWEST);
33 | $api->registerEvent(function(WorldUnloadEvent $event) : void{
34 | $this->onWorldUnload($event->getWorld());
35 | }, EventPriority::MONITOR);
36 | $api->registerEvent(function(ChunkLoadEvent $event) : void{
37 | $this->onChunkLoad($event->getWorld(), $event->getChunkX(), $event->getChunkZ());
38 | }, EventPriority::LOWEST);
39 | $api->registerEvent(function(ChunkUnloadEvent $event) : void{
40 | $this->onChunkUnload($event->getWorld(), $event->getChunkX(), $event->getChunkZ());
41 | }, EventPriority::MONITOR);
42 |
43 | foreach($api->getServer()->getWorldManager()->getWorlds() as $world){
44 | $this->onWorldLoad($world);
45 | }
46 | }
47 |
48 | private function onWorldLoad(World $world) : void{
49 | $this->worlds[$world->getId()] = new AggressiveOptzWorldCache($world);
50 | }
51 |
52 | private function onWorldUnload(World $world) : void{
53 | $cache = $this->worlds[$id = $world->getId()];
54 | foreach($this->unload_listeners as $listener){
55 | $listener($world, $cache);
56 | }
57 | unset($this->worlds[$id]);
58 | }
59 |
60 | private function onChunkLoad(World $world, int $x, int $z) : void{
61 | $this->worlds[$world->getId()]->onChunkLoad($x, $z);
62 | }
63 |
64 | private function onChunkUnload(World $world, int $x, int $z) : void{
65 | if(array_key_exists($id = $world->getId(), $this->worlds)){ // WorldUnloadEvent is called before ChunkUnloadEvent :(
66 | $this->worlds[$id]->onChunkUnload($x, $z);
67 | }
68 | }
69 |
70 | public function get(World $world) : AggressiveOptzWorldCache{
71 | return $this->worlds[$world->getId()];
72 | }
73 |
74 | /**
75 | * @param Closure(World, AggressiveOptzWorldCache) : void $listener
76 | * @return Closure() : void
77 | */
78 | public function registerUnloadListener(Closure $listener) : Closure{
79 | $this->unload_listeners[$id = spl_object_id($listener)] = $listener;
80 | return function() use($id) : void{
81 | unset($this->unload_listeners[$id]);
82 | };
83 | }
84 | }
--------------------------------------------------------------------------------
/src/muqsit/aggressiveoptz/AggressiveOptzApi.php:
--------------------------------------------------------------------------------
1 | loader = $loader;
35 | $this->helper = new AggressiveOptzHelper();
36 | }
37 |
38 | public function load() : void{
39 | $this->loadComponent();
40 | }
41 |
42 | private function loadComponent() : void{
43 | $prefix = strtolower($this->loader->getName());
44 |
45 | $this->component_factory = new OptimizationComponentFactory();
46 | $this->component_factory->register("{$prefix}:break_vertically_supported_block", BreakVerticallySupportedBlockOptimizationComponent::class);
47 | $this->component_factory->register("{$prefix}:falling_block", FallingBlockOptimizationComponent::class);
48 | $this->component_factory->register("{$prefix}:liquid_falling", LiquidFallingOptimizationComponent::class);
49 |
50 | $this->component_manager = new OptimizationComponentManager($this);
51 | }
52 |
53 | public function init() : void{
54 | $this->helper->init($this);
55 | }
56 |
57 | public function getHelper() : AggressiveOptzHelper{
58 | return $this->helper;
59 | }
60 |
61 | public function getServer() : Server{
62 | return $this->loader->getServer();
63 | }
64 |
65 | public function getScheduler() : TaskScheduler{
66 | return $this->loader->getScheduler();
67 | }
68 |
69 | public function getLogger() : Logger{
70 | return $this->loader->getLogger();
71 | }
72 |
73 | public function getComponentFactory() : OptimizationComponentFactory{
74 | return $this->component_factory;
75 | }
76 |
77 | public function getComponentManager() : OptimizationComponentManager{
78 | return $this->component_manager;
79 | }
80 |
81 | /**
82 | * Registers an event handler and returns a closure which unregisters
83 | * the handler.
84 | *
85 | * @template TEvent of Event
86 | * @param Closure(TEvent) : void $event_handler
87 | * @param int $priority
88 | * @param bool $handleCancelled
89 | * @return Closure() : void
90 | */
91 | public function registerEvent(Closure $event_handler, int $priority = EventPriority::NORMAL, bool $handleCancelled = false) : Closure{
92 | $event_class_instance = (new ReflectionFunction($event_handler))->getParameters()[0]->getType();
93 | if(!($event_class_instance instanceof ReflectionNamedType)){
94 | throw new InvalidArgumentException("Invalid parameter #1 supplied to event handler");
95 | }
96 |
97 | /** @var class-string $event_class */
98 | $event_class = $event_class_instance->getName();
99 | try{
100 | $listener = $this->getServer()->getPluginManager()->registerEvent($event_class, $event_handler, $priority, $this->loader, $handleCancelled);
101 | }catch(ReflectionException $e){
102 | throw new RuntimeException($e->getMessage(), $e->getCode(), $e);
103 | }
104 |
105 | return static function() use($event_class, $listener) : void{
106 | HandlerListManager::global()->getListFor($event_class)->unregister($listener);
107 | };
108 | }
109 | }
--------------------------------------------------------------------------------
/src/muqsit/aggressiveoptz/component/defaults/BreakVerticallySupportedBlockOptimizationComponent.php:
--------------------------------------------------------------------------------
1 | parse($block_string) ?? throw new InvalidArgumentException("Invalid block {$block_string}");
31 | $block = $item->getBlock();
32 | $block->getTypeId() !== BlockTypeIds::AIR || throw new InvalidArgumentException("Invalid block {$block_string}");
33 | $blocks[] = $block;
34 | }
35 | return new self($blocks);
36 | }
37 |
38 | /** @var array */
39 | readonly private array $blocks;
40 |
41 | /** @var Closure */
42 | private array $unregisters = [];
43 |
44 | /**
45 | * @param list $blocks
46 | */
47 | public function __construct(array $blocks){
48 | $block_ids = [];
49 | foreach($blocks as $block){
50 | $block_ids[$block->getTypeId()] = true;
51 | }
52 | $this->blocks = $block_ids;
53 | }
54 |
55 | public function enable(AggressiveOptzApi $api) : void{
56 | if(count($this->unregisters) > 0){
57 | throw new LogicException("Tried to register event handlers twice");
58 | }
59 | $this->unregisters = [
60 | $api->registerEvent(function(BlockUpdateEvent $event) : void{
61 | $block = $event->getBlock();
62 | $type_id = $block->getTypeId();
63 | if(!isset($this->blocks[$type_id])){
64 | return;
65 | }
66 | if(!$block->getSide(Facing::DOWN)->canBeReplaced()){
67 | return;
68 | }
69 | $pos = $block->getPosition();
70 |
71 | /** @var list $unsupported_blocks */
72 | $unsupported_blocks = [];
73 | for($y = $pos->y; $y < World::Y_MAX; ++$y){
74 | $unsupported_block = $pos->world->getBlockAt($pos->x, $y, $pos->z);
75 | if($unsupported_block->getTypeId() !== $type_id){
76 | break;
77 | }
78 | $unsupported_blocks[] = $unsupported_block;
79 | }
80 | if(count($unsupported_blocks) === 0){
81 | return;
82 | }
83 | $event->cancel();
84 |
85 | $air_block = VanillaBlocks::AIR();
86 | $item = VanillaItems::AIR();
87 | $drops = [];
88 | foreach($unsupported_blocks as $unsupported_block){
89 | array_push($drops, ...$unsupported_block->getDrops($item));
90 | $pos = $unsupported_block->getPosition();
91 | $pos->world->setBlockAt($pos->x, $pos->y, $pos->z, $air_block, false);
92 | }
93 |
94 | $source = $block->getPosition()->add(0.5, 0.5, 0.5);
95 | foreach($this->compressItems($drops) as $item){
96 | $pos->world->dropItem($source, $item);
97 | }
98 | })
99 | ];
100 | }
101 |
102 | /**
103 | * @param list- $items
104 | * @return list
-
105 | */
106 | private function compressItems(array $items) : array{
107 | /** @var array> $buckets */
108 | $buckets = []; // put items of same Item::getStateId() in one bucket to reduce Item::equals() calls
109 | while(($item = array_pop($items)) !== null){
110 | $state_id = $item->getStateId();
111 | if(!isset($buckets[$state_id])){
112 | $buckets[$state_id][] = $item;
113 | continue;
114 | }
115 | foreach($buckets[$state_id] as $entry){
116 | if($entry->equals($item)){
117 | $entry->setCount($entry->getCount() + $item->getCount());
118 | }
119 | }
120 | }
121 | // flatten buckets
122 | $result = [];
123 | foreach($buckets as $bucket){
124 | array_push($result, ...$bucket);
125 | }
126 | return $result;
127 | }
128 |
129 | public function disable(AggressiveOptzApi $api) : void{
130 | if(count($this->unregisters) === 0){
131 | throw new LogicException("Tried to unregister an unregistered event handler");
132 | }
133 |
134 | foreach($this->unregisters as $unregister){
135 | $unregister();
136 | }
137 | $this->unregisters = [];
138 | }
139 | }
--------------------------------------------------------------------------------
/src/muqsit/aggressiveoptz/component/defaults/FallingBlockOptimizationComponent.php:
--------------------------------------------------------------------------------
1 | */
43 | private array $unregisters = [];
44 |
45 | /** @var array */
46 | private array $entity_spawn_chunks = [];
47 |
48 | public function __construct(int $falling_block_queue_size, int $falling_block_max_height, int $falling_block_max_count){
49 | if($falling_block_queue_size < 0){
50 | throw new InvalidArgumentException("Falling block queue size cannot be negative");
51 | }
52 | $this->falling_block_queue_size = $falling_block_queue_size;
53 |
54 | if($falling_block_max_height < 0){
55 | throw new InvalidArgumentException("Falling block queue size cannot be negative");
56 | }
57 | if($falling_block_queue_size > $falling_block_max_height){
58 | throw new InvalidArgumentException("Falling block queue size cannot be greater than falling block max height");
59 | }
60 | $this->falling_block_max_height = $falling_block_max_height >= World::Y_MAX ? -1 : $falling_block_max_height;
61 |
62 | $this->falling_block_max_count = $falling_block_max_count;
63 | }
64 |
65 | private function getChunkInfo(AggressiveOptzChunkCache $chunk) : FallingBlockChunkInfo{
66 | $info = $chunk->get(self::CACHE_KEY_FALLING_BLOCK_INFO);
67 | if($info === null){
68 | $chunk->set(self::CACHE_KEY_FALLING_BLOCK_INFO, $info = new FallingBlockChunkInfo());
69 | }
70 | return $info;
71 | }
72 |
73 | private function canBeReplaced(int $state_id) : bool{
74 | static $cache = [];
75 | return $cache[$state_id] ??= RuntimeBlockStateRegistry::getInstance()->fromStateId($state_id)->canBeReplaced();
76 | }
77 |
78 | public function enable(AggressiveOptzApi $api) : void{
79 | if(count($this->unregisters) > 0){
80 | throw new LogicException("Tried to register event handlers twice");
81 | }
82 |
83 | $world_cache_manager = $api->getHelper()->getWorldCacheManager();
84 | $registry = RuntimeBlockStateRegistry::getInstance();
85 | $this->unregisters = [
86 | $api->registerEvent(function(EntitySpawnEvent $event) use($world_cache_manager) : void{
87 | $entity = $event->getEntity();
88 | if(!($entity instanceof FallingBlock) || $entity->isClosed() || $entity->isFlaggedForDespawn()){
89 | return;
90 | }
91 |
92 | $real_pos = $entity->getPosition();
93 | $world = $real_pos->getWorld();
94 |
95 | $chunk = $world_cache_manager->get($world)->getChunk($chunkX = $real_pos->getFloorX() >> Chunk::COORD_BIT_SIZE, $chunkZ = $real_pos->getFloorZ() >> Chunk::COORD_BIT_SIZE);
96 | if($chunk !== null){
97 | $this->entity_spawn_chunks[$entity->getId()] = World::chunkHash($chunkX, $chunkZ);
98 | $info = $this->getChunkInfo($chunk);
99 | $count = ++$info->entity_count;
100 | }else{
101 | $count = 1;
102 | }
103 |
104 | $motion = $entity->getMotion();
105 | if($motion->x != 0.0 || $motion->z != 0.0){
106 | // not moved exclusively by gravitation
107 | return;
108 | }
109 |
110 | $iterator = new SubChunkExplorer($world);
111 | $pos = $real_pos->add(-$entity->size->getWidth() / 2, $entity->size->getHeight(), -$entity->size->getWidth() / 2)->floor();
112 |
113 | /** @var int $x */
114 | $x = $pos->x;
115 | /** @var int $y */
116 | $y = $pos->y;
117 | /** @var int $z */
118 | $z = $pos->z;
119 |
120 | $xc = $x & Chunk::COORD_MASK;
121 | $zc = $z & Chunk::COORD_MASK;
122 |
123 | if($count >= $this->falling_block_queue_size){
124 | while($y > 0){
125 | if($iterator->moveTo($x, $y, $z) === SubChunkExplorerStatus::INVALID){
126 | break;
127 | }
128 |
129 | assert($iterator->currentSubChunk !== null);
130 | if(!$this->canBeReplaced($iterator->currentSubChunk->getBlockStateId($xc, $y & Chunk::COORD_MASK, $zc))){
131 | $entity->teleport(new Vector3($real_pos->x, $y + 1 + ($entity->size->getHeight() / 2), $real_pos->z));
132 | $entity->setMotion($motion);
133 | break;
134 | }
135 | --$y;
136 | }
137 | }elseif($this->falling_block_max_height !== -1){
138 | $begin = $y;
139 | while($y > 0){
140 | if($iterator->moveTo($x, $y, $z) === SubChunkExplorerStatus::INVALID){
141 | break;
142 | }
143 |
144 | assert($iterator->currentSubChunk !== null);
145 | if(!$this->canBeReplaced($iterator->currentSubChunk->getBlockStateId($xc, $y & Chunk::COORD_MASK, $zc))){
146 | break;
147 | }
148 |
149 | --$y;
150 | }
151 | if($begin - $y >= $this->falling_block_max_height){
152 | $entity->teleport(new Vector3($real_pos->x, $y + 1 + ($entity->size->getHeight() / 2), $real_pos->z));
153 | $entity->setMotion($motion);
154 | }
155 | }
156 | }),
157 |
158 | $api->registerEvent(function(EntityDespawnEvent $event) use($world_cache_manager) : void{
159 | $entity = $event->getEntity();
160 | if(!array_key_exists($id = $entity->getId(), $this->entity_spawn_chunks)){
161 | return;
162 | }
163 |
164 | World::getXZ($this->entity_spawn_chunks[$id], $chunkX, $chunkZ);
165 | unset($this->entity_spawn_chunks[$id]);
166 | $chunk = $world_cache_manager->get($world = $entity->getWorld())->getChunk($chunkX, $chunkZ);
167 |
168 | if($chunk === null){
169 | return;
170 | }
171 |
172 | $info = $this->getChunkInfo($chunk);
173 | --$info->entity_count;
174 | if(!$world->isChunkLoaded($chunkX, $chunkZ) || ($hash = array_key_first($info->queued)) === null){
175 | return;
176 | }
177 |
178 | /** @var int $hash */
179 | unset($info->queued[$hash]);
180 |
181 | World::getBlockXYZ($hash, $x, $y, $z);
182 | $block = $world->getBlockAt($x, $y, $z);
183 | if($block instanceof Fallable){
184 | ($ev = new BlockUpdateEvent($block))->call();
185 | if(!$ev->isCancelled()){
186 | $block->onNearbyBlockChange();
187 | }
188 | }
189 | }),
190 |
191 | $api->registerEvent(function(BlockUpdateEvent $event) use($world_cache_manager, $registry) : void{
192 | $block = $event->getBlock();
193 | if(!($block instanceof Fallable)){
194 | return;
195 | }
196 |
197 | $pos = $block->getPosition();
198 | $world = $pos->getWorld();
199 |
200 | /** @var int $x */
201 | $x = $pos->x;
202 | /** @var int $y */
203 | $y = $pos->y;
204 | /** @var int $z */
205 | $z = $pos->z;
206 |
207 | $chunkX = $x >> Chunk::COORD_BIT_SIZE;
208 | $chunkZ = $z >> Chunk::COORD_BIT_SIZE;
209 | $chunk = $world->getChunk($chunkX, $chunkZ);
210 | if($chunk === null){
211 | return;
212 | }
213 |
214 | $fb_chunk = $world_cache_manager->get($world)->getChunk($chunkX, $chunkZ);
215 | if($fb_chunk === null){
216 | return;
217 | }
218 |
219 | $info = $this->getChunkInfo($fb_chunk);
220 |
221 | if($y > World::Y_MIN + 1 && $world->getBlockAt($x, $y - 1, $z) instanceof Air){
222 | $block_state_id = $block->getStateId();
223 | $explorer = new SubChunkExplorer($world);
224 | $cx = $x & Chunk::COORD_MASK;
225 | $cz = $z & Chunk::COORD_MASK;
226 | $depth = 1;
227 | $cursor = $y - 1;
228 | $last_block_id = null;
229 | while(
230 | $explorer->moveTo($x, $cursor, $z) !== SubChunkExplorerStatus::INVALID &&
231 | $this->canBeReplaced($last_block_id = $explorer->currentSubChunk->getBlockStateId($cx, $cursor & Chunk::COORD_MASK, $cz))
232 | ){
233 | ++$depth;
234 | --$cursor;
235 | }
236 | if($last_block_id === null || !$registry->fromStateId($last_block_id)->hasEntityCollision()){
237 | $cursor = $y + 1;
238 | $tower_height = 0;
239 | while(
240 | $explorer->moveTo($x, $cursor, $z) !== SubChunkExplorerStatus::INVALID &&
241 | $explorer->currentSubChunk->getBlockStateId($cx, $cursor & Chunk::COORD_MASK, $cz) === $block_state_id)
242 | {
243 | ++$tower_height;
244 | ++$cursor;
245 | }
246 | if($tower_height > $depth){
247 | for($i = 1; $i < $depth; $i++){
248 | $world->setBlockAt($x, $y - $i, $z, $block, false);
249 | }
250 | $block_air = VanillaBlocks::AIR();
251 | for($i = ($tower_height - $depth) + 2; $i <= $tower_height; $i++){
252 | $world->setBlockAt($x, $y + $i, $z, $block_air, false);
253 | }
254 | $event->cancel();
255 | $explorer->invalidate();
256 | return;
257 | }
258 | }
259 | $explorer->invalidate();
260 | }
261 |
262 | if($info->entity_count >= $this->falling_block_max_count){
263 | $event->cancel();
264 | $info->queued[World::blockHash($x, $y, $z)] = null;
265 | return;
266 | }
267 |
268 | unset($info->queued[World::blockHash($x, $y, $z)]);
269 | }),
270 |
271 | $world_cache_manager->registerUnloadListener(function(World $world, AggressiveOptzWorldCache $cache) : void{
272 | foreach($world->getEntities() as $entity){
273 | unset($this->entity_spawn_chunks[$entity->getId()]);
274 | }
275 | })
276 | ];
277 | }
278 |
279 | public function disable(AggressiveOptzApi $api) : void{
280 | if(count($this->unregisters) === 0){
281 | throw new LogicException("Tried to unregister an unregistered event handler");
282 | }
283 |
284 | foreach($this->unregisters as $unregister){
285 | $unregister();
286 | }
287 | $this->unregisters = [];
288 | }
289 | }
--------------------------------------------------------------------------------