Modify mob spawn rates in a Forge Minecraft mod (1.16) - java

I'm new to modding Minecraft and I was wondering how to change mob spawn rates. Let's say we want to spawn lots of Endermen for example.
So far I've found the code that seems to sets the spawn frequency in net.minecraft.world.biome DefaultBiomeFeatures.java:
public static void withHostileMobs(MobSpawnInfo.Builder builder) {
...
builder.withSpawner(EntityClassification.MONSTER, new MobSpawnInfo.Spawners(EntityType.ENDERMAN, 10, 1, 4));
...
}
meaning Endermen spawn in most biomes, albeit rarely (10 is the weight, creepers and spiders have 100).
I know this DefaultBiome is then used by BiomeMaker.java to makeGiantTaigaBiome, makeBirchForestBiome etc. My conclusion is that I need to change the biomes to change spawn rates.
I can access the biomes using either BiomeRegistry or ForgeRegistries.BIOMES. I see 2 approaches here:
Replace the map of biomes completely. Sadly its register method is private so I cannot add new biomes to replace the existing ones. I also read here that removing them is apparently not possible.
Modify the existing map of biomes. This would use biome.withMobSpawnSettings(MobSpawnInfo mobSpawnSettings) to modify the biome in-place. But the MobSpawnInfo class once again does not have any public setters, so I don't see how I can get a modified MobSpawnInfo without re-creating the entire MobSpawnInfo object by hand.
Most solutions online (1, 2) seem to suggest the following which sadly do no longer work in the current 1.16.4:
ModLoader.addSpawn(YOURENTITY.class, 25, 1, 3);
EntityRegistry.addSpawn(...)
Any help would be greatly appreciated.

Do not try to modify the existing Minecraft package using Mixins -- that is called coremodding and frowned upon for various reasons. The correct approach for 1.16 is to subscribe to a BiomeLoadingEvent and then monkey-patch all biomes after they have been loaded:
1.16
#Mod("example")
public class ExampleMod
{
public ExampleMod() {
MinecraftForge.EVENT_BUS.register(this);
}
#SubscribeEvent(priority = EventPriority.HIGH)
public void onBiomeLoadingEvent(BiomeLoadingEvent event) {
List<MobSpawnInfo.Spawners> spawns =
event.getSpawns().getSpawner(EntityClassification.MONSTER);
// Remove existing Enderman spawn information
spawns.removeIf(e -> e.type == EntityType.ENDERMAN);
// Make Enderman spawns more frequent and add Blaze spawns in all biomes
spawns.add(new MobSpawnInfo.Spawners(EntityType.BLAZE, 200, 1, 4));
spawns.add(new MobSpawnInfo.Spawners(EntityType.ENDERMAN, 200, 1, 4));
}
}
1.15 (might also work in 1.14, 1.13, 1.12, ...)
#Mod("example")
public class ExampleMod
{
public ExampleMod() {
ForgeRegistries.BIOMES.forEach(biome -> {
List<Biome.SpawnListEntry> spawns = biome.getSpawns(EntityClassification.MONSTER);
spawns.removeIf(e -> e.entityType == EntityType.ENDERMAN);
spawns.add(new Biome.SpawnListEntry(EntityType.BLAZE, 200, 1, 4));
spawns.add(new Biome.SpawnListEntry(EntityType.ENDERMAN, 200, 1, 4));
});
}
}
Edit: Note that the InControl can be used to achieve a similar effect, requiring no coding.

Related

How to compare nested JsonNode field values with Java object parameter values in DROOLS?

