Fixed certain race buffs and checks not accounting for worn amulets (or failing if wearing an amulet)

This commit is contained in:
Sollace 2023-09-01 19:09:13 +01:00
parent 75a6912459
commit 76a7ecc3bb
No known key found for this signature in database
GPG key ID: E52FACE7B5C773DB
32 changed files with 104 additions and 114 deletions

View file

@ -5,7 +5,6 @@ import java.util.function.Predicate;
import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.entity.Equine; import com.minelittlepony.unicopia.entity.Equine;
import com.minelittlepony.unicopia.entity.MagicImmune; import com.minelittlepony.unicopia.entity.MagicImmune;
import com.minelittlepony.unicopia.entity.player.Pony;
import com.minelittlepony.unicopia.item.enchantment.UEnchantments; import com.minelittlepony.unicopia.item.enchantment.UEnchantments;
import net.minecraft.enchantment.EnchantmentHelper; import net.minecraft.enchantment.EnchantmentHelper;
@ -16,11 +15,13 @@ import net.minecraft.predicate.entity.EntityPredicates;
public interface EquinePredicates { public interface EquinePredicates {
Predicate<Entity> IS_PLAYER = e -> e instanceof PlayerEntity; Predicate<Entity> IS_PLAYER = e -> e instanceof PlayerEntity;
Predicate<Entity> BAT = physicalRaceMatches(Race.BAT::equals);
Predicate<Entity> CHANGELING = physicalRaceMatches(Race.CHANGELING::equals);
Predicate<Entity> RACE_INTERACT_WITH_CLOUDS = raceMatches(Race::canInteractWithClouds); Predicate<Entity> RACE_INTERACT_WITH_CLOUDS = raceMatches(Race::canInteractWithClouds);
Predicate<Entity> PLAYER_EARTH = IS_PLAYER.and(ofRace(Race.EARTH)); Predicate<Entity> PLAYER_EARTH = IS_PLAYER.and(ofRace(Race.EARTH));
Predicate<Entity> PLAYER_BAT = IS_PLAYER.and(ofRace(Race.BAT)).or(physicalRaceMatches(Race.BAT::equals)); Predicate<Entity> PLAYER_BAT = IS_PLAYER.and(BAT);
Predicate<Entity> PLAYER_UNICORN = IS_PLAYER.and(raceMatches(Race::canCast)); Predicate<Entity> PLAYER_UNICORN = IS_PLAYER.and(raceMatches(Race::canCast));
Predicate<Entity> PLAYER_CHANGELING = IS_PLAYER.and(ofRace(Race.CHANGELING)); Predicate<Entity> PLAYER_CHANGELING = IS_PLAYER.and(ofRace(Race.CHANGELING));
Predicate<Entity> PLAYER_PEGASUS = IS_PLAYER.and(e -> ((PlayerEntity)e).getAbilities().creativeMode || RACE_INTERACT_WITH_CLOUDS.test(e)); Predicate<Entity> PLAYER_PEGASUS = IS_PLAYER.and(e -> ((PlayerEntity)e).getAbilities().creativeMode || RACE_INTERACT_WITH_CLOUDS.test(e));
@ -45,10 +46,10 @@ public interface EquinePredicates {
} }
static Predicate<Entity> raceMatches(Predicate<Race> predicate) { static Predicate<Entity> raceMatches(Predicate<Race> predicate) {
return e -> Equine.of(e).map(Equine::getSpecies).filter(predicate).isPresent(); return e -> Equine.of(e).filter(pony -> pony.getCompositeRace().any(predicate)).isPresent();
} }
static Predicate<Entity> physicalRaceMatches(Predicate<Race> predicate) { static Predicate<Entity> physicalRaceMatches(Predicate<Race> predicate) {
return e -> Pony.of(e).map(Pony::getActualSpecies).filter(predicate).isPresent(); return e -> Equine.of(e).filter(pony -> predicate.test(pony.getCompositeRace().physical())).isPresent();
} }
} }

View file

@ -187,6 +187,12 @@ public record Race (boolean canCast, FlightType flightType, boolean canUseEarth,
} }
public record Composite (Race physical, @Nullable Race pseudo) { public record Composite (Race physical, @Nullable Race pseudo) {
public static Composite DEFAULT = new Composite(Race.HUMAN, null);
public Race collapsed() {
return pseudo == null ? physical : pseudo;
}
public boolean includes(Race race) { public boolean includes(Race race) {
return physical == race || pseudo == race; return physical == race || pseudo == race;
} }
@ -194,6 +200,18 @@ public record Race (boolean canCast, FlightType flightType, boolean canUseEarth,
public boolean any(Predicate<Race> test) { public boolean any(Predicate<Race> test) {
return test.test(physical) || (pseudo != null && test.test(pseudo)); return test.test(physical) || (pseudo != null && test.test(pseudo));
} }
public boolean canUseEarth() {
return any(Race::canUseEarth);
}
public boolean canFly() {
return any(Race::canFly);
}
public boolean canCast() {
return any(Race::canCast);
}
} }
} }

View file

@ -103,8 +103,8 @@ public class Unicopia implements ModInitializer {
public interface SidedAccess { public interface SidedAccess {
Optional<Pony> getPony(); Optional<Pony> getPony();
default Race getPlayerSpecies() { default Race.Composite getPlayerSpecies() {
return getPony().map(Pony::getSpecies).orElse(Race.HUMAN); return getPony().map(Pony::getCompositeRace).orElse(Race.Composite.DEFAULT);
} }
} }
} }

View file

