Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow Memory Cards to Copy Upgrades & Filters #6638

Merged
merged 3 commits into from
Sep 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/main/java/appeng/block/AEBaseEntityBlock.java
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
import appeng.block.networking.CableBusBlock;
import appeng.blockentity.AEBaseBlockEntity;
import appeng.blockentity.AEBaseInvBlockEntity;
import appeng.items.tools.MemoryCardItem;
import appeng.util.InteractionUtil;
import appeng.util.Platform;
import appeng.util.SettingsFrom;
Expand Down Expand Up @@ -217,7 +218,7 @@ public InteractionResult use(BlockState state, Level level, BlockPos pos, Player
blockEntity.importSettings(SettingsFrom.MEMORY_CARD, data, player);
memoryCard.notifyUser(player, MemoryCardMessages.SETTINGS_LOADED);
} else {
memoryCard.notifyUser(player, MemoryCardMessages.INVALID_MACHINE);
MemoryCardItem.importGenericSettingsAndNotify(blockEntity, data, player);
}
}

Expand Down
29 changes: 6 additions & 23 deletions src/main/java/appeng/blockentity/AEBaseBlockEntity.java
Original file line number Diff line number Diff line change
Expand Up @@ -55,16 +55,14 @@
import appeng.api.inventories.ISegmentedInventory;
import appeng.api.inventories.InternalInventory;
import appeng.api.networking.GridHelper;
import appeng.api.util.IConfigurableObject;
import appeng.api.util.IOrientable;
import appeng.block.AEBaseBlock;
import appeng.block.AEBaseEntityBlock;
import appeng.client.render.model.AEModelData;
import appeng.core.AELog;
import appeng.helpers.IConfigInvHost;
import appeng.helpers.ICustomNameObject;
import appeng.helpers.IPriorityHost;
import appeng.hooks.ticking.TickHandler;
import appeng.items.tools.MemoryCardItem;
import appeng.util.CustomNameUtil;
import appeng.util.Platform;
import appeng.util.SettingsFrom;
Expand Down Expand Up @@ -297,17 +295,7 @@ public void exportSettings(SettingsFrom mode, CompoundTag output, @Nullable Play
CustomNameUtil.setCustomName(output, customName);

if (mode == SettingsFrom.MEMORY_CARD) {
if (this instanceof IConfigurableObject configurableObject) {
configurableObject.getConfigManager().writeToNBT(output);
}

if (this instanceof IPriorityHost pHost) {
output.putInt("priority", pHost.getPriority());
}

if (this instanceof IConfigInvHost configInvHost) {
configInvHost.getConfig().writeToChildTag(output, "config");
}
MemoryCardItem.exportGenericSettings(this, output);
}
}