I've been trying to execute a set of rules on a request object on the basis of some set of configuration value. Below is the example on what I am trying to do:
Configuration:
config.json
{
"company1": {
"site1": {
"maxWeeklyWorkingHours": 40,
"rule2": [1, 3, 4],
},
"site2": {
"maxWeeklyWorkingHours": 40,
"rule2": [1, 3, 4],
}
},
"company2": {
"site1": {
"maxWeeklyWorkingHours": 40,
"rule2": [1, 3, 4],
},
"site2": {
"maxWeeklyWorkingHours": 40,
"rule2": [1, 3, 4],
}
}
}
Request Class Object: policyDataToBeVerified
PolicyDataToBeVerified(company=company1, site=site1, workerWeeklyWorkingHours=39, isRequestValid=0)
I converted the config.json into a JsonNode object:configJson and passed both policyDataToBeVerified and configJson to drools working memory. Below are the approaches I tried to take to frame the rule but failed anyways:
APPROACH 1:
drools.drl
rule "maxWorkingHours"
when
$configJson: JsonNode()
$policyData: PolicyDataToBeVerified(maximumWeeklyWorkingHours <= $configJson.get(company).get(site).get("workerWeeklyWorkingHours"))
then
$policyData.setIsRequestValid(true)
end
Issue: I am getting null pointer exception at $configJson.get(company).get(site).get("workerWeeklyWorkingHours")
APPROACH 2:
I even tried to use configJSON as a global variable but, then drools didn't allow me to use get methods of JsonNode to get the nested fields from JSON
I've been trying to find a solution to my problem for past few days, but nothing worked. I'd be happy to learn even if we can view this problem from a different perspective and solve it in a different way, or if we can debug this, whatever works.
Thank you
NOTE: IntellijIDEA has inaccurate linting for .drl files, so request you to not rely on it.
I created a validateSlotsResponse as following for better handling of response:
package com.example.demo;
import lombok.Builder;
import lombok.Data;
import java.util.List;
#Data
#Builder
public class ValidateSlotsResponse {
boolean isRequestValid;
List<String> comments;
}
I'll pass this class's object into the working memory of DROOLS, and will update it inside the drools program, which will then be used as the respose for validation.
Below are the rules inside my drools file, please make sure you have made the right imports:
rule "fetchMaximumAllowedWeeklyWorkingHours"
when
$validateSlotsResponse: ValidateSlotsResponse()
$policyDataToBeVerified: PolicyDataToBeVerified()
$configJson: JsonNode()
$workingHoursAllowed:Integer() from $configJson.get($policyDataToBeVerified.getCompany()).get($policyDataToBeVerified.getSite()).get("maxWeeklyWorkingHours").intValue()
then
end
rule "maxWorkingHoursAccept" extends "fetchMaximumAllowedWeeklyWorkingHours"
when
eval($policyDataToBeVerified.workerWeeklyWorkingHours() <= $workingHoursAllowed)
then
$policyDataToBeVerified.setIsRequestValid(true);
$validateSlotsResponse.setRequestValid(true);
$validateSlotsResponse.setComments(new ArrayList<>(Arrays.asList("Worker allowed to work for "+$policyDataToBeVerified.getMaximumWeeklyWorkingHours() + " hours")));
end
rule "maxWorkingHoursReject" extends "fetchMaximumAllowedWeeklyWorkingHours"
when
eval($policyDataToBeVerified.workerWeeklyWorkingHours() > Integer.parseInt($workingHoursAllowed.toString()))
then
$policyDataToBeVerified.setIsRequestValid(false);
$validateSlotsResponse.setRequestValid(false);
$validateSlotsResponse.setComments(new ArrayList<>(Arrays.asList("Worker not allowed to work more than "+ $workingHoursAllowed + " hours")));
end

Spawn invisible Bat - convert org.bukkit.Entity to org.bukkit.craftbukkit.v1_8_R3.entity.CraftBat;

