diff --git a/assets/prefabs/quests/card.prefab b/assets/prefabs/quests/card.prefab new file mode 100644 index 00000000..0a2498da --- /dev/null +++ b/assets/prefabs/quests/card.prefab @@ -0,0 +1,32 @@ +{ + "DisplayName" : { + "name" : "Quest Card" + }, + "Item" : { + "icon" : "Tasks:icons#Questing", + "usage" : "ON_USER", + "consumedOnUse" : true + }, + "Quest" : { + "shortName" : "FetchQuest", + "description" : "Bring me some meat!", + "tasks" : [ + { + "id" : "collectMeat", + "type" : "CollectBlocksTask", + "data" : { + "itemId" : "WildAnimals:Meat", + "amount" : 2 + } + }, + { + "id" : "returnHome", + "type" : "GoToBeaconTask", + "dependsOn" : "collectMeat", + "data" : { + "beaconId" : "homeBeacon" + } + } + ] + } +} diff --git a/assets/textures/beaconIcon.png b/assets/textures/beaconIcon.png new file mode 100644 index 00000000..f9071bc9 Binary files /dev/null and b/assets/textures/beaconIcon.png differ diff --git a/module.txt b/module.txt index 8186f937..3f26e751 100644 --- a/module.txt +++ b/module.txt @@ -15,7 +15,8 @@ {"id" : "CombatSystem", "minVersion" : "0.1.1"}, {"id" : "WildAnimals", "minVersion" : "0.2.0"}, {"id" : "Dialogs", "minVersion" : "0.1.0"}, - {"id": "Hunger", "minVersion": "1.0.1"} + {"id" : "Hunger", "minVersion": "1.0.1"}, + {"id" : "Tasks", "minVersion": "0.2.0"} ], "serverSideOnly" : false, "isGameplay" : true, diff --git a/src/main/java/org/terasology/metalrenegades/quests/AddBeaconOverlayEvent.java b/src/main/java/org/terasology/metalrenegades/quests/AddBeaconOverlayEvent.java new file mode 100644 index 00000000..15b3fedb --- /dev/null +++ b/src/main/java/org/terasology/metalrenegades/quests/AddBeaconOverlayEvent.java @@ -0,0 +1,33 @@ +/* + * Copyright 2019 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.quests; + +import org.terasology.entitySystem.entity.EntityRef; +import org.terasology.entitySystem.event.Event; + +/** + * Adds the beacon overlay to the minimap + */ +public class AddBeaconOverlayEvent implements Event { + public EntityRef beaconEntity; + + public AddBeaconOverlayEvent(EntityRef beaconEntity) { + this.beaconEntity = beaconEntity; + } + + public AddBeaconOverlayEvent() { + } +} diff --git a/src/main/java/org/terasology/metalrenegades/quests/DestroyActiveEntityEvent.java b/src/main/java/org/terasology/metalrenegades/quests/DestroyActiveEntityEvent.java new file mode 100644 index 00000000..be7841c9 --- /dev/null +++ b/src/main/java/org/terasology/metalrenegades/quests/DestroyActiveEntityEvent.java @@ -0,0 +1,24 @@ +/* + * Copyright 2019 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.quests; + +import org.terasology.entitySystem.event.Event; + +/** + * Destroys the active quest entity + */ +public class DestroyActiveEntityEvent implements Event { +} diff --git a/src/main/java/org/terasology/metalrenegades/quests/FetchQuestSystem.java b/src/main/java/org/terasology/metalrenegades/quests/FetchQuestSystem.java new file mode 100644 index 00000000..2ec23ddb --- /dev/null +++ b/src/main/java/org/terasology/metalrenegades/quests/FetchQuestSystem.java @@ -0,0 +1,195 @@ +/* + * Copyright 2019 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.quests; + +import org.terasology.dynamicCities.buildings.GenericBuildingComponent; +import org.terasology.dynamicCities.buildings.components.DynParcelRefComponent; +import org.terasology.dynamicCities.buildings.components.SettlementRefComponent; +import org.terasology.dynamicCities.construction.events.BuildingEntitySpawnedEvent; +import org.terasology.dynamicCities.parcels.DynParcel; +import org.terasology.entitySystem.entity.EntityManager; +import org.terasology.entitySystem.entity.EntityRef; +import org.terasology.entitySystem.event.ReceiveEvent; +import org.terasology.entitySystem.prefab.Prefab; +import org.terasology.entitySystem.systems.BaseComponentSystem; +import org.terasology.entitySystem.systems.RegisterMode; +import org.terasology.entitySystem.systems.RegisterSystem; +import org.terasology.logic.inventory.InventoryManager; +import org.terasology.logic.location.LocationComponent; +import org.terasology.logic.nameTags.NameTagComponent; +import org.terasology.logic.players.LocalPlayer; +import org.terasology.math.geom.Rect2i; +import org.terasology.math.geom.Vector3f; +import org.terasology.metalrenegades.economy.systems.CurrencyManagementSystem; +import org.terasology.network.ClientComponent; +import org.terasology.registry.In; +import org.terasology.rendering.nui.Color; +import org.terasology.tasks.CollectBlocksTask; +import org.terasology.tasks.Task; +import org.terasology.tasks.components.QuestComponent; +import org.terasology.tasks.components.QuestListComponent; +import org.terasology.tasks.components.QuestSourceComponent; +import org.terasology.tasks.events.BeforeQuestEvent; +import org.terasology.tasks.events.QuestCompleteEvent; +import org.terasology.tasks.events.StartTaskEvent; +import org.terasology.tasks.systems.QuestSystem; +import org.terasology.utilities.Assets; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +/** + * Manages the fetch meat quest + */ +@RegisterSystem(RegisterMode.AUTHORITY) +public class FetchQuestSystem extends BaseComponentSystem { + + @In + private EntityManager entityManager; + + @In + private CurrencyManagementSystem currencyManagementSystem; + + @In + private InventoryManager inventoryManager; + + @In + private QuestSystem questSystem; + + @In + private LocalPlayer localPlayer; + + private EntityRef activeQuestEntity; + private Map amounts = new HashMap<>(); + + private final String HOME_TASK_ID = "returnHome"; + private final String FETCH_QUEST_ID = "FetchQuest"; + private final String ITEM_ID = "WildAnimals:Meat"; + private final int REWARD = 50; + + @Override + public void postBegin() { + activeQuestEntity = EntityRef.NULL; + } + + @ReceiveEvent(components = GenericBuildingComponent.class) + public void onChurchSpawn(BuildingEntitySpawnedEvent event, EntityRef entityRef) { + GenericBuildingComponent genericBuildingComponent = entityRef.getComponent(GenericBuildingComponent.class); + if (genericBuildingComponent.name.equals("simplechurch")) { + DynParcel dynParcel = entityRef.getComponent(DynParcelRefComponent.class).dynParcel; + + Optional questPointOptional = Assets.getPrefab("Tasks:QuestPoint"); + if (questPointOptional.isPresent()) { + Rect2i rect2i = dynParcel.shape; + Vector3f spawnPosition = new Vector3f(rect2i.minX() + rect2i.sizeX() / 2, dynParcel.getHeight() + 2, rect2i.minY() + rect2i.sizeY() / 2); + EntityRef questPoint = entityManager.create(questPointOptional.get(), spawnPosition); + SettlementRefComponent settlementRefComponent = entityRef.getComponent(SettlementRefComponent.class); + questPoint.addComponent(settlementRefComponent); + + // Prepare the QuestListComponent + QuestListComponent questListComponent = new QuestListComponent(); + questListComponent.questItems = new ArrayList<>(); + questListComponent.questItems.add("card"); + questPoint.addComponent(questListComponent); + + + // Prepare the NameTagComponent + NameTagComponent nameTagComponent = new NameTagComponent(); + nameTagComponent.text = "Quest"; + nameTagComponent.textColor = Color.YELLOW; + nameTagComponent.scale = 2; + nameTagComponent.yOffset = 2; + questPoint.addComponent(nameTagComponent); + + + // Prepare the LocationComponent + LocationComponent locationComponent = new LocationComponent(); + locationComponent.setWorldPosition(spawnPosition); + questPoint.addOrSaveComponent(locationComponent); + } + } + } + + @ReceiveEvent + public void onQuestActivated(BeforeQuestEvent event, EntityRef questItem) { + activeQuestEntity = questItem.getComponent(QuestSourceComponent.class).source; + + QuestComponent questComponent = questItem.getComponent(QuestComponent.class); + List tasks = questComponent.tasks; + for (Task t : tasks) { + if (t instanceof CollectBlocksTask) { + amounts.put(((CollectBlocksTask) t).getItemId(), ((CollectBlocksTask) t).getTargetAmount()); + } + } + } + + @ReceiveEvent + public void onReturnTaskInitiated(StartTaskEvent event, EntityRef entityRef) { + if (!Objects.equals(event.getQuest().getShortName(), FETCH_QUEST_ID) + || !Objects.equals(event.getTask().getId(), HOME_TASK_ID)) { + return; + } + + LocationComponent locationComponent = activeQuestEntity.getComponent(LocationComponent.class); + Optional beaconOptional = Assets.getPrefab("Tasks:BeaconMark"); + if (beaconOptional.isPresent()) { + EntityRef beacon = entityManager.create(beaconOptional.get(), locationComponent.getWorldPosition()); + activeQuestEntity.destroy(); + activeQuestEntity = beacon; + } + + localPlayer.getCharacterEntity().send(new AddBeaconOverlayEvent(activeQuestEntity)); + } + + @ReceiveEvent + public void onQuestComplete(QuestCompleteEvent event, EntityRef client) { + if (event.isSuccess()) { + // Remove items from inventory + ClientComponent component = client.getComponent(ClientComponent.class); + EntityRef character = component.character; + EntityRef item = EntityRef.NULL; + + for (int i = 0; i < inventoryManager.getNumSlots(character); i++) { + EntityRef current = inventoryManager.getItemInSlot(character, i); + if (!EntityRef.NULL.equals(current) && ITEM_ID.equalsIgnoreCase(current.getParentPrefab().getName())) { + item = current; + break; + } + } + inventoryManager.removeItem(character, EntityRef.NULL, item, true, amounts.getOrDefault(ITEM_ID, 0)); + + // Pay the player + currencyManagementSystem.changeWallet(REWARD); + + // Remove the minmap overlay + localPlayer.getCharacterEntity().send(new RemoveBeaconOverlayEvent(activeQuestEntity)); + + // remove the quest + questSystem.removeQuest(event.getQuest(), true); + +// activeQuestEntity.destroy(); + } + } + + @ReceiveEvent + public void onDestroyActiveEntityEvent(DestroyActiveEntityEvent event, EntityRef character) { + activeQuestEntity.destroy(); + } +} diff --git a/src/main/java/org/terasology/metalrenegades/quests/RemoveBeaconOverlayEvent.java b/src/main/java/org/terasology/metalrenegades/quests/RemoveBeaconOverlayEvent.java new file mode 100644 index 00000000..3a204641 --- /dev/null +++ b/src/main/java/org/terasology/metalrenegades/quests/RemoveBeaconOverlayEvent.java @@ -0,0 +1,30 @@ +/* + * Copyright 2019 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.quests; + +import org.terasology.entitySystem.entity.EntityRef; +import org.terasology.entitySystem.event.Event; + +/** + * Removes the beacon overlay from the minimap + */ +public class RemoveBeaconOverlayEvent implements Event { + public EntityRef beaconEntity; + + public RemoveBeaconOverlayEvent(EntityRef beaconEntity) { + this.beaconEntity = beaconEntity; + } +} diff --git a/src/main/java/org/terasology/metalrenegades/quests/TaskOverlay.java b/src/main/java/org/terasology/metalrenegades/quests/TaskOverlay.java new file mode 100644 index 00000000..1ee3ff80 --- /dev/null +++ b/src/main/java/org/terasology/metalrenegades/quests/TaskOverlay.java @@ -0,0 +1,106 @@ +/* + * Copyright 2019 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.quests; + +import org.terasology.entitySystem.entity.EntityRef; +import org.terasology.logic.location.LocationComponent; +import org.terasology.math.geom.Rect2f; +import org.terasology.math.geom.Rect2fTransformer; +import org.terasology.math.geom.Rect2i; +import org.terasology.math.geom.Vector2f; +import org.terasology.math.geom.Vector2i; +import org.terasology.math.geom.Vector3f; +import org.terasology.minimap.overlays.MinimapOverlay; +import org.terasology.rendering.assets.texture.Texture; +import org.terasology.rendering.nui.Canvas; +import org.terasology.utilities.Assets; + +import java.util.Optional; + +/** + * A minimap overlay to mark the home beacon for the meat quest + */ +public class TaskOverlay implements MinimapOverlay { + + private final int ICON_SIZE = 24; + private EntityRef beaconEntity; + + public TaskOverlay(EntityRef beaconEntity) { + this.beaconEntity = beaconEntity; + } + + @Override + public void render(Canvas canvas, Rect2f worldRect) { + Rect2f screenRect = Rect2f.createFromMinAndSize( + new Vector2f(canvas.getRegion().minX(), canvas.getRegion().minY()), + new Vector2f(canvas.getRegion().maxX(), canvas.getRegion().maxY()) + ); + + Rect2fTransformer transformer = new Rect2fTransformer(worldRect, screenRect); + + Vector3f localPosition = beaconEntity.getComponent(LocationComponent.class).getWorldPosition(); + Vector2f mapPoint = transformer.apply(localPosition.x, localPosition.y); + + Vector2i min = clamp(mapPoint, screenRect); + Rect2i region = Rect2i.createFromMinAndSize(min.x, min.y, ICON_SIZE, ICON_SIZE); + + Optional icon = Assets.getTexture("MetalRenegades:beaconIcon"); + icon.ifPresent(texture -> canvas.drawTexture(texture, region)); + } + + @Override + public int getZOrder() { + return 0; + } + + public EntityRef getBeaconEntity() { + return beaconEntity; + } + + /** + * Constrains a point to a specified region. Works like a vector clamp. + * + * @param point: the coordinates of the point to be clamped + * @param box: limits + * @return new clamped coordinates of point + */ + private Vector2i clamp(Vector2f point, Rect2f box) { + float x; + float y; + Rect2f iconRegion = Rect2f.createFromMinAndSize(point.x, point.y, ICON_SIZE, ICON_SIZE); + + if (box.contains(iconRegion)) { + return new Vector2i(point.x, point.y); + } else { + if (iconRegion.maxX() >= box.maxX()) { + x = (int) box.maxX() - ICON_SIZE; + } else if (iconRegion.minX() <= box.minX()) { + x = (int) box.minX(); + } else { + x = point.x; + } + + if (iconRegion.maxY() >= box.maxY()) { + y = (int) box.maxY() - ICON_SIZE; + } else if (iconRegion.minY() <= box.minY()) { + y = (int) box.minY(); + } else { + y = point.y; + } + } + return new Vector2i(x, y); + } +} diff --git a/src/main/java/org/terasology/metalrenegades/quests/TaskOverlaySystem.java b/src/main/java/org/terasology/metalrenegades/quests/TaskOverlaySystem.java new file mode 100644 index 00000000..20c981e3 --- /dev/null +++ b/src/main/java/org/terasology/metalrenegades/quests/TaskOverlaySystem.java @@ -0,0 +1,89 @@ +/* + * Copyright 2019 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.quests; + +import org.terasology.entitySystem.entity.EntityManager; +import org.terasology.entitySystem.entity.EntityRef; +import org.terasology.entitySystem.event.ReceiveEvent; +import org.terasology.entitySystem.systems.BaseComponentSystem; +import org.terasology.entitySystem.systems.RegisterMode; +import org.terasology.entitySystem.systems.RegisterSystem; +import org.terasology.logic.players.LocalPlayer; +import org.terasology.logic.players.MinimapSystem; +import org.terasology.network.ClientComponent; +import org.terasology.network.NetworkMode; +import org.terasology.network.NetworkSystem; +import org.terasology.registry.In; + +/** + * Manages the beacon minimap overlay + */ +@RegisterSystem(RegisterMode.CLIENT) +public class TaskOverlaySystem extends BaseComponentSystem { + + @In + private NetworkSystem networkSystem; + + @In + private EntityManager entityManager; + + @In + private MinimapSystem minimapSystem; + + @In + private LocalPlayer localPlayer; + + private TaskOverlay overlay; + private EntityRef clientEntity; + private boolean isOverlayAdded = false; + + @Override + public void initialise() { + if (networkSystem.getMode() == NetworkMode.CLIENT) { + clientEntity = networkSystem.getServer().getClientEntity(); + } + } + + @ReceiveEvent + public void onAddBeaconOverlayEvent(AddBeaconOverlayEvent event, EntityRef character) { + overlay = new TaskOverlay(event.beaconEntity); + + if (networkSystem.getMode() == NetworkMode.NONE) { + minimapSystem.addOverlay(overlay); + isOverlayAdded = true; + } + + if (networkSystem.getMode() == NetworkMode.CLIENT) { + if (clientEntity.getComponent(ClientComponent.class).character.getId() == character.getId() && !isOverlayAdded) { + minimapSystem.addOverlay(overlay); + isOverlayAdded = true; + } + } + + if (networkSystem.getMode() == NetworkMode.DEDICATED_SERVER && !isOverlayAdded) { + if (localPlayer.getCharacterEntity() == character) { + minimapSystem.addOverlay(overlay); + } + } + } + + @ReceiveEvent + public void onRemoveBeaconOverlayEvent(RemoveBeaconOverlayEvent event, EntityRef character) { + minimapSystem.removeOverlay(overlay); + isOverlayAdded = false; + character.send(new DestroyActiveEntityEvent()); + } +}