Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Distance option to prevent new towns to block old towns expansion. #7364

Open
wants to merge 17 commits into
base: master
Choose a base branch
from

Conversation

HydrolienF
Copy link

Description:

Add a new config option to prevent town encasement a bit better for older town.
The idea is that if a new town is created close to an older town, the old town will be able to claim more land between the 2 town.
New town could have choose an other location while the old town can't move. So it's unfair for the old town if it get claim blocked by a new town.


New Nodes/Commands/ConfigOptions:

New config option min_plot_distance_from_older_town_plot.
The option is disabled by default and can be enabled by making min_plot_distance_from_older_town_plot > min_plot_distance_from_town_plot


Relevant Towny Issue ticket:

It is the feature that we talk about in #7361


  • I have tested this pull request for defects on a server. The feature have worked in any case I have tested and I haven't found anything broken about claim distance.

By making this pull request, I represent that I have the right to waive copyright and related rights to my contribution, and agree that all copyright and related rights in my contributions are waived, and I acknowledge that the TownyAdvanced organization has the copyright to use and modify my contribution under the Towny License for perpetuity.

Copy link
Member

@LlmDl LlmDl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was able to do a bit of a review, found some changes that should be made. I should be able to do a second review after the requested changes are made. Overall it looks decent.

HydrolienF and others added 5 commits April 16, 2024 18:57
- Inverse the logic of the predicate to isIgnorableTown.
- Move public methods upper than the private one.
- Move predicate test
- Remove a non necessary anymore ==null test.
…thub.com:HydrolienF/Towny into min_plot_distance_from_town_plot_for_newest_town
@HydrolienF
Copy link
Author

Thanks for reviewing step by step and helping me doing a better pull request.
I have done the changes.
Let me know if it's look good or if there is somethings else to improve.

are blocked.

Extracts a few methods to keep things more readable.
@LlmDl
Copy link
Member

LlmDl commented Apr 30, 2024

I've gotten this about to the point that I'm happy with it.

There's a few things I don't really like. Namely, that we're essentially running the distance checks two times every time we test the distance. We are also currently testing the distance rules about twice every time there is claiming already. Once when the claiming selection is made, and then (somewhat redundantly,) checking it again in the proximity rules.

So we're basically going from testing distance twice to testing it four times with this PR.

@Warriorrrr what are you thoughts?

@HydrolienF
Copy link
Author

There's a few things I don't really like. Namely, that we're essentially running the distance checks two times every time we test the distance. We are also currently testing the distance rules about twice every time there is claiming already. Once when the claiming selection is made, and then (somewhat redundantly,) checking it again in the proximity rules.

So we're basically going from testing distance twice to testing it four times with this PR.

Something that we can do to avoid calculating distance multiple time is add a new function TownyWorld#notTooCloseToOtherTowns(WorldCoord worldCoord, Town town) and make distance test in it instead of in AreaSelectionUtil#worldCoordNotTooCloseToOtherTowns(Town town, WorldCoord worldCoord)

private static boolean worldCoordNotTooCloseToOtherTowns(Town town, WorldCoord worldCoord) {
		TownyWorld townyWorld = worldCoord.getTownyWorld();
		return townyWorld != null 
				&& townyWorld.getMinDistanceFromOtherTownsPlots(worldCoord, town) >= TownySettings.getMinDistanceFromTownPlotblocks()
				&& townyWorld.getMinDistanceFromOtherOlderTownsPlots(worldCoord, town) >= TownySettings.getMinDistanceFromOlderTownPlotblocks();
	}

whould become

private static boolean worldCoordNotTooCloseToOtherTowns(Town town, WorldCoord worldCoord) {
		TownyWorld townyWorld = worldCoord.getTownyWorld();
		return townyWorld != null 
				&& townyWorld.notTooCloseToOtherTowns(worldCoord, town);
	}

And we will add a new function in TownyWorld similar to getMinDistanceFromOtherTownsPlots(...) but where the 2 distance values and the 2 predicates will be tested after distance caclualtion.

public boolean notTooCloseToOtherTowns(Coord key, Town homeTown) {
	final Map<Integer, Predicate<Town>> minDistances = Map.of(TownySettings.getMinDistanceFromTownPlotblocks(), t -> false, TownySettings.getMinDistanceFromOlderTownPlotblocks(), t -> t.getRegistered() > homeTown.getRegistered());
	final int keyX = key.getX();
	final int keyZ = key.getZ();

	for(Town town : getTowns().values()){
		if (homeTown != null && townSkippedByProximityFilter(town, homeTown, t -> false)) // No skip with the predicate here because it will be done later by the map.
			continue;

		for (TownBlock b : town.getTownBlocks()) {
			if (!b.getWorld().equals(this)) continue;

			final int tbX = b.getX();
			final int tbZ = b.getZ();
			
			if (keyX == tbX && keyZ == tbZ)
				continue;
			
			final double distSqr = MathUtil.distanceSquared((double) tbX - keyX, (double) tbZ - keyZ);
			// If there is a town that is too close, return false.
			if (minDistances.entrySet().stream().anyMatch(entry -> distSqr >= entry.getKey() && !entry.getValue().test(town)))
				return false;
		}
	}
	return true;
}

