forked from FabricMC/fabric
add flammable block registry, *2ObjectMap.clear to remove Fabric overrides (#75)
This commit is contained in:
parent
004281cdac
commit
a585b4833e
10 changed files with 399 additions and 9 deletions
|
@ -0,0 +1,73 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2016, 2017, 2018 FabricMC
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.fabricmc.fabric.api.registry;
|
||||||
|
|
||||||
|
import net.fabricmc.fabric.api.util.Block2ObjectMap;
|
||||||
|
import net.fabricmc.fabric.impl.registry.FlammableBlockRegistryImpl;
|
||||||
|
import net.minecraft.block.Block;
|
||||||
|
import net.minecraft.block.Blocks;
|
||||||
|
import net.minecraft.tag.Tag;
|
||||||
|
|
||||||
|
public interface FlammableBlockRegistry extends Block2ObjectMap<FlammableBlockRegistry.Entry> {
|
||||||
|
static FlammableBlockRegistry getDefaultInstance() {
|
||||||
|
return getInstance(Blocks.FIRE);
|
||||||
|
}
|
||||||
|
|
||||||
|
static FlammableBlockRegistry getInstance(Block block) {
|
||||||
|
return FlammableBlockRegistryImpl.getInstance(block);
|
||||||
|
}
|
||||||
|
|
||||||
|
default void add(Block block, int burn, int spread) {
|
||||||
|
this.add(block, new Entry(burn, spread));
|
||||||
|
}
|
||||||
|
|
||||||
|
default void add(Tag<Block> tag, int burn, int spread) {
|
||||||
|
this.add(tag, new Entry(burn, spread));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class Entry {
|
||||||
|
private final int burn, spread;
|
||||||
|
|
||||||
|
public Entry(int burn, int spread) {
|
||||||
|
this.burn = burn;
|
||||||
|
this.spread = spread;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getBurnChance() {
|
||||||
|
return burn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSpreadChance() {
|
||||||
|
return spread;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (!(o instanceof Entry)) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
Entry other = (Entry) o;
|
||||||
|
return other.burn == burn && other.spread == spread;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return burn * 11 + spread;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,6 +17,7 @@
|
||||||
package net.fabricmc.fabric.api.util;
|
package net.fabricmc.fabric.api.util;
|
||||||
|
|
||||||
import net.minecraft.block.Block;
|
import net.minecraft.block.Block;
|
||||||
|
import net.minecraft.item.Item;
|
||||||
import net.minecraft.item.ItemProvider;
|
import net.minecraft.item.ItemProvider;
|
||||||
import net.minecraft.tag.Tag;
|
import net.minecraft.tag.Tag;
|
||||||
|
|
||||||
|
@ -26,4 +27,6 @@ public interface Block2ObjectMap<V> {
|
||||||
void add(Tag<Block> tag, V value);
|
void add(Tag<Block> tag, V value);
|
||||||
void remove(Block block);
|
void remove(Block block);
|
||||||
void remove(Tag<Block> tag);
|
void remove(Tag<Block> tag);
|
||||||
|
void clear(Block block);
|
||||||
|
void clear(Tag<Block> tag);
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,4 +26,6 @@ public interface Item2ObjectMap<V> {
|
||||||
void add(Tag<Item> tag, V value);
|
void add(Tag<Item> tag, V value);
|
||||||
void remove(ItemProvider item);
|
void remove(ItemProvider item);
|
||||||
void remove(Tag<Item> tag);
|
void remove(Tag<Item> tag);
|
||||||
|
void clear(ItemProvider item);
|
||||||
|
void clear(Tag<Item> tag);
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,9 @@ import net.minecraft.item.Item;
|
||||||
import net.minecraft.item.ItemProvider;
|
import net.minecraft.item.ItemProvider;
|
||||||
import net.minecraft.tag.Tag;
|
import net.minecraft.tag.Tag;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public class CompostingChanceRegistryImpl implements CompostingChanceRegistry {
|
public class CompostingChanceRegistryImpl implements CompostingChanceRegistry {
|
||||||
@Override
|
@Override
|
||||||
public Float get(ItemProvider item) {
|
public Float get(ItemProvider item) {
|
||||||
|
@ -47,4 +50,14 @@ public class CompostingChanceRegistryImpl implements CompostingChanceRegistry {
|
||||||
public void remove(Tag<Item> tag) {
|
public void remove(Tag<Item> tag) {
|
||||||
throw new UnsupportedOperationException("Tags currently not supported!");
|
throw new UnsupportedOperationException("Tags currently not supported!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clear(ItemProvider item) {
|
||||||
|
throw new UnsupportedOperationException("CompostingChanceRegistry operates directly on the vanilla map - clearing not supported!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clear(Tag<Item> tag) {
|
||||||
|
throw new UnsupportedOperationException("CompostingChanceRegistry operates directly on the vanilla map - clearing not supported!");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2016, 2017, 2018 FabricMC
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.fabricmc.fabric.impl.registry;
|
||||||
|
|
||||||
|
import net.fabricmc.fabric.api.registry.FlammableBlockRegistry;
|
||||||
|
import net.minecraft.block.Block;
|
||||||
|
|
||||||
|
public interface FireBlockHooks {
|
||||||
|
FlammableBlockRegistry.Entry fabric_getVanillaEntry(Block block);
|
||||||
|
}
|
|
@ -0,0 +1,162 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2016, 2017, 2018 FabricMC
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.fabricmc.fabric.impl.registry;
|
||||||
|
|
||||||
|
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||||
|
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
|
||||||
|
import net.fabricmc.fabric.api.registry.FlammableBlockRegistry;
|
||||||
|
import net.fabricmc.fabric.api.resource.IdentifiableResourceReloadListener;
|
||||||
|
import net.fabricmc.fabric.api.resource.ResourceManagerHelper;
|
||||||
|
import net.fabricmc.fabric.api.resource.ResourceReloadListenerKeys;
|
||||||
|
import net.minecraft.block.Block;
|
||||||
|
import net.minecraft.resource.ResourceManager;
|
||||||
|
import net.minecraft.resource.ResourceType;
|
||||||
|
import net.minecraft.tag.Tag;
|
||||||
|
import net.minecraft.util.Identifier;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class FlammableBlockRegistryImpl implements FlammableBlockRegistry, IdentifiableResourceReloadListener {
|
||||||
|
private static final FlammableBlockRegistry.Entry REMOVED = new FlammableBlockRegistry.Entry(0, 0);
|
||||||
|
private static final Map<Block, FlammableBlockRegistryImpl> REGISTRIES = new HashMap<>();
|
||||||
|
private static final Collection<Identifier> RELOAD_DEPS = Collections.singletonList(ResourceReloadListenerKeys.TAGS);
|
||||||
|
private static int idCounter = 0;
|
||||||
|
|
||||||
|
private final Map<Block, FlammableBlockRegistry.Entry> registeredEntriesBlock = new HashMap<>();
|
||||||
|
private final Map<Tag<Block>, FlammableBlockRegistry.Entry> registeredEntriesTag = new HashMap<>();
|
||||||
|
private final Map<Block, FlammableBlockRegistry.Entry> computedEntries = new HashMap<>();
|
||||||
|
private final Object2IntMap<Block> computedBurnChances = new Object2IntOpenHashMap<>();
|
||||||
|
private final Object2IntMap<Block> computedSpreadChances = new Object2IntOpenHashMap<>();
|
||||||
|
private final Identifier id;
|
||||||
|
private final Block key;
|
||||||
|
private boolean tagsPresent = false;
|
||||||
|
|
||||||
|
private FlammableBlockRegistryImpl(Block key) {
|
||||||
|
ResourceManagerHelper.get(ResourceType.DATA).addReloadListener(this);
|
||||||
|
this.id = new Identifier("fabric:private/fire_registry_" + (++idCounter));
|
||||||
|
this.key = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResourceReload(ResourceManager var1) {
|
||||||
|
reload();
|
||||||
|
tagsPresent = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void reload() {
|
||||||
|
computedEntries.clear();
|
||||||
|
// tags take precedence before blocks
|
||||||
|
for (Tag<Block> tag : registeredEntriesTag.keySet()) {
|
||||||
|
FlammableBlockRegistry.Entry entry = registeredEntriesTag.get(tag);
|
||||||
|
for (Block block : tag.values()) {
|
||||||
|
computedEntries.put(block, entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
computedEntries.putAll(registeredEntriesBlock);
|
||||||
|
|
||||||
|
computedBurnChances.clear();
|
||||||
|
computedSpreadChances.clear();
|
||||||
|
|
||||||
|
for (Block block : computedEntries.keySet()) {
|
||||||
|
FlammableBlockRegistry.Entry entry = computedEntries.get(block);
|
||||||
|
computedBurnChances.put(block, entry.getBurnChance());
|
||||||
|
computedSpreadChances.put(block, entry.getSpreadChance());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// User-facing fire registry interface - queries vanilla fire block
|
||||||
|
@Override
|
||||||
|
public Entry get(Block block) {
|
||||||
|
Entry entry = computedEntries.get(block);
|
||||||
|
if (entry != null) {
|
||||||
|
return entry;
|
||||||
|
} else {
|
||||||
|
return ((FireBlockHooks) key).fabric_getVanillaEntry(block);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Entry getFabric(Block block) {
|
||||||
|
return computedEntries.get(block);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void add(Block block, Entry value) {
|
||||||
|
registeredEntriesBlock.put(block, value);
|
||||||
|
|
||||||
|
if (tagsPresent) {
|
||||||
|
reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void add(Tag<Block> tag, Entry value) {
|
||||||
|
registeredEntriesTag.put(tag, value);
|
||||||
|
|
||||||
|
if (tagsPresent) {
|
||||||
|
reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remove(Block block) {
|
||||||
|
add(block, REMOVED);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remove(Tag<Block> tag) {
|
||||||
|
add(tag, REMOVED);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clear(Block block) {
|
||||||
|
registeredEntriesBlock.remove(block);
|
||||||
|
|
||||||
|
if (tagsPresent) {
|
||||||
|
reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clear(Tag<Block> tag) {
|
||||||
|
registeredEntriesTag.remove(tag);
|
||||||
|
|
||||||
|
if (tagsPresent) {
|
||||||
|
reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static FlammableBlockRegistryImpl getInstance(Block block) {
|
||||||
|
if (!(block instanceof FireBlockHooks)) {
|
||||||
|
throw new RuntimeException("Not a hookable fire block: " + block);
|
||||||
|
}
|
||||||
|
|
||||||
|
return REGISTRIES.computeIfAbsent(block, FlammableBlockRegistryImpl::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Identifier getFabricId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<Identifier> getFabricDependencies() {
|
||||||
|
return RELOAD_DEPS;
|
||||||
|
}
|
||||||
|
}
|
|
@ -70,16 +70,18 @@ public class FuelRegistryImpl implements FuelRegistry {
|
||||||
add(tag, 0);
|
add(tag, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void apply(Map<Item, Integer> map) {
|
@Override
|
||||||
for (ItemProvider item : itemCookTimes.keySet()) {
|
public void clear(ItemProvider item) {
|
||||||
int time = itemCookTimes.getInt(item);
|
itemCookTimes.removeInt(item);
|
||||||
if (time <= 0) {
|
}
|
||||||
map.remove(item.getItem());
|
|
||||||
} else {
|
|
||||||
map.put(item.getItem(), time);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clear(Tag<Item> tag) {
|
||||||
|
tagCookTimes.removeInt(tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void apply(Map<Item, Integer> map) {
|
||||||
|
// tags take precedence before blocks
|
||||||
for (Tag<Item> tag : tagCookTimes.keySet()) {
|
for (Tag<Item> tag : tagCookTimes.keySet()) {
|
||||||
int time = tagCookTimes.getInt(tag);
|
int time = tagCookTimes.getInt(tag);
|
||||||
if (time <= 0) {
|
if (time <= 0) {
|
||||||
|
@ -92,5 +94,14 @@ public class FuelRegistryImpl implements FuelRegistry {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (ItemProvider item : itemCookTimes.keySet()) {
|
||||||
|
int time = itemCookTimes.getInt(item);
|
||||||
|
if (time <= 0) {
|
||||||
|
map.remove(item.getItem());
|
||||||
|
} else {
|
||||||
|
map.put(item.getItem(), time);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2016, 2017, 2018 FabricMC
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.fabricmc.fabric.mixin.block;
|
||||||
|
|
||||||
|
import net.fabricmc.fabric.api.registry.FlammableBlockRegistry;
|
||||||
|
import net.fabricmc.fabric.impl.registry.FireBlockHooks;
|
||||||
|
import net.fabricmc.fabric.impl.registry.FlammableBlockRegistryImpl;
|
||||||
|
import net.minecraft.block.Block;
|
||||||
|
import net.minecraft.block.FireBlock;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.Shadow;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||||
|
|
||||||
|
@Mixin(FireBlock.class)
|
||||||
|
public class MixinFireBlock implements FireBlockHooks {
|
||||||
|
private FlammableBlockRegistryImpl fabric_registry;
|
||||||
|
|
||||||
|
@Shadow
|
||||||
|
private int getSpreadChance(Block block_1) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Shadow
|
||||||
|
private int getBurnChance(Block block_1) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(at = @At("RETURN"), method = "<init>")
|
||||||
|
private void afterConstruct(Block.Settings settings, CallbackInfo info) {
|
||||||
|
fabric_registry = FlammableBlockRegistryImpl.getInstance((Block) (Object) this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(at = @At("HEAD"), method = "getBurnChance", cancellable = true)
|
||||||
|
private void getFabricBurnChance(Block block, CallbackInfoReturnable info) {
|
||||||
|
FlammableBlockRegistry.Entry entry = fabric_registry.getFabric(block);
|
||||||
|
if (entry != null) {
|
||||||
|
info.setReturnValue(entry.getBurnChance());
|
||||||
|
info.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(at = @At("HEAD"), method = "getSpreadChance", cancellable = true)
|
||||||
|
private void getFabricSpreadChance(Block block, CallbackInfoReturnable info) {
|
||||||
|
FlammableBlockRegistry.Entry entry = fabric_registry.getFabric(block);
|
||||||
|
if (entry != null) {
|
||||||
|
info.setReturnValue(entry.getSpreadChance());
|
||||||
|
info.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FlammableBlockRegistry.Entry fabric_getVanillaEntry(Block block) {
|
||||||
|
return new FlammableBlockRegistry.Entry(getBurnChance(block), getSpreadChance(block));
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,6 +4,7 @@
|
||||||
"compatibilityLevel": "JAVA_8",
|
"compatibilityLevel": "JAVA_8",
|
||||||
"mixins": [
|
"mixins": [
|
||||||
"block.MixinBlockBuilder",
|
"block.MixinBlockBuilder",
|
||||||
|
"block.MixinFireBlock",
|
||||||
"block.entity.MixinBlockEntity",
|
"block.entity.MixinBlockEntity",
|
||||||
"commands.MixinServerCommandManager",
|
"commands.MixinServerCommandManager",
|
||||||
"container.MixinServerPlayerEntity",
|
"container.MixinServerPlayerEntity",
|
||||||
|
|
29
src/test/java/net/fabricmc/fabric/block/FireMod.java
Normal file
29
src/test/java/net/fabricmc/fabric/block/FireMod.java
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2016, 2017, 2018 FabricMC
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.fabricmc.fabric.block;
|
||||||
|
|
||||||
|
import net.fabricmc.api.ModInitializer;
|
||||||
|
import net.fabricmc.fabric.api.registry.FlammableBlockRegistry;
|
||||||
|
import net.minecraft.block.Blocks;
|
||||||
|
|
||||||
|
public class FireMod implements ModInitializer {
|
||||||
|
@Override
|
||||||
|
public void onInitialize() {
|
||||||
|
FlammableBlockRegistry.getDefaultInstance().add(Blocks.STONE, 100, 100);
|
||||||
|
FlammableBlockRegistry.getDefaultInstance().remove(Blocks.OAK_PLANKS);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue