JSON-based living upgrade definitions

Since they're just data, it's okay for them to be data driven. Also makes
it easier for outside sources (ie: a wiki) to parse the data to keep it
up to date.
This commit is contained in:
Nicholas Ignoffo 2018-09-06 20:20:49 -07:00
parent 2ca458aaea
commit 59142c2a9c
8 changed files with 258 additions and 21 deletions

View file

@ -1,22 +1,31 @@
package com.wayoftime.bloodmagic.core;
import com.google.common.collect.Maps;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.wayoftime.bloodmagic.BloodMagic;
import com.wayoftime.bloodmagic.core.living.LivingUpgrade;
import com.wayoftime.bloodmagic.core.util.ResourceUtil;
import net.minecraft.item.Item;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.event.RegistryEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import javax.annotation.Nonnull;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.Map;
import java.util.stream.Collectors;
/*
* TODO - See checklist
* - [-] Upgrades (Names pulled from 2.0 class names)
* - [ ] Arrow Protect
* - [ ] Arrow Shot
* - [ ] Critical Strike
* - [-] Arrow Protect
* - [-] Arrow Shot
* - [-] Critical Strike
* - [ ] Digging
* - [ ] Elytra
* - This will wait for Forge to add the ability to make them properly. I'm not adding that hacky shit back in.
@ -63,20 +72,37 @@ import java.util.Map;
@Mod.EventBusSubscriber(modid = BloodMagic.MODID)
public class RegistrarBloodMagicLivingArmor {
private static final Map<String, Path> DEFINITIONS = ResourceUtil.gatherResources("/data", "living_armor", p -> FilenameUtils.getExtension(p.toFile().getName()).equals("json")).stream().collect(Collectors.toMap(key -> FilenameUtils.getBaseName(key.toFile().getName()), value -> value));
private static final Gson GSON = new GsonBuilder().serializeNulls().create();
public static final Map<ResourceLocation, LivingUpgrade> UPGRADES = Maps.newHashMap();
public static final LivingUpgrade JUMP = new LivingUpgrade(new ResourceLocation(BloodMagic.MODID, "jump"), levels -> {
levels.add(new LivingUpgrade.UpgradeLevel(10, 1));
levels.add(new LivingUpgrade.UpgradeLevel(20, 5));
levels.add(new LivingUpgrade.UpgradeLevel(30, 25));
levels.add(new LivingUpgrade.UpgradeLevel(40, 125));
});
public static final LivingUpgrade UPGRADE_ARROW_PROTECT = parseDefinition("arrow_protect");
public static final LivingUpgrade UPGRADE_ARROW_SHOT = parseDefinition("arrow_shot");
public static final LivingUpgrade UPGRADE_CRITICAL_STRIKE = parseDefinition("critical_strike");
public static final LivingUpgrade UPGRADE_JUMP = parseDefinition("jump");
@SubscribeEvent
public static void registerUpgrades(RegistryEvent.Register<Item> event) {
addUpgrade(JUMP);
addUpgrade(UPGRADE_ARROW_PROTECT);
addUpgrade(UPGRADE_ARROW_SHOT);
addUpgrade(UPGRADE_CRITICAL_STRIKE);
addUpgrade(UPGRADE_JUMP);
}
private static void addUpgrade(LivingUpgrade upgrade) {
UPGRADES.put(upgrade.getKey(), upgrade);
}
@Nonnull
public static LivingUpgrade parseDefinition(String fileName) {
Path path = DEFINITIONS.get(fileName);
if (path == null)
return LivingUpgrade.DUMMY;
try {
return GSON.fromJson(IOUtils.toString(path.toUri(), StandardCharsets.UTF_8), LivingUpgrade.class);
} catch (Exception e) {
e.printStackTrace();
return LivingUpgrade.DUMMY;
}
}
}

View file

@ -17,11 +17,11 @@ public class LivingStatusWatcher {
return;
EntityPlayer player = (EntityPlayer) event.getEntity();
LivingStats stats = LivingUtil.applyNewExperience(player, RegistrarBloodMagicLivingArmor.JUMP, 1);
LivingStats stats = LivingUtil.applyNewExperience(player, RegistrarBloodMagicLivingArmor.UPGRADE_JUMP, 1);
if (stats == null)
return;
int level = stats.getLevel(RegistrarBloodMagicLivingArmor.JUMP.getKey());
int level = stats.getLevel(RegistrarBloodMagicLivingArmor.UPGRADE_JUMP.getKey());
player.motionY += 0.05 * level;
if (level >= 3) {

View file

@ -4,45 +4,67 @@ import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import com.google.gson.*;
import com.google.gson.annotations.JsonAdapter;
import com.google.gson.annotations.SerializedName;
import com.google.gson.reflect.TypeToken;
import net.minecraft.entity.ai.attributes.AttributeModifier;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.util.DamageSource;
import net.minecraft.util.NonNullList;
import net.minecraft.util.ResourceLocation;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.lang.reflect.Type;
import java.util.*;
import java.util.function.Consumer;
@JsonAdapter(LivingUpgrade.Deserializer.class)
public class LivingUpgrade {
public static final LivingUpgrade DUMMY = new LivingUpgrade(new ResourceLocation("dummy"), levels -> levels.add(new Level(0, 0)));
private final ResourceLocation key;
private final Set<ResourceLocation> incompatible;
private final TreeMap<Integer, Integer> experienceToLevel;
private final Map<Integer, Integer> levelToCost;
private final boolean isNegative;
private final Map<String, Bonus> bonuses;
private boolean isNegative;
private String unlocalizedName = null;
private IAttributeProvider attributeProvider;
private IArmorProvider armorProvider;
public LivingUpgrade(ResourceLocation key, Consumer<List<UpgradeLevel>> experienceMapper /* xp needed -> level */, boolean isNegative) {
public LivingUpgrade(ResourceLocation key, Consumer<List<Level>> experienceMapper) {
this.key = key;
this.incompatible = Sets.newHashSet();
this.experienceToLevel = Maps.newTreeMap();
this.levelToCost = Maps.newHashMap();
this.isNegative = isNegative;
this.bonuses = Maps.newHashMap();
List<UpgradeLevel> levels = Lists.newArrayList();
List<Level> levels = Lists.newArrayList();
experienceMapper.accept(levels);
for (int i = 0; i < levels.size(); i++) {
UpgradeLevel level = levels.get(i);
Level level = levels.get(i);
experienceToLevel.put(level.experienceNeeded, i + 1);
levelToCost.put(i + 1, level.upgradeCost);
}
}
public LivingUpgrade(ResourceLocation key, Consumer<List<UpgradeLevel>> experienceMapper /* xp needed -> level */) {
this(key, experienceMapper, false);
public LivingUpgrade withBonusSet(String id, Consumer<List<Number>> modifiers) {
List<Number> values = NonNullList.create();
modifiers.accept(values);
if (values.size() != levelToCost.size())
throw new RuntimeException("Bonus size and level size must be the same.");
bonuses.put(id, new Bonus(id, values));
return this;
}
@Nonnull
public Number getBonusValue(String id, int level) {
return bonuses.getOrDefault(id, Bonus.DEFAULT).modifiers.get(level);
}
public LivingUpgrade withAttributeProvider(IAttributeProvider attributeProvider) {
@ -100,6 +122,11 @@ public class LivingUpgrade {
return key;
}
public LivingUpgrade asDowngrade() {
this.isNegative = true;
return this;
}
@Override
public String toString() {
return key.toString();
@ -113,13 +140,45 @@ public class LivingUpgrade {
double getProtection(EntityPlayer player, DamageSource source, int level);
}
public static class UpgradeLevel {
public static class Level {
@SerializedName("xp")
private final int experienceNeeded;
@SerializedName("cost")
private final int upgradeCost;
public UpgradeLevel(int experienceNeeded, int upgradeCost) {
public Level(int experienceNeeded, int upgradeCost) {
this.experienceNeeded = experienceNeeded;
this.upgradeCost = upgradeCost;
}
}
public static class Bonus {
private static final Bonus DEFAULT = new Bonus("null", Collections.emptyList());
private final String id;
private final List<Number> modifiers;
public Bonus(String id, List<Number> modifiers) {
this.id = id;
this.modifiers = modifiers;
}
}
public static class Deserializer implements JsonDeserializer<LivingUpgrade> {
@Override
public LivingUpgrade deserialize(JsonElement element, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
JsonObject json = element.getAsJsonObject();
ResourceLocation id = new ResourceLocation(json.getAsJsonPrimitive("id").getAsString());
List<LivingUpgrade.Level> levels = context.deserialize(json.getAsJsonArray("levels"), new TypeToken<List<Level>>(){}.getType());
LivingUpgrade upgrade = new LivingUpgrade(id, upgradeLevels -> upgradeLevels.addAll(levels));
if (json.has("bonuses")) {
Map<String, Number[]> bonuses = context.deserialize(json.getAsJsonObject("bonuses"), new TypeToken<Map<String, Number[]>>(){}.getType());
bonuses.forEach((k, v) -> upgrade.withBonusSet(k, numbers -> Collections.addAll(numbers, v)));
}
return upgrade;
}
}
}

View file

@ -0,0 +1,85 @@
package com.wayoftime.bloodmagic.core.util;
import net.minecraft.client.Minecraft;
import net.minecraft.client.resources.FolderResourcePack;
import net.minecraft.client.resources.IResourcePack;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.fml.relauncher.ReflectionHelper;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
import org.apache.commons.io.IOUtils;
import javax.annotation.Nonnull;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.*;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
public class ResourceUtil {
@Nonnull
public static Set<Path> gatherResources(String home, String following, Predicate<Path> predicate) {
FileSystem fileSystem = null;
try {
URL url = ResourceUtil.class.getResource(home);
if (url != null) {
URI uri = url.toURI();
Path path;
if (uri.getScheme().equals("file")) {
path = Paths.get(ResourceUtil.class.getResource(home + "/" + following).toURI());
} else {
if (!uri.getScheme().equals("jar")) {
BMLog.DEFAULT.error("Unsupported URI scheme {}", uri.getScheme());
return Collections.emptySet();
}
fileSystem = FileSystems.newFileSystem(uri, Collections.emptyMap());
path = fileSystem.getPath(home + "/" + following);
}
return Files.walk(path).filter(predicate).collect(Collectors.toSet());
}
} catch (IOException | URISyntaxException e) {
e.printStackTrace();
} finally {
IOUtils.closeQuietly(fileSystem);
}
return Collections.emptySet();
}
@Nonnull
public static Set<Path> gatherResources(String home, String following) {
return gatherResources(home, following, p -> true);
}
@Nonnull
public static ResourceLocation addContext(ResourceLocation rl, String context) {
return new ResourceLocation(rl.getNamespace(), context + rl.getPath());
}
@SideOnly(Side.CLIENT)
public static void injectDirectoryAsResource(File resourceDir) {
if (!resourceDir.exists() || !resourceDir.isDirectory())
return;
FolderResourcePack resourcePack = new FolderResourcePack(resourceDir);
Field _defaultResourcePacks = ReflectionHelper.findField(Minecraft.class, "field_110449_ao", "defaultResourcePacks");
try {
_defaultResourcePacks.setAccessible(true);
// noinspection unchecked
List<IResourcePack> defaultResourcePacks = (List<IResourcePack>) _defaultResourcePacks.get(Minecraft.getMinecraft());
defaultResourcePacks.add(resourcePack);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}

View file

@ -0,0 +1,29 @@
{
"id": "bloodmagic:arrow_protect",
"levels": [
{ "xp": 30, "cost": 4 },
{ "xp": 200, "cost": 9 },
{ "xp": 400, "cost": 16 },
{ "xp": 800, "cost": 30 },
{ "xp": 1500, "cost": 60 },
{ "xp": 2500, "cost": 90 },
{ "xp": 3500, "cost": 125 },
{ "xp": 5000, "cost": 165 },
{ "xp": 7000, "cost": 210 },
{ "xp": 15000, "cost": 250 }
],
"bonuses": {
"protection": [
0.1,
0.3,
0.4,
0.6,
0.7,
0.75,
0.77,
0.8,
0.83,
0.85
]
}
}

View file

@ -0,0 +1,10 @@
{
"id": "bloodmagic:arrow_shot",
"levels": [
{ "xp": 50, "cost": 20 },
{ "xp": 200, "cost": 50 },
{ "xp": 700, "cost": 90 },
{ "xp": 1500, "cost": 160 },
{ "xp": 3000, "cost": 290 }
]
}

View file

@ -0,0 +1,19 @@
{
"id": "bloodmagic:critical_strike",
"levels": [
{ "xp": 200, "cost": 5 },
{ "xp": 800, "cost": 12 },
{ "xp": 1300, "cost": 22 },
{ "xp": 2500, "cost": 35 },
{ "xp": 3000, "cost": 49 }
],
"bonuses": {
"damage_boost": [
0.1,
0.2,
0.3,
0.4,
0.5
]
}
}

View file

@ -0,0 +1,9 @@
{
"id": "bloodmagic:jump",
"levels": [
{ "xp": 10, "cost": 1 },
{ "xp": 20, "cost": 5 },
{ "xp": 30, "cost": 25 },
{ "xp": 40, "cost": 125 }
]
}