I'm creating a Plugin that need some invisible Bats. I therefore have a method that spawns and stores my bats. This is the code, I currently have:
public class BatManager {
private static final List<Entity> bats = new ArrayList<>();
public static void spawnBat(Location location) {
Entity entity = location.getWorld().spawnEntity(location, EntityType.BAT);
try {
CraftBat l = (CraftBat) entity;
l.getHandle().setInvisible(true);
} catch (RuntimeException e) {
e.printStackTrace();
}
bats.add(entity);
}
}
This code does compile, however it does not make the bat invisible. I strongly suspect that I create a copy of the object at some point, and set the invisibility there. I'm not sure however how I can set the invisibility without the conversion to CraftBat, since there are no methods I know of, to make a org.bukkit.Entity invisible on its' own.
How can I correct this?
I'm open for other suggestions concerning the invisibility as well, but I do explicitly not want to use Potion effects, since those leave some particles for the Player to see.
I found a solution without packet. With a quick timer, it's fine :
Entity bat = location.getWorld().spawnEntity(location, EntityType.BAT);
Bukkit.getScheduler().runTaskLater(MyPlugin.getInstance(), () -> {
((CraftBat) bat).getHandle().setInvisible(true);
}, 2);
I've found an alternate solution. It's actually possible to hide the particles per default on bukkit side. I therefore used PotionEffect's, looking like this:
Entity entity = location.getWorld().spawnEntity(location, EntityType.BAT);
LivingEntity livingEntity = (LivingEntity) entity;
PotionEffect effect = new PotionEffect(PotionEffectType.INVISIBILITY, Integer.MAX_VALUE, 1, false, false);
livingEntity.addPotionEffect(effect);
bats.add(livingEntity);

Which state machine design to use if your target state is not the next one?

I've been reading up on State Machines since its likely I need to use for my next project. Most examples I find online show how to go from StateA to StateB. But what if your next desired state is not an adjacent state? Are there any common patterns/practices to achieve this? Ideally in Java, but I can read other programming languages just as well.
# Example States
WakeUp->Get Dressed->Get Car Keys->Get in Car->Drive to Work->Work
Current State: Get in Car
Problems to solve
# Scenario 1: Desired State == Work
Forgot car keys, so you have to return to previous state and then move forward in states again.
# Scenario 2: Desired State == Work
Have car keys, so move forward in states to get to Desired State.
It's very likely that State Machine may not solve this problem elegantly and I just need to hand-craft the logic, I don't mind, but thought I'd follow a common design pattern to help others understand it.
From the example above, I do not need to worry about 'internal' states, which is also true for the project I'm tackling; just in case that makes a difference in possible solutions.
Here is a simple way to define a state machine.
Define in an enum all the states that you want.
enum StateType {
WAKE_UP, GET_DRESSED, GET_CAR_KEYS, GET_IN_CAR, DRIVE_TO_WORK, WORK
}
Have a statemachine which controls states, and a state interface which performs an action on the statemachine. The state then returns the next state to go to.
interface State {
StateType next(StateMachine sm);
}
Implement this state for multiple types
class GetInCarState implements State {
#Override
public StateType next(StateMachine sm) {
if (sm.hasKeys()) {
return StateType.DRIVE_TO_WORK;
}
return StateType.GET_CAR_KEYS;
}
}
Now define the State Machine
class StateMachine {
private Map<StateType, State> states = new HashMap<StateType, State>() {{
put(StateType.WAKE_UP, new WakeUpState());
put(StateType.GET_DRESSED, new GetDressedState());
put(StateType.GET_CAR_KEYS, new GetCarKeysState());
put(StateType.GET_IN_CAR, new GetInCarState());
put(StateType.DRIVE_TO_WORK, new DriveToWorkState());
put(StateType.WORK, new WorkState());
}};
private StateType currentState = StateType.WAKE_UP;
private boolean hasCarKeys;
public boolean hasKeys() {
return hasCarKeys;
}
public void setHasKeys(boolean hasKeys) {
hasCarKeys = hasKeys;
}
public void update() {
currentState = states.get(currentState).next(this);
}
}

Iterating through arraylist in one class, while adding an element in other class

i'm getting a concurrentmodification exception, the problem is that i'm iterating through an arrayList and drawing and updating the sprites in the arraylist, while adding new sprites in another java class.
public abstract class Scene
{
ArrayList<UIElement> uiElements = new ArrayList<>();
ArrayList<GameObject> sprites = new ArrayList<>();
public ArrayList<GameObject> getSprites()
{
return sprites;
}
public ArrayList<UIElement> getUiElements()
{
return uiElements;
}
public abstract void load();
public abstract void unload();
public void update()
{
sprites.forEach(GameObject::update);
uiElements.forEach(UIElement::update);
}
public void draw(Graphics2D g2)
{
sprites.forEach(e -> e.draw(g2));
uiElements.forEach(e -> e.draw(g2));
}
}
so these are the iterating methods in the java class named Scene.java
public void onButtonsEvent(WiimoteButtonsEvent wiimoteButtonsEvent)
{
// shoot firebolt when button b is held
if( wiimoteButtonsEvent.isButtonBHeld())
{
if(updateIndexTwo % UPDATE_SPEED == 0)
{
Vector2D fireboltVector = new Vector2D(vector.getX()+SPRITE_WIDTH/2, vector.getY(), crossHair.getX() + CH_SIZE/2, crossHair.getY() + CH_SIZE/2);
Firebolt firebolt = new Firebolt(fireboltVector, damage, color, scene);
scene.getSprites().add(firebolt);
updateIndexTwo = 0;
}
updateIndexTwo++;
}
}
this is the code where the Firebolt(sprite) will be added to the sprites arraylist in Scene
i'm getting a concurrentmodification exception, the problem is that i'm iterating through an arrayList and drawing and updating the sprites in the arraylist, while adding new sprites in another java class.
The basic solution is fairly simple: don't do that. It is an intentional design feature that if an ArrayList is structurally modified, then any iterators over that list that were obtained prior to the modification are invalidated; further use of them causes ConcurrentModificationException to be thrown. This happens whether you're using Iterators directly, using an enhanced for loop, or using forEach(). The other basic collections classes (LinkedList, HashSet, etc.) all work the same way.
If indeed you must accommodate the collection being modified without breaking an ongoing iteration over it, then you need a collection class that accomodates such activity. For example, you could consider ConcurrentLinkedDeque, or one of the other collections from java.util.concurrent. It is possible to write your own, but it's tricky, and why do that when there are existing classes in the standard library that will serve?
Note also that although a solution can be found in java.util.concurrent, the problem does not depend on multiple threads being involved. In fact, if you do have multiple threads then using one of the concurrent collections serves a dual purpose: it also ensures consistent operations on the collection without need of external synchronization. If by any chance you were previously accessing the list from multiple threads without proper synchronization then getting the CME was a lucky break -- you might instead have just gotten silent malfunction.
Look at an existing thread safe class such as ArrayBlockingQueue. You can wrap your ArrayList in synchronization code but it's easier to use a data structure that already does what you want.

Iteration over a Set

I'm having issues with getting an iteration done (and modification) through the Set, which contains Objects. I've tried so many ways of iteration (4), but none of them seem to work and still throw me the Error java.util.ConcurrentModificationException.
[Code is written in Groovy]
private void replaceRock() {
ObjectNodeManager.OBJECTS.each {
System.out.println("Going...");
if(it.getPosition().withinDistance(player.getPosition(), 30)) {
System.out.println("Found...");
Position position = it.getPosition();
ObjectNode newRock = new ObjectNode(439, position, ObjectDirection.NORTH, ObjectType.DEFAULT);
ObjectNodeManager.unregister(it);
ObjectNodeManager.register(newRock);
it.remove();
}
}
}
I've tried synchronization to prevent access from other Threads, but this also didn't work. Please help me, I'm very desperate.
First find them (this will give you basically a list of refs) and then deal with them:
ObjectNodeManager.OBJECTS.findAll {
it.getPosition().withinDistance(player.getPosition(), 30))
}.each{
ObjectNode newRock = new ObjectNode(439, it.position, ObjectDirection.NORTH, ObjectType.DEFAULT)
ObjectNodeManager.unregister(it)
ObjectNodeManager.register(newRock)
it.remove()
}
On a random site note: i'd add a replace method in the ObjectNodeManager to combine unregister, register, remove. Also working with class methods and properties is not the best thing to do (but since it looks like a game...)
The problem is that you are modifying the list of objects while you are looping through the objects.
Try iterating through a copy of the objects instead.
ArrayList<YourType> copy = new ArrayList<YourType>(ObjectNodeManager.OBJECTS);
copy.each(...)

Categories

Resources