Skip to content

Commit 5133f57

Browse files
committed
basic controls on non-RoN mobs
1 parent d352e5c commit 5133f57

14 files changed

+277
-13
lines changed

TODO.txt

+14-2
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,22 @@ HEROES (in order of priority)
1919
- equipment
2020

2121

22+
Controlling non-RoN mobs
23+
------------------------
24+
25+
[❌] Continue moving past the follow range
26+
- echo the original packet once we reach the end goal
27+
[❌] Sending a move command doesn't immediately enact the move if the unit has a target
28+
29+
[❌] Allow selecting multiple non-units, but do not mix with units (and prioritise units)
30+
- Also allow double-click to select multiple
31+
32+
[❌] Attack move (just make it a move action but don't cancel NearestAttackableTargetGoal)
33+
34+
2235
Bugfixes
2336
--------
24-
[❌] Prevent superflat worlds generating regular worlds after a certain range
25-
[❌] Kicked for sending too many packets when spam clicking large army (rate-limit in server.properties?)
37+
2638

2739

2840
Quality of Life

TODO_backlog.txt

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ Features
1515

1616
Bugfixes
1717
--------
18+
[❌] Kicked for sending too many packets when spam clicking large army (rate-limit in server.properties?)
1819

1920
[❌] Dying in survival puts you in a bugged survival mode
2021

src/main/java/com/solegendary/reignofnether/hud/HudClientEvents.java

+21
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import com.solegendary.reignofnether.minimap.MinimapClientEvents;
2323
import com.solegendary.reignofnether.orthoview.OrthoviewClientEvents;
2424
import com.solegendary.reignofnether.player.PlayerClientEvents;
25+
import com.solegendary.reignofnether.research.ResearchClient;
2526
import com.solegendary.reignofnether.resources.ResourceName;
2627
import com.solegendary.reignofnether.resources.ResourceSources;
2728
import com.solegendary.reignofnether.resources.Resources;
@@ -33,6 +34,7 @@
3334
import com.solegendary.reignofnether.survival.SurvivalClientEvents;
3435
import com.solegendary.reignofnether.tutorial.TutorialClientEvents;
3536
import com.solegendary.reignofnether.tutorial.TutorialStage;
37+
import com.solegendary.reignofnether.unit.NonUnitClientEvents;
3638
import com.solegendary.reignofnether.unit.Relationship;
3739
import com.solegendary.reignofnether.unit.UnitAction;
3840
import com.solegendary.reignofnether.unit.UnitClientEvents;
@@ -962,6 +964,25 @@ else if (MC.player != null && SandboxClientEvents.isSandboxPlayer(MC.player.getN
962964
}
963965
}
964966
}
967+
// -----------------
968+
// Non-unit controls
969+
// -----------------
970+
else if (!getSelectedUnits().isEmpty() && NonUnitClientEvents.canControlNonUnits()) {
971+
blitX = 0;
972+
blitY = screenHeight - iconFrameSize;
973+
ArrayList<Button> actionButtons = new ArrayList<>();
974+
975+
if (NonUnitClientEvents.canAttack(getSelectedUnits().get(0)))
976+
actionButtons.add(ActionButtons.ATTACK);
977+
978+
actionButtons.add(ActionButtons.STOP);
979+
980+
for (Button actionButton : actionButtons) {
981+
actionButton.render(evt.getGuiGraphics(), blitX, blitY, mouseX, mouseY);
982+
renderedButtons.add(actionButton);
983+
blitX += iconFrameSize;
984+
}
985+
}
965986

966987
// ---------------------------
967988
// Resources icons and amounts

src/main/java/com/solegendary/reignofnether/mixin/TitleScreenMixin.java

