diff --git a/module.txt b/module.txt index 10937f8..dff2687 100644 --- a/module.txt +++ b/module.txt @@ -43,7 +43,7 @@ }, { "id": "ModuleTestingEnvironment", - "minVersion": "0.2.0", + "minVersion": "0.3.2", "optional": true }, { diff --git a/src/main/java/org/terasology/dynamicCities/districts/DistrictManager.java b/src/main/java/org/terasology/dynamicCities/districts/DistrictManager.java index 2803e76..74a8326 100644 --- a/src/main/java/org/terasology/dynamicCities/districts/DistrictManager.java +++ b/src/main/java/org/terasology/dynamicCities/districts/DistrictManager.java @@ -1,24 +1,10 @@ -/* - * Copyright 2016 MovingBlocks - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +// Copyright 2021 The Terasology Foundation +// SPDX-License-Identifier: Apache-2.0 package org.terasology.dynamicCities.districts; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.terasology.gestalt.assets.management.AssetManager; import org.terasology.dynamicCities.utilities.Toolbox; import org.terasology.engine.entitySystem.prefab.Prefab; import org.terasology.engine.entitySystem.systems.BaseComponentSystem; @@ -26,6 +12,7 @@ import org.terasology.engine.entitySystem.systems.RegisterSystem; import org.terasology.engine.registry.In; import org.terasology.engine.registry.Share; +import org.terasology.gestalt.assets.management.AssetManager; import java.util.ArrayList; import java.util.HashSet; @@ -52,12 +39,7 @@ public void postBegin() { //Get building data if (prefab.hasComponent(DistrictType.class)) { DistrictType districtType = prefab.getComponent(DistrictType.class); - if (!districtType.zones.isEmpty()) { - Toolbox.stringsToLowerCase(districtType.zones); - districts.add(districtType); - } else { - logger.warn("Found district prefab with empty zone list"); - } + addDistrict(districtType); } } @@ -73,6 +55,15 @@ public void postBegin() { logger.info("Finished loading districts: " + districts.size() + " district types found: " + districtNames); } + public void addDistrict(DistrictType districtType) { + if (!districtType.zones.isEmpty()) { + Toolbox.stringsToLowerCase(districtType.zones); + districts.add(districtType); + } else { + logger.warn("Found district prefab with empty zone list"); + } + } + public Optional getDistrictFromName(String name) { for (DistrictType districtType : districts) { if (districtType.name.equals(name)) { diff --git a/src/main/java/org/terasology/dynamicCities/districts/DistrictType.java b/src/main/java/org/terasology/dynamicCities/districts/DistrictType.java index eee34a1..4460135 100644 --- a/src/main/java/org/terasology/dynamicCities/districts/DistrictType.java +++ b/src/main/java/org/terasology/dynamicCities/districts/DistrictType.java @@ -9,6 +9,7 @@ import org.terasology.nui.Color; import org.terasology.reflection.MappedContainer; +import java.util.Arrays; import java.util.List; //TODO: give mixing factors for zones @@ -20,7 +21,12 @@ public class DistrictType implements Component { public int color; public List zones = Lists.newArrayList(); - public DistrictType ( ) { } + public DistrictType() { } + + public DistrictType(String name, String... zones) { + this.name = name; + this.zones.addAll(Arrays.asList(zones)); + }; public boolean isValidType(DynParcel parcel) { String zone = parcel.getZone(); diff --git a/src/main/java/org/terasology/dynamicCities/population/CultureManager.java b/src/main/java/org/terasology/dynamicCities/population/CultureManager.java index 9713c50..5c14c51 100644 --- a/src/main/java/org/terasology/dynamicCities/population/CultureManager.java +++ b/src/main/java/org/terasology/dynamicCities/population/CultureManager.java @@ -1,24 +1,10 @@ -/* - * Copyright 2016 MovingBlocks - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +// Copyright 2021 The Terasology Foundation +// SPDX-License-Identifier: Apache-2.0 package org.terasology.dynamicCities.population; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.terasology.gestalt.assets.management.AssetManager; import org.terasology.dynamicCities.utilities.Toolbox; import org.terasology.engine.entitySystem.prefab.Prefab; import org.terasology.engine.entitySystem.systems.BaseComponentSystem; @@ -27,6 +13,7 @@ import org.terasology.engine.registry.In; import org.terasology.engine.registry.Share; import org.terasology.engine.utilities.random.MersenneRandom; +import org.terasology.gestalt.assets.management.AssetManager; import java.util.HashSet; import java.util.Set; @@ -49,27 +36,7 @@ public void postBegin() { //Get building data if (prefab.hasComponent(CultureComponent.class)) { CultureComponent cultureComponent = prefab.getComponent(CultureComponent.class); - if (cultureComponent.theme != null) { - cultureComponent.theme = cultureComponent.theme.toLowerCase(); - } else { - logger.warn("No theme defined for culture " + cultureComponent.name); - } - if (!cultureComponent.buildingNeedPerZone.isEmpty()) { - cultureComponents.add(cultureComponent); - cultureComponent.buildingNeedPerZone = Toolbox.stringsToLowerCase(cultureComponent.buildingNeedPerZone); - } else { - logger.warn("Found culture prefab with empty buildingNeedPerZone list"); - } - if (cultureComponent.availableBuildings != null) { - Toolbox.stringsToLowerCase(cultureComponent.availableBuildings); - } else { - logger.warn("No available Buildings defined for culture " + cultureComponent.name); - } - if (cultureComponent.residentialZones != null) { - Toolbox.stringsToLowerCase(cultureComponent.residentialZones); - } else { - logger.warn("No residential zones defined for culture " + cultureComponent.name); - } + addCulture(cultureComponent); } } @@ -83,13 +50,37 @@ public void postBegin() { rng = new MersenneRandom(assetManager.hashCode() * 5 + this.hashCode()); } + public void addCulture(CultureComponent cultureComponent) { + if (cultureComponent.theme != null) { + cultureComponent.theme = cultureComponent.theme.toLowerCase(); + } else { + logger.warn("No theme defined for culture " + cultureComponent.name); + } + if (!cultureComponent.buildingNeedPerZone.isEmpty()) { + cultureComponents.add(cultureComponent); + cultureComponent.buildingNeedPerZone = Toolbox.stringsToLowerCase(cultureComponent.buildingNeedPerZone); + } else { + logger.warn("Found culture prefab with empty buildingNeedPerZone list"); + } + if (cultureComponent.availableBuildings != null) { + Toolbox.stringsToLowerCase(cultureComponent.availableBuildings); + } else { + logger.warn("No available Buildings defined for culture " + cultureComponent.name); + } + if (cultureComponent.residentialZones != null) { + Toolbox.stringsToLowerCase(cultureComponent.residentialZones); + } else { + logger.warn("No residential zones defined for culture " + cultureComponent.name); + } + } + public CultureComponent getRandomCulture() { if (!cultureComponents.isEmpty()) { int max = cultureComponents.size(); int index = rng.nextInt(max); return (CultureComponent) cultureComponents.toArray()[index]; } - logger.error("No culture found...barbarians..." ); + logger.error("No culture found...barbarians..."); return null; } diff --git a/src/main/java/org/terasology/dynamicCities/settlements/components/DistrictFacetComponent.java b/src/main/java/org/terasology/dynamicCities/settlements/components/DistrictFacetComponent.java index 57cc947..8e8a494 100644 --- a/src/main/java/org/terasology/dynamicCities/settlements/components/DistrictFacetComponent.java +++ b/src/main/java/org/terasology/dynamicCities/settlements/components/DistrictFacetComponent.java @@ -32,6 +32,8 @@ import java.util.stream.Collectors; import java.util.stream.IntStream; +import static com.google.common.base.Preconditions.checkArgument; + public class DistrictFacetComponent implements Component { /** @@ -39,7 +41,7 @@ public class DistrictFacetComponent implements Component * TODO: Assign districts similar to parcels (look up if needs are already fulfilled before placement) */ @Replicate - public BlockAreac relativeRegion =new BlockArea(BlockArea.INVALID); + public BlockAreac relativeRegion = new BlockArea(BlockArea.INVALID); @Replicate public BlockAreac worldRegion = new BlockArea(BlockArea.INVALID); @Replicate @@ -63,7 +65,8 @@ public class DistrictFacetComponent implements Component public DistrictFacetComponent() { } - public DistrictFacetComponent(BlockRegion targetRegion, Border3D border, int gridSize, long seed, DistrictManager districtManager, CultureComponent cultureComponent) { + public DistrictFacetComponent(BlockRegion targetRegion, Border3D border, int gridSize, long seed, DistrictManager districtManager, + CultureComponent cultureComponent) { worldRegion = border.expandTo2D(targetRegion); relativeRegion = border.expandTo2D(targetRegion.getSize(new Vector3i())); this.gridSize = gridSize; @@ -110,6 +113,7 @@ public DistrictFacetComponent(BlockRegion targetRegion, Border3D border, int gri private void mapDistrictTypes(DistrictManager districtManager, CultureComponent cultureComponent) { + checkArgument(!districtManager.getDistrictTypes().isEmpty(), "There are no district types!"); Map zoneArea = new HashMap<>(); ProbabilityDistribution probabilityDistribution = new ProbabilityDistribution<>(districtManager.hashCode() | 413357); Map culturalNeedsPercentage = cultureComponent.getProcentualsForZone(); @@ -133,14 +137,14 @@ private void mapDistrictTypes(DistrictManager districtManager, CultureComponent totalAssignedArea += districtSize.get(i); //Calculate probabilities - Map probabilites = new HashMap<>(districtManager.getDistrictTypes().size()); + Map probabilities = new HashMap<>(districtManager.getDistrictTypes().size()); float totalDiff = 0; for (DistrictType districtType : districtManager.getDistrictTypes()) { float diff = 0; Map tempZoneArea = new HashMap<>(zoneArea); for (String zone : districtType.zones) { - float area = districtSize.get(i) / districtType.zones.size(); + float area = (float) districtSize.get(i) / districtType.zones.size(); tempZoneArea.put(zone, tempZoneArea.getOrDefault(zone, 0f) + area); if (!culturalNeedsPercentage.containsKey(zone)) { diff = Float.MAX_VALUE; @@ -152,18 +156,18 @@ private void mapDistrictTypes(DistrictManager districtManager, CultureComponent } diff = (diff == 0) ? 0 : 1 / diff; - probabilites.put(districtType, diff); + probabilities.put(districtType, diff); totalDiff += diff; } for (DistrictType districtType : districtManager.getDistrictTypes()) { - probabilites.put(districtType, probabilites.getOrDefault(districtType, 0f) / totalDiff); + probabilities.put(districtType, probabilities.getOrDefault(districtType, 0f) / totalDiff); } //Assign District - probabilityDistribution.initialise(probabilites); + probabilityDistribution.initialise(probabilities); DistrictType nextDistrict = probabilityDistribution.get(); for (String zone : nextDistrict.zones) { - float area = districtSize.get(i) / nextDistrict.zones.size(); + float area = (float) districtSize.get(i) / nextDistrict.zones.size(); zoneArea.put(zone, zoneArea.getOrDefault(zone, 0f) + area); } districtTypeMap.put(Integer.toString(districtCenters.indexOf(minCenter)), nextDistrict); diff --git a/src/main/java/org/terasology/dynamicCities/sites/SiteComponent.java b/src/main/java/org/terasology/dynamicCities/sites/SiteComponent.java index babfae3..efb6505 100644 --- a/src/main/java/org/terasology/dynamicCities/sites/SiteComponent.java +++ b/src/main/java/org/terasology/dynamicCities/sites/SiteComponent.java @@ -66,6 +66,7 @@ public String toString() { @Override public void copyFrom(SiteComponent other) { - + this.coords.set(other.coords); + this.radius = other.radius; } } diff --git a/src/test/java/org/terasology/dynamicCities/settlements/SettlementEntityManagerTest.java b/src/test/java/org/terasology/dynamicCities/settlements/SettlementEntityManagerTest.java new file mode 100644 index 0000000..6da142a --- /dev/null +++ b/src/test/java/org/terasology/dynamicCities/settlements/SettlementEntityManagerTest.java @@ -0,0 +1,114 @@ +// Copyright 2021 The Terasology Foundation +// SPDX-License-Identifier: Apache-2.0 + +package org.terasology.dynamicCities.settlements; + +import org.joml.RoundingMode; +import org.joml.Vector2i; +import org.joml.Vector2ic; +import org.joml.Vector3fc; +import org.joml.Vector3i; +import org.joml.Vector3ic; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.extension.ExtendWith; +import org.terasology.dynamicCities.buildings.BuildingManager; +import org.terasology.dynamicCities.buildings.BuildingQueue; +import org.terasology.dynamicCities.districts.DistrictManager; +import org.terasology.dynamicCities.districts.DistrictType; +import org.terasology.dynamicCities.parcels.DynParcel; +import org.terasology.dynamicCities.parcels.ParcelList; +import org.terasology.dynamicCities.population.CultureComponent; +import org.terasology.dynamicCities.population.CultureManager; +import org.terasology.dynamicCities.settlements.components.DistrictFacetComponent; +import org.terasology.dynamicCities.sites.SiteComponent; +import org.terasology.engine.context.Context; +import org.terasology.engine.entitySystem.entity.EntityManager; +import org.terasology.engine.entitySystem.entity.EntityRef; +import org.terasology.engine.logic.location.LocationComponent; +import org.terasology.engine.registry.In; +import org.terasology.engine.registry.InjectionHelper; +import org.terasology.moduletestingenvironment.MTEExtension; +import org.terasology.moduletestingenvironment.extension.Dependencies; +import org.terasology.namegenerator.town.TownAssetTheme; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import static com.google.common.truth.Truth8.assertThat; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +@Tag("MteTest") +@ExtendWith(MTEExtension.class) +@Dependencies("DynamicCities") +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class SettlementEntityManagerTest { + + static final int MAX_PLACEMENT_ATTEMPTS = 20; + static final String zone = "TestZone"; + static final Vector2ic buildingSize = new Vector2i(9, 9); + static final Vector3ic siteLocation = new Vector3i(1234, 0, -5678); + static final int population = 3; + + @In + Context context; + + @BeforeAll + void initialiseZones() { + // FIXME: mockito kludge to give us a way to define buildings. + // Should have a way for this test to add a building prefab, + // or a way to add to BuildingManager without prefabs. + BuildingManager buildingManager = spy(context.get(BuildingManager.class)); + when(buildingManager.getMinMaxSizePerZone()).thenReturn(Map.of(zone, List.of(buildingSize, buildingSize))); + context.put(BuildingManager.class, buildingManager); + + // re-inject this so it has the mock version. + InjectionHelper.inject(context.get(SettlementEntityManager.class), context); + } + + @BeforeAll + void initialiseCulture(CultureManager cultures) { + CultureComponent culture = new CultureComponent(); + culture.name = "Testers"; + culture.theme = TownAssetTheme.FANTASY.name(); + culture.availableBuildings.add("Test Building"); + culture.buildingNeedPerZone.put(zone, 1f); + culture.residentialZones.add(zone); + + cultures.addCulture(culture); + } + + @BeforeAll + void initialiseDistricts(DistrictManager districts) { + districts.addDistrict(new DistrictType("The District", zone)); + } + + EntityRef newSite() { + SiteComponent site = new SiteComponent(siteLocation.x(), siteLocation.z()); + return context.get(EntityManager.class).create( + site, + new SettlementComponent(site, population), + new LocationComponent(siteLocation) + ); + } + + @Test + void placeParcel(SettlementEntityManager manager) { + ParcelList parcels = new ParcelList(); + BuildingQueue buildingQueue = new BuildingQueue(); + + EntityRef site = newSite(); + EntityRef settlement = manager.createSettlement(site); + Vector3fc center = settlement.getComponent(LocationComponent.class).getLocalPosition(); + + Optional parcel = manager.placeParcel( + new Vector3i(center, RoundingMode.FLOOR), zone, parcels, buildingQueue, + settlement.getComponent(DistrictFacetComponent.class), MAX_PLACEMENT_ATTEMPTS + ); + assertThat(parcel).isPresent(); + } +}