PocketMine-MP 5.18.2 git-00e39821f06a4b6d728d35053c2621dbb19369ff
BlockStateUpgrader.php
1<?php
2
3/*
4 *
5 * ____ _ _ __ __ _ __ __ ____
6 * | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
7 * | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
8 * | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
9 * |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
10 *
11 * This program is free software: you can redistribute it and/or modify
12 * it under the terms of the GNU Lesser General Public License as published by
13 * the Free Software Foundation, either version 3 of the License, or
14 * (at your option) any later version.
15 *
16 * @author PocketMine Team
17 * @link http://www.pocketmine.net/
18 *
19 *
20 */
21
22declare(strict_types=1);
23
24namespace pocketmine\data\bedrock\block\upgrade;
25
30use function count;
31use function is_string;
32use function ksort;
33use function max;
34use function sprintf;
35use const SORT_NUMERIC;
36
42 private array $upgradeSchemas = [];
43
44 private int $outputVersion = 0;
45
50 public function __construct(array $upgradeSchemas){
51 foreach($upgradeSchemas as $schema){
52 $this->addSchema($schema);
53 }
54 }
55
56 public function addSchema(BlockStateUpgradeSchema $schema) : void{
57 $schemaId = $schema->getSchemaId();
58 $versionId = $schema->getVersionId();
59 if(isset($this->upgradeSchemas[$versionId][$schemaId])){
60 throw new \InvalidArgumentException("Cannot add two schemas with the same schema ID and version ID");
61 }
62
63 //schema ID tells us the order when multiple schemas use the same version ID
64 $this->upgradeSchemas[$versionId][$schemaId] = $schema;
65
66 ksort($this->upgradeSchemas, SORT_NUMERIC);
67 ksort($this->upgradeSchemas[$versionId], SORT_NUMERIC);
68
69 $this->outputVersion = max($this->outputVersion, $schema->getVersionId());
70 }
71
72 public function upgrade(BlockStateData $blockStateData) : BlockStateData{
73 $version = $blockStateData->getVersion();
74 foreach($this->upgradeSchemas as $resultVersion => $schemaList){
75 /*
76 * Sometimes Mojang made changes without bumping the version ID.
77 * A notable example is 0131_1.18.20.27_beta_to_1.18.30.json, which renamed a bunch of blockIDs.
78 * When this happens, all the schemas must be applied even if the version is the same, because the input
79 * version doesn't tell us which of the schemas have already been applied.
80 * If there's only one schema for a version (the norm), we can safely assume it's already been applied if
81 * the version is the same, and skip over it.
82 */
83 if($version > $resultVersion || (count($schemaList) === 1 && $version === $resultVersion)){
84 continue;
85 }
86
87 foreach($schemaList as $schema){
88 $blockStateData = $this->applySchema($schema, $blockStateData);
89 }
90 }
91
92 if($this->outputVersion > $version){
93 //always update the version number of the blockstate, even if it didn't change - this is needed for
94 //external tools
95 $blockStateData = new BlockStateData($blockStateData->getName(), $blockStateData->getStates(), $this->outputVersion);
96 }
97 return $blockStateData;
98 }
99
100 private function applySchema(BlockStateUpgradeSchema $schema, BlockStateData $blockStateData) : BlockStateData{
101 $newStateData = $this->applyStateRemapped($schema, $blockStateData);
102 if($newStateData !== null){
103 return $newStateData;
104 }
105
106 $oldName = $blockStateData->getName();
107 $newName = $schema->renamedIds[$oldName] ?? null;
108
109 $stateChanges = 0;
110 $states = $blockStateData->getStates();
111
112 $states = $this->applyPropertyAdded($schema, $oldName, $states, $stateChanges);
113 $states = $this->applyPropertyRemoved($schema, $oldName, $states, $stateChanges);
114 $states = $this->applyPropertyRenamedOrValueChanged($schema, $oldName, $states, $stateChanges);
115 $states = $this->applyPropertyValueChanged($schema, $oldName, $states, $stateChanges);
116
117 if($newName !== null || $stateChanges > 0){
118 return new BlockStateData($newName ?? $oldName, $states, $schema->getVersionId());
119 }
120
121 return $blockStateData;
122 }
123
124 private function applyStateRemapped(BlockStateUpgradeSchema $schema, BlockStateData $blockStateData) : ?BlockStateData{
125 $oldName = $blockStateData->getName();
126 $oldState = $blockStateData->getStates();
127
128 if(isset($schema->remappedStates[$oldName])){
129 foreach($schema->remappedStates[$oldName] as $remap){
130 if(count($remap->oldState) > count($oldState)){
131 //match criteria has more requirements than we have state properties
132 continue; //try next state
133 }
134 foreach(Utils::stringifyKeys($remap->oldState) as $k => $v){
135 if(!isset($oldState[$k]) || !$oldState[$k]->equals($v)){
136 continue 2; //try next state
137 }
138 }
139
140 if(is_string($remap->newName)){
141 $newName = $remap->newName;
142 }else{
143 $flattenedValue = $oldState[$remap->newName->flattenedProperty] ?? null;
144 if($flattenedValue instanceof StringTag){
145 $embedValue = $remap->newName->flattenedValueRemaps[$flattenedValue->getValue()] ?? $flattenedValue->getValue();
146 $newName = sprintf("%s%s%s", $remap->newName->prefix, $embedValue, $remap->newName->suffix);
147 unset($oldState[$remap->newName->flattenedProperty]);
148 }else{
149 //flattened property is not a TAG_String, so this transformation is not applicable
150 continue;
151 }
152 }
153
154 $newState = $remap->newState;
155 foreach($remap->copiedState as $stateName){
156 if(isset($oldState[$stateName])){
157 $newState[$stateName] = $oldState[$stateName];
158 }
159 }
160
161 return new BlockStateData($newName, $newState, $schema->getVersionId());
162 }
163 }
164
165 return null;
166 }
167
175 private function applyPropertyAdded(BlockStateUpgradeSchema $schema, string $oldName, array $states, int &$stateChanges) : array{
176 if(isset($schema->addedProperties[$oldName])){
177 foreach(Utils::stringifyKeys($schema->addedProperties[$oldName]) as $propertyName => $value){
178 if(!isset($states[$propertyName])){
179 $stateChanges++;
180 $states[$propertyName] = $value;
181 }
182 }
183 }
184
185 return $states;
186 }
187
195 private function applyPropertyRemoved(BlockStateUpgradeSchema $schema, string $oldName, array $states, int &$stateChanges) : array{
196 if(isset($schema->removedProperties[$oldName])){
197 foreach($schema->removedProperties[$oldName] as $propertyName){
198 if(isset($states[$propertyName])){
199 $stateChanges++;
200 unset($states[$propertyName]);
201 }
202 }
203 }
204
205 return $states;
206 }
207
208 private function locateNewPropertyValue(BlockStateUpgradeSchema $schema, string $oldName, string $oldPropertyName, Tag $oldValue) : Tag{
209 if(isset($schema->remappedPropertyValues[$oldName][$oldPropertyName])){
210 foreach($schema->remappedPropertyValues[$oldName][$oldPropertyName] as $mappedPair){
211 if($mappedPair->old->equals($oldValue)){
212 return $mappedPair->new;
213 }
214 }
215 }
216
217 return $oldValue;
218 }
219
227 private function applyPropertyRenamedOrValueChanged(BlockStateUpgradeSchema $schema, string $oldName, array $states, int &$stateChanges) : array{
228 if(isset($schema->renamedProperties[$oldName])){
229 foreach(Utils::stringifyKeys($schema->renamedProperties[$oldName]) as $oldPropertyName => $newPropertyName){
230 $oldValue = $states[$oldPropertyName] ?? null;
231 if($oldValue !== null){
232 $stateChanges++;
233 unset($states[$oldPropertyName]);
234
235 //If a value remap is needed, we need to do it here, since we won't be able to locate the property
236 //after it's been renamed - value remaps are always indexed by old property name for the sake of
237 //being able to do changes in any order.
238 $states[$newPropertyName] = $this->locateNewPropertyValue($schema, $oldName, $oldPropertyName, $oldValue);
239 }
240 }
241 }
242
243 return $states;
244 }
245
253 private function applyPropertyValueChanged(BlockStateUpgradeSchema $schema, string $oldName, array $states, int &$stateChanges) : array{
254 if(isset($schema->remappedPropertyValues[$oldName])){
255 foreach(Utils::stringifyKeys($schema->remappedPropertyValues[$oldName]) as $oldPropertyName => $remappedValues){
256 $oldValue = $states[$oldPropertyName] ?? null;
257 if($oldValue !== null){
258 $newValue = $this->locateNewPropertyValue($schema, $oldName, $oldPropertyName, $oldValue);
259 if($newValue !== $oldValue){
260 $stateChanges++;
261 $states[$oldPropertyName] = $newValue;
262 }
263 }
264 }
265 }
266
267 return $states;
268 }
269}
equals(self $other)
Definition: EnumTrait.php:106