Skip to content

Commit

Permalink
Rework action targeting to support custom targets
Browse files Browse the repository at this point in the history
  • Loading branch information
Matyrobbrt committed Oct 9, 2023
1 parent ac536ef commit 2dc7c6d
Show file tree
Hide file tree
Showing 35 changed files with 386 additions and 149 deletions.
2 changes: 2 additions & 0 deletions src/main/java/com/lovetropics/minigames/LoveTropics.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import com.lovetropics.minigames.common.core.dimension.RuntimeDimensions;
import com.lovetropics.minigames.common.core.game.IGameManager;
import com.lovetropics.minigames.common.core.game.behavior.GameBehaviorTypes;
import com.lovetropics.minigames.common.core.game.behavior.action.ActionTargetTypes;
import com.lovetropics.minigames.common.core.game.client_state.GameClientStateTypes;
import com.lovetropics.minigames.common.core.game.impl.GameEventDispatcher;
import com.lovetropics.minigames.common.core.game.predicate.entity.EntityPredicates;
Expand Down Expand Up @@ -132,6 +133,7 @@ public LoveTropics() {
MinigameItems.init();

GameBehaviorTypes.init(modBus);
ActionTargetTypes.init(modBus);
EntityPredicates.init(modBus);
GameClientStateTypes.init(modBus);
StreamHosts.init();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public class EqualizeCurrencyBehavior implements IGameBehavior {
public void register(IGamePhase game, EventRegistrar events) throws GameException {
CurrencyManager currency = game.getState().getOrThrow(CurrencyManager.KEY);

events.listen(GameActionEvents.APPLY, (context, sources) -> {
events.listen(GameActionEvents.APPLY, (context) -> {
currency.equalize();
return true;
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import com.lovetropics.minigames.common.core.game.behavior.event.EventRegistrar;
import com.lovetropics.minigames.common.core.game.behavior.event.GameActionEvents;
import com.lovetropics.minigames.common.core.game.behavior.event.GamePhaseEvents;
import com.mojang.authlib.GameProfile;
import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
Expand All @@ -33,9 +34,11 @@
import net.minecraft.world.level.block.FarmBlock;
import net.minecraft.world.level.block.LevelEvent;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraftforge.common.util.FakePlayerFactory;

import java.util.HashSet;
import java.util.Set;
import java.util.UUID;

// TODO: this is extremely hardcoded for now, need to split up!
public class BbTutorialAction implements IGameBehavior {
Expand Down Expand Up @@ -63,22 +66,17 @@ public BbTutorialAction(PlantType diffusa, PlantType grass, PlantType wheat, boo

@Override
public void register(IGamePhase game, EventRegistrar events) throws GameException {
PlotsState plots = game.getState().getOrThrow(PlotsState.KEY);
tutorialActions = new Reference2ObjectOpenHashMap<>();
tutorialPlots = new HashSet<>();

// TODO: make an "apply to plot" event
events.listen(GameActionEvents.APPLY_TO_PLAYER, (context, target) -> {
Plot playerPlot = plots.getPlotFor(target);
if (playerPlot == null) {
return false;
}

events.listen(GameActionEvents.APPLY_TO_PLOT, (context, playerPlot) -> {
// Don't run the same tutorial twice per plot
if (!tutorialPlots.add(playerPlot)) {
return false;
}

final GameProfile playerProfile = new GameProfile(UUID.randomUUID(), "PlotFakePlayer");
final ServerPlayer target = FakePlayerFactory.get(game.getWorld(), playerProfile);
Long2ObjectMap<Runnable> actions = new Long2ObjectOpenHashMap<>();
tutorialActions.put(target, actions);
long ticks = game.ticks() + 4;
Expand All @@ -102,14 +100,14 @@ public void register(IGamePhase game, EventRegistrar events) throws GameExceptio
actions.put(ticks, () -> {
BlockPos pos = sample.relative(playerPlot.forward, 12);

Mob entity = new BbTutorialHuskEntity(EntityType.HUSK, target.level(), playerPlot);
Mob entity = new BbTutorialHuskEntity(EntityType.HUSK, game.getWorld(), playerPlot);

Direction direction = playerPlot.forward.getOpposite();
entity.moveTo(pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5, direction.toYRot(), 0);

target.level().addFreshEntity(entity);
game.getWorld().addFreshEntity(entity);

entity.finalizeSpawn(target.serverLevel(), target.level().getCurrentDifficultyAt(pos), MobSpawnType.MOB_SUMMONED, null, null);
entity.finalizeSpawn(game.getWorld(), game.getWorld().getCurrentDifficultyAt(pos), MobSpawnType.MOB_SUMMONED, null, null);
});

ticks += 240;
Expand Down Expand Up @@ -163,7 +161,7 @@ private long placeBlocks(IGamePhase game, ServerPlayer target, Plot playerPlot,
// Farmland row
for (int i = -1; i < 13; i++) {
BlockPos pos = sample.relative(playerPlot.forward, -5).relative(cw, i - 5);
if (playerPlot.plantBounds.contains(pos) && target.level().getBlockState(pos.below()).getBlock() == Blocks.GRASS_BLOCK) {
if (playerPlot.plantBounds.contains(pos) && game.getWorld().getBlockState(pos.below()).getBlock() == Blocks.GRASS_BLOCK) {
actions.put(ticks, new SetFarmland(target, pos.below()));
ticks += 5;
}
Expand All @@ -172,7 +170,7 @@ private long placeBlocks(IGamePhase game, ServerPlayer target, Plot playerPlot,
// Farmland row
for (int i = -1; i < 13; i++) {
BlockPos pos = sample.relative(playerPlot.forward, -4).relative(cw, i - 5);
if (playerPlot.plantBounds.contains(pos) && target.level().getBlockState(pos.below()).getBlock() == Blocks.GRASS_BLOCK) {
if (playerPlot.plantBounds.contains(pos) && game.getWorld().getBlockState(pos.below()).getBlock() == Blocks.GRASS_BLOCK) {
actions.put(ticks, new SetFarmland(target, pos.below()));
ticks += 5;
}
Expand Down Expand Up @@ -234,7 +232,7 @@ private long breakBlocks(IGamePhase game, ServerPlayer target, Plot playerPlot,
for (int i = -1; i < 13; i++) {
BlockPos pos = sample.relative(playerPlot.forward, -4).relative(cw, i - 5);
// how does this work??? there's farmland here!! but removing this breaks it?!?!
if (playerPlot.plantBounds.contains(pos) && target.level().getBlockState(pos.below()).getBlock() == Blocks.GRASS_BLOCK) {
if (playerPlot.plantBounds.contains(pos) && game.getWorld().getBlockState(pos.below()).getBlock() == Blocks.GRASS_BLOCK) {
actions.put(ticks, new SetGrass(target, pos.below()));
ticks += 3;
}
Expand All @@ -243,7 +241,7 @@ private long breakBlocks(IGamePhase game, ServerPlayer target, Plot playerPlot,
// Farmland row
for (int i = -1; i < 13; i++) {
BlockPos pos = sample.relative(playerPlot.forward, -5).relative(cw, i - 5);
if (playerPlot.plantBounds.contains(pos) && target.level().getBlockState(pos.below()).getBlock() == Blocks.GRASS_BLOCK) {
if (playerPlot.plantBounds.contains(pos) && game.getWorld().getBlockState(pos.below()).getBlock() == Blocks.GRASS_BLOCK) {
actions.put(ticks, new SetGrass(target, pos.below()));
ticks += 3;
}
Expand Down Expand Up @@ -287,8 +285,8 @@ public record SetPlant(IGamePhase game, ServerPlayer target, Plot playerPlot, Bl
public void run() {
Plant plant = game.invoker(BbEvents.PLACE_PLANT).placePlant(target, playerPlot, sample, type).getObject();
if (plant != null) {
target.level().levelEvent(null, LevelEvent.PARTICLES_DESTROY_BLOCK, sample, Block.getId(target.level().getBlockState(plant.coverage().getOrigin())));
target.level().playSound(null, sample, sound, SoundSource.BLOCKS, 0.4F, 1.0F);
game.getWorld().levelEvent(null, LevelEvent.PARTICLES_DESTROY_BLOCK, sample, Block.getId(game.getWorld().getBlockState(plant.coverage().getOrigin())));
game.getWorld().playSound(null, sample, sound, SoundSource.BLOCKS, 0.4F, 1.0F);
}
}
}
Expand All @@ -303,8 +301,8 @@ public void run() {

boolean placed = game.invoker(BbEvents.BREAK_PLANT).breakPlant(target, playerPlot, plant);
if (placed) {
target.level().levelEvent(null, LevelEvent.PARTICLES_DESTROY_BLOCK, sample, Block.getId(target.level().getBlockState(plant.coverage().getOrigin())));
target.level().playSound(null, sample, sound, SoundSource.BLOCKS, 0.4F, 1.0F);
game.getWorld().levelEvent(null, LevelEvent.PARTICLES_DESTROY_BLOCK, sample, Block.getId(game.getWorld().getBlockState(plant.coverage().getOrigin())));
game.getWorld().playSound(null, sample, sound, SoundSource.BLOCKS, 0.4F, 1.0F);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ private void registerCheckpoints(IGamePhase game, EventRegistrar events) {

for (BlockBox region : regions) {
registerCheckpoint(region, (player, state) -> {
actions.apply(game, GameActionContext.EMPTY, player);
actions.applyPlayer(game, GameActionContext.EMPTY, player);
return false;
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ public class GameBehaviorTypes {
public static final GameBehaviorEntry<OnDeathTrigger> ON_DEATH = register("on_death", OnDeathTrigger.CODEC);
public static final GameBehaviorEntry<OnDamageTrigger> ON_DAMAGE = register("on_damage", OnDamageTrigger.CODEC);
public static final GameBehaviorEntry<WhileInRegionTrigger> WHILE_IN_REGION = register("while_in_region", WhileInRegionTrigger.CODEC);
public static final GameBehaviorEntry<ScheduledActionsTrigger> SCHEDULED_ACTIONS = register("scheduled_actions", ScheduledActionsTrigger.CODEC);
public static final GameBehaviorEntry<ScheduledActionsTrigger<?, ?>> SCHEDULED_ACTIONS = register("scheduled_actions", ScheduledActionsTrigger.CODEC);
public static final GameBehaviorEntry<PhaseChangeTrigger> PHASE_CHANGE = register("phase_change", PhaseChangeTrigger.CODEC);
public static final GameBehaviorEntry<OnKillTrigger> ON_KILL = register("on_kill", OnKillTrigger.CODEC);
public static final GameBehaviorEntry<BindControlsBehavior> BIND_CONTROLS = register("bind_controls", BindControlsBehavior.CODEC);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.lovetropics.minigames.common.core.game.behavior.action;

import com.lovetropics.minigames.common.core.game.IGamePhase;
import com.lovetropics.minigames.common.core.game.behavior.event.GameEventListeners;
import com.mojang.datafixers.util.Either;
import com.mojang.serialization.Codec;
import net.minecraft.util.ExtraCodecs;

import java.util.List;
import java.util.function.Function;

public interface ActionTarget<T> {
Codec<ActionTarget<?>> CODEC = ExtraCodecs.lazyInitializedCodec(() -> ActionTargetTypes.REGISTRY.get().getCodec())
.dispatch(ActionTarget::type, Function.identity());
Codec<ActionTarget<?>> FALLBACK_PLAYER = ExtraCodecs.xor(CODEC, PlayerActionTarget.Target.CODEC)
.xmap(e -> e.map(Function.identity(), PlayerActionTarget::new), Either::left);

List<T> resolve(IGamePhase phase, Iterable<T> sources);

boolean apply(IGamePhase game, GameEventListeners listeners, GameActionContext context, Iterable<T> sources);

Codec<? extends ActionTarget<T>> type();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.lovetropics.minigames.common.core.game.behavior.action;

import com.lovetropics.minigames.Constants;
import com.lovetropics.minigames.LoveTropics;
import com.lovetropics.minigames.common.core.game.predicate.entity.EntityPredicate;
import com.lovetropics.minigames.common.core.game.predicate.entity.EntityTypeEntityPredicate;
import com.lovetropics.minigames.common.util.registry.LoveTropicsRegistrate;
import com.mojang.serialization.Codec;
import com.tterrag.registrate.util.entry.RegistryEntry;
import net.minecraft.core.Registry;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.registries.DeferredRegister;
import net.minecraftforge.registries.IForgeRegistry;
import net.minecraftforge.registries.RegistryBuilder;
import net.minecraftforge.registries.RegistryObject;

import java.util.function.Supplier;

public class ActionTargetTypes {
public static final ResourceKey<Registry<Codec<? extends ActionTarget<?>>>> REGISTRY_KEY = ResourceKey.createRegistryKey(new ResourceLocation(Constants.MODID, "action_target_types"));
public static final DeferredRegister<Codec<? extends ActionTarget<?>>> REGISTER = DeferredRegister.create(REGISTRY_KEY, Constants.MODID);

public static final Supplier<IForgeRegistry<Codec<? extends ActionTarget<?>>>> REGISTRY = REGISTER.makeRegistry(() -> new RegistryBuilder<Codec<? extends ActionTarget<?>>>()
.disableSync()
.disableSaving());

private static final LoveTropicsRegistrate REGISTRATE = LoveTropics.registrate();

public static final RegistryObject<Codec<PlayerActionTarget>> PLAYER = register("player", PlayerActionTarget.CODEC);
public static final RegistryObject<Codec<PlotActionTarget>> PLOT = register("plot", PlotActionTarget.CODEC);
public static final RegistryObject<Codec<NoneActionTarget>> NONE = register("none", NoneActionTarget.CODEC);

public static <T extends ActionTarget<?>> RegistryObject<Codec<T>> register(final String name, final Codec<T> codec) {
return REGISTER.register(name, () -> codec);
}

public static void init(IEventBus modBus) {
REGISTER.register(modBus);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.lovetropics.minigames.common.core.game.behavior.action;

import com.google.common.collect.Lists;
import com.lovetropics.lib.codec.MoreCodecs;
import com.lovetropics.minigames.common.core.game.IGamePhase;
import com.lovetropics.minigames.common.core.game.behavior.IGameBehavior;
Expand All @@ -13,92 +12,72 @@
import com.mojang.serialization.MapCodec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.util.StringRepresentable;

import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import java.util.function.Supplier;

public class GameActionList<T, A extends ActionTarget<T>> {
public static final GameActionList<ServerPlayer, PlayerActionTarget> EMPTY = new GameActionList<>(List.of(), PlayerActionTarget.SOURCE);

public static final MapCodec<GameActionList<?, ?>> MAP_CODEC = RecordCodecBuilder.mapCodec(i -> i.group(
MoreCodecs.listOrUnit(IGameBehavior.CODEC).fieldOf("actions").forGetter(list -> list.behaviors),
ActionTarget.FALLBACK_PLAYER.optionalFieldOf("target", PlayerActionTarget.SOURCE).forGetter(list -> list.target)
).apply(i, GameActionList::new));

private static final Codec<GameActionList<?, ?>> SIMPLE_CODEC = MoreCodecs.listOrUnit(IGameBehavior.CODEC)
.flatComapMap(
behaviors -> new GameActionList<>(behaviors, PlayerActionTarget.SOURCE),
list -> {
if (!(list.target instanceof PlayerActionTarget tg) || tg.target() != PlayerActionTarget.Target.SOURCE) {
return DataResult.error(() -> "Cannot encode simple action list with target: " + list.target);
}
return DataResult.success(list.behaviors);
}
);

public static final Codec<GameActionList> CODEC = Codec.either(SIMPLE_CODEC, MAP_CODEC.codec())
.xmap(either -> either.map(Function.identity(), Function.identity()), Either::right);
public static final Codec<GameActionList<?, ?>> TYPE_SAFE_CODEC = (Codec<GameActionList<?,?>>) (Object) CODEC;

private final List<IGameBehavior> behaviors;
public final A target;

private final GameEventListeners listeners = new GameEventListeners();

public GameActionList(List<IGameBehavior> behaviors, A target) {
this.behaviors = behaviors;
this.target = target;
}

public void register(IGamePhase game, EventRegistrar events) {
for (IGameBehavior behavior : behaviors) {
behavior.register(game, events.redirect(GameActionEvents::matches, listeners));
}
}

public boolean applyPlayer(IGamePhase game, GameActionContext context, ServerPlayer... sources) {
return applyPlayer(game, context, Arrays.asList(sources));
}

public boolean applyPlayer(IGamePhase game, GameActionContext context, Iterable<ServerPlayer> sources) {
return this.applyIf(ActionTargetTypes.PLAYER, game, context, sources);
}

public <T1, A1 extends ActionTarget<T1>> boolean applyIf(Supplier<Codec<A1>> type, IGamePhase phase, GameActionContext context, Iterable<T1> sources) {
if (type.get() == target.type()) {
return apply(phase, context, (Iterable) sources);
}
return false;
}

public boolean apply(IGamePhase phase, GameActionContext context) {
return apply(phase, context, target.resolve(phase, List.of()));
}

public boolean apply(IGamePhase phase, GameActionContext context, Iterable<T> sources) {
return listeners.invoker(GameActionEvents.APPLY).apply(context) | target.apply(phase, listeners, context, sources);
}

public class GameActionList {
public static final GameActionList EMPTY = new GameActionList(List.of(), Target.SOURCE);

public static final MapCodec<GameActionList> MAP_CODEC = RecordCodecBuilder.mapCodec(i -> i.group(
MoreCodecs.listOrUnit(IGameBehavior.CODEC).fieldOf("actions").forGetter(list -> list.behaviors),
Target.CODEC.optionalFieldOf("target", Target.SOURCE).forGetter(list -> list.target)
).apply(i, GameActionList::new));

private static final Codec<GameActionList> SIMPLE_CODEC = MoreCodecs.listOrUnit(IGameBehavior.CODEC)
.flatComapMap(
behaviors -> new GameActionList(behaviors, Target.SOURCE),
list -> {
if (list.target != Target.SOURCE) {
return DataResult.error(() -> "Cannot encode simple action list with target: " + list.target.getSerializedName());
}
return DataResult.success(list.behaviors);
}
);

public static final Codec<GameActionList> CODEC = Codec.either(SIMPLE_CODEC, MAP_CODEC.codec())
.xmap(either -> either.map(Function.identity(), Function.identity()), Either::right);

private final List<IGameBehavior> behaviors;
private final Target target;

private final GameEventListeners listeners = new GameEventListeners();

public GameActionList(List<IGameBehavior> behaviors, Target target) {
this.behaviors = behaviors;
this.target = target;
}

public void register(IGamePhase game, EventRegistrar events) {
for (IGameBehavior behavior : behaviors) {
behavior.register(game, events.redirect(GameActionEvents::matches, listeners));
}
}

public boolean apply(IGamePhase game, GameActionContext context, ServerPlayer... sources) {
return apply(game, context, Arrays.asList(sources));
}

public boolean apply(IGamePhase game, GameActionContext context, Iterable<ServerPlayer> sources) {
boolean result = listeners.invoker(GameActionEvents.APPLY).apply(context, sources);
for (ServerPlayer target : target.resolve(game, sources)) {
result |= listeners.invoker(GameActionEvents.APPLY_TO_PLAYER).apply(context, target);
}
return result;
}

public enum Target implements StringRepresentable {
NONE("none"),
SOURCE("source"),
PARTICIPANTS("participants"),
SPECTATORS("spectators"),
ALL("all"),
;

public static final Codec<Target> CODEC = MoreCodecs.stringVariants(values(), Target::getSerializedName);

private final String name;

Target(String name) {
this.name = name;
}

public List<ServerPlayer> resolve(IGamePhase game, Iterable<ServerPlayer> sources) {
// Copy the lists because we might otherwise get concurrent modification from whatever the actions do!
return switch (this) {
case NONE -> List.of();
case SOURCE -> Lists.newArrayList(sources);
case PARTICIPANTS -> Lists.newArrayList(game.getParticipants());
case SPECTATORS -> Lists.newArrayList(game.getSpectators());
case ALL -> Lists.newArrayList(game.getAllPlayers());
};
}

@Override
public String getSerializedName() {
return name;
}
}
}
Loading

0 comments on commit 2dc7c6d

Please sign in to comment.