I see this change having 3 advantages:

  • It do distance caclulation only once.
  • function stop as soon as it find a too close town instead of caclulating distance for each townplot.
  • it make it easy to add new distance test on some town. (Just add a new int and a new predicate on the map)

If getMinDistanceFromOtherTownsPlots(...) and getMinDistanceFromOtherOlderTownsPlots(...) are used only to see if a plot is too close from a town, then we can even remove completly this 2 functions and replace them by notTooCloseToOtherTowns(...).

@LlmDl let me know what you think about it. If you think it's a great way to do it, I can update the pull request.

@LlmDl
Copy link
Member

LlmDl commented Jun 2, 2024

I think the direction you're planning is the way to go yes, what would be really nice is if we had test coverage applied to the distance/proximity code. Then we'd know if something breaks.

@HydrolienF
Copy link
Author

Ok, then I will try to do a distance check function with a single for loop, link all current distance check to that function.
And add unit test about it.

@HydrolienF HydrolienF marked this pull request as draft June 3, 2024 08:44
@HydrolienF
Copy link
Author

I'm struggling with test part. Any advice on how to claim area with MockBukkit ?

@LlmDl
Copy link
Member

LlmDl commented Jul 6, 2024

You don't want to claim stuff, you just want to test the variables work, using values you know will work/not work and passing them into the ProximityUtil methods that would decide if something works or doesn't work.

@HydrolienF
Copy link
Author

I don't understand how to test ProximityUtil#allowTownHomeBlockOrThrow without having at least one claim on the map.

@LlmDl
Copy link
Member

LlmDl commented Jul 6, 2024

For something like that you just need to make the object. Create a town and an townblock, using their constructors, then add the townblock to the town (using the same method used in the TownClaim task ie: townblock.setTown(town) if thats what TownClaim uses.

Afterwards use the town and townblock to do the test.

It's not working for now because townBlock.setTown(...) try to use the pugins, witch is null.
@HydrolienF
Copy link
Author

Hi @LlmDl
I have create a test with a Town and a TownBlock.
Unfortunately townblock.setTown(town) witch is used by TownClaim to add a towny block to a town isn't working. It would like to have Towny.plugin private static var initialized, but it's not initialized.
Did I forget something in the init part?
Other tests only have

MockBukkit.getOrCreateMock();
TownySettings.loadDefaultConfig();

This is the error:

java.lang.IllegalStateException: Attempted to use getPlugin() while the plugin is null, are you shading Towny? If you do not understand this message, join the Towny discord using https://discord.com/invite/gnpVs5m and ask for support.
		at com.palmergames.bukkit.towny.Towny.getPlugin(Towny.java:764)
		at com.palmergames.bukkit.towny.TownyUniverse.(TownyUniverse.java:100)
		at com.palmergames.bukkit.towny.TownyUniverse.getInstance(TownyUniverse.java:106)
		at com.palmergames.bukkit.towny.object.TownBlock.setTown(TownBlock.java:79)
		at com.palmergames.bukkit.towny.object.TownBlock.setTown(TownBlock.java:68)

@LlmDl
Copy link
Member

LlmDl commented Aug 20, 2024

Hi @LlmDl
I have create a test with a Town and a TownBlock.
Unfortunately townblock.setTown(town) witch is used by TownClaim to add a towny block to a town isn't working. It would like to have Towny.plugin private static var initialized, but it's not initialized.
Did I forget something in the init part?
Other tests only have

MockBukkit.getOrCreateMock();
TownySettings.loadDefaultConfig();

This is the error:

java.lang.IllegalStateException: Attempted to use getPlugin() while the plugin is null, are you shading Towny? If you do not understand this message, join the Towny discord using https://discord.com/invite/gnpVs5m and ask for support.
		at com.palmergames.bukkit.towny.Towny.getPlugin(Towny.java:764)
		at com.palmergames.bukkit.towny.TownyUniverse.(TownyUniverse.java:100)
		at com.palmergames.bukkit.towny.TownyUniverse.getInstance(TownyUniverse.java:106)
		at com.palmergames.bukkit.towny.object.TownBlock.setTown(TownBlock.java:79)
		at com.palmergames.bukkit.towny.object.TownBlock.setTown(TownBlock.java:68)

I am away on vacation for a couple more days and I'll look at this after I return. My laptop which I took with me has gotten stuck in a boot loop, and I have only got my phone (which is useless for viewing code on.)

@HydrolienF HydrolienF marked this pull request as ready for review September 30, 2024 08:13
@HydrolienF
Copy link
Author

Let me know if you have any idea to improve the tests.

@LlmDl
Copy link
Member

LlmDl commented Oct 1, 2024

I brought your branch into my IDE and see the issue with the tests. I'm not sure if we'll be able to do a test class that uses anything adding townblocks to towns. That's really quite unfortunate.

@HydrolienF
Copy link
Author

🙁

@HydrolienF
Copy link
Author

Any news about this pr ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants