Big map update 8

Dialog updates.
- Dialogs can be assigned to enemies.
- Dialogs have a few more conditions and effects.
- Dummy objects that can be removed from dialogs by ID.
- Dialogs can force a battle by ID.
This commit is contained in:
Magpie
2022-04-15 08:36:59 +02:00
parent 8573608ec8
commit ad2e2ac76a
8 changed files with 288 additions and 142 deletions

View File

@@ -1,123 +1,36 @@
package forge.adventure.character;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.Json;
import com.badlogic.gdx.utils.SerializationException;
import forge.Forge;
import forge.adventure.data.DialogData;
import forge.adventure.data.RewardData;
import forge.adventure.stage.MapStage;
import forge.adventure.util.Config;
import forge.adventure.util.Controls;
import forge.adventure.util.Current;
import forge.adventure.util.MapDialog;
/**
* Map actor that will show a text message with optional choices
*/
public class DialogActor extends MapActor{
public class DialogActor extends MapActor {
private final MapStage stage;
private final String dialogJSON;
private final TextureRegion textureRegion;
private final String defaultJSON = "[\n" +
" {\n" +
" \"effect\":[],\n" +
" \"name\":\"Error\",\n" +
" \"text\":\"This is a fallback dialog.\\nPlease check Forge logs for errors.\",\n" +
" \"condition\":[],\n" +
" \"options\":[\n" +
" { \"name\":\"OK\" }\n" +
" ]\n" +
" }\n" +
"]";
private final MapDialog dialog;
public DialogActor(MapStage stage, int id, String dialog, TextureRegion textureRegion) {
public DialogActor(MapStage stage, int id, String S, TextureRegion textureRegion) {
super(id);
this.stage = stage;
if (!dialog.isEmpty()){
System.err.printf("Dialog error. Dialog property is empty.\n");
this.dialogJSON = defaultJSON;
}
else this.dialogJSON = dialog;
dialog = new MapDialog(S, stage, id);
this.textureRegion = textureRegion;
}
@Override
public void onPlayerCollide() {
Json json = new Json();
Array<DialogData> data;
stage.resetPosition();
stage.showDialog();
try { data = json.fromJson(Array.class, DialogData.class, dialogJSON); }
catch(SerializationException E){
//JSON parsing could fail. Since this an user written part, assume failure is possible (it happens).
System.err.printf("[%s] while loading JSON file for dialog actor. JSON:\n%s\nUsing a default dialog.", E.getMessage(), dialogJSON);
data = json.fromJson(Array.class, DialogData.class, defaultJSON);
}
for(DialogData dialog:data) {
if(isConditionOk(dialog.condition)) {
loadDialog(dialog);
}
}
}
private void loadDialog(DialogData dialog) {
setEffects(dialog.effect);
stage.getDialog().getContentTable().clear();
stage.getDialog().getButtonTable().clear();
String text = "";
if(dialog.loctext != null && !dialog.loctext.isEmpty()){ //Check for localized string, otherwise print text.
text = Forge.getLocalizer().getMessage(dialog.loctext);
} else {
text = dialog.text;
}
stage.getDialog().text(text);
if(dialog.options != null) {
for(DialogData option:dialog.options) {
if( isConditionOk(option.condition) ) {
stage.getDialog().getButtonTable().add(Controls.newTextButton(option.name,() -> {
loadDialog(option);
}));
}
}
stage.showDialog();
}
else {
stage.hideDialog();
}
}
void setEffects(DialogData.EffectData[] data) {
if(data==null) return;
for(DialogData.EffectData effectData:data) {
Current.player().removeItem(effectData.removeItem);
if(effectData.deleteMapObject<0)
stage.deleteObject(getObjectId());
else if(effectData.deleteMapObject>0)
stage.deleteObject(effectData.deleteMapObject);
}
}
boolean isConditionOk(DialogData.ConditionData[] data) {
if(data==null) return true;
for(DialogData.ConditionData condition:data) {
if(condition.item!=null && !condition.item.equals("")) {
if(!Current.player().hasItem(condition.item)) {
return false;
}
}
}
return true;
dialog.activate();
}
@Override
public void draw(Batch batch, float alpha) {
batch.draw(textureRegion,getX(),getY(),getWidth(),getHeight());
super.draw(batch,alpha);
batch.draw(textureRegion, getX(), getY(), getWidth(), getHeight());
super.draw(batch, alpha);
}
}

View File

@@ -0,0 +1,24 @@
package forge.adventure.character;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import forge.adventure.stage.MapStage;
public class DummySprite extends MapActor {
private final TextureRegion textureRegion;
private final MapStage stage;
public DummySprite(int id, TextureRegion textureRegion, MapStage stage) {
super(id);
this.textureRegion = textureRegion;
this.stage = stage;
}
@Override
public void onPlayerCollide() { stage.resetPosition(); }
@Override
public void draw(Batch batch, float alpha) {
batch.draw(textureRegion, getX(), getY(), getWidth(), getHeight());
super.draw(batch, alpha);
}
}

View File

@@ -7,6 +7,7 @@ import com.badlogic.gdx.utils.Array;
import forge.adventure.data.EnemyData;
import forge.adventure.data.RewardData;
import forge.adventure.util.Current;
import forge.adventure.util.MapDialog;
import forge.adventure.util.Reward;
/**
@@ -16,6 +17,7 @@ import forge.adventure.util.Reward;
public class EnemySprite extends CharacterSprite {
EnemyData data;
private int id;
public MapDialog dialog;
public EnemySprite(EnemyData enemyData) {
super(enemyData.sprite);

View File

@@ -94,6 +94,10 @@ public class MapActor extends Actor {
}
public int getId(){
return objectId;
}
public boolean collideWith(MapActor other) {
return collideWith(other.boundingRect());
}

View File

@@ -9,11 +9,16 @@ public class DialogData {
public DialogData[] options;
static public class EffectData {
public String removeItem;
public int deleteMapObject;
public String removeItem; //Remove item name from inventory.
public String addItem; //Add item name to inventory.
public int deleteMapObject = 0; //Remove ID from the map. -1 for self.
public int battleWithActorID = 0; //Start a battle with enemy ID. -1 for self if possible.
}
static public class ConditionData {
public String item;
public int flag = 0;
public int actorID = 0;
public boolean not = false;
}
}

View File

@@ -26,10 +26,7 @@ import forge.adventure.pointofintrest.PointOfInterestChanges;
import forge.adventure.scene.DuelScene;
import forge.adventure.scene.RewardScene;
import forge.adventure.scene.SceneType;
import forge.adventure.util.Config;
import forge.adventure.util.Controls;
import forge.adventure.util.Current;
import forge.adventure.util.Reward;
import forge.adventure.util.*;
import forge.adventure.world.WorldSave;
import forge.screens.TransitionScreen;
import forge.sound.SoundEffectType;
@@ -190,6 +187,7 @@ public class MapStage extends GameStage {
text += E.getDescription();
dialog.text(text);
dialog.getButtonTable().add(Controls.newTextButton("OK", () -> { this.hideDialog(); }));
dialog.setKeepWithinStage(true);
showDialog();
}
@@ -286,6 +284,14 @@ public class MapStage extends GameStage {
case "enemy":
EnemySprite mob = new EnemySprite(id, WorldData.getEnemy(prop.get("enemy").toString()));
addMapActor(obj, mob);
if(prop.get("dialog") != null && !prop.get("dialog").toString().isEmpty()) {
mob.dialog = new MapDialog(prop.get("dialog").toString(), this, mob.getId());
}
break;
case "dummy": //Does nothing. Mostly obstacles to be removed by ID by switches or such.
TiledMapTileMapObject obj2 = (TiledMapTileMapObject) obj;
DummySprite D = new DummySprite(id, obj2.getTextureRegion(), this);
addMapActor(obj, D);
break;
case "inn":
addMapActor(obj, new OnCollide(new Runnable() {
@@ -305,7 +311,6 @@ public class MapStage extends GameStage {
break;
case "dialog":
if(obj instanceof TiledMapTileMapObject) {
//Sanity check
TiledMapTileMapObject tiledObj = (TiledMapTileMapObject) obj;
DialogActor dialog = new DialogActor(this, id, prop.get("dialog").toString(), tiledObj.getTextureRegion());
addMapActor(obj, dialog);
@@ -353,6 +358,7 @@ public class MapStage extends GameStage {
public boolean exit() {
isLoadingMatch = false;
effect = null; //Reset dungeon effects.
clearIsInMap();
Forge.switchScene(SceneType.GameScene.instance);
return true;
@@ -399,7 +405,22 @@ public class MapStage extends GameStage {
}
}
return false;
}
public boolean lookForID(int id){
for(MapActor A : new Array.ArrayIterator<>(actors)){
if(A.getId() == id)
return true;
}
return false;
}
public EnemySprite getEnemyByID(int id) {
for(MapActor A : new Array.ArrayIterator<>(actors)){
if(A instanceof EnemySprite && ((EnemySprite) A).getId() == id)
return ((EnemySprite) A);
}
return null;
}
protected void getReward() {
@@ -423,41 +444,13 @@ public class MapStage extends GameStage {
if (actor instanceof EnemySprite) {
EnemySprite mob = (EnemySprite) actor;
currentMob = mob;
if (mob.getData().deck == null || mob.getData().deck.isEmpty()) {
currentMob.setAnimation(CharacterSprite.AnimationTypes.Death);
Gdx.input.vibrate(50);
startPause(0.3f, new Runnable() {
@Override
public void run() {
MapStage.this.getReward();
}
});
if (mob.dialog != null){ //This enemy has something to say. Display a dialog like if it was a DialogActor.
resetPosition();
showDialog();
mob.dialog.activate();
break;
} else {
player.setAnimation(CharacterSprite.AnimationTypes.Attack);
mob.setAnimation(CharacterSprite.AnimationTypes.Attack);
Gdx.input.vibrate(50);
Forge.setCursor(null, Forge.magnifyToggle ? "1" : "2");
SoundSystem.instance.play(SoundEffectType.ManaBurn, false);
if (!isLoadingMatch) {
isLoadingMatch = true;
Forge.setTransitionScreen(new TransitionScreen(new Runnable() {
@Override
public void run() {
Forge.clearTransitionScreen();
}
}, ScreenUtils.getFrameBufferTexture(), true, false));
}
startPause(0.4f, new Runnable() {
@Override
public void run() {
DuelScene S = ((DuelScene) SceneType.DuelScene.instance);
S.setEnemy(mob);
S.setPlayer(player);
S.setDungeonEffect(effect);
Forge.switchScene(SceneType.DuelScene.instance);
}
});
} else { //Duel the enemy.
beginDuel(mob);
break;
}
} else if (actor instanceof RewardSprite) {
@@ -479,6 +472,34 @@ public class MapStage extends GameStage {
}
}
public void beginDuel(EnemySprite mob){
if(mob == null) return;
currentMob = mob;
player.setAnimation(CharacterSprite.AnimationTypes.Attack);
mob.setAnimation(CharacterSprite.AnimationTypes.Attack);
Gdx.input.vibrate(50);
Forge.setCursor(null, Forge.magnifyToggle ? "1" : "2");
SoundSystem.instance.play(SoundEffectType.ManaBurn, false);
if (!isLoadingMatch) {
isLoadingMatch = true;
Forge.setTransitionScreen(new TransitionScreen(new Runnable() {
@Override
public void run() {
Forge.clearTransitionScreen();
}
}, ScreenUtils.getFrameBufferTexture(), true, false));
}
startPause(0.4f, new Runnable() {
@Override
public void run() {
DuelScene S = ((DuelScene) SceneType.DuelScene.instance);
S.setEnemy(mob);
S.setPlayer(player);
S.setDungeonEffect(effect);
Forge.switchScene(SceneType.DuelScene.instance);
}
});
}
public void setPointOfInterest(PointOfInterestChanges change) {
changes = change;
@@ -488,8 +509,6 @@ public class MapStage extends GameStage {
return isInMap;
}
public void showDialog() {
dialog.show(dialogStage);
dialogOnlyInput=true;

View File

@@ -0,0 +1,125 @@
package forge.adventure.util;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.Json;
import com.badlogic.gdx.utils.SerializationException;
import forge.Forge;
import forge.adventure.data.DialogData;
import forge.adventure.stage.MapStage;
public class MapDialog {
private final MapStage stage;
private Array<DialogData> data;
private final int parentID;
static private final String defaultJSON = "[\n" +
" {\n" +
" \"effect\":[],\n" +
" \"name\":\"Error\",\n" +
" \"text\":\"This is a fallback dialog.\\nPlease check Forge logs for errors.\",\n" +
" \"condition\":[],\n" +
" \"options\":[\n" +
" { \"name\":\"OK\" }\n" +
" ]\n" +
" }\n" +
"]";
public MapDialog(String S, MapStage ST, int parentID) {
this.stage = ST;
this.parentID = parentID;
Json json = new Json();
if (S.isEmpty()){
System.err.print("Dialog error. Dialog property is empty.\n");
this.data = json.fromJson(Array.class, DialogData.class, defaultJSON);
return;
}
try { data = json.fromJson(Array.class, DialogData.class, S); }
catch(SerializationException E){
//JSON parsing could fail. Since this an user written part, assume failure is possible (it happens).
System.err.printf("[%s] while loading JSON file for dialog actor. JSON:\n%s\nUsing a default dialog.", E.getMessage(), S);
this.data = json.fromJson(Array.class, DialogData.class, defaultJSON);
}
}
private void loadDialog(DialogData dialog) {
setEffects(dialog.effect);
stage.getDialog().getContentTable().clear();
stage.getDialog().getButtonTable().clear();
String text;
if(dialog.loctext != null && !dialog.loctext.isEmpty()){ //Check for localized string, otherwise print text.
text = Forge.getLocalizer().getMessage(dialog.loctext);
} else {
text = dialog.text;
}
int charCount = 0;
stage.getDialog().text(text);
if(dialog.options != null) {
for(DialogData option:dialog.options) {
if( isConditionOk(option.condition) ) {
charCount += option.name.length();
if(charCount > 35){ //Gross hack.
stage.getDialog().getButtonTable().row();
charCount = 0;
}
stage.getDialog().getButtonTable().add(Controls.newTextButton(option.name,() -> loadDialog(option)));
}
}
stage.showDialog();
}
else {
stage.hideDialog();
}
}
public void activate() {
for(DialogData dialog:data) {
if(isConditionOk(dialog.condition)) {
loadDialog(dialog);
}
}
}
void setEffects(DialogData.EffectData[] data) {
if(data==null) return;
for(DialogData.EffectData E:data) {
if (E.removeItem != null){
Current.player().removeItem(E.removeItem);
}
if (E.addItem != null){
Current.player().addItem(E.addItem);
}
if (E.deleteMapObject != 0){
if(E.deleteMapObject < 0) stage.deleteObject(parentID);
else stage.deleteObject(E.deleteMapObject);
}
if (E.battleWithActorID != 0){
if(E.battleWithActorID < 0) stage.beginDuel(stage.getEnemyByID(parentID));
else stage.beginDuel(stage.getEnemyByID(E.battleWithActorID));
}
}
}
boolean isConditionOk(DialogData.ConditionData[] data) {
if(data==null) return true;
for(DialogData.ConditionData condition:data) {
if(condition.item != null && !condition.item.isEmpty()) { //Check for item.
if(Current.player().hasItem(condition.item)) {
return ((condition.not) ? false : true);
}
}
if(condition.actorID != 0) { //Check for actor ID.
boolean result = stage.lookForID(condition.actorID);
if(condition.not) result = !result;
return result;
}
}
return true;
}
}

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<map version="1.8" tiledversion="1.8.4" orientation="orthogonal" renderorder="right-down" width="30" height="30" tilewidth="16" tileheight="16" infinite="0" nextlayerid="6" nextobjectid="81">
<map version="1.8" tiledversion="1.8.4" orientation="orthogonal" renderorder="right-down" width="30" height="30" tilewidth="16" tileheight="16" infinite="0" nextlayerid="6" nextobjectid="84">
<editorsettings>
<export format="tmx"/>
</editorsettings>
@@ -20,7 +20,7 @@
</layer>
<layer id="2" name="Ground" width="30" height="30">
<data encoding="base64" compression="zlib">
eJyTEGBgkBjFGFgaiLdhwVI0tlcSRAthYvFRe6mKa/kZGKqAuJofYl8llF/DT1t70f1Na3/S015pKI2eZ0Bi26H2bsUiT2nekoTS6ACbGDZAbliQam+jJANDkyR17IWFGQyg85HBXKCd86hkLyVg1N5Re0ftHbV3sNirREB+1F7iMKwthYzx2YuulpptLnz2UssOQnYj20trO5HjHmQvpe06ANvAfIE=
eJyTEGBgkBjFGFgaiLdhwVI0tlcSRAthYvFRe6mKa/kZGKqAuJofYl8llF/DT1t70f1Na3/S015pKI2eZ0Bi26H2bsUiT2nekoTS6ACbGDZAbliQam+jJANDkyR17IWFGQyg85HBXKCd86hkLyVg1N5RewfaXuQyiZ72UhOQY+9IiV9S7FUiID9qL3EY1pZCxvjsRVdLzTYXPnupZQchu5HtpbWdyHEPspfSdh0A9Qp8zA==
</data>
</layer>
<layer id="3" name="Foreground" width="30" height="30">
@@ -72,9 +72,9 @@
</object>
<object id="69" template="../obj/treasure.tx" x="192" y="352"/>
<object id="74" template="../obj/treasure.tx" x="224" y="352"/>
<object id="70" template="../obj/booster.tx" x="192" y="320"/>
<object id="70" template="../obj/booster.tx" x="32" y="336"/>
<object id="72" template="../obj/booster.tx" x="224" y="320"/>
<object id="71" template="../obj/gold.tx" x="192" y="288"/>
<object id="71" template="../obj/gold.tx" x="48" y="352"/>
<object id="73" template="../obj/gold.tx" x="224" y="288"/>
<object id="78" template="../obj/gate.tx" x="144" y="208" visible="0">
<properties>
@@ -106,8 +106,62 @@
</object>
<object id="80" template="../obj/enemy.tx" x="96" y="304">
<properties>
<property name="dialog">[
{
&quot;effect&quot;:[],
&quot;name&quot;:&quot;ABC&quot;,
&quot;text&quot;:&quot;I am an elf. I do elf things like hugging trees and being pretty.&quot;,
&quot;loctext&quot;:&quot;&quot;,
&quot;condition&quot;:[],
&quot;options&quot;:[
{ &quot;name&quot;:&quot;OK&quot; },
{
&quot;name&quot;:&quot;Fight me, elf!&quot;,
&quot;text&quot;: &quot;Gladly.&quot;,
&quot;options&quot;: [ { &quot;name&quot;: &quot;I FEAR NOTHING!!?&quot;, &quot;effect&quot;: [ { &quot;battleWithActorID&quot;: -1 } ]} ]
},
{
&quot;name&quot;:&quot;I wanna fight Emrakul over there!&quot;,
&quot;condition&quot;: [ { &quot;actorID&quot;: 50 } ],
&quot;text&quot;: &quot;Really? Oh well... your funeral.\nHEY EMMY! THIS DUDE WANTS TO DANCE!&quot;,
&quot;options&quot;: [ { &quot;name&quot;: &quot;WAIT IT WAS A JOKE!!!&quot;, &quot;effect&quot;: [ { &quot;battleWithActorID&quot;: 50 } ]} ]
},
{
&quot;name&quot;:&quot;I wanna fight Emrakul over there!&quot;,
&quot;condition&quot;: [ { &quot;actorID&quot;: 50, &quot;not&quot;: true } ],
&quot;text&quot;: &quot;She left. Crying. You monster.&quot;,
&quot;options&quot;: [ { &quot;name&quot;: &quot;Sorry...&quot; } ]
},
{
&quot;name&quot;:&quot;That's cool dude.&quot;,
&quot;condition&quot;: [ { &quot;item&quot;: &quot;Treasure&quot;, &quot;not&quot;: true } ],
&quot;effect&quot;: [ { &quot;addItem&quot;: &quot;Treasure&quot; } ],
&quot;text&quot;: &quot;You get it. Take this.&quot;,
&quot;options&quot;: [ { &quot;name&quot;: &quot;Thanks bro.&quot; } ]
},
{
&quot;name&quot;:&quot;Can you open that hidden wall?&quot;,
&quot;condition&quot;: [ { &quot;actorID&quot;: 83 } ],
&quot;text&quot;: &quot;Since you asked nicely, I shall.&quot;,
&quot;options&quot;: [ { &quot;name&quot;: &quot;Thanks bro.&quot;, &quot;effect&quot;: [ { &quot;deleteMapObject&quot;: 83 } ]} ]
}
]
}
]</property>
<property name="enemy" value="Elf"/>
<property name="permanent" type="bool" value="true"/>
</properties>
</object>
<object id="81" template="../obj/enemy.tx" x="288" y="224">
<properties>
<property name="enemy" value="Goblin"/>
</properties>
</object>
<object id="82" template="../obj/enemy.tx" gid="2147491275" x="272" y="224">
<properties>
<property name="enemy" value="Goblin"/>
</properties>
</object>
<object id="83" template="../obj/gate.tx" type="dummy" gid="3651" x="64" y="336" width="16" height="16"/>
</objectgroup>
</map>