Skip to content

Commit

Permalink
feat: addiction & boat magnet traits
Browse files Browse the repository at this point in the history
refactor: refactor server storage system
refactor: change tick inject into PlayerEntity format
BREAKING CHANGE: save file format change
  • Loading branch information
Sigmarik committed Jun 28, 2023
1 parent ce49934 commit 9b7aa48
Show file tree
Hide file tree
Showing 6 changed files with 181 additions and 67 deletions.
10 changes: 9 additions & 1 deletion src/main/java/net/sigmarik/abilitymod/AbilityMod.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,21 @@ public class AbilityMod implements DedicatedServerModInitializer {

public static final String TRAIT_BOAT_MAGNET = "boat_magnet";
public static final String TRAIT_CLEAN_COSTUME = "clean_costume";
public static final String TRAIT_QING = "qing";
public static final String TRAIT_ADDICTION = "addiction";

public static final String TRAIT_FAST = "fast";
public static final String TRAIT_STRONG = "strong";
public static final String TRAIT_HATED = "hated";
public static final String TRAIT_HOT_IRON = "hot_iron";

public static final int BOAT_ATTRACTION_DISTANCE = 4;

public static final double BOAT_ATTRACTION_FACTOR = 0.1;

public static final int ADDICTION_START_TIMER = 1800 * 20; // 30 minutes
public static final int ADDICTION_MID_TIMER = 180 * 20; // 3 minutes
public static final int ADDICTION_WARNING_TIMER = 20 * 20;

@Override
public void onInitializeServer() {
LOGGER.info("Initializing AbilityMod.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ private static int printTraits(CommandContext<ServerCommandSource> context) thro
ServerPlayerEntity player = EntityArgumentType.getPlayer(context, "player");

ServerState states = ServerState.getTraitStates(player.server);
Set<String> traits = states.getTraitList(player.getUuid()).getKeys();
Set<String> traits = states.getTraitList(player.getUuid());

if (traits.isEmpty()) {
player.sendMessage(Text.literal(player.getName().getString() + " has no traits."));
Expand Down
84 changes: 55 additions & 29 deletions src/main/java/net/sigmarik/abilitymod/mixin/PlayerEntityMixin.java
Original file line number Diff line number Diff line change
@@ -1,67 +1,93 @@
package net.sigmarik.abilitymod.mixin;

import net.minecraft.block.Blocks;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.damage.DamageSources;
import net.minecraft.entity.effect.StatusEffect;
import net.minecraft.entity.effect.StatusEffectInstance;
import net.minecraft.entity.effect.StatusEffects;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.entity.vehicle.BoatEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.world.LightType;
import net.minecraft.potion.PotionUtil;
import net.minecraft.potion.Potions;
import net.minecraft.util.TypeFilter;
import net.minecraft.util.math.Box;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
import net.minecraft.world.dimension.DimensionTypes;
import net.sigmarik.abilitymod.AbilityMod;
import net.sigmarik.abilitymod.util.ServerState;
import org.joml.Vector3f;
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;

import java.util.function.Predicate;

@Mixin(PlayerEntity.class)
public abstract class PlayerEntityMixin extends LivingEntity {

@Shadow public abstract PlayerInventory getInventory();

protected PlayerEntityMixin(EntityType<? extends LivingEntity> entityType, World world) {
super(entityType, world);
}

@Inject(method = "tick", at = @At("HEAD"))
private void damageOnWater(CallbackInfo ci) {
private void tickFearOfWater() {
if (ServerState.hasTrait((PlayerEntity)(Object)this, AbilityMod.TRAIT_FEAR_OF_WATER) && isWet()) {
damage(getDamageSources().magic(), 3);
}
}

@Inject(method = "tick", at = @At("HEAD"))
private void damageByDaylight(CallbackInfo ci) {
if (ServerState.hasTrait((PlayerEntity) ((LivingEntity) this), AbilityMod.TRAIT_DAMAGED_BY_LIGHT)) {
World world = this.getWorld();
ItemStack helmet = this.getInventory().getArmorStack(3);
long time = world.getTimeOfDay() % 24000;
int lightLevel = world.getLightLevel(LightType.SKY, this.getBlockPos());
int fireResistanceStatusEffect = 12;
int fireChance = 55;
if (!(lightLevel <= 11 || !world.isSkyVisible(this.getBlockPos()) || this.isTouchingWaterOrRain() || this.isInLava() || this.hasStatusEffect(StatusEffect.byRawId(fireResistanceStatusEffect)) || this.getBlockStateAtPos().getBlock().equals(Blocks.COBWEB) || (time >= 12542 && time < 23460) || !world.getRegistryKey().getValue().equals(DimensionTypes.OVERWORLD_ID))) {
if (world.random.nextInt(fireChance) == 0) {
if (helmet != null && helmet.isDamageable()) {
this.getInventory().damageArmor((new DamageSources(world.getRegistryManager())).inFire(), 1, PlayerInventory.HELMET_SLOTS);
} else {
this.setOnFireFromLava();
}
}
private void tickAddiction() {
if (ServerState.hasTrait((PlayerEntity)(Object)this, AbilityMod.TRAIT_ADDICTION)) {
ServerState.tickAddictionTimer((PlayerEntity)(Object)this);

int timer = ServerState.getAddictionTimer((PlayerEntity)(Object)this);

if (timer < AbilityMod.ADDICTION_WARNING_TIMER) {
addStatusEffect(new StatusEffectInstance(StatusEffects.WEAKNESS, timer));
}
if (timer == 0) {
addStatusEffect(new StatusEffectInstance(StatusEffects.WEAKNESS, 20));
addStatusEffect(new StatusEffectInstance(StatusEffects.HUNGER, 20));
addStatusEffect(new StatusEffectInstance(StatusEffects.NAUSEA, 20));
addStatusEffect(new StatusEffectInstance(StatusEffects.DARKNESS, 20));
addStatusEffect(new StatusEffectInstance(StatusEffects.SPEED, 20));
}
}
}

private void tickBoatMagnet() {
if (ServerState.hasTrait((PlayerEntity)(Object)this, AbilityMod.TRAIT_BOAT_MAGNET) && hasVehicle() &&
(getVehicle().getType() == EntityType.BOAT || getVehicle().getType() == EntityType.CHEST_BOAT)) {
Box attractionBox = Box.of(getPos(),
AbilityMod.BOAT_ATTRACTION_DISTANCE,
AbilityMod.BOAT_ATTRACTION_DISTANCE,
AbilityMod.BOAT_ATTRACTION_DISTANCE);

final Predicate<Entity> boatPredicate = boatEntity -> true;

for (Entity entity : getEntityWorld().getEntitiesByClass(BoatEntity.class, attractionBox, boatPredicate)) {
if (entity.hasPassenger(this)) continue;
if (entity.hasVehicle()) continue;
if (entity.getBoundingBox().intersects(getVehicle().getBoundingBox())) continue;

entity.addVelocity(getPos().subtract(entity.getPos()).add(getVelocity())
.multiply(1.0, 0.0, 1.0).multiply(AbilityMod.BOAT_ATTRACTION_FACTOR));
}
}
}

@Inject(method = "tick", at = @At("HEAD"))
private void traitedTick(CallbackInfo ci) {
tickFearOfWater();
tickAddiction();
tickBoatMagnet();
}

@Inject(method = "eatFood", at = @At("RETURN"))
private void poisonOnRotten(World world, ItemStack stack, CallbackInfoReturnable<ItemStack> cir) {
private void traitedEatFood(World world, ItemStack stack, CallbackInfoReturnable<ItemStack> cir) {
if (stack.getItem().equals(Items.ROTTEN_FLESH) &&
ServerState.hasTrait((PlayerEntity)(Object)this, AbilityMod.TRAIT_HARMFUL_ROTTEN_FLESH)) {
if (ServerState.hasTrait((PlayerEntity)(Object)this, AbilityMod.TRAIT_INVERT_EFFECTS)) {
Expand Down
33 changes: 33 additions & 0 deletions src/main/java/net/sigmarik/abilitymod/mixin/PotionItemMixin.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package net.sigmarik.abilitymod.mixin;

import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.item.PotionItem;
import net.minecraft.potion.PotionUtil;
import net.minecraft.potion.Potions;
import net.minecraft.world.World;
import net.sigmarik.abilitymod.AbilityMod;
import net.sigmarik.abilitymod.util.ServerState;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;

@Mixin(PotionItem.class)
public abstract class PotionItemMixin extends Item {

public PotionItemMixin(Settings settings) {
super(settings);
}

@Inject(method = "finishUsing", at = @At("HEAD"))
private void traitedFinishUsing(ItemStack stack, World world, LivingEntity user, CallbackInfoReturnable<ItemStack> cir) {
if (user instanceof PlayerEntity && PotionUtil.getPotion(stack) == Potions.THICK &&
ServerState.hasTrait((PlayerEntity)user, AbilityMod.TRAIT_ADDICTION) &&
ServerState.getAddictionTimer((PlayerEntity)user) < AbilityMod.ADDICTION_MID_TIMER) {
ServerState.setAddictionTimer((PlayerEntity)user, AbilityMod.ADDICTION_MID_TIMER);
}
}
}
116 changes: 81 additions & 35 deletions src/main/java/net/sigmarik/abilitymod/util/ServerState.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,65 +7,82 @@
import net.minecraft.world.PersistentStateManager;
import net.sigmarik.abilitymod.AbilityMod;

import java.util.HashMap;
import java.util.UUID;
import java.util.*;

public class ServerState extends PersistentState {
public HashMap<UUID, NbtCompound> traits = new HashMap<>();
public static class PlayerData {
Set<String> traits = new HashSet<>(Collections.emptySet());
int addictionTimer = AbilityMod.ADDICTION_START_TIMER;
}

public HashMap<UUID, PlayerData> data = new HashMap<>();

@Override
public NbtCompound writeNbt(NbtCompound nbt) {
nbt.put(AbilityMod.MOD_ID + ":player_traits", new NbtCompound());
nbt.put(AbilityMod.MOD_ID + ":player_data", new NbtCompound());

NbtCompound storage = nbt.getCompound(AbilityMod.MOD_ID + ":player_data");

for (UUID playerUUID : data.keySet()) {
NbtCompound cell = new NbtCompound();

NbtCompound storage = nbt.getCompound(AbilityMod.MOD_ID + ":player_traits");
NbtCompound traitList = new NbtCompound();
for (String trait : data.get(playerUUID).traits) {
traitList.putBoolean(trait, true);
}
cell.put("traits", traitList);

for (UUID playerUUID : traits.keySet()) {
storage.put(String.valueOf(playerUUID), traits.get(playerUUID));
cell.putInt("addiction_timer", data.get(playerUUID).addictionTimer);

storage.put(String.valueOf(playerUUID), cell);
}

return nbt;
}

public static ServerState createFromNbt(NbtCompound nbt) {
ServerState states = new ServerState();
states.traits.clear();
ServerState state = new ServerState();
state.data.clear();

NbtCompound playerNbtList = nbt.getCompound(AbilityMod.MOD_ID + ":player_data");

NbtCompound player_data = nbt.getCompound(AbilityMod.MOD_ID + ":player_traits");
for (String playerKey : playerNbtList.getKeys()) {
NbtCompound playerNbt = playerNbtList.getCompound(playerKey);
PlayerData playerData = state.registerPlayer(UUID.fromString(playerKey));

for (String key : player_data.getKeys()) {
states.traits.put(UUID.fromString(key), player_data.getCompound(key));
playerData.traits.addAll(playerNbt.getCompound("traits").getKeys());
playerData.addictionTimer = playerNbt.getInt("addiction_timer");
}

return states;
return state;
}

public NbtCompound getTraitList(UUID playerId) {
if (!traits.containsKey(playerId)) return new NbtCompound();
private PlayerData registerPlayer(UUID playerId) {
if (data.containsKey(playerId)) return data.get(playerId);

PlayerData playerData = new PlayerData();

data.put(playerId, playerData);

return traits.get(playerId);
return data.get(playerId);
}

public void addTrait(UUID playerId, String trait) {
if (!traits.containsKey(playerId)) {
traits.put(playerId, new NbtCompound());
}
public Set<String> getTraitList(UUID playerId) {
if (!data.containsKey(playerId)) registerPlayer(playerId);

traits.get(playerId).putBoolean(trait, true);
return data.get(playerId).traits;
}

public void removeTrait(UUID playerId, String trait) {
if (!traits.containsKey(playerId)) return;
public void addTrait(UUID playerId, String trait) {
if (!data.containsKey(playerId)) registerPlayer(playerId);

if (traits.get(playerId).contains(trait))
traits.get(playerId).remove(trait);
data.get(playerId).traits.add(trait);
}

public void setSilenced(UUID playerId, String trait, boolean silenced) {
if (!traits.containsKey(playerId)) return;
public void removeTrait(UUID playerId, String trait) {
if (!data.containsKey(playerId)) return;

if (traits.get(playerId).contains(trait)) {
traits.get(playerId).putBoolean(trait, silenced);
}
data.get(playerId).traits.remove(trait);
}

public static ServerState getTraitStates(MinecraftServer server) {
Expand All @@ -77,11 +94,40 @@ public static ServerState getTraitStates(MinecraftServer server) {
public static boolean hasTrait(PlayerEntity player, String trait) {
if (player.getServer() == null) return false;

ServerState states = getTraitStates(player.getServer());
ServerState state = getTraitStates(player.getServer());

if (!state.data.containsKey(player.getUuid())) return false;
return state.data.get(player.getUuid()).traits.contains(trait);
}

public static int getAddictionTimer(PlayerEntity player) {
if (player.getServer() == null) return AbilityMod.ADDICTION_START_TIMER;

ServerState state = getTraitStates(player.getServer());

if (!state.data.containsKey(player.getUuid())) state.registerPlayer(player.getUuid());

return state.data.get(player.getUuid()).addictionTimer;
}

public static void tickAddictionTimer(PlayerEntity player) {
if (player.getServer() == null) return;

ServerState state = getTraitStates(player.getServer());

if (!state.data.containsKey(player.getUuid())) state.registerPlayer(player.getUuid());

if (state.data.get(player.getUuid()).addictionTimer > 0)
--state.data.get(player.getUuid()).addictionTimer;
}

public static void setAddictionTimer(PlayerEntity player, int newAddictionTimer) {
if (player.getServer() == null) return;

ServerState state = getTraitStates(player.getServer());

if (!state.data.containsKey(player.getUuid())) state.registerPlayer(player.getUuid());

if (!states.traits.containsKey(player.getUuid())) return false;
NbtCompound playerTraitsNbt = states.traits.get(player.getUuid());
if (!playerTraitsNbt.contains(trait)) return false;
return playerTraitsNbt.getBoolean(trait);
state.data.get(player.getUuid()).addictionTimer = newAddictionTimer;
}
}
3 changes: 2 additions & 1 deletion src/main/resources/abilitymod.mixins.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
"EndermanEntityMixin",
"PlayerEntityMixin",
"TargetPredicateMixin",
"StatusEffectMixin"
"StatusEffectMixin",
"PotionItemMixin"
],
"injectors": {
"defaultRequire": 1
Expand Down

0 comments on commit 9b7aa48

Please sign in to comment.