From 056fce969c04f65350c980966efd211af8aa5bdf Mon Sep 17 00:00:00 2001 From: AndyTechGuy Date: Thu, 15 Aug 2019 17:33:55 -0230 Subject: [PATCH 1/4] Add settlement faction based citizen spawning --- .../component/FactionAlignmentComponent.java | 33 +++++++++ .../ai/event/CitizenSpawnedEvent.java | 22 ++++++ .../ai/system/CitizenAlignmentSystem.java | 69 +++++++++++++++++++ .../ai/system/CitizenSpawnSystem.java | 47 ++++++------- 4 files changed, 146 insertions(+), 25 deletions(-) create mode 100644 src/main/java/org/terasology/metalrenegades/ai/component/FactionAlignmentComponent.java create mode 100644 src/main/java/org/terasology/metalrenegades/ai/event/CitizenSpawnedEvent.java create mode 100644 src/main/java/org/terasology/metalrenegades/ai/system/CitizenAlignmentSystem.java diff --git a/src/main/java/org/terasology/metalrenegades/ai/component/FactionAlignmentComponent.java b/src/main/java/org/terasology/metalrenegades/ai/component/FactionAlignmentComponent.java new file mode 100644 index 00000000..5fb256db --- /dev/null +++ b/src/main/java/org/terasology/metalrenegades/ai/component/FactionAlignmentComponent.java @@ -0,0 +1,33 @@ +/* + * Copyright 2018 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. + */ +package org.terasology.metalrenegades.ai.component; + +import org.terasology.entitySystem.Component; +import org.terasology.metalrenegades.ai.system.CitizenAlignmentSystem.Alignment; + +public class FactionAlignmentComponent implements Component { + + public Alignment alignment; + + public FactionAlignmentComponent() { + this.alignment = Alignment.NEUTRAL; + } + + public FactionAlignmentComponent(Alignment alignment) { + this.alignment = alignment; + } + +} diff --git a/src/main/java/org/terasology/metalrenegades/ai/event/CitizenSpawnedEvent.java b/src/main/java/org/terasology/metalrenegades/ai/event/CitizenSpawnedEvent.java new file mode 100644 index 00000000..cef0fe60 --- /dev/null +++ b/src/main/java/org/terasology/metalrenegades/ai/event/CitizenSpawnedEvent.java @@ -0,0 +1,22 @@ +/* + * Copyright 2018 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. + */ +package org.terasology.metalrenegades.ai.event; + +import org.terasology.entitySystem.event.Event; + +public class CitizenSpawnedEvent implements Event { + +} diff --git a/src/main/java/org/terasology/metalrenegades/ai/system/CitizenAlignmentSystem.java b/src/main/java/org/terasology/metalrenegades/ai/system/CitizenAlignmentSystem.java new file mode 100644 index 00000000..4ac98deb --- /dev/null +++ b/src/main/java/org/terasology/metalrenegades/ai/system/CitizenAlignmentSystem.java @@ -0,0 +1,69 @@ +/* + * Copyright 2018 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. + */ +package org.terasology.metalrenegades.ai.system; + +import org.lwjgl.opengl.Display; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.terasology.assets.management.AssetManager; +import org.terasology.dynamicCities.buildings.components.SettlementRefComponent; +import org.terasology.dynamicCities.construction.events.BuildingEntitySpawnedEvent; +import org.terasology.dynamicCities.settlements.events.SettlementRegisterEvent; +import org.terasology.entitySystem.entity.EntityRef; +import org.terasology.entitySystem.event.ReceiveEvent; +import org.terasology.entitySystem.prefab.Prefab; +import org.terasology.entitySystem.prefab.PrefabManager; +import org.terasology.entitySystem.systems.BaseComponentSystem; +import org.terasology.entitySystem.systems.RegisterMode; +import org.terasology.entitySystem.systems.RegisterSystem; +import org.terasology.metalrenegades.ai.component.FactionAlignmentComponent; +import org.terasology.metalrenegades.ai.component.HomeComponent; +import org.terasology.metalrenegades.ai.event.CitizenSpawnedEvent; +import org.terasology.registry.In; +import org.terasology.registry.Share; + +@RegisterSystem(RegisterMode.AUTHORITY) +@Share(value = CitizenAlignmentSystem.class) +public class CitizenAlignmentSystem extends BaseComponentSystem { + + @In + private AssetManager assetManager; + + @In + private PrefabManager prefabManager; + + public enum Alignment { + GOOD("goodCitizen"), + BAD("badCitizen"), + NEUTRAL("gooeyCitizen"); + + private final String assetId; + + Alignment(String assetId) { + this.assetId = assetId; + } + } + + public Prefab getPrefab(Alignment alignment) { + return prefabManager.getPrefab(alignment.assetId); + } + + @ReceiveEvent + public void onSettlementRegisterEvent(SettlementRegisterEvent buildingEntitySpawnedEvent, EntityRef entityRef) { + entityRef.addComponent(new FactionAlignmentComponent(Alignment.values()[(int) (Math.random() * Alignment.values().length)])); + } + +} diff --git a/src/main/java/org/terasology/metalrenegades/ai/system/CitizenSpawnSystem.java b/src/main/java/org/terasology/metalrenegades/ai/system/CitizenSpawnSystem.java index b55fc417..d33e14d5 100644 --- a/src/main/java/org/terasology/metalrenegades/ai/system/CitizenSpawnSystem.java +++ b/src/main/java/org/terasology/metalrenegades/ai/system/CitizenSpawnSystem.java @@ -19,6 +19,7 @@ import org.terasology.dialogs.components.DialogComponent; import org.terasology.dialogs.components.DialogPage; import org.terasology.dialogs.components.DialogResponse; +import org.terasology.dynamicCities.buildings.components.SettlementRefComponent; import org.terasology.entitySystem.entity.EntityBuilder; import org.terasology.entitySystem.entity.EntityManager; import org.terasology.entitySystem.entity.EntityRef; @@ -28,17 +29,21 @@ import org.terasology.entitySystem.systems.RegisterMode; import org.terasology.entitySystem.systems.RegisterSystem; import org.terasology.entitySystem.systems.UpdateSubscriberSystem; +import org.terasology.logic.behavior.core.Actor; import org.terasology.logic.inventory.InventoryManager; import org.terasology.logic.inventory.events.GiveItemEvent; import org.terasology.logic.location.LocationComponent; import org.terasology.metalrenegades.ai.CitizenNeed; import org.terasology.metalrenegades.ai.component.CitizenComponent; +import org.terasology.metalrenegades.ai.component.FactionAlignmentComponent; import org.terasology.metalrenegades.ai.component.HomeComponent; import org.terasology.metalrenegades.ai.component.NeedsComponent; import org.terasology.metalrenegades.ai.component.PotentialHomeComponent; +import org.terasology.metalrenegades.ai.event.CitizenSpawnedEvent; import org.terasology.metalrenegades.economy.MarketCitizenComponent; import org.terasology.metalrenegades.economy.TraderComponent; import org.terasology.metalrenegades.economy.actions.ShowTradingScreenAction; +import org.terasology.registry.CoreRegistry; import org.terasology.registry.In; import java.util.ArrayList; @@ -59,10 +64,19 @@ public class CitizenSpawnSystem extends BaseComponentSystem implements UpdateSub private EntityManager entityManager; @In - private PrefabManager prefabManager; + private InventoryManager inventoryManager; @In - private InventoryManager inventoryManager; + private CitizenAlignmentSystem citizenAlignmentSystem; + + @In + private PrefabManager prefabManager; + + @Override + public void initialise() { + // TODO: Temporary fix for injection malfunction in actions, ideally remove this in the future. + citizenAlignmentSystem = CoreRegistry.get(CitizenAlignmentSystem.class); + } @Override public void update(float delta) { @@ -96,12 +110,10 @@ public void update(float delta) { * @return The new citizen entity, or null if spawning is not possible. */ private EntityRef spawnCitizen(EntityRef homeEntity) { - Prefab citizenPrefab = chooseCitizenPrefab(); - if (citizenPrefab == null) { // if no prefab is available. - return null; - } + SettlementRefComponent settlementRefComponent = homeEntity.getComponent(SettlementRefComponent.class); + FactionAlignmentComponent settlementAlignmentComponent = settlementRefComponent.settlement.getComponent(FactionAlignmentComponent.class); - EntityBuilder entityBuilder = entityManager.newBuilder(chooseCitizenPrefab()); + EntityBuilder entityBuilder = entityManager.newBuilder(citizenAlignmentSystem.getPrefab(settlementAlignmentComponent.alignment)); LocationComponent homeLocationComponent = homeEntity.getComponent(LocationComponent.class); LocationComponent citizenLocationComponent = entityBuilder.getComponent(LocationComponent.class); @@ -121,6 +133,7 @@ private EntityRef spawnCitizen(EntityRef homeEntity) { needsComponent.needs.put(CitizenNeed.Type.REST, new CitizenNeed(50, 0.5f, 20, 50)); entityBuilder.saveComponent(needsComponent); + entityBuilder.addComponent(new TraderComponent()); EntityRef entityRef = entityBuilder.build(); @@ -129,25 +142,9 @@ private EntityRef spawnCitizen(EntityRef homeEntity) { setupStartInventory(entityRef); } - return entityRef; - } + entityRef.send(new CitizenSpawnedEvent()); - /** - * Selects a random citizen prefab from a collection of prefabs with {@link CitizenComponent}. - * - * @return A random citizen prefab, or null if none are available. - */ - private Prefab chooseCitizenPrefab() { - Collection citizenList = prefabManager.listPrefabs(CitizenComponent.class); - citizenList.removeIf(prefab -> prefab.hasComponent(MarketCitizenComponent.class)); - - int i = (int) (Math.random() * citizenList.size()); - for (Prefab prefab : citizenList) { - if (i-- <= 0) { - return prefab; - } - } - return null; + return entityRef; } private void setupStartInventory(EntityRef citizen) { From e507095453bcb4b69f667737d384eb41dd4ef720 Mon Sep 17 00:00:00 2001 From: AndyTechGuy Date: Tue, 20 Aug 2019 19:44:21 -0230 Subject: [PATCH 2/4] Add capability for enemy faciton citizens to attack each other --- assets/behaviors/citizen.behavior | 122 +++++++++++------- assets/prefabs/characters/badCitizen.prefab | 5 +- assets/prefabs/characters/baseCitizen.prefab | 2 + assets/prefabs/characters/goodCitizen.prefab | 5 +- assets/prefabs/characters/gooeyCitizen.prefab | 5 +- .../actions/CheckForCitizenEnemiesAction.java | 86 ++++++++++++ .../ai/actions/SetTargetToEnemyAction.java | 43 ++++++ .../NearbyCitizenEnemiesComponent.java | 31 +++++ 8 files changed, 250 insertions(+), 49 deletions(-) create mode 100644 src/main/java/org/terasology/metalrenegades/ai/actions/CheckForCitizenEnemiesAction.java create mode 100644 src/main/java/org/terasology/metalrenegades/ai/actions/SetTargetToEnemyAction.java create mode 100644 src/main/java/org/terasology/metalrenegades/ai/component/NearbyCitizenEnemiesComponent.java diff --git a/assets/behaviors/citizen.behavior b/assets/behaviors/citizen.behavior index c5673938..51e7ca0c 100644 --- a/assets/behaviors/citizen.behavior +++ b/assets/behaviors/citizen.behavior @@ -1,78 +1,108 @@ { - dynamic: [ + parallel: [ { - selector: [ + dynamic: [ { - sequence: [ - { - check_need: { - needType: "FOOD" - } - }, - { - lookup: { - tree: "MetalRenegades:findFood" - } + guard: { + componentPresent: "MetalRenegades:NearbyCitizenEnemies", + values: ["N enemiesWithinRange nonEmpty"], + child: { + sequence: [ + set_target_to_enemy, + { + lookup: { + tree: "Behaviors:hostile" + } + } + ] } - ] + } }, { - sequence: [ + selector: [ { - check_need: { - needType: "WATER" - } + sequence: [ + { + check_need: { + needType: "FOOD" + } + }, + { + lookup: { + tree: "MetalRenegades:findFood" + } + } + ] }, { - lookup: { - tree: "MetalRenegades:findWater" - } - } - ] - }, - { - sequence: [ + sequence: [ + { + check_need: { + needType: "WATER" + } + }, + { + lookup: { + tree: "MetalRenegades:findWater" + } + } + ] + }, { - check_need: { - needType: "SOCIAL" - } + sequence: [ + { + check_need: { + needType: "SOCIAL" + } + }, + { + lookup: { + tree: "MetalRenegades:findSocial" + } + } + ] }, { - lookup: { - tree: "MetalRenegades:findSocial" - } + sequence: [ + { + check_need: { + needType: "REST" + } + }, + { + lookup: { + tree: "MetalRenegades:findRest" + } + } + ] } ] }, { sequence: [ - { - check_need: { - needType: "REST" - } - }, + check_nighttime, { lookup: { - tree: "MetalRenegades:findRest" + tree: "MetalRenegades:returnHome" } } ] - } - ] - }, - { - sequence: [ - check_nighttime, + }, { lookup: { - tree: "MetalRenegades:returnHome" + tree: "Behaviors:stray" } } ] }, { - lookup: { - tree: "Behaviors:stray" + loop: { + child: { + sequence: [ + { sleep: {time: 2}}, + check_for_citizen_enemies + ] + } } } ] diff --git a/assets/prefabs/characters/badCitizen.prefab b/assets/prefabs/characters/badCitizen.prefab index 0df15ab4..54de84a3 100644 --- a/assets/prefabs/characters/badCitizen.prefab +++ b/assets/prefabs/characters/badCitizen.prefab @@ -43,5 +43,8 @@ "Behavior": { "tree": "MetalRenegades:citizen" }, - "Trader": {} + "Trader": {}, + "FactionAlignment": { + "alignment": "BAD" + } } diff --git a/assets/prefabs/characters/baseCitizen.prefab b/assets/prefabs/characters/baseCitizen.prefab index 81558926..fca52d4e 100644 --- a/assets/prefabs/characters/baseCitizen.prefab +++ b/assets/prefabs/characters/baseCitizen.prefab @@ -63,6 +63,8 @@ ] }, "Citizen": {}, + "NearbyCitizenEnemies": {}, + "AttackOnHit": {}, "Inventory": { "privateToOwner": false, "itemSlots": [ diff --git a/assets/prefabs/characters/goodCitizen.prefab b/assets/prefabs/characters/goodCitizen.prefab index 4bf33810..bfab4236 100644 --- a/assets/prefabs/characters/goodCitizen.prefab +++ b/assets/prefabs/characters/goodCitizen.prefab @@ -43,5 +43,8 @@ "Behavior": { "tree": "MetalRenegades:citizen" }, - "Trader": {} + "Trader": {}, + "FactionAlignment": { + "alignment": "GOOD" + } } diff --git a/assets/prefabs/characters/gooeyCitizen.prefab b/assets/prefabs/characters/gooeyCitizen.prefab index 0f757dc6..bb903edb 100644 --- a/assets/prefabs/characters/gooeyCitizen.prefab +++ b/assets/prefabs/characters/gooeyCitizen.prefab @@ -43,5 +43,8 @@ "Behavior": { "tree": "MetalRenegades:citizen" }, - "Trader": {} + "Trader": {}, + "FactionAlignment": { + "alignment": "NEUTRAL" + } } diff --git a/src/main/java/org/terasology/metalrenegades/ai/actions/CheckForCitizenEnemiesAction.java b/src/main/java/org/terasology/metalrenegades/ai/actions/CheckForCitizenEnemiesAction.java new file mode 100644 index 00000000..2f78acdb --- /dev/null +++ b/src/main/java/org/terasology/metalrenegades/ai/actions/CheckForCitizenEnemiesAction.java @@ -0,0 +1,86 @@ +/* + * Copyright 2018 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. + */ +package org.terasology.metalrenegades.ai.actions; + +import com.google.common.collect.Lists; +import org.terasology.entitySystem.entity.EntityManager; +import org.terasology.entitySystem.entity.EntityRef; +import org.terasology.logic.behavior.BehaviorAction; +import org.terasology.logic.behavior.core.Actor; +import org.terasology.logic.behavior.core.BaseAction; +import org.terasology.logic.behavior.core.BehaviorState; +import org.terasology.logic.location.LocationComponent; +import org.terasology.math.geom.Vector3f; +import org.terasology.metalrenegades.ai.component.CitizenComponent; +import org.terasology.metalrenegades.ai.component.FactionAlignmentComponent; +import org.terasology.metalrenegades.ai.component.NearbyCitizenEnemiesComponent; +import org.terasology.metalrenegades.ai.system.CitizenAlignmentSystem.Alignment; +import org.terasology.registry.In; + +@BehaviorAction(name = "check_for_citizen_enemies") +public class CheckForCitizenEnemiesAction extends BaseAction { + + @In + private EntityManager entityManager; + + @Override + public BehaviorState modify(Actor actor, BehaviorState result) { + if (!actor.hasComponent(NearbyCitizenEnemiesComponent.class) + || !actor.hasComponent(FactionAlignmentComponent.class)) { + return BehaviorState.SUCCESS; + } + + NearbyCitizenEnemiesComponent enemiesComponent = actor.getComponent(NearbyCitizenEnemiesComponent.class); + FactionAlignmentComponent alignmentComponent = actor.getComponent(FactionAlignmentComponent.class); + + enemiesComponent.enemiesWithinRange = Lists.newArrayList(); + enemiesComponent.closestEnemy = EntityRef.NULL; + + float minDistance = Float.MAX_VALUE; + Vector3f actorPosition = actor.getComponent(LocationComponent.class).getWorldPosition(); + + for (EntityRef citizen : entityManager.getEntitiesWith(CitizenComponent.class)) { + if(!citizen.hasComponent(FactionAlignmentComponent.class) + || citizen.equals(actor.getEntity())) { + continue; + } + + FactionAlignmentComponent citizenAlignmentComponent = citizen.getComponent(FactionAlignmentComponent.class); + if (citizenAlignmentComponent.alignment.equals(alignmentComponent.alignment) // continue if alignments are + || citizenAlignmentComponent.alignment.equals(Alignment.NEUTRAL) // the same, or either alignment + || alignmentComponent.alignment.equals(Alignment.NEUTRAL)) { // is neutral. + continue; + } + + LocationComponent citizenLocationComponent = citizen.getComponent(LocationComponent.class); + float distanceApart = citizenLocationComponent.getWorldPosition().distanceSquared(actorPosition); + + if(distanceApart > enemiesComponent.searchRadius) { + continue; + } + + if (distanceApart < minDistance) { + enemiesComponent.closestEnemy = citizen; + minDistance = citizenLocationComponent.getWorldPosition().distanceSquared(actorPosition); + } + + enemiesComponent.enemiesWithinRange.add(citizen); + } + + return BehaviorState.SUCCESS; + } + +} diff --git a/src/main/java/org/terasology/metalrenegades/ai/actions/SetTargetToEnemyAction.java b/src/main/java/org/terasology/metalrenegades/ai/actions/SetTargetToEnemyAction.java new file mode 100644 index 00000000..e3b215e5 --- /dev/null +++ b/src/main/java/org/terasology/metalrenegades/ai/actions/SetTargetToEnemyAction.java @@ -0,0 +1,43 @@ +/* + * Copyright 2018 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. + */ +package org.terasology.metalrenegades.ai.actions; + +import org.terasology.behaviors.components.FollowComponent; +import org.terasology.logic.behavior.BehaviorAction; +import org.terasology.logic.behavior.core.Actor; +import org.terasology.logic.behavior.core.BaseAction; +import org.terasology.logic.behavior.core.BehaviorState; +import org.terasology.metalrenegades.ai.component.NearbyCitizenEnemiesComponent; + +@BehaviorAction(name = "set_target_to_enemy") +public class SetTargetToEnemyAction extends BaseAction { + + @Override + public BehaviorState modify(Actor actor, BehaviorState result) { + if (!actor.hasComponent(NearbyCitizenEnemiesComponent.class)) { + return BehaviorState.SUCCESS; + } + + NearbyCitizenEnemiesComponent enemiesComponent = actor.getComponent(NearbyCitizenEnemiesComponent.class); + FollowComponent followComponent = new FollowComponent(); + + followComponent.entityToFollow = enemiesComponent.closestEnemy; + actor.getEntity().addComponent(followComponent); + + return BehaviorState.SUCCESS; + } + +} diff --git a/src/main/java/org/terasology/metalrenegades/ai/component/NearbyCitizenEnemiesComponent.java b/src/main/java/org/terasology/metalrenegades/ai/component/NearbyCitizenEnemiesComponent.java new file mode 100644 index 00000000..11bb462d --- /dev/null +++ b/src/main/java/org/terasology/metalrenegades/ai/component/NearbyCitizenEnemiesComponent.java @@ -0,0 +1,31 @@ +/* + * Copyright 2018 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. + */ +package org.terasology.metalrenegades.ai.component; + +import org.terasology.entitySystem.Component; +import org.terasology.entitySystem.entity.EntityRef; + +import java.util.List; + +public class NearbyCitizenEnemiesComponent implements Component { + + public float searchRadius = 10f; + + public List enemiesWithinRange; + + public EntityRef closestEnemy; + +} From 9eaa56ccb0385221b8b040e9749a1b9ecba28b04 Mon Sep 17 00:00:00 2001 From: AndyTechGuy Date: Thu, 22 Aug 2019 22:51:08 -0230 Subject: [PATCH 3/4] Change faction enemy detection to be system based --- assets/behaviors/citizen.behavior | 138 ++++++++---------- assets/prefabs/characters/baseCitizen.prefab | 6 +- .../actions/CheckForCitizenEnemiesAction.java | 86 ----------- .../ai/actions/CheckNeedAction.java | 4 + .../NearbyCitizenEnemiesComponent.java | 2 +- .../ai/system/FactionEnemiesSystem.java | 102 +++++++++++++ 6 files changed, 174 insertions(+), 164 deletions(-) delete mode 100644 src/main/java/org/terasology/metalrenegades/ai/actions/CheckForCitizenEnemiesAction.java create mode 100644 src/main/java/org/terasology/metalrenegades/ai/system/FactionEnemiesSystem.java diff --git a/assets/behaviors/citizen.behavior b/assets/behaviors/citizen.behavior index 51e7ca0c..b332987b 100644 --- a/assets/behaviors/citizen.behavior +++ b/assets/behaviors/citizen.behavior @@ -1,108 +1,94 @@ { - parallel: [ + dynamic: [ { - dynamic: [ + guard: { + componentPresent: "MetalRenegades:NearbyCitizenEnemies", + values: ["N enemiesWithinRange nonEmpty"], + child: { + sequence: [ + set_target_to_enemy, + { + lookup: { + tree: "Behaviors:hostile" + } + } + ] + } + } + }, + { + selector: [ { - guard: { - componentPresent: "MetalRenegades:NearbyCitizenEnemies", - values: ["N enemiesWithinRange nonEmpty"], - child: { - sequence: [ - set_target_to_enemy, - { - lookup: { - tree: "Behaviors:hostile" - } - } - ] + sequence: [ + { + check_need: { + needType: "FOOD" + } + }, + { + lookup: { + tree: "MetalRenegades:findFood" + } } - } + ] }, { - selector: [ + sequence: [ { - sequence: [ - { - check_need: { - needType: "FOOD" - } - }, - { - lookup: { - tree: "MetalRenegades:findFood" - } - } - ] + check_need: { + needType: "WATER" + } }, { - sequence: [ - { - check_need: { - needType: "WATER" - } - }, - { - lookup: { - tree: "MetalRenegades:findWater" - } - } - ] - }, + lookup: { + tree: "MetalRenegades:findWater" + } + } + ] + }, + { + sequence: [ { - sequence: [ - { - check_need: { - needType: "SOCIAL" - } - }, - { - lookup: { - tree: "MetalRenegades:findSocial" - } - } - ] + check_need: { + needType: "SOCIAL" + } }, { - sequence: [ - { - check_need: { - needType: "REST" - } - }, - { - lookup: { - tree: "MetalRenegades:findRest" - } - } - ] + lookup: { + tree: "MetalRenegades:findSocial" + } } ] }, { sequence: [ - check_nighttime, + { + check_need: { + needType: "REST" + } + }, { lookup: { - tree: "MetalRenegades:returnHome" + tree: "MetalRenegades:findRest" } } ] - }, + } + ] + }, + { + sequence: [ + check_nighttime, { lookup: { - tree: "Behaviors:stray" + tree: "MetalRenegades:returnHome" } } ] }, { - loop: { - child: { - sequence: [ - { sleep: {time: 2}}, - check_for_citizen_enemies - ] - } + lookup: { + tree: "Behaviors:stray" } } ] diff --git a/assets/prefabs/characters/baseCitizen.prefab b/assets/prefabs/characters/baseCitizen.prefab index fca52d4e..19d79f34 100644 --- a/assets/prefabs/characters/baseCitizen.prefab +++ b/assets/prefabs/characters/baseCitizen.prefab @@ -48,7 +48,11 @@ }, "Network": {}, "MinionMove": {}, - "Health": {}, + "Health": { + "destroyEntityOnNoHealth" : true, + "currentHealth": 30, + "maxHealth": 30 + }, "BoxShape": { "extents": [ 1.0, diff --git a/src/main/java/org/terasology/metalrenegades/ai/actions/CheckForCitizenEnemiesAction.java b/src/main/java/org/terasology/metalrenegades/ai/actions/CheckForCitizenEnemiesAction.java deleted file mode 100644 index 2f78acdb..00000000 --- a/src/main/java/org/terasology/metalrenegades/ai/actions/CheckForCitizenEnemiesAction.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright 2018 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. - */ -package org.terasology.metalrenegades.ai.actions; - -import com.google.common.collect.Lists; -import org.terasology.entitySystem.entity.EntityManager; -import org.terasology.entitySystem.entity.EntityRef; -import org.terasology.logic.behavior.BehaviorAction; -import org.terasology.logic.behavior.core.Actor; -import org.terasology.logic.behavior.core.BaseAction; -import org.terasology.logic.behavior.core.BehaviorState; -import org.terasology.logic.location.LocationComponent; -import org.terasology.math.geom.Vector3f; -import org.terasology.metalrenegades.ai.component.CitizenComponent; -import org.terasology.metalrenegades.ai.component.FactionAlignmentComponent; -import org.terasology.metalrenegades.ai.component.NearbyCitizenEnemiesComponent; -import org.terasology.metalrenegades.ai.system.CitizenAlignmentSystem.Alignment; -import org.terasology.registry.In; - -@BehaviorAction(name = "check_for_citizen_enemies") -public class CheckForCitizenEnemiesAction extends BaseAction { - - @In - private EntityManager entityManager; - - @Override - public BehaviorState modify(Actor actor, BehaviorState result) { - if (!actor.hasComponent(NearbyCitizenEnemiesComponent.class) - || !actor.hasComponent(FactionAlignmentComponent.class)) { - return BehaviorState.SUCCESS; - } - - NearbyCitizenEnemiesComponent enemiesComponent = actor.getComponent(NearbyCitizenEnemiesComponent.class); - FactionAlignmentComponent alignmentComponent = actor.getComponent(FactionAlignmentComponent.class); - - enemiesComponent.enemiesWithinRange = Lists.newArrayList(); - enemiesComponent.closestEnemy = EntityRef.NULL; - - float minDistance = Float.MAX_VALUE; - Vector3f actorPosition = actor.getComponent(LocationComponent.class).getWorldPosition(); - - for (EntityRef citizen : entityManager.getEntitiesWith(CitizenComponent.class)) { - if(!citizen.hasComponent(FactionAlignmentComponent.class) - || citizen.equals(actor.getEntity())) { - continue; - } - - FactionAlignmentComponent citizenAlignmentComponent = citizen.getComponent(FactionAlignmentComponent.class); - if (citizenAlignmentComponent.alignment.equals(alignmentComponent.alignment) // continue if alignments are - || citizenAlignmentComponent.alignment.equals(Alignment.NEUTRAL) // the same, or either alignment - || alignmentComponent.alignment.equals(Alignment.NEUTRAL)) { // is neutral. - continue; - } - - LocationComponent citizenLocationComponent = citizen.getComponent(LocationComponent.class); - float distanceApart = citizenLocationComponent.getWorldPosition().distanceSquared(actorPosition); - - if(distanceApart > enemiesComponent.searchRadius) { - continue; - } - - if (distanceApart < minDistance) { - enemiesComponent.closestEnemy = citizen; - minDistance = citizenLocationComponent.getWorldPosition().distanceSquared(actorPosition); - } - - enemiesComponent.enemiesWithinRange.add(citizen); - } - - return BehaviorState.SUCCESS; - } - -} diff --git a/src/main/java/org/terasology/metalrenegades/ai/actions/CheckNeedAction.java b/src/main/java/org/terasology/metalrenegades/ai/actions/CheckNeedAction.java index 9d435380..78764073 100644 --- a/src/main/java/org/terasology/metalrenegades/ai/actions/CheckNeedAction.java +++ b/src/main/java/org/terasology/metalrenegades/ai/actions/CheckNeedAction.java @@ -32,6 +32,10 @@ public class CheckNeedAction extends BaseAction { @Override public BehaviorState modify(Actor actor, BehaviorState result) { + if(!actor.hasComponent(NeedsComponent.class)) { + return BehaviorState.FAILURE; + } + CitizenNeed.Type needTypeValue = CitizenNeed.Type.valueOf(needType); NeedsComponent needsComponent = actor.getComponent(NeedsComponent.class); diff --git a/src/main/java/org/terasology/metalrenegades/ai/component/NearbyCitizenEnemiesComponent.java b/src/main/java/org/terasology/metalrenegades/ai/component/NearbyCitizenEnemiesComponent.java index 11bb462d..20d026fa 100644 --- a/src/main/java/org/terasology/metalrenegades/ai/component/NearbyCitizenEnemiesComponent.java +++ b/src/main/java/org/terasology/metalrenegades/ai/component/NearbyCitizenEnemiesComponent.java @@ -22,7 +22,7 @@ public class NearbyCitizenEnemiesComponent implements Component { - public float searchRadius = 10f; + public float searchRadius = 20f; public List enemiesWithinRange; diff --git a/src/main/java/org/terasology/metalrenegades/ai/system/FactionEnemiesSystem.java b/src/main/java/org/terasology/metalrenegades/ai/system/FactionEnemiesSystem.java new file mode 100644 index 00000000..ebed975f --- /dev/null +++ b/src/main/java/org/terasology/metalrenegades/ai/system/FactionEnemiesSystem.java @@ -0,0 +1,102 @@ +/* + * Copyright 2018 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. + */ +package org.terasology.metalrenegades.ai.system; + +import com.google.common.collect.Lists; +import org.terasology.entitySystem.entity.EntityManager; +import org.terasology.entitySystem.entity.EntityRef; +import org.terasology.entitySystem.systems.BaseComponentSystem; +import org.terasology.entitySystem.systems.RegisterMode; +import org.terasology.entitySystem.systems.RegisterSystem; +import org.terasology.entitySystem.systems.UpdateSubscriberSystem; +import org.terasology.logic.location.LocationComponent; +import org.terasology.math.geom.Vector3f; +import org.terasology.metalrenegades.ai.component.CitizenComponent; +import org.terasology.metalrenegades.ai.component.FactionAlignmentComponent; +import org.terasology.metalrenegades.ai.component.NearbyCitizenEnemiesComponent; +import org.terasology.registry.In; + +@RegisterSystem(value = RegisterMode.AUTHORITY) +public class FactionEnemiesSystem extends BaseComponentSystem implements UpdateSubscriberSystem { + + private static final float ENEMY_CHECK_DELAY = 5; + + private float counter; + + @In + private EntityManager entityManager; + + @Override + public void update(float delta) { + counter+=delta; + + if(counter < ENEMY_CHECK_DELAY) { + return; + } + + counter = 0; + + for (EntityRef entity : entityManager.getEntitiesWith(NearbyCitizenEnemiesComponent.class)) { + checkForEnemies(entity); + } + } + + private void checkForEnemies(EntityRef citizen) { + if (!citizen.hasComponent(NearbyCitizenEnemiesComponent.class) + || !citizen.hasComponent(FactionAlignmentComponent.class)) { + return; + } + + NearbyCitizenEnemiesComponent enemiesComponent = citizen.getComponent(NearbyCitizenEnemiesComponent.class); + FactionAlignmentComponent alignmentComponent = citizen.getComponent(FactionAlignmentComponent.class); + + enemiesComponent.enemiesWithinRange = Lists.newArrayList(); + enemiesComponent.closestEnemy = EntityRef.NULL; + + float minDistance = Float.MAX_VALUE; + Vector3f actorPosition = citizen.getComponent(LocationComponent.class).getWorldPosition(); + + for (EntityRef otherCitizen : entityManager.getEntitiesWith(CitizenComponent.class)) { + if (!otherCitizen.hasComponent(FactionAlignmentComponent.class) + || otherCitizen.equals(citizen)) { + continue; + } + + FactionAlignmentComponent otherAlignmentComponent = otherCitizen.getComponent(FactionAlignmentComponent.class); + if (otherAlignmentComponent.alignment.equals(alignmentComponent.alignment) // continue if alignments are + || otherAlignmentComponent.alignment.equals(CitizenAlignmentSystem.Alignment.NEUTRAL) // the same, or either alignment + || alignmentComponent.alignment.equals(CitizenAlignmentSystem.Alignment.NEUTRAL)) { // is neutral. + continue; + } + + LocationComponent otherLocationComponent = otherCitizen.getComponent(LocationComponent.class); + float distanceApart = otherLocationComponent.getWorldPosition().distanceSquared(actorPosition); + + if (distanceApart > enemiesComponent.searchRadius * enemiesComponent.searchRadius) { + continue; + } + + if (distanceApart < minDistance) { + enemiesComponent.closestEnemy = otherCitizen; + minDistance = otherLocationComponent.getWorldPosition().distanceSquared(actorPosition); + } + + enemiesComponent.enemiesWithinRange.add(otherCitizen); + } + + citizen.saveComponent(enemiesComponent); + } +} From a8470718f66e79f52acc5227451cb016a091f16b Mon Sep 17 00:00:00 2001 From: AndyTechGuy Date: Thu, 22 Aug 2019 23:04:25 -0230 Subject: [PATCH 4/4] Documentation & readability updates for factions --- .../ai/actions/SetTargetToEnemyAction.java | 4 ++++ .../component/FactionAlignmentComponent.java | 5 +++- .../NearbyCitizenEnemiesComponent.java | 3 +++ .../ai/event/CitizenSpawnedEvent.java | 3 +++ .../ai/system/CitizenSpawnSystem.java | 4 ++-- ...ystem.java => FactionAlignmentSystem.java} | 24 ++++++++++++------- .../ai/system/FactionEnemiesSystem.java | 19 +++++++++++++-- 7 files changed, 48 insertions(+), 14 deletions(-) rename src/main/java/org/terasology/metalrenegades/ai/system/{CitizenAlignmentSystem.java => FactionAlignmentSystem.java} (79%) diff --git a/src/main/java/org/terasology/metalrenegades/ai/actions/SetTargetToEnemyAction.java b/src/main/java/org/terasology/metalrenegades/ai/actions/SetTargetToEnemyAction.java index e3b215e5..f9717ab9 100644 --- a/src/main/java/org/terasology/metalrenegades/ai/actions/SetTargetToEnemyAction.java +++ b/src/main/java/org/terasology/metalrenegades/ai/actions/SetTargetToEnemyAction.java @@ -22,6 +22,10 @@ import org.terasology.logic.behavior.core.BehaviorState; import org.terasology.metalrenegades.ai.component.NearbyCitizenEnemiesComponent; +/** + * Action which sets this agent's move target to the nearest citizen from an enemy faction, as + * defined in {@link NearbyCitizenEnemiesComponent}. + */ @BehaviorAction(name = "set_target_to_enemy") public class SetTargetToEnemyAction extends BaseAction { diff --git a/src/main/java/org/terasology/metalrenegades/ai/component/FactionAlignmentComponent.java b/src/main/java/org/terasology/metalrenegades/ai/component/FactionAlignmentComponent.java index 5fb256db..055b87aa 100644 --- a/src/main/java/org/terasology/metalrenegades/ai/component/FactionAlignmentComponent.java +++ b/src/main/java/org/terasology/metalrenegades/ai/component/FactionAlignmentComponent.java @@ -16,8 +16,11 @@ package org.terasology.metalrenegades.ai.component; import org.terasology.entitySystem.Component; -import org.terasology.metalrenegades.ai.system.CitizenAlignmentSystem.Alignment; +import org.terasology.metalrenegades.ai.system.FactionAlignmentSystem.Alignment; +/** + * Defines the faction alignment of a particular character, building, or settlement. + */ public class FactionAlignmentComponent implements Component { public Alignment alignment; diff --git a/src/main/java/org/terasology/metalrenegades/ai/component/NearbyCitizenEnemiesComponent.java b/src/main/java/org/terasology/metalrenegades/ai/component/NearbyCitizenEnemiesComponent.java index 20d026fa..dcfecde7 100644 --- a/src/main/java/org/terasology/metalrenegades/ai/component/NearbyCitizenEnemiesComponent.java +++ b/src/main/java/org/terasology/metalrenegades/ai/component/NearbyCitizenEnemiesComponent.java @@ -20,6 +20,9 @@ import java.util.List; +/** + * Stores the faction enemies in range of this citizen. + */ public class NearbyCitizenEnemiesComponent implements Component { public float searchRadius = 20f; diff --git a/src/main/java/org/terasology/metalrenegades/ai/event/CitizenSpawnedEvent.java b/src/main/java/org/terasology/metalrenegades/ai/event/CitizenSpawnedEvent.java index cef0fe60..974aebc3 100644 --- a/src/main/java/org/terasology/metalrenegades/ai/event/CitizenSpawnedEvent.java +++ b/src/main/java/org/terasology/metalrenegades/ai/event/CitizenSpawnedEvent.java @@ -17,6 +17,9 @@ import org.terasology.entitySystem.event.Event; +/** + * Fired when a new citizen character is spawned by {@link org.terasology.metalrenegades.ai.system.CitizenSpawnSystem}. + */ public class CitizenSpawnedEvent implements Event { } diff --git a/src/main/java/org/terasology/metalrenegades/ai/system/CitizenSpawnSystem.java b/src/main/java/org/terasology/metalrenegades/ai/system/CitizenSpawnSystem.java index d33e14d5..8c159588 100644 --- a/src/main/java/org/terasology/metalrenegades/ai/system/CitizenSpawnSystem.java +++ b/src/main/java/org/terasology/metalrenegades/ai/system/CitizenSpawnSystem.java @@ -67,7 +67,7 @@ public class CitizenSpawnSystem extends BaseComponentSystem implements UpdateSub private InventoryManager inventoryManager; @In - private CitizenAlignmentSystem citizenAlignmentSystem; + private FactionAlignmentSystem citizenAlignmentSystem; @In private PrefabManager prefabManager; @@ -75,7 +75,7 @@ public class CitizenSpawnSystem extends BaseComponentSystem implements UpdateSub @Override public void initialise() { // TODO: Temporary fix for injection malfunction in actions, ideally remove this in the future. - citizenAlignmentSystem = CoreRegistry.get(CitizenAlignmentSystem.class); + citizenAlignmentSystem = CoreRegistry.get(FactionAlignmentSystem.class); } @Override diff --git a/src/main/java/org/terasology/metalrenegades/ai/system/CitizenAlignmentSystem.java b/src/main/java/org/terasology/metalrenegades/ai/system/FactionAlignmentSystem.java similarity index 79% rename from src/main/java/org/terasology/metalrenegades/ai/system/CitizenAlignmentSystem.java rename to src/main/java/org/terasology/metalrenegades/ai/system/FactionAlignmentSystem.java index 4ac98deb..3f10e1a1 100644 --- a/src/main/java/org/terasology/metalrenegades/ai/system/CitizenAlignmentSystem.java +++ b/src/main/java/org/terasology/metalrenegades/ai/system/FactionAlignmentSystem.java @@ -15,12 +15,7 @@ */ package org.terasology.metalrenegades.ai.system; -import org.lwjgl.opengl.Display; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.terasology.assets.management.AssetManager; -import org.terasology.dynamicCities.buildings.components.SettlementRefComponent; -import org.terasology.dynamicCities.construction.events.BuildingEntitySpawnedEvent; import org.terasology.dynamicCities.settlements.events.SettlementRegisterEvent; import org.terasology.entitySystem.entity.EntityRef; import org.terasology.entitySystem.event.ReceiveEvent; @@ -30,14 +25,16 @@ import org.terasology.entitySystem.systems.RegisterMode; import org.terasology.entitySystem.systems.RegisterSystem; import org.terasology.metalrenegades.ai.component.FactionAlignmentComponent; -import org.terasology.metalrenegades.ai.component.HomeComponent; -import org.terasology.metalrenegades.ai.event.CitizenSpawnedEvent; import org.terasology.registry.In; import org.terasology.registry.Share; +/** + * Assigns a faction to new settlements on spawn, and stores references to the character prefabs for all faction + * types. + */ @RegisterSystem(RegisterMode.AUTHORITY) -@Share(value = CitizenAlignmentSystem.class) -public class CitizenAlignmentSystem extends BaseComponentSystem { +@Share(value = FactionAlignmentSystem.class) +public class FactionAlignmentSystem extends BaseComponentSystem { @In private AssetManager assetManager; @@ -45,6 +42,9 @@ public class CitizenAlignmentSystem extends BaseComponentSystem { @In private PrefabManager prefabManager; + /** + * Defines a particular alignment with an associated character prefab. + */ public enum Alignment { GOOD("goodCitizen"), BAD("badCitizen"), @@ -57,6 +57,12 @@ public enum Alignment { } } + /** + * Returns the prefab associated with a provided faction alignment. + * + * @param alignment The alignment to return a prefab for. + * @return The character prefab for this alignment. + */ public Prefab getPrefab(Alignment alignment) { return prefabManager.getPrefab(alignment.assetId); } diff --git a/src/main/java/org/terasology/metalrenegades/ai/system/FactionEnemiesSystem.java b/src/main/java/org/terasology/metalrenegades/ai/system/FactionEnemiesSystem.java index ebed975f..b7928369 100644 --- a/src/main/java/org/terasology/metalrenegades/ai/system/FactionEnemiesSystem.java +++ b/src/main/java/org/terasology/metalrenegades/ai/system/FactionEnemiesSystem.java @@ -29,6 +29,10 @@ import org.terasology.metalrenegades.ai.component.NearbyCitizenEnemiesComponent; import org.terasology.registry.In; +/** + * Tracks nearby faction enemies much like {@link org.terasology.behaviors.system.FindNearbyPlayersSystem}, and stores + * the results in each citizens {@link NearbyCitizenEnemiesComponent}. + */ @RegisterSystem(value = RegisterMode.AUTHORITY) public class FactionEnemiesSystem extends BaseComponentSystem implements UpdateSubscriberSystem { @@ -54,6 +58,17 @@ public void update(float delta) { } } + /** + * Checks for faction enemies nearby a particular citizen. Enemies are defined as follows: + * {@link org.terasology.metalrenegades.ai.system.FactionAlignmentSystem.Alignment#GOOD} citizens are enemies of + * {@link org.terasology.metalrenegades.ai.system.FactionAlignmentSystem.Alignment#BAD} citizens, and vice-versa, + * while {@link org.terasology.metalrenegades.ai.system.FactionAlignmentSystem.Alignment#NEUTRAL} are enemies with + * no other citizens. + * + * Results are stored in {@link NearbyCitizenEnemiesComponent}. + * + * @param citizen The citizen to check enemies for. + */ private void checkForEnemies(EntityRef citizen) { if (!citizen.hasComponent(NearbyCitizenEnemiesComponent.class) || !citizen.hasComponent(FactionAlignmentComponent.class)) { @@ -77,8 +92,8 @@ private void checkForEnemies(EntityRef citizen) { FactionAlignmentComponent otherAlignmentComponent = otherCitizen.getComponent(FactionAlignmentComponent.class); if (otherAlignmentComponent.alignment.equals(alignmentComponent.alignment) // continue if alignments are - || otherAlignmentComponent.alignment.equals(CitizenAlignmentSystem.Alignment.NEUTRAL) // the same, or either alignment - || alignmentComponent.alignment.equals(CitizenAlignmentSystem.Alignment.NEUTRAL)) { // is neutral. + || otherAlignmentComponent.alignment.equals(FactionAlignmentSystem.Alignment.NEUTRAL) // the same, or either alignment + || alignmentComponent.alignment.equals(FactionAlignmentSystem.Alignment.NEUTRAL)) { // is neutral. continue; }