@ -66,8 +66,9 @@ public class AbilityDispatcher implements Tickable, NbtSerialisable {
} }
public int getMaxPage() { public int getMaxPage() {
if (maxPage < 0 || prevRace != player.getSpecies()) { Race newRace = player.getCompositeRace().collapsed();
prevRace = player.getSpecies(); if (maxPage < 0 || prevRace != newRace) {
prevRace = newRace;
maxPage = 0; maxPage = 0;
for (AbilitySlot slot : AbilitySlot.values()) { for (AbilitySlot slot : AbilitySlot.values()) {
maxPage = Math.max(maxPage, getStat(slot).getMaxPage() - 1); maxPage = Math.max(maxPage, getStat(slot).getMaxPage() - 1);

View file

@ -127,7 +127,7 @@ public class EarthPonyStompAbility implements Ability<Hit> {
double amount = (1.5F * player.getAttributeInstance(EntityAttributes.GENERIC_ATTACK_DAMAGE).getValue() + heavyness * 0.4) / (float)(dist * 1.3F); double amount = (1.5F * player.getAttributeInstance(EntityAttributes.GENERIC_ATTACK_DAMAGE).getValue() + heavyness * 0.4) / (float)(dist * 1.3F);
if (i instanceof PlayerEntity) { if (i instanceof PlayerEntity) {
Race race = Pony.of((PlayerEntity)i).getSpecies(); Race.Composite race = Pony.of((PlayerEntity)i).getCompositeRace();
if (race.canUseEarth()) { if (race.canUseEarth()) {
amount /= 3; amount /= 3;
} }

View file

@ -45,7 +45,7 @@ public class PegasusFlightToggleAbility implements Ability<Hit> {
Identifier id = Abilities.REGISTRY.getId(this); Identifier id = Abilities.REGISTRY.getId(this);
return new Identifier(id.getNamespace(), "textures/gui/ability/" + id.getPath() return new Identifier(id.getNamespace(), "textures/gui/ability/" + id.getPath()
+ (player.getPhysics().isFlying() ? "_land" : "_takeoff") + (player.getPhysics().isFlying() ? "_land" : "_takeoff")
+ (player.getSpecies() == Race.CHANGELING ? "_changeling" : "") + (player.getObservedSpecies() == Race.CHANGELING ? "_changeling" : "")
+ ".png"); + ".png");
} }

View file

@ -48,13 +48,13 @@ public interface AttractionUtils {
return Pony.of(entity).map(pony -> { return Pony.of(entity).map(pony -> {
double force = 0.75; double force = 0.75;
if (pony.getSpecies().canUseEarth()) { if (pony.getCompositeRace().canUseEarth()) {
force /= 2; force /= 2;
if (pony.asEntity().isSneaking()) { if (pony.asEntity().isSneaking()) {
force /= 6; force /= 6;
} }
} else if (pony.getSpecies().canFly()) { } else if (pony.getCompositeRace().canFly()) {
force *= 2; force *= 2;
} }

View file

@ -138,7 +138,7 @@ public class SiphoningSpell extends AbstractAreaEffectSpell {
if (e instanceof PlayerEntity) { if (e instanceof PlayerEntity) {
Pony player = Pony.of((PlayerEntity)e); Pony player = Pony.of((PlayerEntity)e);
Race race = player.getSpecies(); Race.Composite race = player.getCompositeRace();
if (race.canCast()) { if (race.canCast()) {
dealt /= 2; dealt /= 2;

View file

@ -83,7 +83,7 @@ public class CustomEventCriterion extends AbstractCriterion<CustomEventCriterion
public boolean test(String event, int count, ServerPlayerEntity player) { public boolean test(String event, int count, ServerPlayerEntity player) {
return this.event.equalsIgnoreCase(event) return this.event.equalsIgnoreCase(event)
&& (races.isEmpty() || races.contains(Pony.of(player).getActualSpecies())) && (races.isEmpty() || races.contains(Pony.of(player).getSpecies()))
&& (flying == null || flying == Pony.of(player).getPhysics().isFlying()) && (flying == null || flying == Pony.of(player).getPhysics().isFlying())
&& (repeatCount <= 0 || (count > 0 && count % repeatCount == 0)); && (repeatCount <= 0 || (count > 0 && count % repeatCount == 0));
} }

View file

@ -44,7 +44,7 @@ public class RaceChangeCriterion extends AbstractCriterion<RaceChangeCriterion.C
} }
public boolean test(ServerPlayerEntity player) { public boolean test(ServerPlayerEntity player) {
return Pony.of(player).getActualSpecies() == race; return Pony.of(player).getSpecies() == race;
} }
@Override @Override

View file

@ -77,7 +77,7 @@ public class BaseZapAppleLeavesBlock extends LeavesBlock implements TintedBlock
float delta = super.calcBlockBreakingDelta(state, player, world, pos); float delta = super.calcBlockBreakingDelta(state, player, world, pos);
if (Pony.of(player).getSpecies().canUseEarth()) { if (Pony.of(player).getCompositeRace().canUseEarth()) {
delta *= 50; delta *= 50;
} }

View file

@ -56,7 +56,7 @@ public class ZapAppleLogBlock extends PillarBlock {
float delta = super.calcBlockBreakingDelta(state, player, world, pos); float delta = super.calcBlockBreakingDelta(state, player, world, pos);
if (Pony.of(player).getSpecies().canUseEarth()) { if (Pony.of(player).getCompositeRace().canUseEarth()) {
delta *= 50; delta *= 50;
} }

View file

@ -54,7 +54,7 @@ public class ZapBlock extends Block {
float delta = super.calcBlockBreakingDelta(state, player, world, pos); float delta = super.calcBlockBreakingDelta(state, player, world, pos);
if (Pony.of(player).getSpecies().canUseEarth()) { if (Pony.of(player).getCompositeRace().canUseEarth()) {
delta *= 50; delta *= 50;
} }

View file

@ -1,22 +0,0 @@
package com.minelittlepony.unicopia.client.gui.spellbook;
import java.util.List;
import com.minelittlepony.common.client.gui.Tooltip;
import com.minelittlepony.unicopia.entity.player.Pony;
import net.minecraft.text.Text;
public class ProfileTooltip {
public static Tooltip get(Pony pony) {
return () -> {
return List.of(
Text.literal(String.format("Level %d ", pony.getLevel().get() + 1)).append(pony.getActualSpecies().getDisplayName()).formatted(pony.getSpecies().getAffinity().getColor()),
Text.literal(String.format("Mana: %d%%", (int)(pony.getMagicalReserves().getMana().getPercentFill() * 100))),
Text.literal(String.format("Corruption: %d%%", (int)(pony.getCorruption().getScaled(100)))),
Text.literal(String.format("Experience: %d", (int)(pony.getMagicalReserves().getXp().getPercentFill() * 100))),
Text.literal(String.format("Next level in: %dxp", 100 - (int)(pony.getMagicalReserves().getXp().getPercentFill() * 100)))
);
};
}
}

View file

@ -1,7 +1,10 @@
package com.minelittlepony.unicopia.client.gui.spellbook; package com.minelittlepony.unicopia.client.gui.spellbook;
import java.util.List;
import com.minelittlepony.common.client.gui.IViewRoot; import com.minelittlepony.common.client.gui.IViewRoot;
import com.minelittlepony.common.client.gui.dimension.Bounds; import com.minelittlepony.common.client.gui.dimension.Bounds;
import com.minelittlepony.unicopia.Race;
import com.minelittlepony.unicopia.client.gui.*; import com.minelittlepony.unicopia.client.gui.*;
import com.minelittlepony.unicopia.entity.player.*; import com.minelittlepony.unicopia.entity.player.*;
import com.minelittlepony.unicopia.util.ColorHelper; import com.minelittlepony.unicopia.util.ColorHelper;
@ -35,14 +38,21 @@ public class SpellbookProfilePageContent implements SpellbookChapterList.Content
screen.addDrawable(new SpellbookScreen.ImageButton(x, y, size, size)) screen.addDrawable(new SpellbookScreen.ImageButton(x, y, size, size))
.getStyle() .getStyle()
.setIcon(TribeButton.createSprite(pony.getActualSpecies(), 0, 0, size)) .setIcon(TribeButton.createSprite(pony.getSpecies(), 0, 0, size))
.setTooltip(ProfileTooltip.get(pony)); .setTooltip(() -> List.of(
Text.literal(String.format("Level %d ", pony.getLevel().get() + 1)).append(pony.getSpecies().getDisplayName()).formatted(pony.getSpecies().getAffinity().getColor()),
Text.literal(String.format("Mana: %d%%", (int)(pony.getMagicalReserves().getMana().getPercentFill() * 100))),
Text.literal(String.format("Corruption: %d%%", (int)(pony.getCorruption().getScaled(100)))),
Text.literal(String.format("Experience: %d", (int)(pony.getMagicalReserves().getXp().getPercentFill() * 100))),
Text.literal(String.format("Next level in: %dxp", 100 - (int)(pony.getMagicalReserves().getXp().getPercentFill() * 100)))
));
if (pony.getSpecies() != pony.getActualSpecies()) { Race inherited = pony.getCompositeRace().collapsed();
if (inherited != pony.getSpecies()) {
int halfSize = size / 2; int halfSize = size / 2;
screen.addDrawable(new SpellbookScreen.ImageButton(x + halfSize, y + halfSize, halfSize, halfSize)) screen.addDrawable(new SpellbookScreen.ImageButton(x + halfSize, y + halfSize, halfSize, halfSize))
.getStyle() .getStyle()
.setIcon(TribeButton.createSprite(pony.getSpecies(), 0, 0, halfSize)); .setIcon(TribeButton.createSprite(inherited, 0, 0, halfSize));
} }
} }

View file

@ -87,7 +87,7 @@ class SpeciesCommand {
} }
static int get(ServerCommandSource source, PlayerEntity player, boolean isSelf) { static int get(ServerCommandSource source, PlayerEntity player, boolean isSelf) {
Race spec = Pony.of(player).getActualSpecies(); Race spec = Pony.of(player).getSpecies();
String name = "commands.race.tell."; String name = "commands.race.tell.";
name += isSelf ? "self" : "other"; name += isSelf ? "self" : "other";

View file

@ -20,6 +20,10 @@ public interface Equine<T extends Entity> extends NbtSerialisable, Tickable, Pro
void setSpecies(Race race); void setSpecies(Race race);
default Race.Composite getCompositeRace() {
return new Race.Composite(getSpecies(), null);
}
/** /**
* Called at the beginning of an update cycle. * Called at the beginning of an update cycle.
*/ */

View file

@ -81,7 +81,7 @@ public class RaceChangeStatusEffect extends StatusEffect {
int progression = ticks % (stage.ordinal() * STAGE_DURATION); int progression = ticks % (stage.ordinal() * STAGE_DURATION);
if ((eq instanceof Pony pony ? pony.getActualSpecies() : eq.getSpecies()) == race || !race.isPermitted(entity instanceof PlayerEntity player ? player : null)) { if (eq.getSpecies() == race || !race.isPermitted(entity instanceof PlayerEntity player ? player : null)) {
if (progression == 0 && entity instanceof PlayerEntity player && stage == Stage.CRAWLING) { if (progression == 0 && entity instanceof PlayerEntity player && stage == Stage.CRAWLING) {
player.sendMessage(Stage.INITIAL.getMessage(race), true); player.sendMessage(Stage.INITIAL.getMessage(race), true);
} }

View file

@ -95,13 +95,13 @@ class ManaContainer implements MagicReserves, Tickable, NbtSerialisable, Copyabl
energy.addPercent(-1); energy.addPercent(-1);
} }
if (pony.getSpecies().canFly() && !pony.getPhysics().isFlying()) { if (pony.getCompositeRace().canFly() && !pony.getPhysics().isFlying()) {
exhaustion.multiply(0.8F); exhaustion.multiply(0.8F);
} else { } else {
exhaustion.addPercent(-1); exhaustion.addPercent(-1);
} }
if (!pony.getSpecies().canFly() || !pony.getPhysics().isFlying()) { if (!pony.getCompositeRace().canFly() || !pony.getPhysics().isFlying()) {
if (mana.getPercentFill() < 1 && mana.getShadowFill(1) <= mana.getPercentFill(1)) { if (mana.getPercentFill() < 1 && mana.getShadowFill(1) <= mana.getPercentFill(1)) {
mana.addPercent(MathHelper.clamp(1 + pony.getLevel().get(), 1, 50) / 4F); mana.addPercent(MathHelper.clamp(1 + pony.getLevel().get(), 1, 50) / 4F);
} }

View file

@ -17,9 +17,9 @@ public class PlayerAttributes implements Tickable {
private static final EntityAttributeModifier EARTH_PONY_STRENGTH = private static final EntityAttributeModifier EARTH_PONY_STRENGTH =
new EntityAttributeModifier(UUID.fromString("777a5505-521e-480b-b9d5-6ea54f259564"), "Earth Pony Strength", 0.6, Operation.MULTIPLY_TOTAL); new EntityAttributeModifier(UUID.fromString("777a5505-521e-480b-b9d5-6ea54f259564"), "Earth Pony Strength", 0.6, Operation.MULTIPLY_TOTAL);
private static final EntityAttributeModifier EARTH_PONY_MINING_SPEED = private static final EntityAttributeModifier EARTH_PONY_MINING_SPEED =
new EntityAttributeModifier(UUID.fromString("9fc9e269-152e-0b48-9bd5-564a546e59f2"), "Earth Pony Mining Speed", 0.4, Operation.MULTIPLY_TOTAL); new EntityAttributeModifier(UUID.fromString("9fc9e269-152e-0b48-9bd5-564a546e59f2"), "Earth Pony Mining Speed", 0.5, Operation.MULTIPLY_TOTAL);
private static final EntityAttributeModifier EARTH_PONY_KNOCKBACK_RESISTANCE = private static final EntityAttributeModifier EARTH_PONY_KNOCKBACK_RESISTANCE =
new EntityAttributeModifier(UUID.fromString("79e269a8-03e8-b9d5-5853-e25fdcf6706d"), "Earth Pony Knockback Resistance", 2, Operation.ADDITION); new EntityAttributeModifier(UUID.fromString("79e269a8-03e8-b9d5-5853-e25fdcf6706d"), "Earth Pony Knockback Resistance", 6, Operation.ADDITION);
private static final EntityAttributeModifier PEGASUS_SPEED = private static final EntityAttributeModifier PEGASUS_SPEED =
new EntityAttributeModifier(UUID.fromString("9e2699fc-3b8d-4f71-9d2d-fb92ee19b4f7"), "Pegasus Speed", 0.2, Operation.MULTIPLY_TOTAL); new EntityAttributeModifier(UUID.fromString("9e2699fc-3b8d-4f71-9d2d-fb92ee19b4f7"), "Pegasus Speed", 0.2, Operation.MULTIPLY_TOTAL);
@ -41,15 +41,18 @@ public class PlayerAttributes implements Tickable {
@Override @Override
public void tick() { public void tick() {
PlayerEntity entity = pony.asEntity(); PlayerEntity entity = pony.asEntity();
Race race = pony.getSpecies(); Race.Composite race = pony.getCompositeRace();
toggleAttribute(entity, EntityAttributes.GENERIC_ATTACK_DAMAGE, EARTH_PONY_STRENGTH, race.canUseEarth()); boolean earth = race.canUseEarth();
toggleAttribute(entity, EntityAttributes.GENERIC_KNOCKBACK_RESISTANCE, EARTH_PONY_STRENGTH, race.canUseEarth()); boolean flight = race.canFly();
toggleAttribute(entity, EntityAttributes.GENERIC_KNOCKBACK_RESISTANCE, EARTH_PONY_KNOCKBACK_RESISTANCE, race.canUseEarth() && entity.isSneaking());
toggleAttribute(entity, EntityAttributes.GENERIC_MOVEMENT_SPEED, PEGASUS_SPEED, race.canFly()); toggleAttribute(entity, EntityAttributes.GENERIC_ATTACK_DAMAGE, EARTH_PONY_STRENGTH, earth);
toggleAttribute(entity, EntityAttributes.GENERIC_ATTACK_SPEED, PEGASUS_SPEED, race.canFly()); toggleAttribute(entity, EntityAttributes.GENERIC_KNOCKBACK_RESISTANCE, EARTH_PONY_STRENGTH, earth);
toggleAttribute(entity, UEntityAttributes.EXTENDED_REACH_DISTANCE, PEGASUS_REACH, race.canFly()); toggleAttribute(entity, EntityAttributes.GENERIC_KNOCKBACK_RESISTANCE, EARTH_PONY_KNOCKBACK_RESISTANCE, earth && entity.isSneaking());
toggleAttribute(entity, UEntityAttributes.EXTRA_MINING_SPEED, EARTH_PONY_MINING_SPEED, race.canUseEarth()); toggleAttribute(entity, EntityAttributes.GENERIC_MOVEMENT_SPEED, PEGASUS_SPEED, flight);
toggleAttribute(entity, EntityAttributes.GENERIC_ATTACK_SPEED, PEGASUS_SPEED, flight);
toggleAttribute(entity, UEntityAttributes.EXTENDED_REACH_DISTANCE, PEGASUS_REACH, flight);
toggleAttribute(entity, UEntityAttributes.EXTRA_MINING_SPEED, EARTH_PONY_MINING_SPEED, earth);
} }
private void toggleAttribute(PlayerEntity entity, EntityAttribute attribute, EntityAttributeModifier modifier, boolean enable) { private void toggleAttribute(PlayerEntity entity, EntityAttribute attribute, EntityAttributeModifier modifier, boolean enable) {

View file

@ -353,7 +353,7 @@ public class PlayerPhysics extends EntityPhysics<PlayerEntity> implements Tickab
descentRate = 0; descentRate = 0;
ticksDiving = 0; ticksDiving = 0;
if (Abilities.RAINBOOM.canUse(pony.getActualSpecies()) && entity.isOnGround()) { if (Abilities.RAINBOOM.canUse(pony.getSpecies()) && entity.isOnGround()) {
pony.getMagicalReserves().getCharge().set(0); pony.getMagicalReserves().getCharge().set(0);
} }
@ -683,7 +683,7 @@ public class PlayerPhysics extends EntityPhysics<PlayerEntity> implements Tickab
SoundEmitter.playSoundAt(entity, USounds.AMBIENT_WIND_GUST, SoundCategory.AMBIENT, 3, 1); SoundEmitter.playSoundAt(entity, USounds.AMBIENT_WIND_GUST, SoundCategory.AMBIENT, 3, 1);
} }
float weight = 1 + (EnchantmentHelper.getEquipmentLevel(UEnchantments.HEAVY, entity) * 0.8F) + (pony.getActualSpecies().canUseEarth() ? 1 : 0); float weight = 1 + (EnchantmentHelper.getEquipmentLevel(UEnchantments.HEAVY, entity) * 0.8F) + (pony.getCompositeRace().canUseEarth() ? 1 : 0);
velocity.add(WeatherConditions.getAirflow(entity.getBlockPos(), entity.getWorld()), 0.04F * effectStrength); velocity.add(WeatherConditions.getAirflow(entity.getBlockPos(), entity.getWorld()), 0.04F * effectStrength);
velocity.add(Vec3d.fromPolar( velocity.add(Vec3d.fromPolar(

View file

@ -190,23 +190,11 @@ public class Pony extends Living<PlayerEntity> implements Copyable<Pony>, Update
} }
/** /**
* Gets this player's species as it appears when interacting physically with other players or the world. * Gets this player's inherent species.
* This includes temporary race swaps due to illusions/shape shifting as well as artifacts that merely
* grant the abilities of a race, such as the alicorn amulet.
*
* @deprecated Use {@link Pony#getCompositeRace()} or {@link Pony#getObservedSpecies()}
*/ */
@Deprecated
@Override @Override
public Race getSpecies() { public Race getSpecies() {
if (AmuletSelectors.ALICORN_AMULET.test(entity)) { return Race.fromName(entity.getDataTracker().get(RACE), Race.HUMAN);
return Race.ALICORN;
}
if (AmuletSelectors.UNICORN_AMULET.test(entity)) {
return Race.UNICORN;
}
return getObservedSpecies();
} }
/** /**
@ -219,30 +207,23 @@ public class Pony extends Living<PlayerEntity> implements Copyable<Pony>, Update
.map(AbstractDisguiseSpell::getDisguise) .map(AbstractDisguiseSpell::getDisguise)
.map(EntityAppearance::getAppearance) .map(EntityAppearance::getAppearance)
.flatMap(Pony::of) .flatMap(Pony::of)
.map(Pony::getActualSpecies) .map(Pony::getSpecies)
.orElseGet(this::getActualSpecies); .orElseGet(this::getSpecies);
} }
/** /**
* Gets the composite race that represents what this player is capable of. * Gets the composite race that represents what this player is capable of.
* Physical is the race they appear to have, whilst pseudo is the race who's abilities they have been granted by magical means. * Physical is the race they appear to have, whilst pseudo is the race who's abilities they have been granted by magical means.
*/ */
@Override
public Race.Composite getCompositeRace() { public Race.Composite getCompositeRace() {
Race observed = getObservedSpecies(); return new Race.Composite(getObservedSpecies(),
return new Race.Composite(observed,
AmuletSelectors.UNICORN_AMULET.test(entity) ? Race.UNICORN AmuletSelectors.UNICORN_AMULET.test(entity) ? Race.UNICORN
: AmuletSelectors.ALICORN_AMULET.test(entity) ? Race.ALICORN : AmuletSelectors.ALICORN_AMULET.test(entity) ? Race.ALICORN
: null : null
); );
} }
/**
* Gets the origin species of the player. This excludes any shapeshifting, illusions, or magic.
*/
public Race getActualSpecies() {
return Race.fromName(entity.getDataTracker().get(RACE), Race.HUMAN);
}
@Override @Override
public void setSpecies(Race race) { public void setSpecies(Race race) {
race = race.validate(entity); race = race.validate(entity);
@ -575,7 +556,7 @@ public class Pony extends Living<PlayerEntity> implements Copyable<Pony>, Update
} }
} }
if (entity.hurtTime == 1 && getSpecies().canCast()) { if (entity.hurtTime == 1 && getCompositeRace().physical().canCast()) {
corruption.add(1); corruption.add(1);
setDirty(); setDirty();
} }
@ -586,7 +567,7 @@ public class Pony extends Living<PlayerEntity> implements Copyable<Pony>, Update
public void tick() { public void tick() {
super.tick(); super.tick();
Race currentRace = getActualSpecies(); Race currentRace = getSpecies();
if (!currentRace.isUnset()) { if (!currentRace.isUnset()) {
Race newRace = currentRace.validate(entity); Race newRace = currentRace.validate(entity);
@ -596,16 +577,12 @@ public class Pony extends Living<PlayerEntity> implements Copyable<Pony>, Update
} }
sendCapabilities(); sendCapabilities();
//if (!isClient()) {
// CrystalShardsEntity.infestBlock((ServerWorld)asWorld(), entity.getBlockPos().down());
//}
} }
@Override @Override
public boolean canBeSeenBy(Entity entity) { public boolean canBeSeenBy(Entity entity) {
if (entity instanceof HostileEntity hostile if (entity instanceof HostileEntity hostile
&& getActualSpecies() == Race.BAT && getSpecies() == Race.BAT
&& hostile.getTarget() != this.entity && hostile.getTarget() != this.entity
&& hostile.getAttacker() != this.entity && hostile.getAttacker() != this.entity
&& entity.distanceTo(this.entity) > entity.getWidth()) { && entity.distanceTo(this.entity) > entity.getWidth()) {
@ -659,7 +636,7 @@ public class Pony extends Living<PlayerEntity> implements Copyable<Pony>, Update
&& !cause.isOf(DamageTypes.THORNS) && !cause.isOf(DamageTypes.THORNS)
&& !cause.isOf(DamageTypes.FREEZE)) { && !cause.isOf(DamageTypes.FREEZE)) {
if (getSpecies().canUseEarth() && entity.isSneaking()) { if (getCompositeRace().canUseEarth() && entity.isSneaking()) {
amount /= (cause.isOf(DamageTypes.MOB_PROJECTILE) ? 3 : 2) * (entity.getHealth() < 5 ? 3 : 1); amount /= (cause.isOf(DamageTypes.MOB_PROJECTILE) ? 3 : 2) * (entity.getHealth() < 5 ? 3 : 1);
return Optional.of(amount); return Optional.of(amount);
@ -692,7 +669,7 @@ public class Pony extends Living<PlayerEntity> implements Copyable<Pony>, Update
} }
} }
if (getSpecies().canFly() || (getSpecies().canUseEarth() && entity.isSneaking())) { if (getCompositeRace().canFly() || (getCompositeRace().canUseEarth() && entity.isSneaking())) {
distance -= 5; distance -= 5;
} }
distance = Math.max(0, distance); distance = Math.max(0, distance);
@ -710,7 +687,7 @@ public class Pony extends Living<PlayerEntity> implements Copyable<Pony>, Update
protected void handleFall(float distance, float damageMultiplier, DamageSource cause) { protected void handleFall(float distance, float damageMultiplier, DamageSource cause) {
super.handleFall(distance, damageMultiplier, cause); super.handleFall(distance, damageMultiplier, cause);
if (getSpecies().canUseEarth() && entity.isSneaking()) { if (getCompositeRace().canUseEarth() && entity.isSneaking()) {
double radius = distance / 10; double radius = distance / 10;
if (radius > 0) { if (radius > 0) {
EarthPonyStompAbility.spawnEffectAround(entity, entity.getLandingPos(), radius, radius); EarthPonyStompAbility.spawnEffectAround(entity, entity.getLandingPos(), radius, radius);
@ -775,7 +752,7 @@ public class Pony extends Living<PlayerEntity> implements Copyable<Pony>, Update
} }
public ActionResult canSleepNow() { public ActionResult canSleepNow() {
if (asWorld().getGameRules().getBoolean(UGameRules.DO_NOCTURNAL_BAT_PONIES) && getActualSpecies().isNocturnal()) { if (asWorld().getGameRules().getBoolean(UGameRules.DO_NOCTURNAL_BAT_PONIES) && getSpecies().isNocturnal()) {
return asWorld().isDay() ? ActionResult.SUCCESS : ActionResult.FAIL; return asWorld().isDay() ? ActionResult.SUCCESS : ActionResult.FAIL;
} }
@ -790,7 +767,7 @@ public class Pony extends Living<PlayerEntity> implements Copyable<Pony>, Update
@Override @Override
public void toSyncronisedNbt(NbtCompound compound) { public void toSyncronisedNbt(NbtCompound compound) {
super.toSyncronisedNbt(compound); super.toSyncronisedNbt(compound);
compound.putString("playerSpecies", Race.REGISTRY.getId(getActualSpecies()).toString()); compound.putString("playerSpecies", Race.REGISTRY.getId(getSpecies()).toString());
compound.putFloat("magicExhaustion", magicExhaustion); compound.putFloat("magicExhaustion", magicExhaustion);
compound.putInt("ticksHanging", ticksHanging); compound.putInt("ticksHanging", ticksHanging);
BLOCK_POS.writeOptional("hangingPosition", compound, getHangingPosition()); BLOCK_POS.writeOptional("hangingPosition", compound, getHangingPosition());
@ -843,7 +820,7 @@ public class Pony extends Living<PlayerEntity> implements Copyable<Pony>, Update
&& entity instanceof ServerPlayerEntity && entity instanceof ServerPlayerEntity
&& entity.getWorld().getGameRules().getBoolean(UGameRules.SWAP_TRIBE_ON_DEATH) && entity.getWorld().getGameRules().getBoolean(UGameRules.SWAP_TRIBE_ON_DEATH)
&& oldPlayer.respawnRace.isUnset()) && oldPlayer.respawnRace.isUnset())
|| oldPlayer.getActualSpecies().isUnset(); || oldPlayer.getSpecies().isUnset();
if (alive) { if (alive) {
oldPlayer.getSpellSlot().stream(true).forEach(getSpellSlot()::put); oldPlayer.getSpellSlot().stream(true).forEach(getSpellSlot()::put);
@ -866,7 +843,7 @@ public class Pony extends Living<PlayerEntity> implements Copyable<Pony>, Update
} }
} }
setSpecies(oldPlayer.respawnRace != Race.UNSET && !alive ? oldPlayer.respawnRace : oldPlayer.getActualSpecies()); setSpecies(oldPlayer.respawnRace != Race.UNSET && !alive ? oldPlayer.respawnRace : oldPlayer.getSpecies());
getDiscoveries().copyFrom(oldPlayer.getDiscoveries(), alive); getDiscoveries().copyFrom(oldPlayer.getDiscoveries(), alive);
getPhysics().copyFrom(oldPlayer.getPhysics(), alive); getPhysics().copyFrom(oldPlayer.getPhysics(), alive);
if (!forcedSwap) { if (!forcedSwap) {

View file

@ -138,7 +138,7 @@ public class BellItem extends Item implements ChargeableItem {
if (living instanceof Pony pony) { if (living instanceof Pony pony) {
amountDrawn = pony.getMagicalReserves().getMana().get() * 0.2F; amountDrawn = pony.getMagicalReserves().getMana().get() * 0.2F;
pony.getMagicalReserves().getMana().multiply(0.8F); pony.getMagicalReserves().getMana().multiply(0.8F);
if (pony.getActualSpecies() == Race.CHANGELING) { if (pony.getSpecies() == Race.CHANGELING) {
particleType = ParticleTypes.HEART; particleType = ParticleTypes.HEART;
} }
} else { } else {

View file

@ -130,7 +130,7 @@ public class CrystalHeartItem extends Item implements FloatingArtefactEntity.Art
} }
}); });
VecHelper.findInRange(entity, entity.getWorld(), entity.getPos(), 20, i -> { VecHelper.findInRange(entity, entity.getWorld(), entity.getPos(), 20, i -> {
return i instanceof ItemEntity ie && isFillable(ie.getStack()) && Equine.of(i).filter(p -> p.getSpecies() == Race.CHANGELING).isPresent(); return i instanceof ItemEntity ie && isFillable(ie.getStack()) && EquinePredicates.CHANGELING.test(i);
}).forEach(i -> containers.add((ItemEntity)i)); }).forEach(i -> containers.add((ItemEntity)i));
int demand = outputs.size() + containers.stream().mapToInt(i -> i.getStack().getCount()).sum(); int demand = outputs.size() + containers.stream().mapToInt(i -> i.getStack().getCount()).sum();

View file

@ -41,7 +41,7 @@ public interface EnchantmentUtil {
} }
static int getLuck(int baseline, LivingEntity entity) { static int getLuck(int baseline, LivingEntity entity) {
boolean naturallyLucky = Living.getOrEmpty(entity).filter(c -> c.getSpecies().canUseEarth()).isPresent(); boolean naturallyLucky = Living.getOrEmpty(entity).filter(c -> c.getCompositeRace().canUseEarth()).isPresent();
if (naturallyLucky) { if (naturallyLucky) {
baseline += 15; baseline += 15;
} }

View file

@ -30,7 +30,7 @@ abstract class MixinDamageSource {
}); });
Pony.of(entity).filter(e -> e.getSpecies().canFly()).ifPresent(pony -> { Pony.of(entity).filter(e -> e.getCompositeRace().canFly()).ifPresent(pony -> {
if (pony.getPhysics().isFlying()) { if (pony.getPhysics().isFlying()) {
info.setReturnValue(Text.translatable("death.attack.unicopia.generic.whilst_flying", info.getReturnValue())); info.setReturnValue(Text.translatable("death.attack.unicopia.generic.whilst_flying", info.getReturnValue()));
} }
@ -47,7 +47,7 @@ abstract class MixinFallLocation {
return; return;
} }
Pony.of(entity).ifPresent(pony -> { Pony.of(entity).ifPresent(pony -> {
if (pony.getSpecies().canFly()) { if (pony.getCompositeRace().canFly()) {
info.setReturnValue(new FallLocation(location.id() + ".pegasus")); info.setReturnValue(new FallLocation(location.id() + ".pegasus"));
} }
}); });

View file

@ -5,7 +5,6 @@ import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import com.minelittlepony.unicopia.Race;
import com.minelittlepony.unicopia.entity.player.Pony; import com.minelittlepony.unicopia.entity.player.Pony;
import net.minecraft.block.PowderSnowBlock; import net.minecraft.block.PowderSnowBlock;
@ -15,7 +14,7 @@ import net.minecraft.entity.Entity;
abstract class MixinPowderSnowBlock { abstract class MixinPowderSnowBlock {
@Inject(method = "canWalkOnPowderSnow", at = @At("HEAD"), cancellable = true) @Inject(method = "canWalkOnPowderSnow", at = @At("HEAD"), cancellable = true)
private static void onCanWalkOnPowderSnow(Entity entity, CallbackInfoReturnable<Boolean> info) { private static void onCanWalkOnPowderSnow(Entity entity, CallbackInfoReturnable<Boolean> info) {
if (Pony.of(entity).map(Pony::getSpecies).filter(Race::canFly).isPresent()) { if (Pony.of(entity).filter(pony -> pony.getCompositeRace().canFly()).isPresent()) {
info.setReturnValue(true); info.setReturnValue(true);
} }
} }

View file

@ -40,7 +40,7 @@ abstract class MixinServerPlayerEntity extends PlayerEntity implements ScreenHan
at = @At(value = "FIELD", target = "net/minecraft/entity/player/PlayerEntity$SleepFailureReason.NOT_POSSIBLE_NOW:Lnet/minecraft/entity/player/PlayerEntity$SleepFailureReason;"), at = @At(value = "FIELD", target = "net/minecraft/entity/player/PlayerEntity$SleepFailureReason.NOT_POSSIBLE_NOW:Lnet/minecraft/entity/player/PlayerEntity$SleepFailureReason;"),
cancellable = true) cancellable = true)
private void onTrySleep(BlockPos pos, CallbackInfoReturnable<Either<PlayerEntity.SleepFailureReason, Unit>> info) { private void onTrySleep(BlockPos pos, CallbackInfoReturnable<Either<PlayerEntity.SleepFailureReason, Unit>> info) {
if (get().getActualSpecies().isNocturnal() && get().asWorld().getGameRules().getBoolean(UGameRules.DO_NOCTURNAL_BAT_PONIES)) { if (get().getSpecies().isNocturnal() && get().asWorld().getGameRules().getBoolean(UGameRules.DO_NOCTURNAL_BAT_PONIES)) {
((PlayerEntity)this).sendMessage(Text.translatable("block.unicopia.bed.no_sleep.nocturnal"), true); ((PlayerEntity)this).sendMessage(Text.translatable("block.unicopia.bed.no_sleep.nocturnal"), true);
info.setReturnValue(Either.left(PlayerEntity.SleepFailureReason.OTHER_PROBLEM)); info.setReturnValue(Either.left(PlayerEntity.SleepFailureReason.OTHER_PROBLEM));

View file

@ -35,7 +35,7 @@ public interface Channel {
static void bootstrap() { static void bootstrap() {
ServerPlayConnectionEvents.JOIN.register((handler, sender, server) -> { ServerPlayConnectionEvents.JOIN.register((handler, sender, server) -> {
Pony pony = Pony.of(handler.player); Pony pony = Pony.of(handler.player);
if (pony.getActualSpecies() == Race.UNSET) { if (pony.getSpecies() == Race.UNSET) {
Race race = UnicopiaWorldProperties.forWorld(handler.player.getServerWorld()).getDefaultRace(); Race race = UnicopiaWorldProperties.forWorld(handler.player.getServerWorld()).getDefaultRace();
if (!race.isPermitted(handler.player)) { if (!race.isPermitted(handler.player)) {
race = Race.UNSET; race = Race.UNSET;

View file

@ -35,14 +35,14 @@ public record MsgRequestSpeciesChange (
public void handle(ServerPlayerEntity sender) { public void handle(ServerPlayerEntity sender) {
Pony player = Pony.of(sender); Pony player = Pony.of(sender);
if (force || player.getActualSpecies().isUnset()) { if (force || player.getSpecies().isUnset()) {
player.setSpecies(newRace.isPermitted(sender) ? newRace : UnicopiaWorldProperties.forWorld((ServerWorld)player.asWorld()).getDefaultRace()); player.setSpecies(newRace.isPermitted(sender) ? newRace : UnicopiaWorldProperties.forWorld((ServerWorld)player.asWorld()).getDefaultRace());
if (force) { if (force) {
if (sender.getWorld().getGameRules().getBoolean(UGameRules.ANNOUNCE_TRIBE_JOINS)) { if (sender.getWorld().getGameRules().getBoolean(UGameRules.ANNOUNCE_TRIBE_JOINS)) {
Text message = Text.translatable("respawn.reason.joined_new_tribe", Text message = Text.translatable("respawn.reason.joined_new_tribe",
sender.getDisplayName(), sender.getDisplayName(),
player.getActualSpecies().getDisplayName(), player.getActualSpecies().getAltDisplayName()); player.getSpecies().getDisplayName(), player.getSpecies().getAltDisplayName());
sender.getWorld().getPlayers().forEach(p -> { sender.getWorld().getPlayers().forEach(p -> {
((ServerPlayerEntity)p).sendMessageToClient(message, false); ((ServerPlayerEntity)p).sendMessageToClient(message, false);
}); });

View file

@ -33,7 +33,7 @@ public class NocturnalSleepManager extends SleepManager {
if (world.getGameRules().getBoolean(GameRules.DO_DAYLIGHT_CYCLE) && world.getPlayers().stream() if (world.getGameRules().getBoolean(GameRules.DO_DAYLIGHT_CYCLE) && world.getPlayers().stream()
.filter(LivingEntity::isSleeping) .filter(LivingEntity::isSleeping)
.map(Pony::of) .map(Pony::of)
.map(Pony::getActualSpecies) .map(Pony::getSpecies)
.noneMatch(Race::isDayurnal)) { .noneMatch(Race::isDayurnal)) {
world.setTimeOfDay(world.getLevelProperties().getTimeOfDay() - DAY_LENGTH + 13500); world.setTimeOfDay(world.getLevelProperties().getTimeOfDay() - DAY_LENGTH + 13500);
} }
@ -50,7 +50,7 @@ public class NocturnalSleepManager extends SleepManager {
return players.stream().filter(player -> { return players.stream().filter(player -> {
Pony pony = Pony.of(player); Pony pony = Pony.of(player);
return (pony.getActualSpecies().isNocturnal() == world.isDay()); return (pony.getSpecies().isNocturnal() == world.isDay());
}).toList(); }).toList();
} }

View file

@ -74,8 +74,7 @@ public class MagicalDamageSource extends DamageSource {
} }
Text message = Text.translatable(basic, params.toArray()); Text message = Text.translatable(basic, params.toArray());
return Pony.of(target).filter(e -> e.getSpecies().canFly()).map(pony -> { return Pony.of(target).filter(e -> e.getCompositeRace().canFly()).map(pony -> {
if (pony.getPhysics().isFlying()) { if (pony.getPhysics().isFlying()) {
return Text.translatable("death.attack.unicopia.generic.whilst_flying", message); return Text.translatable("death.attack.unicopia.generic.whilst_flying", message);
} }