diff --git a/assets/materials/characters/traderGooey.mat b/assets/materials/characters/marketGooey.mat similarity index 73% rename from assets/materials/characters/traderGooey.mat rename to assets/materials/characters/marketGooey.mat index d8fc4838..41014de4 100644 --- a/assets/materials/characters/traderGooey.mat +++ b/assets/materials/characters/marketGooey.mat @@ -1,7 +1,7 @@ { "shader" : "engine:genericMeshMaterial", "params" : { - "diffuse" : "MetalRenegades:traderGooey", + "diffuse" : "MetalRenegades:marketGooey", "colorOffset" : [1.0, 1.0, 1.0], "textured" : true } diff --git a/assets/prefabs/characters/badCitizen.prefab b/assets/prefabs/characters/badCitizen.prefab index 8777a642..0df15ab4 100644 --- a/assets/prefabs/characters/badCitizen.prefab +++ b/assets/prefabs/characters/badCitizen.prefab @@ -1,9 +1,8 @@ { - "alwaysRelevant": true, + "parent": "baseCitizen", "displayName": { "name": "badCitizen" }, - "persisted": true, "skeletalmesh": { "mesh": "MetalRenegades:gooey", "heightOffset": -2.0, @@ -44,66 +43,5 @@ "Behavior": { "tree": "MetalRenegades:citizen" }, - "Stand": { - "animationPool": [ - "gooeyIdleBreathing", - "gooeyIdleBreathing", - "gooeyIdleBreathing", - "gooeyIdleBreathing", - "gooeyIdleBreathing", - "gooeyIdleBreathing", - "gooeyIdleBreathing", - "gooeyIdleBreathing", - "gooeyIdleBreathing", - "gooeyIdleBreathing", - "gooeyIdleBreathing", - "gooeyIdleBreathing", - "gooeyIdleBreathing", - "gooeyIdleBreathing", - "gooeyIdleBreathing", - "gooeyIdleBreathing", - "gooeyIdleBreathing", - "gooeyIdleBreathing", - "gooeyIdleBreathing", - "gooeyIdleBreathing", - "gooeyIdleBreathing", - "gooeyLookRightLeft", - "gooeyLookRightLeft", - "gooeyLookRightUp" - ] - }, - "Walk": { - "animationPool": [ - "gooeyIdleBreathing" - ] - }, - "Location": {}, - "Character": {}, - "AliveCharacter": {}, - "CharacterMovement" : { - "groundFriction": 16, - "speedMultiplier": 0.3, - "distanceBetweenFootsteps": 0.2, - "distanceBetweenSwimStrokes": 2.5, - "height": 1.0, - "radius": 0.5, - "jumpSpeed": 12 - }, - "Network": {}, - "MinionMove": {}, - "Health": {}, - "BoxShape": { - "extents": [ - 1.0, - 1.0, - 1.0 - ] - }, - "Trigger": { - "detectGroups": [ - "engine:debris", - "engine:sensor" - ] - }, - "Citizen": {} + "Trader": {} } diff --git a/assets/prefabs/characters/baseCitizen.prefab b/assets/prefabs/characters/baseCitizen.prefab new file mode 100644 index 00000000..81558926 --- /dev/null +++ b/assets/prefabs/characters/baseCitizen.prefab @@ -0,0 +1,76 @@ +{ + "alwaysRelevant": true, + "persisted": true, + "Stand": { + "animationPool": [ + "gooeyIdleBreathing", + "gooeyIdleBreathing", + "gooeyIdleBreathing", + "gooeyIdleBreathing", + "gooeyIdleBreathing", + "gooeyIdleBreathing", + "gooeyIdleBreathing", + "gooeyIdleBreathing", + "gooeyIdleBreathing", + "gooeyIdleBreathing", + "gooeyIdleBreathing", + "gooeyIdleBreathing", + "gooeyIdleBreathing", + "gooeyIdleBreathing", + "gooeyIdleBreathing", + "gooeyIdleBreathing", + "gooeyIdleBreathing", + "gooeyIdleBreathing", + "gooeyIdleBreathing", + "gooeyIdleBreathing", + "gooeyIdleBreathing", + "gooeyLookRightLeft", + "gooeyLookRightLeft", + "gooeyLookRightUp" + ] + }, + "Walk": { + "animationPool": [ + "gooeyIdleBreathing" + ] + }, + "Location": {}, + "Character": {}, + "AliveCharacter": {}, + "CharacterMovement" : { + "groundFriction": 16, + "speedMultiplier": 0.3, + "distanceBetweenFootsteps": 0.2, + "distanceBetweenSwimStrokes": 2.5, + "height": 1.0, + "radius": 0.5, + "jumpSpeed": 12 + }, + "Network": {}, + "MinionMove": {}, + "Health": {}, + "BoxShape": { + "extents": [ + 1.0, + 1.0, + 1.0 + ] + }, + "Trigger": { + "detectGroups": [ + "engine:debris", + "engine:sensor" + ] + }, + "Citizen": {}, + "Inventory": { + "privateToOwner": false, + "itemSlots": [ + 0, + 0, + 0, + 0, + 0 + ] + } +} diff --git a/assets/prefabs/characters/goodCitizen.prefab b/assets/prefabs/characters/goodCitizen.prefab index 6a5402ee..4bf33810 100644 --- a/assets/prefabs/characters/goodCitizen.prefab +++ b/assets/prefabs/characters/goodCitizen.prefab @@ -1,9 +1,8 @@ { - "alwaysRelevant": true, + "parent": "baseCitizen", "displayName": { "name": "goodCitizen" }, - "persisted": true, "skeletalmesh": { "mesh": "MetalRenegades:gooey", "heightOffset": -2.0, @@ -44,66 +43,5 @@ "Behavior": { "tree": "MetalRenegades:citizen" }, - "Stand": { - "animationPool": [ - "gooeyIdleBreathing", - "gooeyIdleBreathing", - "gooeyIdleBreathing", - "gooeyIdleBreathing", - "gooeyIdleBreathing", - "gooeyIdleBreathing", - "gooeyIdleBreathing", - "gooeyIdleBreathing", - "gooeyIdleBreathing", - "gooeyIdleBreathing", - "gooeyIdleBreathing", - "gooeyIdleBreathing", - "gooeyIdleBreathing", - "gooeyIdleBreathing", - "gooeyIdleBreathing", - "gooeyIdleBreathing", - "gooeyIdleBreathing", - "gooeyIdleBreathing", - "gooeyIdleBreathing", - "gooeyIdleBreathing", - "gooeyIdleBreathing", - "gooeyLookRightLeft", - "gooeyLookRightLeft", - "gooeyLookRightUp" - ] - }, - "Walk": { - "animationPool": [ - "gooeyIdleBreathing" - ] - }, - "Location": {}, - "Character": {}, - "AliveCharacter": {}, - "CharacterMovement" : { - "groundFriction": 16, - "speedMultiplier": 0.3, - "distanceBetweenFootsteps": 0.2, - "distanceBetweenSwimStrokes": 2.5, - "height": 1.0, - "radius": 0.5, - "jumpSpeed": 12 - }, - "Network": {}, - "MinionMove": {}, - "Health": {}, - "BoxShape": { - "extents": [ - 1.0, - 1.0, - 1.0 - ] - }, - "Trigger": { - "detectGroups": [ - "engine:debris", - "engine:sensor" - ] - }, - "Citizen": {} + "Trader": {} } diff --git a/assets/prefabs/characters/gooeyCitizen.prefab b/assets/prefabs/characters/gooeyCitizen.prefab index 3a2f3f35..0f757dc6 100644 --- a/assets/prefabs/characters/gooeyCitizen.prefab +++ b/assets/prefabs/characters/gooeyCitizen.prefab @@ -1,9 +1,8 @@ { - "alwaysRelevant": true, + "parent": "baseCitizen", "displayName": { "name": "gooeyCitizen" }, - "persisted": true, "skeletalmesh": { "mesh": "MetalRenegades:gooey", "heightOffset": -2.0, @@ -44,66 +43,5 @@ "Behavior": { "tree": "MetalRenegades:citizen" }, - "Stand": { - "animationPool": [ - "gooeyIdleBreathing", - "gooeyIdleBreathing", - "gooeyIdleBreathing", - "gooeyIdleBreathing", - "gooeyIdleBreathing", - "gooeyIdleBreathing", - "gooeyIdleBreathing", - "gooeyIdleBreathing", - "gooeyIdleBreathing", - "gooeyIdleBreathing", - "gooeyIdleBreathing", - "gooeyIdleBreathing", - "gooeyIdleBreathing", - "gooeyIdleBreathing", - "gooeyIdleBreathing", - "gooeyIdleBreathing", - "gooeyIdleBreathing", - "gooeyIdleBreathing", - "gooeyIdleBreathing", - "gooeyIdleBreathing", - "gooeyIdleBreathing", - "gooeyLookRightLeft", - "gooeyLookRightLeft", - "gooeyLookRightUp" - ] - }, - "Walk": { - "animationPool": [ - "gooeyIdleBreathing" - ] - }, - "Location": {}, - "Character": {}, - "AliveCharacter": {}, - "CharacterMovement" : { - "groundFriction": 16, - "speedMultiplier": 0.3, - "distanceBetweenFootsteps": 0.2, - "distanceBetweenSwimStrokes": 2.5, - "height": 1.0, - "radius": 0.5, - "jumpSpeed": 12 - }, - "Network": {}, - "MinionMove": {}, - "Health": {}, - "BoxShape": { - "extents": [ - 1.0, - 1.0, - 1.0 - ] - }, - "Trigger": { - "detectGroups": [ - "engine:debris", - "engine:sensor" - ] - }, - "Citizen": {} + "Trader": {} } diff --git a/assets/prefabs/characters/marketCitizen.prefab b/assets/prefabs/characters/marketCitizen.prefab new file mode 100644 index 00000000..960b303c --- /dev/null +++ b/assets/prefabs/characters/marketCitizen.prefab @@ -0,0 +1,47 @@ +{ + "parent": "baseCitizen", + "displayName": { + "name": "shopkeeper" + }, + "skeletalmesh": { + "mesh": "MetalRenegades:gooey", + "heightOffset": -2.0, + "material": "MetalRenegades:marketGooey", + "animationPool": [ + "gooeyIdleBreathing", + "gooeyIdleBreathing", + "gooeyIdleBreathing", + "gooeyIdleBreathing", + "gooeyIdleBreathing", + "gooeyIdleBreathing", + "gooeyIdleBreathing", + "gooeyIdleBreathing", + "gooeyIdleBreathing", + "gooeyIdleBreathing", + "gooeyIdleBreathing", + "gooeyIdleBreathing", + "gooeyIdleBreathing", + "gooeyIdleBreathing", + "gooeyIdleBreathing", + "gooeyIdleBreathing", + "gooeyIdleBreathing", + "gooeyIdleBreathing", + "gooeyIdleBreathing", + "gooeyIdleBreathing", + "gooeyIdleBreathing", + "gooeyLookRightLeft", + "gooeyLookRightLeft", + "gooeyLookRightUp" + ], + "loop": true, + "scale": [ + 0.25, + 0.25, + 0.25 + ] + }, + "Behavior" : { + "tree" : "Behaviors:stray" + }, + "MarketCitizen": {} +} diff --git a/assets/prefabs/characters/traderGooey.prefab b/assets/prefabs/characters/traderGooey.prefab deleted file mode 100644 index 3f7e72ec..00000000 --- a/assets/prefabs/characters/traderGooey.prefab +++ /dev/null @@ -1,46 +0,0 @@ -{ - "displayName": { - "name": "trader" - }, - "persisted": true, - "skeletalmesh" : { - "mesh" : "MetalRenegades:gooey", - "heightOffset" : -0.8, - "material" : "MetalRenegades:traderGooey", - "animation" : "gooeyIdleBreathing", - "loop" : true, - "scale": [ - 0.25, - 0.25, - 0.25 - ] - }, - "Behavior" : { - "tree" : "Behaviors:stray" - }, - "Stand" :{ - "animationPool": ["gooeyIdleBreathing"] - }, - "Walk" :{ - "animationPool": ["gooeyIdleBreathing"] - }, - "location": {}, - "Character": {}, - "AliveCharacter": {}, - "Network": {}, - "MinionMove": {}, - "BoxShape": { - "extents": [ - 1.0, - 1.0, - 1.0 - ] - }, - "Trigger": { - "detectGroups": [ - "engine:debris", - "engine:sensor" - ] - }, - "Resident": {} -} diff --git a/assets/textures/characters/traderGooey.png b/assets/textures/characters/marketGooey.png similarity index 100% rename from assets/textures/characters/traderGooey.png rename to assets/textures/characters/marketGooey.png diff --git a/assets/ui/tradingScreen.ui b/assets/ui/tradingScreen.ui new file mode 100644 index 00000000..750e1c27 --- /dev/null +++ b/assets/ui/tradingScreen.ui @@ -0,0 +1,140 @@ +{ + "type": "tradingScreen", + "skin": "engineDefault", + "contents": { + "type": "relativeLayout", + "contents": [ + { + "type": "UIBox", + "layoutInfo": { + "width": 850, + "height": 850, + "position-horizontal-center": {}, + "position-vertical-center": {} + }, + "content": { + "type": "relativeLayout", + "contents": [ + { + "type": "UILabel", + "text": "Citizen", + "layoutInfo": { + "use-content-width": true, + "use-content-height": true, + "position-left": {}, + "position-top": { + "offset": 20 + } + } + }, + { + "type": "UIList", + "id": "citizenList", + "layoutInfo": { + "width": 400, + "use-content-height": true, + "position-left": {}, + "position-top": { + "offset": 50 + } + } + }, + { + "type": "UILabel", + "id": "citizenCost", + "text": "Cost", + "layoutInfo": { + "use-content-width": true, + "use-content-height": true, + "position-left": {}, + "position-bottom": { + "offset": 10 + } + } + }, + { + "type": "UILabel", + "text": "Player", + "layoutInfo": { + "use-content-width": true, + "use-content-height": true, + "position-right": {}, + "position-top": { + "offset": 20 + } + } + }, + { + "type": "UIList", + "id": "playerList", + "layoutInfo": { + "width": 400, + "use-content-height": true, + "position-right": {}, + "position-top": { + "offset": 50 + } + } + }, + { + "type": "UILabel", + "id": "playerCost", + "text": "Cost", + "layoutInfo": { + "use-content-width": true, + "use-content-height": true, + "position-right": {}, + "position-bottom": { + "offset": 10 + } + } + }, + { + "type": "UILabel", + "id": "message", + "text": "label", + "layoutInfo": { + "use-content-width": true, + "use-content-height": true, + "position-horizontal-center": {}, + "position-bottom": { + "offset": 60 + } + } + }, + { + "type": "UIButton", + "id": "tradeButton", + "text": "Trade", + "layoutInfo": { + "width": 90, + "use-content-height": true, + "position-horizontal-center": { + "offset": -50 + }, + "position-bottom": { + "offset": 20 + } + } + }, + { + "type": "UIButton", + "id": "cancelButton", + "text": "Cancel", + "layoutInfo": { + "width": 90, + "use-content-height": true, + "position-horizontal-center": { + "offset": 50 + }, + "position-bottom": { + "offset": 20 + } + } + } + ] + } + } + ] + } +} 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 9663de0f..b55fc417 100644 --- a/src/main/java/org/terasology/metalrenegades/ai/system/CitizenSpawnSystem.java +++ b/src/main/java/org/terasology/metalrenegades/ai/system/CitizenSpawnSystem.java @@ -15,6 +15,10 @@ */ package org.terasology.metalrenegades.ai.system; +import org.terasology.dialogs.action.CloseDialogAction; +import org.terasology.dialogs.components.DialogComponent; +import org.terasology.dialogs.components.DialogPage; +import org.terasology.dialogs.components.DialogResponse; import org.terasology.entitySystem.entity.EntityBuilder; import org.terasology.entitySystem.entity.EntityManager; import org.terasology.entitySystem.entity.EntityRef; @@ -24,14 +28,20 @@ import org.terasology.entitySystem.systems.RegisterMode; import org.terasology.entitySystem.systems.RegisterSystem; import org.terasology.entitySystem.systems.UpdateSubscriberSystem; +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.HomeComponent; import org.terasology.metalrenegades.ai.component.NeedsComponent; import org.terasology.metalrenegades.ai.component.PotentialHomeComponent; +import org.terasology.metalrenegades.economy.MarketCitizenComponent; +import org.terasology.metalrenegades.economy.TraderComponent; +import org.terasology.metalrenegades.economy.actions.ShowTradingScreenAction; import org.terasology.registry.In; +import java.util.ArrayList; import java.util.Collection; /** @@ -51,6 +61,9 @@ public class CitizenSpawnSystem extends BaseComponentSystem implements UpdateSub @In private PrefabManager prefabManager; + @In + private InventoryManager inventoryManager; + @Override public void update(float delta) { spawnTimer += delta; @@ -109,7 +122,14 @@ private EntityRef spawnCitizen(EntityRef homeEntity) { entityBuilder.saveComponent(needsComponent); - return entityBuilder.build(); + EntityRef entityRef = entityBuilder.build(); + + if (entityRef.hasComponent(TraderComponent.class)) { + entityRef.addComponent(createTradeDialogComponent()); + setupStartInventory(entityRef); + } + + return entityRef; } /** @@ -119,13 +139,51 @@ private EntityRef spawnCitizen(EntityRef homeEntity) { */ 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) { + for (Prefab prefab : citizenList) { if (i-- <= 0) { return prefab; } } return null; } + + private void setupStartInventory(EntityRef citizen) { + Prefab railgun = prefabManager.getPrefab("Core:railgunTool"); + EntityRef item = entityManager.create(railgun); + item.send(new GiveItemEvent(citizen)); + } + + private DialogComponent createTradeDialogComponent() { + DialogComponent component = new DialogComponent(); + component.pages = new ArrayList<>(); + + DialogPage page = new DialogPage(); + page.id = "main"; + page.title = "Wanna trade?"; + page.paragraphText = new ArrayList<>(); + page.responses = new ArrayList<>(); + + page.paragraphText.add("I've got wares"); + + DialogResponse tradeResponse = new DialogResponse(); + tradeResponse.text = "Show me what you got"; + tradeResponse.action = new ArrayList<>(); + tradeResponse.action.add(new ShowTradingScreenAction()); + + DialogResponse closeResponse = new DialogResponse(); + closeResponse.text = "Later"; + closeResponse.action = new ArrayList<>(); + closeResponse.action.add(new CloseDialogAction()); + + page.responses.add(tradeResponse); + page.responses.add(closeResponse); + + component.pages.add(page); + component.firstPage = page.id; + + return component; + } } diff --git a/src/main/java/org/terasology/metalrenegades/economy/MarketCitizenComponent.java b/src/main/java/org/terasology/metalrenegades/economy/MarketCitizenComponent.java new file mode 100644 index 00000000..c9a24577 --- /dev/null +++ b/src/main/java/org/terasology/metalrenegades/economy/MarketCitizenComponent.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.economy; + +import org.terasology.entitySystem.Component; + +/** + * Marks a citizen which will only spawn in markets + */ +public class MarketCitizenComponent implements Component { +} diff --git a/src/main/java/org/terasology/metalrenegades/economy/TraderComponent.java b/src/main/java/org/terasology/metalrenegades/economy/TraderComponent.java new file mode 100644 index 00000000..54f2ac41 --- /dev/null +++ b/src/main/java/org/terasology/metalrenegades/economy/TraderComponent.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.economy; + +import org.terasology.entitySystem.Component; + +/** + * Marks a citizen with which the player can trade + */ +public class TraderComponent implements Component { +} diff --git a/src/main/java/org/terasology/metalrenegades/economy/actions/ShowTradingScreenAction.java b/src/main/java/org/terasology/metalrenegades/economy/actions/ShowTradingScreenAction.java new file mode 100644 index 00000000..6a3aeb9c --- /dev/null +++ b/src/main/java/org/terasology/metalrenegades/economy/actions/ShowTradingScreenAction.java @@ -0,0 +1,34 @@ +/* + * 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.economy.actions; + +import org.terasology.dialogs.action.PlayerAction; +import org.terasology.entitySystem.entity.EntityRef; +import org.terasology.metalrenegades.economy.events.TradeScreenRequestEvent; + +/** + * Calls an event which would bring up the trading UI + */ +public class ShowTradingScreenAction implements PlayerAction { + + public ShowTradingScreenAction() { + } + + @Override + public void execute(EntityRef charEntity, EntityRef talkTo) { + talkTo.send(new TradeScreenRequestEvent()); + } +} diff --git a/src/main/java/org/terasology/metalrenegades/economy/actions/ShowTradingScreenActionTypeHandler.java b/src/main/java/org/terasology/metalrenegades/economy/actions/ShowTradingScreenActionTypeHandler.java new file mode 100644 index 00000000..e4daa4ad --- /dev/null +++ b/src/main/java/org/terasology/metalrenegades/economy/actions/ShowTradingScreenActionTypeHandler.java @@ -0,0 +1,45 @@ +/* + * 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.economy.actions; + +import com.google.common.collect.ImmutableMap; +import org.terasology.persistence.typeHandling.DeserializationContext; +import org.terasology.persistence.typeHandling.PersistedData; +import org.terasology.persistence.typeHandling.PersistedDataMap; +import org.terasology.persistence.typeHandling.RegisterTypeHandler; +import org.terasology.persistence.typeHandling.SerializationContext; +import org.terasology.persistence.typeHandling.SimpleTypeHandler; + +import java.util.Map; + +@RegisterTypeHandler +public class ShowTradingScreenActionTypeHandler extends SimpleTypeHandler { + + @Override + public PersistedData serialize(ShowTradingScreenAction action, SerializationContext context) { + Map data = ImmutableMap.of( + "type", context.create(action.getClass().getSimpleName())); + + return context.create(data); + } + + @Override + public ShowTradingScreenAction deserialize(PersistedData data, DeserializationContext context) { + return new ShowTradingScreenAction(); + } + +} diff --git a/src/main/java/org/terasology/metalrenegades/economy/events/TradeScreenRequestEvent.java b/src/main/java/org/terasology/metalrenegades/economy/events/TradeScreenRequestEvent.java new file mode 100644 index 00000000..0efd5fab --- /dev/null +++ b/src/main/java/org/terasology/metalrenegades/economy/events/TradeScreenRequestEvent.java @@ -0,0 +1,26 @@ +/* + * 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.economy.events; + +import org.terasology.entitySystem.event.Event; + +/** + * Event fired when the trading UI needs to be brought up + */ +public class TradeScreenRequestEvent implements Event { + public TradeScreenRequestEvent() { + } +} diff --git a/src/main/java/org/terasology/metalrenegades/economy/systems/TraderSpawnSystem.java b/src/main/java/org/terasology/metalrenegades/economy/systems/MarketCitizenSpawnSystem.java similarity index 95% rename from src/main/java/org/terasology/metalrenegades/economy/systems/TraderSpawnSystem.java rename to src/main/java/org/terasology/metalrenegades/economy/systems/MarketCitizenSpawnSystem.java index 10abf8dc..995e2c2f 100644 --- a/src/main/java/org/terasology/metalrenegades/economy/systems/TraderSpawnSystem.java +++ b/src/main/java/org/terasology/metalrenegades/economy/systems/MarketCitizenSpawnSystem.java @@ -44,15 +44,15 @@ import java.util.Optional; /** - * Spawns a trader in all markets + * Spawns a market citizen in all markets */ @RegisterSystem -public class TraderSpawnSystem extends BaseComponentSystem { +public class MarketCitizenSpawnSystem extends BaseComponentSystem { @In private EntityManager entityManager; - private Logger logger = LoggerFactory.getLogger(TraderSpawnSystem.class); + private Logger logger = LoggerFactory.getLogger(MarketCitizenSpawnSystem.class); @ReceiveEvent(components = GenericBuildingComponent.class) public void onMarketPlaceSpawn(BuildingEntitySpawnedEvent event, EntityRef entityRef) { @@ -60,7 +60,7 @@ public void onMarketPlaceSpawn(BuildingEntitySpawnedEvent event, EntityRef entit if (genericBuildingComponent.name.equals("marketplace")) { DynParcel dynParcel = entityRef.getComponent(DynParcelRefComponent.class).dynParcel; - Optional traderGooeyOptional = Assets.getPrefab("MetalRenegades:neutralGooey"); + Optional traderGooeyOptional = Assets.getPrefab("MetalRenegades:marketCitizen"); if (traderGooeyOptional.isPresent()) { Rect2i rect2i = dynParcel.shape; Vector3f spawnPosition = new Vector3f(rect2i.minX() + rect2i.sizeX() / 2, dynParcel.getHeight() + 1, rect2i.minY() + rect2i.sizeY() / 2); diff --git a/src/main/java/org/terasology/metalrenegades/economy/ui/MarketScreen.java b/src/main/java/org/terasology/metalrenegades/economy/ui/MarketScreen.java index fcbcee62..f19c7a1d 100644 --- a/src/main/java/org/terasology/metalrenegades/economy/ui/MarketScreen.java +++ b/src/main/java/org/terasology/metalrenegades/economy/ui/MarketScreen.java @@ -22,6 +22,7 @@ import org.terasology.metalrenegades.economy.systems.MarketManagementSystem; import org.terasology.registry.In; import org.terasology.rendering.nui.CoreScreenLayer; +import org.terasology.rendering.nui.NUIManager; import org.terasology.rendering.nui.databinding.ReadOnlyBinding; import org.terasology.rendering.nui.itemRendering.StringTextRenderer; import org.terasology.rendering.nui.widgets.UIButton; @@ -43,6 +44,9 @@ public class MarketScreen extends CoreScreenLayer { @In private MarketManagementSystem marketManagementSystem; + @In + private NUIManager nuiManager; + private UIList items; private UILabel name; @@ -142,9 +146,7 @@ public List get() { // Initialise back button back = find("back", UIButton.class); back.subscribe(widget -> { - // TODO: Close the window when back is pressed - selected = MarketItemBuilder.getEmpty(); - logger.info("Closing the UI..."); + nuiManager.closeScreen("MetalRenegades:marketScreen"); }); } @@ -153,6 +155,13 @@ public boolean isModal() { return true; } + @Override + public void onClosed() { + super.onClosed(); + selected = MarketItemBuilder.getEmpty(); + items.setSelection(null); + } + public void setItemList(List resources) { list = resources; } diff --git a/src/main/java/org/terasology/metalrenegades/economy/ui/TradingScreen.java b/src/main/java/org/terasology/metalrenegades/economy/ui/TradingScreen.java new file mode 100644 index 00000000..dca71c7a --- /dev/null +++ b/src/main/java/org/terasology/metalrenegades/economy/ui/TradingScreen.java @@ -0,0 +1,180 @@ +/* + * 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.economy.ui; + +import org.terasology.registry.In; +import org.terasology.rendering.nui.CoreScreenLayer; +import org.terasology.rendering.nui.NUIManager; +import org.terasology.rendering.nui.databinding.ReadOnlyBinding; +import org.terasology.rendering.nui.itemRendering.StringTextRenderer; +import org.terasology.rendering.nui.widgets.UIButton; +import org.terasology.rendering.nui.widgets.UILabel; +import org.terasology.rendering.nui.widgets.UIList; + +import java.util.ArrayList; +import java.util.List; + +/** + * UI for trading with citizens + */ +public class TradingScreen extends CoreScreenLayer { + + @In + private TradingUISystem tradingUISystem; + + @In + private NUIManager nuiManager; + + /** + * UI Elements + */ + private UIList pList; + private UIList cList; + private UIButton confirm; + private UIButton cancel; + private UILabel result; + private UILabel pCost; + private UILabel cCost; + + /** + * Information for UILists + */ + private List pItems = new ArrayList<>(); + private List cItems = new ArrayList<>(); + + /** + * Selected items + */ + private MarketItem pSelected = MarketItemBuilder.getEmpty(); + private MarketItem cSelected = MarketItemBuilder.getEmpty(); + + /** + * Trade result message + */ + private String message; + + @Override + public void initialise() { + + // Initialize player inventory list + pList = find("playerList", UIList.class); + pList.setList(new ArrayList<>()); + pList.setItemRenderer(new StringTextRenderer() { + @Override + public String getString(MarketItem value) { + return value.name; + } + }); + pList.subscribeSelection(((widget, item) -> pSelected = item)); + pList.bindList(new ReadOnlyBinding>() { + @Override + public List get() { + return pItems; + } + }); + + // Initialize citizen inventory list + cList = find("citizenList", UIList.class); + cList.setList(new ArrayList<>()); + cList.setItemRenderer(new StringTextRenderer() { + @Override + public String getString(MarketItem value) { + return value.name; + } + }); + cList.subscribeSelection(((widget, item) -> cSelected = item)); + cList.bindList(new ReadOnlyBinding>() { + @Override + public List get() { + return cItems; + } + }); + + // Initialize result message label + result = find("message", UILabel.class); + result.bindText(new ReadOnlyBinding() { + @Override + public String get() { + return message; + } + }); + + // Initialize confirm trade button + confirm = find("tradeButton", UIButton.class); + confirm.subscribe(widget -> { + if (tradingUISystem.isAcceptable(pList.getSelection(), cList.getSelection())) { + if (tradingUISystem.trade(pList.getSelection(), cList.getSelection())) { + message = "Trade completed."; + tradingUISystem.refreshLists(); + } else { + message = "Trade failed."; + } + } else { + message = "Offer declined."; + } + }); + + // Initialize close dialogue button + cancel = find("cancelButton", UIButton.class); + cancel.subscribe(widget -> { + nuiManager.closeScreen("MetalRenegades:tradingScreen"); + }); + + // Initialize player item cost label + pCost = find("playerCost", UILabel.class); + pCost.bindText(new ReadOnlyBinding() { + @Override + public String get() { + int cost = (pSelected != null) ? (pSelected.cost) : 0; + return "Cost: " + cost; + } + }); + + // Initialize citizen item cost label + cCost = find("citizenCost", UILabel.class); + cCost.bindText(new ReadOnlyBinding() { + @Override + public String get() { + int cost = (cSelected != null) ? (cSelected.cost) : 0; + return "Cost: " + cost; + } + }); + } + + /** + * Set the player's inventory items + * @param list: Content for the player's UIList + */ + public void setPlayerItems(List list) { + pItems = list; + } + + /** + * Set the citizen's inventory items + * @param list: Content for the citizen's UIList + */ + public void setCitizenItems(List list) { + this.cItems = list; + } + + @Override + public void onClosed() { + super.onClosed(); + message = ""; + pList.setSelection(null); + cList.setSelection(null); + } +} diff --git a/src/main/java/org/terasology/metalrenegades/economy/ui/TradingUISystem.java b/src/main/java/org/terasology/metalrenegades/economy/ui/TradingUISystem.java new file mode 100644 index 00000000..8bd110ec --- /dev/null +++ b/src/main/java/org/terasology/metalrenegades/economy/ui/TradingUISystem.java @@ -0,0 +1,252 @@ +/* + * 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.economy.ui; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.terasology.assets.ResourceUrn; +import org.terasology.assets.management.AssetManager; +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.characters.interactions.InteractionUtil; +import org.terasology.logic.inventory.InventoryManager; +import org.terasology.logic.inventory.ItemComponent; +import org.terasology.logic.inventory.events.GiveItemEvent; +import org.terasology.logic.players.LocalPlayer; +import org.terasology.math.TeraMath; +import org.terasology.metalrenegades.economy.events.TradeScreenRequestEvent; +import org.terasology.registry.In; +import org.terasology.registry.Share; +import org.terasology.rendering.nui.NUIManager; +import org.terasology.world.block.entity.BlockCommands; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import java.util.Set; + +/** + * System which handles the data presented to the TradingScreen + */ +@Share(TradingUISystem.class) +@RegisterSystem(RegisterMode.CLIENT) +public class TradingUISystem extends BaseComponentSystem { + + @In + private NUIManager nuiManager; + + @In + private InventoryManager inventoryManager; + + @In + private LocalPlayer localPlayer; + + @In + private AssetManager assetManager; + + @In + private EntityManager entityManager; + + @In + private BlockCommands blockCommands; + + /** + * Maximum percentage difference between two values for them to be considered about equal + */ + private final int MARGIN_PERCENTAGE = 20; + + /** + * Probability that a trade will be accepted, provided the costs are about equal + */ + private final int PROBABILITY = 50; + + /** + * Citizen entity that the player is trading with + */ + private EntityRef targetCitizen = EntityRef.NULL; + + private TradingScreen tradingScreen; + private Logger logger = LoggerFactory.getLogger(TradingUISystem.class); + + @Override + public void initialise() { + tradingScreen = (TradingScreen) nuiManager.createScreen("MetalRenegades:tradingScreen"); + } + + @ReceiveEvent + public void onToggleInventory(TradeScreenRequestEvent event, EntityRef entity) { + ResourceUrn activeInteractionScreenUri = InteractionUtil.getActiveInteractionScreenUri(entity); + if (activeInteractionScreenUri != null) { + InteractionUtil.cancelInteractionAsClient(entity); + } + + nuiManager.toggleScreen("MetalRenegades:tradingScreen"); + } + + @ReceiveEvent + public void onTradingScreenAction(TradeScreenRequestEvent event, EntityRef citizen) { + targetCitizen = citizen; + refreshLists(); + } + + /** + * Start the trading process for the specified items + * @param pItem: MarketItem for the player's item + * @param cItem: MarketItem for the citizen's item + * @return boolean indicating successful or failed trade attempt + */ + public boolean trade(MarketItem pItem, MarketItem cItem) { + if (targetCitizen == EntityRef.NULL) { + return false; + } + + try { + // remove item from citizen's inventory + remove(cItem, targetCitizen); + + // add item to player's inventory + add(cItem, localPlayer.getCharacterEntity()); + + // remove item from player's inventory + remove(pItem, localPlayer.getCharacterEntity()); + + // add item to citizen's inventory + add(pItem, targetCitizen); + } catch (Exception e) { + logger.error("Trade failed. Exception: {}", e.getMessage()); + return false; + } + + return true; + } + + /** + * Calculates if the trade will be acceptable to the citizen based on market costs + * @param pItem: MarketItem for the player's item + * @param cItem: MarketItem for the citizen's item + * @return boolean indicating if the trade is acceptable or not + */ + public boolean isAcceptable(MarketItem pItem, MarketItem cItem) { + Random rnd = new Random(); + return isAboutEqual(pItem.cost, cItem.cost) && (rnd.nextInt(100) < PROBABILITY); + } + + /** + * Calls appropriate functions to update player and citizen's inventories in the UI + */ + public void refreshLists() { + refreshCitizenList(); + refreshPlayerList(); + } + + /** + * Determines if two costs are about equal, depending on MARGIN_PERCENTAGE + * @param pCost: Integer cost of the player's item + * @param cCost: Integer cost of the citizen's item + * @return boolean indicating if the two costs are about equal + */ + private boolean isAboutEqual(int pCost, int cCost) { + int delta = TeraMath.fastAbs(pCost - cCost); + return ((float)(delta / cCost) * 100) < MARGIN_PERCENTAGE; + } + + /** + * Update the content in the citizen's inventory UIList + */ + private void refreshCitizenList() { + if (targetCitizen == EntityRef.NULL) { + return; + } + + List items = new ArrayList<>(); + for (int i = 0; i < inventoryManager.getNumSlots(targetCitizen); i++) { + EntityRef entity = inventoryManager.getItemInSlot(targetCitizen, i); + if (entity.getParentPrefab() != null) { + MarketItem item = MarketItemBuilder.get(entity.getParentPrefab().getName(), 1); + items.add(item); + } + } + + tradingScreen.setCitizenItems(items); + } + + /** + * Update the content in the player's inventory UIList + */ + private void refreshPlayerList() { + List items = new ArrayList<>(); + EntityRef player = localPlayer.getCharacterEntity(); + for (int i = 0; i < inventoryManager.getNumSlots(player); i++) { + EntityRef entity = inventoryManager.getItemInSlot(player, i); + if (entity.getParentPrefab() != null) { + MarketItem item = MarketItemBuilder.get(entity.getParentPrefab().getName(), 1); + items.add(item); + } + } + + tradingScreen.setPlayerItems(items); + } + + /** + * Remove an item from the specified entity's inventory + * @param item: MarketItem to be removed + * @param entity: Entity to be removed from + */ + private void remove(MarketItem item, EntityRef entity) { + EntityRef itemEntity = EntityRef.NULL; + for (int i = 0; i < inventoryManager.getNumSlots(entity); i++) { + EntityRef current = inventoryManager.getItemInSlot(entity, i); + if (current != EntityRef.NULL + && item.name.equalsIgnoreCase(current.getParentPrefab().getName())) { + itemEntity = current; + break; + } + } + inventoryManager.removeItem(entity, EntityRef.NULL, itemEntity, true, 1); + } + + /** + * Add an item to the specified entity's inventory + * @param item: MarketItem to be added + * @param entity: Entity to be added to + * @throws Exception if addition of block to inventory fails + */ + private void add(MarketItem item, EntityRef entity) throws Exception { + Set matches = assetManager.resolve(item.name, Prefab.class); + + if (matches.size() == 1) { + Prefab prefab = assetManager.getAsset(matches.iterator().next(), Prefab.class).orElse(null); + if (prefab != null && prefab.getComponent(ItemComponent.class) != null) { + EntityRef itemEntity = entityManager.create(prefab); + if (itemEntity != EntityRef.NULL) { + itemEntity.send(new GiveItemEvent(entity)); + return; + } + } + } + + String message = blockCommands.giveBlock(entity, item.name, 1, null); + if (message == null) { + String error = "Could not add block " + item.name + " to inventory " + entity; + throw new Exception(error); + } + } +}