Expand All @@ -319,17 +307,12 @@ public void exportSettings(SettingsFrom mode, CompoundTag output, @Nullable Play
*/
@OverridingMethodsMustInvokeSuper
public void importSettings(SettingsFrom mode, CompoundTag input, @Nullable Player player) {
if (this instanceof IConfigurableObject configurableObject) {
configurableObject.getConfigManager().readFromNBT(input);
}

if (this instanceof IPriorityHost pHost) {
pHost.setPriority(input.getInt("priority"));
var customName = CustomNameUtil.getCustomName(input);
if (customName != null) {
this.customName = customName.getString();
}

if (this instanceof IConfigInvHost configInvHost) {
configInvHost.getConfig().readFromChildTag(input, "config");
}
MemoryCardItem.importGenericSettings(this, input, player);
}

/**
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/appeng/core/localization/GuiText.java
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ public enum GuiText implements LocalizationEnum {
FormationPlane("Formation Plane"),
FromStorage("Available: %s"),
Fuzzy("Fuzzy"),
RestoredGenericSettingUpgrades("upgrades"),
RestoredGenericSettingSettings("settings"),
RestoredGenericSettingConfigInv("config inventory"),
RestoredGenericSettingPriority("priority"),
Gray("Gray"),
Green("Green"),
IOBuses("ME Import/Export Bus"),
Expand Down
4 changes: 3 additions & 1 deletion src/main/java/appeng/core/localization/PlayerMessages.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ public enum PlayerMessages implements LocalizationEnum {
CommunicationError("Error Communicating with Network."),
DeviceNotLinked("Device is not linked."),
DeviceNotPowered("Device is low on power."),
InvalidMachine("Invalid Machine."),
MissingUpgrades("Not enough %s to restore upgrades (missing %d)."),
InvalidMachine("Could not restore configuration for an incompatible device."),
InvalidMachinePartiallyRestored("Partially restored configuration for an incompatible device: %s."),
LoadedSettings("Loaded device configuration from memory card."),
MachineNotPowered("Machine is not powered."),
OutOfRange("Wireless Out Of Range."),
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/appeng/core/localization/Tooltips.java
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,9 @@ private static Component list(List<Component> components, GuiText lastJoiner) {
if (components.size() == 2) {
return components.get(0)
.copy()
.append(" ")
.append(lastJoiner.text())
.append(" ")
.append(components.get(1));
}

Expand Down
234 changes: 218 additions & 16 deletions src/main/java/appeng/items/tools/MemoryCardItem.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,24 @@

package appeng.items.tools;

import java.util.ArrayList;
import java.util.EnumSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Set;

import org.jetbrains.annotations.Nullable;

import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.ChatFormatting;
import net.minecraft.ResourceLocationException;
import net.minecraft.client.resources.language.I18n;
import net.minecraft.core.Registry;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.Tag;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.InteractionResultHolder;
Expand All @@ -40,14 +49,21 @@

import appeng.api.implementations.items.IMemoryCard;
import appeng.api.implementations.items.MemoryCardMessages;
import appeng.api.inventories.InternalInventory;
import appeng.api.upgrades.IUpgradeableObject;
import appeng.api.util.AEColor;
import appeng.api.util.IConfigurableObject;
import appeng.core.AELog;
import appeng.core.localization.GuiText;
import appeng.core.localization.PlayerMessages;
import appeng.core.localization.Tooltips;
import appeng.helpers.IConfigInvHost;
import appeng.helpers.IPriorityHost;
import appeng.hooks.AEToolItem;
import appeng.items.AEBaseItem;
import appeng.util.InteractionUtil;
import appeng.util.Platform;
import appeng.util.inv.PlayerInternalInventory;

public class MemoryCardItem extends AEBaseItem implements IMemoryCard, AEToolItem {

Expand All @@ -59,6 +75,200 @@ public MemoryCardItem(Item.Properties properties) {
super(properties);
}

public static Set<SettingsCategory> exportGenericSettings(Object exportFrom, CompoundTag output) {
var exported = EnumSet.noneOf(SettingsCategory.class);

if (exportFrom instanceof IUpgradeableObject upgradeableObject) {
MemoryCardItem.storeUpgrades(upgradeableObject, output);
exported.add(SettingsCategory.UPGRADES);
}

if (exportFrom instanceof IConfigurableObject configurableObject) {
configurableObject.getConfigManager().writeToNBT(output);
exported.add(SettingsCategory.SETTINGS);
}

if (exportFrom instanceof IPriorityHost pHost) {
output.putInt("priority", pHost.getPriority());
exported.add(SettingsCategory.PRIORITY);
}

if (exportFrom instanceof IConfigInvHost configInvHost) {
configInvHost.getConfig().writeToChildTag(output, "config");
exported.add(SettingsCategory.CONFIG_INV);
}

return exported;
}

public static Set<SettingsCategory> importGenericSettings(Object importTo, CompoundTag input,
@Nullable Player player) {
var imported = EnumSet.noneOf(SettingsCategory.class);

if (player != null && importTo instanceof IUpgradeableObject upgradeableObject) {
if (restoreUpgrades(player, input, upgradeableObject)) {
imported.add(SettingsCategory.UPGRADES);
}
}

if (importTo instanceof IConfigurableObject configurableObject) {
// TODO: 1.20 Make it return true if it read any config at all
configurableObject.getConfigManager().readFromNBT(input);
imported.add(SettingsCategory.SETTINGS);
}

if (importTo instanceof IPriorityHost pHost && input.contains("priority", Tag.TAG_INT)) {
pHost.setPriority(input.getInt("priority"));
imported.add(SettingsCategory.PRIORITY);
}

if (importTo instanceof IConfigInvHost configInvHost && input.contains("config")) {
configInvHost.getConfig().readFromChildTag(input, "config");
imported.add(SettingsCategory.CONFIG_INV);
}

return imported;
}

public static void importGenericSettingsAndNotify(Object importTo, CompoundTag input, @Nullable Player player) {
var imported = importGenericSettings(importTo, input, player);

if (player != null && !player.getCommandSenderWorld().isClientSide()) {
if (imported.isEmpty()) {
player.sendSystemMessage(PlayerMessages.InvalidMachine.text());
} else {
var restored = Tooltips.conjunction(imported.stream().map(SettingsCategory::getLabel).toList());
player.sendSystemMessage(PlayerMessages.InvalidMachinePartiallyRestored.text(restored));
}
}
}

private static void storeUpgrades(IUpgradeableObject upgradeableObject, CompoundTag output) {
// Accumulate upgrades as itemId->count NBT
var desiredUpgradesTag = new CompoundTag();
for (var upgrade : upgradeableObject.getUpgrades()) {
var itemId = Registry.ITEM.getKey(upgrade.getItem());
if (itemId.equals(Registry.ITEM.getDefaultKey())) {
AELog.warn("Cannot save unregistered upgrade to memory card %s", upgrade.getItem());
continue;
}
var key = itemId.toString();
desiredUpgradesTag.putInt(key, desiredUpgradesTag.getInt(key) + upgrade.getCount());
}

output.put("upgrades", desiredUpgradesTag);
}

private static boolean restoreUpgrades(Player player, CompoundTag input, IUpgradeableObject upgradeableObject) {
if (!input.contains("upgrades", Tag.TAG_COMPOUND)) {
return false;
}

var desiredUpgradesTag = input.getCompound("upgrades");
var desiredUpgrades = new IdentityHashMap<Item, Integer>();
for (String itemIdStr : desiredUpgradesTag.getAllKeys()) {
ResourceLocation itemId;
try {
itemId = new ResourceLocation(itemIdStr);
} catch (ResourceLocationException e) {
AELog.warn("Memory card contains invalid item id %s", itemIdStr);
continue;
}

var item = Registry.ITEM.getOptional(itemId).orElse(null);
if (item == null) {
AELog.warn("Memory card contains unknown item id %s", itemId);
continue;
}

int desiredCount = desiredUpgradesTag.getInt(itemIdStr);
if (desiredCount > 0) {
desiredUpgrades.put(item, desiredCount);
}
}

var upgrades = upgradeableObject.getUpgrades();

// In creative mode, just set it exactly as the memory card says
if (player.getAbilities().instabuild) {
// Clear it out first
for (int i = 0; i < upgrades.size(); i++) {
upgrades.setItemDirect(i, ItemStack.EMPTY);
}
for (var entry : desiredUpgrades.entrySet()) {
upgrades.addItems(new ItemStack(entry.getKey(), entry.getValue()));
}
return true;
}

var upgradeSources = new ArrayList<InternalInventory>();
upgradeSources.add(new PlayerInternalInventory(player.getInventory()));

// Search the player for a network tool
var networkTool = NetworkToolItem.findNetworkToolInv(player);
if (networkTool != null) {
upgradeSources.add(networkTool.getInventory());
}

// Move out excess
for (int i = 0; i < upgrades.size(); i++) {
var current = upgrades.getStackInSlot(i);
if (current.isEmpty()) {
continue;
}

var desiredCount = desiredUpgrades.getOrDefault(current.getItem(), 0);
var totalInstalled = upgradeableObject.getInstalledUpgrades(current.getItem());
var toRemove = totalInstalled - desiredCount;
if (toRemove > 0) {
var removed = upgrades.extractItem(i, toRemove, false);

for (var upgradeSource : upgradeSources) {
if (!removed.isEmpty()) {
removed = upgradeSource.addItems(removed);
}
}
if (!removed.isEmpty()) {
player.drop(removed, false);
}
}
}

// Move in what's still missing
for (var entry : desiredUpgrades.entrySet()) {
var missingAmount = entry.getValue() - upgradeableObject.getInstalledUpgrades(entry.getKey());
if (missingAmount > 0) {
var potential = new ItemStack(entry.getKey(), missingAmount);
// Determine how many can we *actually* insert
var overflow = upgrades.addItems(potential, true);
if (!overflow.isEmpty()) {
missingAmount -= overflow.getCount();
}

// Try getting them from the network tool or player inventory
for (var upgradeSource : upgradeSources) {
var cards = upgradeSource.removeItems(missingAmount, potential, null);
if (!cards.isEmpty()) {
overflow = upgrades.addItems(cards);
if (!overflow.isEmpty()) {
player.getInventory().placeItemBackInInventory(overflow);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a fan of this as it silently moves the items around in the inventory afaict.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not great, but it shouldn't happen (tm) since I do simulate first if the upgrade inventory has enough space. And since they usually are quite simple, I hope that doesn't produce errors. This is just here to not void the cards in ANY case.

}
missingAmount -= cards.getCount();
}
if (missingAmount <= 0) {
break;
}
}

if (missingAmount > 0 && !player.level.isClientSide()) {
player.sendSystemMessage(
PlayerMessages.MissingUpgrades.text(entry.getKey().getDescription(), missingAmount));
}
}
}
return true;
}

@Override
@Environment(EnvType.CLIENT)
public void appendHoverText(ItemStack stack, Level level, List<Component> lines,
Expand Down Expand Up @@ -149,22 +359,13 @@ public void notifyUser(Player player, MemoryCardMessages msg) {
}

switch (msg) {
case SETTINGS_CLEARED:
player.sendSystemMessage(PlayerMessages.SettingCleared.text());
break;
case INVALID_MACHINE:
player.sendSystemMessage(PlayerMessages.InvalidMachine.text());
break;
case SETTINGS_LOADED:
player.sendSystemMessage(PlayerMessages.LoadedSettings.text());
break;
case SETTINGS_SAVED:
player.sendSystemMessage(PlayerMessages.SavedSettings.text());
break;
case SETTINGS_RESET:
player.sendSystemMessage(PlayerMessages.ResetSettings.text());
break;
default:
case SETTINGS_CLEARED -> player.sendSystemMessage(PlayerMessages.SettingCleared.text());
case INVALID_MACHINE -> player.sendSystemMessage(PlayerMessages.InvalidMachine.text());
case SETTINGS_LOADED -> player.sendSystemMessage(PlayerMessages.LoadedSettings.text());
case SETTINGS_SAVED -> player.sendSystemMessage(PlayerMessages.SavedSettings.text());
case SETTINGS_RESET -> player.sendSystemMessage(PlayerMessages.ResetSettings.text());
default -> {
}
}
}

Expand Down Expand Up @@ -204,4 +405,5 @@ private void clearCard(Player player, Level level, InteractionHand hand) {
mem.notifyUser(player, MemoryCardMessages.SETTINGS_CLEARED);
player.getItemInHand(hand).setTag(null);
}

}