+1-2
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,11 @@
33
import com.mojang.blaze3d.systems.RenderSystem;
44
import com.mojang.blaze3d.vertex.PoseStack;
55
import com.mojang.math.Axis;
6-
import net.minecraft.client.gui.GuiGraphics;
7-
import org.joml.Vector3f;
86
import com.solegendary.reignofnether.ReignOfNether;
97
import com.solegendary.reignofnether.hud.TitleClientEvents;
108
import net.minecraft.Util;
119
import net.minecraft.client.Minecraft;
10+
import net.minecraft.client.gui.GuiGraphics;
1211
import net.minecraft.client.gui.components.AbstractWidget;
1312
import net.minecraft.client.gui.components.events.GuiEventListener;
1413
import net.minecraft.client.gui.narration.NarratedElementType;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package com.solegendary.reignofnether.mixin.goals;
2+
3+
import com.solegendary.reignofnether.unit.NonUnitServerEvents;
4+
import net.minecraft.world.entity.Mob;
5+
import net.minecraft.world.entity.PathfinderMob;
6+
import net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal;
7+
import net.minecraft.world.entity.ai.goal.target.TargetGoal;
8+
import org.spongepowered.asm.mixin.Mixin;
9+
import org.spongepowered.asm.mixin.injection.At;
10+
import org.spongepowered.asm.mixin.injection.Inject;
11+
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
12+
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
13+
14+
@Mixin(NearestAttackableTargetGoal.class)
15+
public abstract class NearestAttackableTargetGoalMixin extends TargetGoal {
16+
17+
public boolean canUse() { return false; }
18+
public NearestAttackableTargetGoalMixin(Mob pMob, boolean pMustSee) { super(pMob, pMustSee); }
19+
20+
@Inject(
21+
method = "findTarget",
22+
at = @At("HEAD"),
23+
cancellable = true
24+
)
25+
public void findTarget(CallbackInfo ci) {
26+
synchronized (NonUnitServerEvents.controlledNonUnits) {
27+
if (mob instanceof PathfinderMob pfMob && NonUnitServerEvents.controlledNonUnits.contains(pfMob))
28+
ci.cancel();
29+
}
30+
}
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package com.solegendary.reignofnether.mixin.goals;
2+
3+
import com.solegendary.reignofnether.unit.NonUnitServerEvents;
4+
import net.minecraft.world.entity.PathfinderMob;
5+
import net.minecraft.world.entity.ai.goal.RandomStrollGoal;
6+
import org.spongepowered.asm.mixin.Final;
7+
import org.spongepowered.asm.mixin.Mixin;
8+
import org.spongepowered.asm.mixin.Shadow;
9+
import org.spongepowered.asm.mixin.injection.At;
10+
import org.spongepowered.asm.mixin.injection.Inject;
11+
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
12+
13+
@Mixin(RandomStrollGoal.class)
14+
public abstract class RandomStrollGoalMixin {
15+
16+
@Shadow @Final protected PathfinderMob mob;
17+
18+
@Inject(
19+
method = "canContinueToUse",
20+
at = @At("HEAD"),
21+
cancellable = true
22+
)
23+
public void canContinueToUse(CallbackInfoReturnable<Boolean> cir) {
24+
synchronized (NonUnitServerEvents.controlledNonUnits) {
25+
if (NonUnitServerEvents.controlledNonUnits.contains(mob))
26+
cir.setReturnValue(false);
27+
}
28+
}
29+
}

src/main/java/com/solegendary/reignofnether/player/PlayerServerEvents.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ public class PlayerServerEvents {
100100
// foodforthought - ignore soft population caps
101101
// thereisnospoon - allow changing survival wave by clicking the wave indicator and using debug commands
102102
// slipslopslap - monster units are unaffected by sunlight
103+
// wouldyoukindly - allow control of non-unit mobs in RTS mode
103104
// thebeastofcaerbannog - spawns the Killer Rabbit
104105
public static final List<String> singleWordCheats = List.of(
105106
"warpten",
@@ -108,7 +109,8 @@ public class PlayerServerEvents {
108109
"medievalman",
109110
"foodforthought",
110111
"thereisnospoon",
111-
"slipslopslap"
112+
"slipslopslap",
113+
"wouldyoukindly"
112114
);
113115

114116
public static void saveRTSPlayers() {

src/main/java/com/solegendary/reignofnether/registrars/ClientEventRegistrar.java

+4
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@
3535
import com.solegendary.reignofnether.tps.TPSServerEvents;
3636
import com.solegendary.reignofnether.tutorial.TutorialClientEvents;
3737
import com.solegendary.reignofnether.tutorial.TutorialServerEvents;
38+
import com.solegendary.reignofnether.unit.NonUnitClientEvents;
39+
import com.solegendary.reignofnether.unit.NonUnitServerEvents;
3840
import com.solegendary.reignofnether.unit.UnitClientEvents;
3941
import com.solegendary.reignofnether.unit.UnitServerEvents;
4042
import com.solegendary.reignofnether.worldborder.WorldBorderClientEvents;
@@ -73,6 +75,7 @@ public void registerClientEvents() {
7375
vanillaEventBus.register(WorldBorderClientEvents.class);
7476
vanillaEventBus.register(SurvivalClientEvents.class);
7577
vanillaEventBus.register(StartPosClientEvents.class);
78+
vanillaEventBus.register(NonUnitClientEvents.class);
7679

7780
// to allow singleplayer integrated server to work
7881
vanillaEventBus.register(GameruleServerEvents.class);
@@ -92,5 +95,6 @@ public void registerClientEvents() {
9295
vanillaEventBus.register(StartPosServerEvents.class);
9396
vanillaEventBus.register(AlliancesServerEvents.class);
9497
vanillaEventBus.register(HeroServerEvents.class);
98+
vanillaEventBus.register(NonUnitServerEvents.class);
9599
}
96100
}

src/main/java/com/solegendary/reignofnether/registrars/ServerEventRegistrar.java

+2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import com.solegendary.reignofnether.survival.SurvivalServerEvents;
1717
import com.solegendary.reignofnether.tps.TPSServerEvents;
1818
import com.solegendary.reignofnether.tutorial.TutorialServerEvents;
19+
import com.solegendary.reignofnether.unit.NonUnitServerEvents;
1920
import com.solegendary.reignofnether.unit.UnitServerEvents;
2021
import net.minecraftforge.common.MinecraftForge;
2122
import net.minecraftforge.eventbus.api.IEventBus;
@@ -52,5 +53,6 @@ public void registerServerEvents() {
5253
vanillaEventBus.register(StartPosServerEvents.class);
5354
vanillaEventBus.register(AlliancesServerEvents.class);
5455
vanillaEventBus.register(HeroServerEvents.class);
56+
vanillaEventBus.register(NonUnitServerEvents.class);
5557
}
5658
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package com.solegendary.reignofnether.unit;
2+
3+
import com.solegendary.reignofnether.orthoview.OrthoviewClientEvents;
4+
import com.solegendary.reignofnether.research.ResearchClient;
5+
import com.solegendary.reignofnether.sandbox.SandboxClientEvents;
6+
import com.solegendary.reignofnether.unit.interfaces.Unit;
7+
import com.solegendary.reignofnether.util.MyRenderer;
8+
import net.minecraft.client.Minecraft;
9+
import net.minecraft.core.BlockPos;
10+
import net.minecraft.core.Direction;
11+
import net.minecraft.resources.ResourceLocation;
12+
import net.minecraft.server.level.ServerLevel;
13+
import net.minecraft.world.entity.LivingEntity;
14+
import net.minecraft.world.entity.PathfinderMob;
15+
import net.minecraft.world.entity.ai.attributes.Attributes;
16+
import net.minecraft.world.entity.ai.goal.WrappedGoal;
17+
import net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal;
18+
import net.minecraft.world.level.Level;
19+
import net.minecraft.world.level.block.SnowLayerBlock;
20+
import net.minecraft.world.phys.AABB;
21+
import net.minecraft.world.phys.Vec3;
22+
import net.minecraftforge.client.event.RenderLevelStageEvent;
23+
import net.minecraftforge.event.TickEvent;
24+
import net.minecraftforge.eventbus.api.SubscribeEvent;
25+
26+
import static com.solegendary.reignofnether.unit.UnitClientEvents.getSelectedUnits;
27+
import static net.minecraftforge.client.event.RenderLevelStageEvent.Stage.AFTER_CUTOUT_BLOCKS;
28+
29+
public class NonUnitClientEvents {
30+
31+
private static final Minecraft MC = Minecraft.getInstance();
32+
33+
public static boolean canControlNonUnits() {
34+
return MC.player != null &&
35+
!getSelectedUnits().isEmpty() &&
36+
(ResearchClient.hasCheat("wouldyoukindly") ||
37+
SandboxClientEvents.isSandboxPlayer(MC.player.getName().getString()));
38+
}
39+
40+
public static boolean canAttack(LivingEntity le) {
41+
if (le instanceof PathfinderMob mob) {
42+
if (mob.getAttributes().hasAttribute(Attributes.ATTACK_DAMAGE) ||
43+
mob.getAttributes().hasAttribute(Attributes.ATTACK_SPEED))
44+
return true;
45+
46+
for (WrappedGoal wrappedGoal : mob.goalSelector.getAvailableGoals())
47+
if (wrappedGoal.getGoal() instanceof NearestAttackableTargetGoal)
48+
return true;
49+
}
50+
return false;
51+
}
52+
53+
// override attack and random move goals while we have an active command
54+
@SubscribeEvent
55+
public static void onRenderLevel(RenderLevelStageEvent evt) {
56+
if (MC.level == null)
57+
return;
58+
59+
// AFTER_CUTOUT_BLOCKS lets us see checkpoints through leaves
60+
if (OrthoviewClientEvents.isEnabled() && evt.getStage() == AFTER_CUTOUT_BLOCKS) {
61+
62+
for (LivingEntity le : UnitClientEvents.getSelectedUnits()) {
63+
if (le instanceof PathfinderMob mob && !(le instanceof Unit) && le.isAlive() && !le.isRemoved()) {
64+
float entityYOffset = 1.74f - le.getEyeHeight() - 1;
65+
Vec3 firstPos = le.getEyePosition().add(0, entityYOffset,0);
66+
67+
if (mob.getTarget() != null && !mob.getTarget().isDeadOrDying()) {
68+
MyRenderer.drawLine(evt.getPoseStack(), firstPos, mob.getTarget().getEyePosition(), 1, 0, 0, 0.5f);
69+
}
70+
else if (!mob.getNavigation().isDone() && mob.getNavigation().getTargetPos() != null) {
71+
72+
double dist = Math.sqrt(mob.distanceToSqr(Vec3.atCenterOf(mob.getNavigation().getTargetPos())));
73+
float a = (float) Math.min(1, dist / 4) - 0.2f;
74+
75+
if (a > 0) {
76+
BlockPos bp = mob.getNavigation().getTargetPos().below();
77+
Vec3 pos = new Vec3(bp.getX() + 0.5f, bp.getY() + 1.0f, bp.getZ() + 0.5f);
78+
MyRenderer.drawLine(evt.getPoseStack(), firstPos, pos, 0, 1, 0, a);
79+
80+
if (MC.level.getBlockState(bp.offset(0, 1, 0)).getBlock() instanceof SnowLayerBlock) {
81+
AABB aabb = new AABB(bp);
82+
aabb = aabb.setMaxY(aabb.maxY + 0.13f);
83+
MyRenderer.drawSolidBox(evt.getPoseStack(), aabb, Direction.UP, 0, 1, 0, a * 0.5f,
84+
new ResourceLocation("forge:textures/white.png"));
85+
} else {
86+
MyRenderer.drawBlockFace(evt.getPoseStack(), Direction.UP, bp, 0, 1, 0, a * 0.5f);
87+
}
88+
}
89+
}
90+
}
91+
}
92+
}
93+
}
94+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package com.solegendary.reignofnether.unit;
2+
3+
import com.solegendary.reignofnether.research.ResearchServerEvents;
4+
import com.solegendary.reignofnether.sandbox.SandboxServer;
5+
import net.minecraft.world.entity.PathfinderMob;
6+
import net.minecraft.world.level.Level;
7+
import net.minecraftforge.event.TickEvent;
8+
import net.minecraftforge.eventbus.api.SubscribeEvent;
9+
10+
import java.util.ArrayList;
11+
import java.util.Collections;
12+
import java.util.List;
13+
14+
public class NonUnitServerEvents {
15+
16+
// any mobs in this list will have their basic movement and attack goals cancelled until they become idle
17+
public static final List<PathfinderMob> controlledNonUnits = Collections.synchronizedList(new ArrayList<>());
18+
19+
public static boolean canControlNonUnits(Level level, String playerName) {
20+
return SandboxServer.isSandboxPlayer(playerName) || ResearchServerEvents.playerHasCheat(playerName, "wouldyoukindly");
21+
}
22+
23+
@SubscribeEvent
24+
public static void onWorldTick(TickEvent.LevelTickEvent evt) {
25+
if (evt.phase != TickEvent.Phase.END || evt.level.isClientSide() || evt.level.dimension() != Level.OVERWORLD) {
26+
return;
27+
}
28+
29+
synchronized (controlledNonUnits) {
30+
controlledNonUnits.removeIf(mob -> (mob.isDeadOrDying() || mob.isRemoved() || (mob.getNavigation().isDone() && mob.getTarget() == null)));
31+
}
32+
}
33+
}

0 commit comments

Comments
 (0)