From 9b21249108c9f29ff125ef7082e8acf13843117e Mon Sep 17 00:00:00 2001 From: xp Date: Wed, 2 Mar 2022 07:48:23 -0800 Subject: [PATCH 1/6] Buff stacks for pre-apps, plus refactored some action effect stuff --- .../state/combatstate/CdTrackerTest.java | 8 +-- .../xp/xivdata/data/StatusEffectLibrary.java | 30 +++++++++++ .../events/actlines/events/BuffApplied.java | 23 +-------- .../events/actlines/events/TickEvent.java | 4 +- .../events/abilityeffect/AbilityEffect.java | 22 ++++++-- .../abilityeffect/BlockedDamageEffect.java | 6 +-- .../abilityeffect/CurrentHpSetEffect.java | 14 +++--- .../abilityeffect/DamageTakenEffect.java | 6 +-- .../abilityeffect/FullyResistedEffect.java | 4 +- .../events/abilityeffect/HealEffect.java | 6 +-- .../InvulnBlockedDamageEffect.java | 6 +-- .../events/abilityeffect/MissEffect.java | 4 +- .../actlines/events/abilityeffect/MpGain.java | 6 +-- .../actlines/events/abilityeffect/MpLoss.java | 6 +-- .../events/abilityeffect/NoEffect.java | 4 +- .../events/abilityeffect/OtherEffect.java | 14 +++--- .../abilityeffect/ParriedDamageEffect.java | 6 +-- .../abilityeffect/StatusAppliedEffect.java | 28 +++++++++-- .../events/abilityeffect/StatusNoEffect.java | 6 +-- .../events/actlines/parsers/FieldMapper.java | 50 ++++++++++++++----- .../renderers/AbilityEffectRenderer.java | 5 +- 21 files changed, 164 insertions(+), 94 deletions(-) diff --git a/triggers/src/test/java/gg/xp/xivsupport/events/state/combatstate/CdTrackerTest.java b/triggers/src/test/java/gg/xp/xivsupport/events/state/combatstate/CdTrackerTest.java index 308154ce3230..9ed67ad06b23 100644 --- a/triggers/src/test/java/gg/xp/xivsupport/events/state/combatstate/CdTrackerTest.java +++ b/triggers/src/test/java/gg/xp/xivsupport/events/state/combatstate/CdTrackerTest.java @@ -34,7 +34,7 @@ public class CdTrackerTest { new XivAbility(cd.getPrimaryAbilityId(), "Reprisal"), player, theBoss, - Collections.singletonList(new StatusAppliedEffect(1193, true)), + Collections.singletonList(new StatusAppliedEffect(0, 0, 1193, 0, true)), 123, 0, 1 @@ -43,7 +43,7 @@ public class CdTrackerTest { new XivAbility(cd.getPrimaryAbilityId(), "Reprisal"), player, theBoss, - Collections.singletonList(new StatusAppliedEffect(1193, true)), + Collections.singletonList(new StatusAppliedEffect(0, 0, 1193, 0, true)), 123, 1, 1 @@ -52,7 +52,7 @@ public class CdTrackerTest { new XivAbility(cd.getPrimaryAbilityId(), "Reprisal"), otherCharInParty, theBoss, - Collections.singletonList(new StatusAppliedEffect(1193, true)), + Collections.singletonList(new StatusAppliedEffect(0, 0, 1193, 0, true)), 123, 0, 1 @@ -61,7 +61,7 @@ public class CdTrackerTest { new XivAbility(cd.getPrimaryAbilityId(), "Reprisal"), otherCharNotInParty, theBoss, - Collections.singletonList(new StatusAppliedEffect(1193, true)), + Collections.singletonList(new StatusAppliedEffect(0, 0, 1193, 0, true)), 123, 0, 1 diff --git a/xivdata/src/main/java/gg/xp/xivdata/data/StatusEffectLibrary.java b/xivdata/src/main/java/gg/xp/xivdata/data/StatusEffectLibrary.java index e390fdfa67ef..644ea63d569e 100644 --- a/xivdata/src/main/java/gg/xp/xivdata/data/StatusEffectLibrary.java +++ b/xivdata/src/main/java/gg/xp/xivdata/data/StatusEffectLibrary.java @@ -111,6 +111,36 @@ public static void readAltCsv(File file) { return csvValues.get(id); } + public static int getMaxStacks(long buffId) { + // There are two main considerations here. + // Sometimes, the 'stacks' value is used to represent something other than stacks (like on NIN) + // Therefore, we have to assume that it is a garbage value and assume 0 stacks (i.e. not a stacking buff) + // if rawStacks > maxStacks. + // However, there are also unknown status effects, therefore we just assume 16 is the max for those, since that + // seems to be the max for any legitimate buff. + StatusEffectInfo statusEffectInfo = forId(buffId); + long maxStacks; + if (statusEffectInfo == null) { + maxStacks = 16; + } + else { + maxStacks = statusEffectInfo.maxStacks(); + } + //noinspection NumericCastThatLosesPrecision - never that high + return (int) maxStacks; + } + + public static int calcActualStacks(long buffId, long rawStacks) { + int maxStacks = getMaxStacks(buffId); + if (rawStacks >= 0 && rawStacks <= maxStacks) { + return (int) rawStacks; + } + else { + return 0; + } + + } + // Special value to indicate no icon private static final StatusEffectIcon NULL_MARKER; diff --git a/xivsupport/src/main/java/gg/xp/xivsupport/events/actlines/events/BuffApplied.java b/xivsupport/src/main/java/gg/xp/xivsupport/events/actlines/events/BuffApplied.java index a8e2bc92b467..aa0775990df1 100644 --- a/xivsupport/src/main/java/gg/xp/xivsupport/events/actlines/events/BuffApplied.java +++ b/xivsupport/src/main/java/gg/xp/xivsupport/events/actlines/events/BuffApplied.java @@ -25,7 +25,7 @@ public class BuffApplied extends BaseEvent implements HasSourceEntity, HasTarget // Only for pre-apps public BuffApplied(AbilityUsedEvent event, StatusAppliedEffect effect) { - this(effect.getStatus(), 9999, event.getSource(), event.getTarget(), 1, true); + this(effect.getStatus(), 9999, event.getSource(), event.getTarget(), effect.getRawStacks(), true); } public BuffApplied(XivStatusEffect buff, double durationRaw, XivCombatant source, XivCombatant target, long stacks) { @@ -54,27 +54,8 @@ public BuffApplied(XivStatusEffect buff, double durationRaw, XivCombatant source this.source = source; this.target = target; this.rawStacks = rawStacks; - // There are two main considerations here. - // Sometimes, the 'stacks' value is used to represent something other than stacks (like on NIN) - // Therefore, we have to assume that it is a garbage value and assume 0 stacks (i.e. not a stacking buff) - // if rawStacks > maxStacks. - // However, there are also unknown status effects, therefore we just assume 16 is the max for those, since that - // seems to be the max for any legitimate buff. - StatusEffectInfo statusEffectInfo = StatusEffectLibrary.forId(buff.getId()); - long maxStacks; - if (statusEffectInfo == null) { - maxStacks = 16; - } - else { - maxStacks = statusEffectInfo.maxStacks(); - } - if (rawStacks >= 0 && rawStacks <= maxStacks) { - stacks = rawStacks; - } - else { - stacks = 0; - } this.isPreApp = isPreApp; + this.stacks = StatusEffectLibrary.calcActualStacks(buff.getId(), rawStacks); } @Override diff --git a/xivsupport/src/main/java/gg/xp/xivsupport/events/actlines/events/TickEvent.java b/xivsupport/src/main/java/gg/xp/xivsupport/events/actlines/events/TickEvent.java index cec0f371a442..cfaa25c3be43 100644 --- a/xivsupport/src/main/java/gg/xp/xivsupport/events/actlines/events/TickEvent.java +++ b/xivsupport/src/main/java/gg/xp/xivsupport/events/actlines/events/TickEvent.java @@ -51,10 +51,10 @@ public long getRawEffectId() { @Override public List getEffects() { if (type == TickType.HOT) { - return Collections.singletonList(new HealEffect(HitSeverity.NORMAL, damage)); + return Collections.singletonList(new HealEffect(0, 0, HitSeverity.NORMAL, damage)); } else { - return Collections.singletonList(new DamageTakenEffect(HitSeverity.NORMAL, damage)); + return Collections.singletonList(new DamageTakenEffect(0, 0, HitSeverity.NORMAL, damage)); } } } diff --git a/xivsupport/src/main/java/gg/xp/xivsupport/events/actlines/events/abilityeffect/AbilityEffect.java b/xivsupport/src/main/java/gg/xp/xivsupport/events/actlines/events/abilityeffect/AbilityEffect.java index d3c626119841..01ec1c4d5c7e 100644 --- a/xivsupport/src/main/java/gg/xp/xivsupport/events/actlines/events/abilityeffect/AbilityEffect.java +++ b/xivsupport/src/main/java/gg/xp/xivsupport/events/actlines/events/abilityeffect/AbilityEffect.java @@ -1,10 +1,14 @@ package gg.xp.xivsupport.events.actlines.events.abilityeffect; -public class AbilityEffect { +public abstract class AbilityEffect { + private final long flags; + private final long value; private final AbilityEffectType effectType; - protected AbilityEffect(AbilityEffectType effectType) { + protected AbilityEffect(long flags, long value, AbilityEffectType effectType) { + this.flags = flags; + this.value = value; this.effectType = effectType; } @@ -12,7 +16,19 @@ public AbilityEffectType getEffectType() { return effectType; } - public String getDescription() { + public final long getFlags() { + return flags; + } + + public final long getValue() { + return value; + } + + protected String getBaseDescription() { return toString(); + }; + + public final String getDescription() { + return String.format("%s (raw: %08x %08x)", getBaseDescription(), flags, value); } } diff --git a/xivsupport/src/main/java/gg/xp/xivsupport/events/actlines/events/abilityeffect/BlockedDamageEffect.java b/xivsupport/src/main/java/gg/xp/xivsupport/events/actlines/events/abilityeffect/BlockedDamageEffect.java index f45cb4f98bb6..bafcf449d93e 100644 --- a/xivsupport/src/main/java/gg/xp/xivsupport/events/actlines/events/abilityeffect/BlockedDamageEffect.java +++ b/xivsupport/src/main/java/gg/xp/xivsupport/events/actlines/events/abilityeffect/BlockedDamageEffect.java @@ -3,8 +3,8 @@ public class BlockedDamageEffect extends AbilityEffect implements DamageEffect { private final long amount; - public BlockedDamageEffect(long amount) { - super(AbilityEffectType.BLOCKED); + public BlockedDamageEffect(long flags, long value, long amount) { + super(flags, value, AbilityEffectType.BLOCKED); this.amount = amount; } @@ -19,7 +19,7 @@ public String toString() { } @Override - public String getDescription() { + public String getBaseDescription() { return String.format("Blocked Damage: %s", amount); } } diff --git a/xivsupport/src/main/java/gg/xp/xivsupport/events/actlines/events/abilityeffect/CurrentHpSetEffect.java b/xivsupport/src/main/java/gg/xp/xivsupport/events/actlines/events/abilityeffect/CurrentHpSetEffect.java index 03536ca55e24..363084f34746 100644 --- a/xivsupport/src/main/java/gg/xp/xivsupport/events/actlines/events/abilityeffect/CurrentHpSetEffect.java +++ b/xivsupport/src/main/java/gg/xp/xivsupport/events/actlines/events/abilityeffect/CurrentHpSetEffect.java @@ -1,18 +1,18 @@ package gg.xp.xivsupport.events.actlines.events.abilityeffect; public class CurrentHpSetEffect extends AbilityEffect { - private final long value; + private final long hpAmount; - public CurrentHpSetEffect(long value) { - super(AbilityEffectType.HP_SET_TO); - this.value = value; + public CurrentHpSetEffect(long flags, long value, long hpAmount) { + super(flags, value, AbilityEffectType.HP_SET_TO); + this.hpAmount = value; } - public long getValue() { - return value; + public long getHpAmount() { + return hpAmount; } public String toString() { - return String.format("HP=%s", value); + return String.format("HP=%s", hpAmount); } } diff --git a/xivsupport/src/main/java/gg/xp/xivsupport/events/actlines/events/abilityeffect/DamageTakenEffect.java b/xivsupport/src/main/java/gg/xp/xivsupport/events/actlines/events/abilityeffect/DamageTakenEffect.java index b532be474153..3b39e1fe5a21 100644 --- a/xivsupport/src/main/java/gg/xp/xivsupport/events/actlines/events/abilityeffect/DamageTakenEffect.java +++ b/xivsupport/src/main/java/gg/xp/xivsupport/events/actlines/events/abilityeffect/DamageTakenEffect.java @@ -4,8 +4,8 @@ public class DamageTakenEffect extends AbilityEffect implements DamageEffect { private final HitSeverity severity; private final long amount; - public DamageTakenEffect(HitSeverity severity, long amount) { - super(AbilityEffectType.DAMAGE); + public DamageTakenEffect(long flags, long value, HitSeverity severity, long amount) { + super(flags, value, AbilityEffectType.DAMAGE); this.severity = severity; this.amount = amount; } @@ -25,7 +25,7 @@ public String toString() { } @Override - public String getDescription() { + public String getBaseDescription() { if (severity == HitSeverity.NORMAL) { return String.format("Damage Taken: %s", amount); } diff --git a/xivsupport/src/main/java/gg/xp/xivsupport/events/actlines/events/abilityeffect/FullyResistedEffect.java b/xivsupport/src/main/java/gg/xp/xivsupport/events/actlines/events/abilityeffect/FullyResistedEffect.java index 4bbb5637d2ff..35af7f77e150 100644 --- a/xivsupport/src/main/java/gg/xp/xivsupport/events/actlines/events/abilityeffect/FullyResistedEffect.java +++ b/xivsupport/src/main/java/gg/xp/xivsupport/events/actlines/events/abilityeffect/FullyResistedEffect.java @@ -2,8 +2,8 @@ public class FullyResistedEffect extends AbilityEffect { - public FullyResistedEffect() { - super(AbilityEffectType.MISS); + public FullyResistedEffect(long flags, long value) { + super(flags, value, AbilityEffectType.MISS); } @Override diff --git a/xivsupport/src/main/java/gg/xp/xivsupport/events/actlines/events/abilityeffect/HealEffect.java b/xivsupport/src/main/java/gg/xp/xivsupport/events/actlines/events/abilityeffect/HealEffect.java index 07e596023078..b8a08681582c 100644 --- a/xivsupport/src/main/java/gg/xp/xivsupport/events/actlines/events/abilityeffect/HealEffect.java +++ b/xivsupport/src/main/java/gg/xp/xivsupport/events/actlines/events/abilityeffect/HealEffect.java @@ -4,8 +4,8 @@ public class HealEffect extends AbilityEffect { private final HitSeverity severity; private final long amount; - public HealEffect(HitSeverity severity, long amount) { - super(AbilityEffectType.HEAL); + public HealEffect(long flags, long value, HitSeverity severity, long amount) { + super(flags, value, AbilityEffectType.HEAL); this.severity = severity; this.amount = amount; } @@ -25,7 +25,7 @@ public String toString() { @Override - public String getDescription() { + public String getBaseDescription() { if (severity == HitSeverity.NORMAL) { return String.format("Heal: %s", amount); } diff --git a/xivsupport/src/main/java/gg/xp/xivsupport/events/actlines/events/abilityeffect/InvulnBlockedDamageEffect.java b/xivsupport/src/main/java/gg/xp/xivsupport/events/actlines/events/abilityeffect/InvulnBlockedDamageEffect.java index fd0859566051..8b2411c4984b 100644 --- a/xivsupport/src/main/java/gg/xp/xivsupport/events/actlines/events/abilityeffect/InvulnBlockedDamageEffect.java +++ b/xivsupport/src/main/java/gg/xp/xivsupport/events/actlines/events/abilityeffect/InvulnBlockedDamageEffect.java @@ -3,8 +3,8 @@ public class InvulnBlockedDamageEffect extends AbilityEffect implements DamageEffect { private final long amount; - public InvulnBlockedDamageEffect(long amount) { - super(AbilityEffectType.INVULN); + public InvulnBlockedDamageEffect(long flags, long value, long amount) { + super(flags, value, AbilityEffectType.INVULN); this.amount = amount; } @@ -19,7 +19,7 @@ public String toString() { } @Override - public String getDescription() { + public String getBaseDescription() { return String.format("Invulnerable: %s", amount); } } diff --git a/xivsupport/src/main/java/gg/xp/xivsupport/events/actlines/events/abilityeffect/MissEffect.java b/xivsupport/src/main/java/gg/xp/xivsupport/events/actlines/events/abilityeffect/MissEffect.java index ff491770e3cb..b96bdda6084a 100644 --- a/xivsupport/src/main/java/gg/xp/xivsupport/events/actlines/events/abilityeffect/MissEffect.java +++ b/xivsupport/src/main/java/gg/xp/xivsupport/events/actlines/events/abilityeffect/MissEffect.java @@ -2,8 +2,8 @@ public class MissEffect extends AbilityEffect { - public MissEffect() { - super(AbilityEffectType.MISS); + public MissEffect(long flags, long value) { + super(flags, value, AbilityEffectType.MISS); } @Override diff --git a/xivsupport/src/main/java/gg/xp/xivsupport/events/actlines/events/abilityeffect/MpGain.java b/xivsupport/src/main/java/gg/xp/xivsupport/events/actlines/events/abilityeffect/MpGain.java index 1298041192b4..94ee43297a31 100644 --- a/xivsupport/src/main/java/gg/xp/xivsupport/events/actlines/events/abilityeffect/MpGain.java +++ b/xivsupport/src/main/java/gg/xp/xivsupport/events/actlines/events/abilityeffect/MpGain.java @@ -3,8 +3,8 @@ public class MpGain extends AbilityEffect { private final long amount; - public MpGain(long amount) { - super(AbilityEffectType.HEAL); + public MpGain(long flags, long value, long amount) { + super(flags, value, AbilityEffectType.HEAL); this.amount = amount; } @@ -18,7 +18,7 @@ public String toString() { } @Override - public String getDescription() { + public String getBaseDescription() { return String.format("Gained MP: %s", amount); } } diff --git a/xivsupport/src/main/java/gg/xp/xivsupport/events/actlines/events/abilityeffect/MpLoss.java b/xivsupport/src/main/java/gg/xp/xivsupport/events/actlines/events/abilityeffect/MpLoss.java index a84423fdae5f..6f497dbe9079 100644 --- a/xivsupport/src/main/java/gg/xp/xivsupport/events/actlines/events/abilityeffect/MpLoss.java +++ b/xivsupport/src/main/java/gg/xp/xivsupport/events/actlines/events/abilityeffect/MpLoss.java @@ -3,8 +3,8 @@ public class MpLoss extends AbilityEffect { private final long amount; - public MpLoss(long amount) { - super(AbilityEffectType.HEAL); + public MpLoss(long flags, long value, long amount) { + super(flags, value, AbilityEffectType.HEAL); this.amount = amount; } @@ -18,7 +18,7 @@ public String toString() { } @Override - public String getDescription() { + public String getBaseDescription() { return String.format("Lost MP: %s", amount); } } diff --git a/xivsupport/src/main/java/gg/xp/xivsupport/events/actlines/events/abilityeffect/NoEffect.java b/xivsupport/src/main/java/gg/xp/xivsupport/events/actlines/events/abilityeffect/NoEffect.java index be23131c00ea..c71748c131a9 100644 --- a/xivsupport/src/main/java/gg/xp/xivsupport/events/actlines/events/abilityeffect/NoEffect.java +++ b/xivsupport/src/main/java/gg/xp/xivsupport/events/actlines/events/abilityeffect/NoEffect.java @@ -2,8 +2,8 @@ public class NoEffect extends AbilityEffect { - public NoEffect() { - super(AbilityEffectType.NO_EFFECT); + public NoEffect(long flags, long value) { + super(flags, value, AbilityEffectType.NO_EFFECT); } @Override diff --git a/xivsupport/src/main/java/gg/xp/xivsupport/events/actlines/events/abilityeffect/OtherEffect.java b/xivsupport/src/main/java/gg/xp/xivsupport/events/actlines/events/abilityeffect/OtherEffect.java index 42ca859a59f2..f2134d90dc59 100644 --- a/xivsupport/src/main/java/gg/xp/xivsupport/events/actlines/events/abilityeffect/OtherEffect.java +++ b/xivsupport/src/main/java/gg/xp/xivsupport/events/actlines/events/abilityeffect/OtherEffect.java @@ -2,17 +2,17 @@ public class OtherEffect extends AbilityEffect { - private final long flags; - private final long value; - public OtherEffect(long flags, long value) { - super(AbilityEffectType.OTHER); - this.flags = flags; - this.value = value; + super(flags, value, AbilityEffectType.OTHER); } @Override public String toString() { - return String.format("Other(%x,%x)", flags, value); + return String.format("Other(%x,%x)", getFlags(), getValue()); + } + + @Override + protected String getBaseDescription() { + return "Other"; } } diff --git a/xivsupport/src/main/java/gg/xp/xivsupport/events/actlines/events/abilityeffect/ParriedDamageEffect.java b/xivsupport/src/main/java/gg/xp/xivsupport/events/actlines/events/abilityeffect/ParriedDamageEffect.java index 31a69116fe0f..4cf6f93dc53f 100644 --- a/xivsupport/src/main/java/gg/xp/xivsupport/events/actlines/events/abilityeffect/ParriedDamageEffect.java +++ b/xivsupport/src/main/java/gg/xp/xivsupport/events/actlines/events/abilityeffect/ParriedDamageEffect.java @@ -3,8 +3,8 @@ public class ParriedDamageEffect extends AbilityEffect implements DamageEffect { private final long amount; - public ParriedDamageEffect(long amount) { - super(AbilityEffectType.PARRIED); + public ParriedDamageEffect(long flags, long value, long amount) { + super(flags, value, AbilityEffectType.PARRIED); this.amount = amount; } @@ -19,7 +19,7 @@ public String toString() { } @Override - public String getDescription() { + public String getBaseDescription() { return String.format("Parried: %s", amount); } } diff --git a/xivsupport/src/main/java/gg/xp/xivsupport/events/actlines/events/abilityeffect/StatusAppliedEffect.java b/xivsupport/src/main/java/gg/xp/xivsupport/events/actlines/events/abilityeffect/StatusAppliedEffect.java index 5a3c5d9ffd10..32ab9f828f1c 100644 --- a/xivsupport/src/main/java/gg/xp/xivsupport/events/actlines/events/abilityeffect/StatusAppliedEffect.java +++ b/xivsupport/src/main/java/gg/xp/xivsupport/events/actlines/events/abilityeffect/StatusAppliedEffect.java @@ -1,16 +1,22 @@ package gg.xp.xivsupport.events.actlines.events.abilityeffect; +import gg.xp.xivdata.data.StatusEffectLibrary; import gg.xp.xivsupport.models.XivStatusEffect; public class StatusAppliedEffect extends AbilityEffect { private final XivStatusEffect status; + private final int rawStacks; + private final int stacks; private final boolean onTarget; - // TODO: target - public StatusAppliedEffect(long id, boolean onTarget) { - super(AbilityEffectType.APPLY_STATUS); + public StatusAppliedEffect(long flags, long value, long id, int rawStacks, boolean onTarget) { + super(flags, value, AbilityEffectType.APPLY_STATUS); + // TODO: get actual name this.status = new XivStatusEffect(id, ""); + this.rawStacks = rawStacks; this.onTarget = onTarget; + this.stacks = StatusEffectLibrary.calcActualStacks(id, rawStacks); + } @Override @@ -31,8 +37,20 @@ public boolean isOnTarget() { return onTarget; } + public int getRawStacks() { + return rawStacks; + } + + public int getStacks() { + return stacks; + } + @Override - public String getDescription() { - return String.format("Applied Status 0x%x to %s", status.getId(), onTarget ? "Target" : "Caster"); + public String getBaseDescription() { + String formatted = String.format("Applied Status 0x%x to %s", status.getId(), onTarget ? "Target" : "Caster"); + if (stacks > 0) { + formatted += String.format(" (%s stacks)", stacks); + } + return formatted; } } diff --git a/xivsupport/src/main/java/gg/xp/xivsupport/events/actlines/events/abilityeffect/StatusNoEffect.java b/xivsupport/src/main/java/gg/xp/xivsupport/events/actlines/events/abilityeffect/StatusNoEffect.java index 13bfc3c7e280..bb1d694864f3 100644 --- a/xivsupport/src/main/java/gg/xp/xivsupport/events/actlines/events/abilityeffect/StatusNoEffect.java +++ b/xivsupport/src/main/java/gg/xp/xivsupport/events/actlines/events/abilityeffect/StatusNoEffect.java @@ -6,8 +6,8 @@ public class StatusNoEffect extends AbilityEffect { private final XivStatusEffect status; // TODO: target - public StatusNoEffect(long id) { - super(AbilityEffectType.STATUS_NO_EFFECT); + public StatusNoEffect(long flags, long value, long id) { + super(flags, value, AbilityEffectType.STATUS_NO_EFFECT); this.status = new XivStatusEffect(id, ""); } @@ -17,7 +17,7 @@ public String toString() { } @Override - public String getDescription() { + public String getBaseDescription() { return String.format("Status %x has no effect", status.getId()); } diff --git a/xivsupport/src/main/java/gg/xp/xivsupport/events/actlines/parsers/FieldMapper.java b/xivsupport/src/main/java/gg/xp/xivsupport/events/actlines/parsers/FieldMapper.java index 05e50c5b9265..aebd2839d58d 100644 --- a/xivsupport/src/main/java/gg/xp/xivsupport/events/actlines/parsers/FieldMapper.java +++ b/xivsupport/src/main/java/gg/xp/xivsupport/events/actlines/parsers/FieldMapper.java @@ -246,43 +246,67 @@ public List getAbilityEffects(int startIndex, int count) { // nothing continue; case 1: - out.add(new MissEffect()); + out.add(new MissEffect(flags, value)); break; case 2: - out.add(new FullyResistedEffect()); + out.add(new FullyResistedEffect(flags, value)); break; case 3: - out.add(new DamageTakenEffect(calcSeverity(severityByte), calcDamage(value))); + out.add(new DamageTakenEffect(flags, value, calcSeverity(severityByte), calcDamage(value))); break; case 4: - out.add(new HealEffect(calcSeverity(healSeverityByte), calcDamage(value))); + out.add(new HealEffect(flags, value, calcSeverity(healSeverityByte), calcDamage(value))); break; case 5: - out.add(new BlockedDamageEffect(calcDamage(value))); + out.add(new BlockedDamageEffect(flags, value, calcDamage(value))); break; case 6: - out.add(new ParriedDamageEffect(calcDamage(value))); + out.add(new ParriedDamageEffect(flags, value, calcDamage(value))); break; case 7: - out.add(new InvulnBlockedDamageEffect(calcDamage(value))); + out.add(new InvulnBlockedDamageEffect(flags, value, calcDamage(value))); break; case 8: - out.add(new NoEffect()); + out.add(new NoEffect(flags, value)); break; case 10: - out.add(new MpLoss(calcDamage(value))); + out.add(new MpLoss(flags, value, calcDamage(value))); break; case 11: - out.add(new MpGain(calcDamage(value))); + out.add(new MpGain(flags, value, calcDamage(value))); break; + + /* + Notes specific to 0e/0f: + flags: + stacks, a, b, 0e/0f + value: status id MSB, status id LSB, x, y + + a = ? + b = ? + x = ? + y = ? + + Examples: + 00F4300E 0A380000: E Dosis (single target, instant, on target, no stacks, 30s) + 0000F60E 0A3A0000: Kera mit (aoe, on target, no stacks, 15s) + 00F4F20E 0B7A0000: Kera regen (aoe, on target, no stacks, 15s) + 0300000F 076E8000: Royal Auth/Sword Oath (st, instant, on self, 3 stacks, 30s) + 00C9090E 0A350000: panhaima persistent buff (aoe, on target, no stacks, 15s) + 05C9090E 0A530000: panhaima stack buff (aoe, on target, 5 stacks, 15s) + 0000000E 00520000: halloed (st, 10s, no stacks) + + */ + + case 14: //0e - out.add(new StatusAppliedEffect(value >> 16, true)); + out.add(new StatusAppliedEffect(flags, value, value >> 16, unknownByte, true)); break; case 15: //0f - out.add(new StatusAppliedEffect(value >> 16, false)); + out.add(new StatusAppliedEffect(flags, value, value >> 16, unknownByte, false)); break; case 20: //14 - out.add(new StatusNoEffect(value >> 16)); + out.add(new StatusNoEffect(flags, value, value >> 16)); break; // 1d,0x60000 = reflect? case 27: //1B diff --git a/xivsupport/src/main/java/gg/xp/xivsupport/gui/tables/renderers/AbilityEffectRenderer.java b/xivsupport/src/main/java/gg/xp/xivsupport/gui/tables/renderers/AbilityEffectRenderer.java index 649e2d6b4a65..798e4fb51492 100644 --- a/xivsupport/src/main/java/gg/xp/xivsupport/gui/tables/renderers/AbilityEffectRenderer.java +++ b/xivsupport/src/main/java/gg/xp/xivsupport/gui/tables/renderers/AbilityEffectRenderer.java @@ -71,12 +71,13 @@ else if (value instanceof InvulnBlockedDamageEffect) { text = Long.toString(((InvulnBlockedDamageEffect) value).getAmount()); icon = ActionLibrary.iconForId(30); } - else if (value instanceof StatusAppliedEffect) { + else if (value instanceof StatusAppliedEffect statusApplied) { text = "+"; - icon = StatusEffectLibrary.iconForId(((StatusAppliedEffect) value).getStatus().getId(), 1); + icon = StatusEffectLibrary.iconForId(statusApplied.getStatus().getId(), statusApplied.getStacks()); textOnRight = true; } else if (value instanceof StatusNoEffect) { + // TODO: does this also have a 'stacks' value? text = "X"; icon = StatusEffectLibrary.iconForId(((StatusNoEffect) value).getStatus().getId(), 1); textOnRight = true; From ce3e1760e15da5502a81fad07f4187f3edf2760f Mon Sep 17 00:00:00 2001 From: xp Date: Wed, 2 Mar 2022 10:04:05 -0800 Subject: [PATCH 2/6] Ability/status icon chooser --- .../java/gg/xp/xivdata/data/ActionInfo.java | 4 +- .../java/gg/xp/xivsupport/gui/GuiMain.java | 2 +- .../xivsupport/gui/library/ActionTable.java | 57 +++++++++++++ .../xivsupport/gui/library/ChooserDialog.java | 43 ++++++++++ .../xivsupport/gui/library/StatusTable.java | 58 +++++++++++++ .../gui/tables/AutoBottomScrollHelper.java | 42 ++++----- .../gui/tables/TableWithFilterAndDetails.java | 75 ++++++++++------ .../gg/xp/xivsupport/gui/tabs/LibraryTab.java | 85 ++----------------- .../gui/timelines/TimelinesTab.java | 55 +++++++++--- 9 files changed, 278 insertions(+), 143 deletions(-) create mode 100644 xivsupport/src/main/java/gg/xp/xivsupport/gui/library/ActionTable.java create mode 100644 xivsupport/src/main/java/gg/xp/xivsupport/gui/library/ChooserDialog.java create mode 100644 xivsupport/src/main/java/gg/xp/xivsupport/gui/library/StatusTable.java diff --git a/xivdata/src/main/java/gg/xp/xivdata/data/ActionInfo.java b/xivdata/src/main/java/gg/xp/xivdata/data/ActionInfo.java index 343d6a22c565..7a877fc2cf58 100644 --- a/xivdata/src/main/java/gg/xp/xivdata/data/ActionInfo.java +++ b/xivdata/src/main/java/gg/xp/xivdata/data/ActionInfo.java @@ -1,12 +1,14 @@ package gg.xp.xivdata.data; +import org.jetbrains.annotations.Nullable; + // TODO: cooldown and stuff? What else would we want here? public record ActionInfo( long actionid, String name, long iconId ) { - public ActionIcon getIcon() { + public @Nullable ActionIcon getIcon() { return ActionLibrary.iconForInfo(this); } } diff --git a/xivsupport/src/main/java/gg/xp/xivsupport/gui/GuiMain.java b/xivsupport/src/main/java/gg/xp/xivsupport/gui/GuiMain.java index 69ed3b429c6f..76048db88b84 100644 --- a/xivsupport/src/main/java/gg/xp/xivsupport/gui/GuiMain.java +++ b/xivsupport/src/main/java/gg/xp/xivsupport/gui/GuiMain.java @@ -153,7 +153,7 @@ public GuiMain(EventMaster master, MutablePicoContainer container) { SwingUtilities.invokeLater(() -> tabPane.addTab("Pulls", getPullsTab())); SwingUtilities.invokeLater(() -> tabPane.addTab("Overlays", getOverlayConfigTab())); SwingUtilities.invokeLater(() -> tabPane.addTab("Map", container.getComponent(MapTab.class))); - SwingUtilities.invokeLater(() -> tabPane.addTab("Library", new LibraryTab(container))); + SwingUtilities.invokeLater(() -> tabPane.addTab("Library", new LibraryTab())); SwingUtilities.invokeLater(() -> tabPane.addTab("Advanced", new AdvancedTab(container))); } diff --git a/xivsupport/src/main/java/gg/xp/xivsupport/gui/library/ActionTable.java b/xivsupport/src/main/java/gg/xp/xivsupport/gui/library/ActionTable.java new file mode 100644 index 000000000000..7bca20ecaac9 --- /dev/null +++ b/xivsupport/src/main/java/gg/xp/xivsupport/gui/library/ActionTable.java @@ -0,0 +1,57 @@ +package gg.xp.xivsupport.gui.library; + +import gg.xp.xivdata.data.ActionIcon; +import gg.xp.xivdata.data.ActionInfo; +import gg.xp.xivdata.data.ActionLibrary; +import gg.xp.xivsupport.gui.tables.CustomColumn; +import gg.xp.xivsupport.gui.tables.TableWithFilterAndDetails; +import gg.xp.xivsupport.gui.tables.filters.IdOrNameFilter; +import gg.xp.xivsupport.gui.tables.renderers.ActionAndStatusRenderer; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + +public final class ActionTable { + private ActionTable() { + } + + public static TableWithFilterAndDetails table() { + return TableWithFilterAndDetails.builder("Actions/Abilities", () -> { + Map csvValues = ActionLibrary.getAll(); + List values = new ArrayList<>(csvValues.values()); + values.sort(Comparator.comparing(ActionInfo::actionid)); + return values; + }, unused -> Collections.emptyList()) + .addMainColumn(new CustomColumn<>("ID", v -> String.format("0x%X (%s)", v.actionid(), v.actionid()), col -> { + col.setMinWidth(100); + col.setMaxWidth(100); + })) + .addMainColumn(new CustomColumn<>("Name", ActionInfo::name, col -> { + col.setPreferredWidth(200); + })) + .addMainColumn(new CustomColumn<>("Icon", ai -> { + ActionIcon icon = ai.getIcon(); + if (icon == null) { + return null; + } + else { + return icon.getIconUrl(); + } + }, col -> { + col.setCellRenderer(new ActionAndStatusRenderer(true, false, false)); + col.setPreferredWidth(500); + })) + .addFilter(t -> new IdOrNameFilter<>("Name/ID", ActionInfo::actionid, ActionInfo::name, t)) + .setFixedData(true) + .build(); + } + + public static void showChooser(Consumer callback) { + TableWithFilterAndDetails table = table(); + ChooserDialog.showChooser(table, callback); + } +} diff --git a/xivsupport/src/main/java/gg/xp/xivsupport/gui/library/ChooserDialog.java b/xivsupport/src/main/java/gg/xp/xivsupport/gui/library/ChooserDialog.java new file mode 100644 index 000000000000..4a109cffd4f1 --- /dev/null +++ b/xivsupport/src/main/java/gg/xp/xivsupport/gui/library/ChooserDialog.java @@ -0,0 +1,43 @@ +package gg.xp.xivsupport.gui.library; + +import gg.xp.xivsupport.gui.WrapLayout; +import gg.xp.xivsupport.gui.tables.TableWithFilterAndDetails; + +import javax.swing.*; +import java.awt.*; +import java.util.function.Consumer; + +public final class ChooserDialog { + private ChooserDialog() { + } + + public static void showChooser(TableWithFilterAndDetails table, Consumer callback) { + JDialog dialog = new JDialog(); + Container pane = dialog.getContentPane(); + pane.setLayout(new BorderLayout()); + pane.add(table, BorderLayout.CENTER); + JPanel buttonsPanel = new JPanel(new WrapLayout()); + JButton select = new JButton("Select"); + JButton cancel = new JButton("Cancel"); + select.addActionListener(l -> { + X selection = table.getCurrentSelection(); + dialog.setVisible(false); + dialog.dispose(); + callback.accept(selection); + }); + cancel.addActionListener(l -> { + dialog.setVisible(false); + dialog.dispose(); + }); + buttonsPanel.add(select); + buttonsPanel.add(cancel); + pane.add(buttonsPanel, BorderLayout.SOUTH); + dialog.setSize(new Dimension(800, 800)); + dialog.revalidate(); + dialog.setModal(true); + dialog.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); + table.signalNewData(); + dialog.setVisible(true); + + } +} diff --git a/xivsupport/src/main/java/gg/xp/xivsupport/gui/library/StatusTable.java b/xivsupport/src/main/java/gg/xp/xivsupport/gui/library/StatusTable.java new file mode 100644 index 000000000000..e74f1711be44 --- /dev/null +++ b/xivsupport/src/main/java/gg/xp/xivsupport/gui/library/StatusTable.java @@ -0,0 +1,58 @@ +package gg.xp.xivsupport.gui.library; + +import gg.xp.xivdata.data.StatusEffectIcon; +import gg.xp.xivdata.data.StatusEffectInfo; +import gg.xp.xivdata.data.StatusEffectLibrary; +import gg.xp.xivsupport.gui.tables.CustomColumn; +import gg.xp.xivsupport.gui.tables.TableWithFilterAndDetails; +import gg.xp.xivsupport.gui.tables.filters.IdOrNameFilter; +import gg.xp.xivsupport.gui.tables.filters.TextBasedFilter; +import gg.xp.xivsupport.gui.tables.renderers.StatusEffectListRenderer; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + +public final class StatusTable { + private StatusTable() { + } + + public static TableWithFilterAndDetails table() { + return TableWithFilterAndDetails.builder("Status Effects", () -> { + Map csvValues = StatusEffectLibrary.getAll(); + List values = new ArrayList<>(csvValues.values()); + values.sort(Comparator.comparing(StatusEffectInfo::statusEffectId)); + return values; + }, unused -> Collections.emptyList()) + .addMainColumn(new CustomColumn<>("ID", v -> String.format("0x%X (%s)", v.statusEffectId(), v.statusEffectId()), col -> { + col.setMinWidth(100); + col.setMaxWidth(100); + })) + .addMainColumn(new CustomColumn<>("Name", StatusEffectInfo::name, col -> { + col.setPreferredWidth(200); + })) + .addMainColumn(new CustomColumn<>("Description", StatusEffectInfo::description, col -> { + col.setPreferredWidth(500); + })) + .addMainColumn(new CustomColumn<>("Stacks", StatusEffectInfo::maxStacks, col -> { + col.setMinWidth(50); + col.setMaxWidth(50); + })) + .addMainColumn(new CustomColumn<>("Icons", statusEffectInfo -> statusEffectInfo.getAllIcons().stream().map(StatusEffectIcon::getIconUrl).toList(), col -> { + col.setCellRenderer(new StatusEffectListRenderer()); + col.setPreferredWidth(500); + })) + .addFilter(t -> new IdOrNameFilter<>("Name/ID", StatusEffectInfo::statusEffectId, StatusEffectInfo::name, t)) + .addFilter(t -> new TextBasedFilter<>(t, "Description", StatusEffectInfo::description)) + .setFixedData(true) + .build(); + } + + public static void showChooser(Consumer callback) { + TableWithFilterAndDetails table = table(); + ChooserDialog.showChooser(table, callback); + } +} diff --git a/xivsupport/src/main/java/gg/xp/xivsupport/gui/tables/AutoBottomScrollHelper.java b/xivsupport/src/main/java/gg/xp/xivsupport/gui/tables/AutoBottomScrollHelper.java index 7d51a2e6bc7d..e90fa2b96b2d 100644 --- a/xivsupport/src/main/java/gg/xp/xivsupport/gui/tables/AutoBottomScrollHelper.java +++ b/xivsupport/src/main/java/gg/xp/xivsupport/gui/tables/AutoBottomScrollHelper.java @@ -24,27 +24,27 @@ public AutoBottomScrollHelper(JTable table, Runnable forceOffCallback) { setPreferredSize(getMaximumSize()); JScrollBar bar = getVerticalScrollBar(); setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS); - addComponentListener(new ComponentAdapter() { - @Override - public void componentResized(ComponentEvent e) { - super.componentResized(e); - } - - @Override - public void componentMoved(ComponentEvent e) { - super.componentMoved(e); - } - - @Override - public void componentShown(ComponentEvent e) { - super.componentShown(e); - } - - @Override - public void componentHidden(ComponentEvent e) { - super.componentHidden(e); - } - }); +// addComponentListener(new ComponentAdapter() { +// @Override +// public void componentResized(ComponentEvent e) { +// super.componentResized(e); +// } +// +// @Override +// public void componentMoved(ComponentEvent e) { +// super.componentMoved(e); +// } +// +// @Override +// public void componentShown(ComponentEvent e) { +// super.componentShown(e); +// } +// +// @Override +// public void componentHidden(ComponentEvent e) { +// super.componentHidden(e); +// } +// }); bar.addAdjustmentListener(event -> SwingUtilities.invokeLater(() -> { int newMax = bar.getMaximum(); int newExtent = bar.getModel().getExtent(); diff --git a/xivsupport/src/main/java/gg/xp/xivsupport/gui/tables/TableWithFilterAndDetails.java b/xivsupport/src/main/java/gg/xp/xivsupport/gui/tables/TableWithFilterAndDetails.java index a15bb702388c..5dbd6ef00bf6 100644 --- a/xivsupport/src/main/java/gg/xp/xivsupport/gui/tables/TableWithFilterAndDetails.java +++ b/xivsupport/src/main/java/gg/xp/xivsupport/gui/tables/TableWithFilterAndDetails.java @@ -4,15 +4,12 @@ import gg.xp.xivsupport.gui.TitleBorderFullsizePanel; import gg.xp.xivsupport.gui.WrapLayout; import gg.xp.xivsupport.gui.tables.filters.VisualFilter; +import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.swing.*; -import javax.swing.event.PopupMenuEvent; -import javax.swing.event.PopupMenuListener; import java.awt.*; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -26,7 +23,7 @@ import java.util.function.Supplier; import java.util.stream.Collectors; -public class TableWithFilterAndDetails extends TitleBorderFullsizePanel { +public final class TableWithFilterAndDetails extends TitleBorderFullsizePanel { private static final Logger log = LoggerFactory.getLogger(TableWithFilterAndDetails.class); private static final ExecutorService exs = Executors.newSingleThreadExecutor(); @@ -34,7 +31,7 @@ public class TableWithFilterAndDetails extends TitleBorderFullsizePanel { private final Supplier> dataGetter; private final List> filters; private final CustomTableModel mainModel; - private final JCheckBox stayAtBottom; + private final @Nullable JCheckBox stayAtBottom; private volatile X currentSelection; private List dataRaw = Collections.emptyList(); private List dataFiltered = Collections.emptyList(); @@ -53,7 +50,8 @@ private TableWithFilterAndDetails( List rightClickOptions, BiPredicate selectionEquivalence, BiPredicate detailsSelectionEquivalence, - boolean appendOrPruneOnly) { + boolean appendOrPruneOnly, + boolean fixedData) { super(title); this.title = title; // TODO: add count of events @@ -92,26 +90,34 @@ private TableWithFilterAndDetails( SwingUtilities.invokeLater(detailsModel::fullRefresh); // detailsModel.fireTableDataChanged(); }); - JButton refreshButton = new JButton("Refresh"); - refreshButton.addActionListener(e -> { - updateAll(); - }); - JCheckBox autoRefresh = new JCheckBox("Auto Refresh"); - autoRefresh.addItemListener(e -> { - isAutoRefreshEnabled = autoRefresh.isSelected(); - }); - autoRefresh.setSelected(true); - - stayAtBottom = new JCheckBox("Scroll to Bottom"); - AutoBottomScrollHelper scroller = new AutoBottomScrollHelper(table, () -> stayAtBottom.setSelected(false)); - stayAtBottom.addItemListener(e -> scroller.setAutoScrollEnabled(stayAtBottom.isSelected())); - stayAtBottom.setSelected(true); // Top panel JPanel topPanel = new JPanel(); - topPanel.add(refreshButton); - topPanel.add(autoRefresh); - topPanel.add(stayAtBottom); + + { + JButton refreshButton = new JButton(fixedData ? "Load" : "Refresh"); + refreshButton.addActionListener(e -> updateAll()); + topPanel.add(refreshButton); + } + + JScrollPane scroller; + if (!fixedData) { + JCheckBox autoRefresh = new JCheckBox("Auto Refresh"); + autoRefresh.addItemListener(e -> isAutoRefreshEnabled = autoRefresh.isSelected()); + autoRefresh.setSelected(true); + + stayAtBottom = new JCheckBox("Scroll to Bottom"); + scroller = new AutoBottomScrollHelper(table, () -> stayAtBottom.setSelected(false)); + stayAtBottom.addItemListener(e -> ((AutoBottomScrollHelper) scroller).setAutoScrollEnabled(stayAtBottom.isSelected())); + stayAtBottom.setSelected(true); + topPanel.add(autoRefresh); + topPanel.add(stayAtBottom); + } + else { + scroller = new JScrollPane(table); + stayAtBottom = null; + isAutoRefreshEnabled = true; + } topPanel.setLayout(new WrapLayout(FlowLayout.LEFT)); filters = filterCreators.stream().map(filterCreator -> filterCreator.apply(this::updateFiltering)) .filter(Objects::nonNull) @@ -176,6 +182,10 @@ private void filterFully() { dataFiltered = out; } + public CustomTableModel getMainModel() { + return mainModel; + } + private enum RefreshType { NONE, APPEND, @@ -270,6 +280,10 @@ private void updateAll() { updateModel(); } + public @Nullable X getCurrentSelection() { + return currentSelection; + } + public static final class TableWithFilterAndDetailsBuilder { private final String title; private final Supplier> dataGetter; @@ -282,6 +296,7 @@ public static final class TableWithFilterAndDetailsBuilder { private BiPredicate selectionEquivalence = Objects::equals; private BiPredicate detailsSelectionEquivalence = Objects::equals; private boolean appendOrPruneOnly; + private boolean fixedData; private TableWithFilterAndDetailsBuilder(String title, Supplier> dataGetter, Function> detailsConverter) { @@ -335,7 +350,13 @@ public TableWithFilterAndDetailsBuilder setAppendOrPruneOnly(boolean appen } public TableWithFilterAndDetails build() { - return new TableWithFilterAndDetails(title, dataGetter, mainColumns, detailsColumns, detailsConverter, filters, widgets, rightClickOptions, selectionEquivalence, detailsSelectionEquivalence, appendOrPruneOnly); + return new TableWithFilterAndDetails<>(title, dataGetter, mainColumns, detailsColumns, detailsConverter, filters, widgets, rightClickOptions, selectionEquivalence, detailsSelectionEquivalence, appendOrPruneOnly, fixedData); + } + + public TableWithFilterAndDetailsBuilder setFixedData(boolean fixedData) { + setAppendOrPruneOnly(true); + this.fixedData = fixedData; + return this; } } @@ -344,6 +365,8 @@ public static TableWithFilterAndDetailsBuilder builder(String title } public void setBottomScroll(boolean value) { - stayAtBottom.setSelected(value); + if (stayAtBottom != null) { + stayAtBottom.setSelected(value); + } } } diff --git a/xivsupport/src/main/java/gg/xp/xivsupport/gui/tabs/LibraryTab.java b/xivsupport/src/main/java/gg/xp/xivsupport/gui/tabs/LibraryTab.java index 1ad15451a3e8..4637933b2788 100644 --- a/xivsupport/src/main/java/gg/xp/xivsupport/gui/tabs/LibraryTab.java +++ b/xivsupport/src/main/java/gg/xp/xivsupport/gui/tabs/LibraryTab.java @@ -1,96 +1,21 @@ package gg.xp.xivsupport.gui.tabs; -import gg.xp.xivdata.data.ActionIcon; -import gg.xp.xivdata.data.ActionInfo; -import gg.xp.xivdata.data.ActionLibrary; -import gg.xp.xivdata.data.StatusEffectIcon; -import gg.xp.xivdata.data.StatusEffectInfo; -import gg.xp.xivdata.data.StatusEffectLibrary; -import gg.xp.xivsupport.gui.tables.CustomColumn; -import gg.xp.xivsupport.gui.tables.TableWithFilterAndDetails; -import gg.xp.xivsupport.gui.tables.filters.IdOrNameFilter; -import gg.xp.xivsupport.gui.tables.filters.TextBasedFilter; -import gg.xp.xivsupport.gui.tables.renderers.ActionAndStatusRenderer; -import gg.xp.xivsupport.gui.tables.renderers.StatusEffectListRenderer; +import gg.xp.xivsupport.gui.library.ActionTable; +import gg.xp.xivsupport.gui.library.StatusTable; import org.picocontainer.PicoContainer; import javax.swing.*; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.Map; public class LibraryTab extends JTabbedPane { - public LibraryTab(PicoContainer container) { + public LibraryTab() { super(LEFT); { - TableWithFilterAndDetails statusTable = TableWithFilterAndDetails.builder("Status Effects", () -> { - Map csvValues = StatusEffectLibrary.getAll(); - List values = new ArrayList<>(csvValues.values()); - values.sort(Comparator.comparing(StatusEffectInfo::statusEffectId)); - return values; - }, unused -> Collections.emptyList()) - .addMainColumn(new CustomColumn<>("ID", v -> String.format("0x%X (%s)", v.statusEffectId(), v.statusEffectId()), col -> { - col.setMinWidth(100); - col.setMaxWidth(100); - })) - .addMainColumn(new CustomColumn<>("Name", StatusEffectInfo::name, col -> { - col.setPreferredWidth(200); - })) - .addMainColumn(new CustomColumn<>("Description", StatusEffectInfo::description, col -> { - col.setPreferredWidth(500); - })) - .addMainColumn(new CustomColumn<>("Stacks", StatusEffectInfo::maxStacks, col -> { - col.setMinWidth(50); - col.setMaxWidth(50); - })) - .addMainColumn(new CustomColumn<>("Icons", statusEffectInfo -> statusEffectInfo.getAllIcons().stream().map(StatusEffectIcon::getIconUrl).toList(), col -> { - col.setCellRenderer(new StatusEffectListRenderer()); - col.setPreferredWidth(500); - })) - .addFilter(t -> new IdOrNameFilter<>("Name/ID", StatusEffectInfo::statusEffectId, StatusEffectInfo::name, t)) - .addFilter(t -> new TextBasedFilter<>(t, "Description", StatusEffectInfo::description)) - .setAppendOrPruneOnly(true) - .build(); - - statusTable.setBottomScroll(false); - addTab("Status Effects", statusTable); + addTab("Status Effects", StatusTable.table()); } { - TableWithFilterAndDetails actionsTable = TableWithFilterAndDetails.builder("Actions/Abilities", () -> { - Map csvValues = ActionLibrary.getAll(); - List values = new ArrayList<>(csvValues.values()); - values.sort(Comparator.comparing(ActionInfo::actionid)); - return values; - }, unused -> Collections.emptyList()) - .addMainColumn(new CustomColumn<>("ID", v -> String.format("0x%X (%s)", v.actionid(), v.actionid()), col -> { - col.setMinWidth(100); - col.setMaxWidth(100); - })) - .addMainColumn(new CustomColumn<>("Name", ActionInfo::name, col -> { - col.setPreferredWidth(200); - })) - .addMainColumn(new CustomColumn<>("Icon", ai -> { - ActionIcon icon = ai.getIcon(); - if (icon == null) { - return null; - } - else { - return icon.getIconUrl(); - } - }, col -> { - col.setCellRenderer(new ActionAndStatusRenderer(true, false, false)); - col.setPreferredWidth(500); - })) - .addFilter(t -> new IdOrNameFilter<>("Name/ID", ActionInfo::actionid, ActionInfo::name, t)) - .setAppendOrPruneOnly(true) - .build(); + addTab("Actions/Abilities", ActionTable.table()); - actionsTable.setBottomScroll(false); - addTab("Actions/Abilities", actionsTable); - } } diff --git a/xivsupport/src/main/java/gg/xp/xivsupport/gui/timelines/TimelinesTab.java b/xivsupport/src/main/java/gg/xp/xivsupport/gui/timelines/TimelinesTab.java index c635e6e72e4a..285b26ab5ac0 100644 --- a/xivsupport/src/main/java/gg/xp/xivsupport/gui/timelines/TimelinesTab.java +++ b/xivsupport/src/main/java/gg/xp/xivsupport/gui/timelines/TimelinesTab.java @@ -1,6 +1,10 @@ package gg.xp.xivsupport.gui.timelines; import gg.xp.reevent.scan.ScanMe; +import gg.xp.xivdata.data.ActionIcon; +import gg.xp.xivdata.data.ActionInfo; +import gg.xp.xivdata.data.StatusEffectIcon; +import gg.xp.xivdata.data.StatusEffectInfo; import gg.xp.xivsupport.events.state.XivState; import gg.xp.xivsupport.events.triggers.duties.timelines.CustomTimelineEntry; import gg.xp.xivsupport.events.triggers.duties.timelines.TimelineCustomizations; @@ -13,6 +17,8 @@ import gg.xp.xivsupport.gui.WrapperPanel; import gg.xp.xivsupport.gui.components.ReadOnlyText; import gg.xp.xivsupport.gui.extra.PluginTab; +import gg.xp.xivsupport.gui.library.ActionTable; +import gg.xp.xivsupport.gui.library.StatusTable; import gg.xp.xivsupport.gui.tables.CustomColumn; import gg.xp.xivsupport.gui.tables.CustomRightClickOption; import gg.xp.xivsupport.gui.tables.CustomTableModel; @@ -42,6 +48,7 @@ public class TimelinesTab extends TitleBorderFullsizePanel implements PluginTab private final TimelineManager backend; private final CustomTableModel> timelineChooserModel; private final CustomTableModel timelineModel; + private final JTable timelineTable; private Long currentZone; private TimelineProcessor currentTimeline; private TimelineCustomizations currentCust; @@ -214,18 +221,14 @@ public TimelinesTab(TimelineManager backend, TimelineOverlay overlay, XivState s ))); CustomRightClickOption delete = CustomRightClickOption.forRow("Delete", CustomTimelineEntry.class, this::deleteEntry); - JTable timelineTable = new JTable(timelineModel) { + CustomRightClickOption chooseAbilityIcon = CustomRightClickOption.forRow("Use Ability Icon", TimelineEntry.class, this::chooseActionIcon); + CustomRightClickOption chooseStatusIcon = CustomRightClickOption.forRow("Use Status Icon", TimelineEntry.class, this::chooseStatusInfo); + + timelineTable = new JTable(timelineModel) { @Override public boolean isCellEditable(int row, int column) { - if (column == 6) { - return false; - } -// TimelineEntry selected = timelineModel.getValueForRow(row); -// if (selected != null && !(selected instanceof CustomTimelineEntry)) { -// addNewEntry(CustomTimelineEntry.overrideFor(selected)); -// } - return true; + return column != 6; } @Override @@ -252,14 +255,11 @@ public void editingStopped(ChangeEvent e) { timelineChooserTable.getSelectionModel().addListSelectionListener(l -> { timelineModel.setSelectedValue(null); - TableCellEditor editor = timelineTable.getCellEditor(); - if (editor != null) { - editor.cancelCellEditing(); - } + stopEditing(); updateTab(); }); - CustomRightClickOption.configureTable(timelineTable, timelineModel, List.of(clone, delete)); + CustomRightClickOption.configureTable(timelineTable, timelineModel, List.of(clone, delete, chooseAbilityIcon, chooseStatusIcon)); c.gridx++; c.weightx = 1; @@ -305,6 +305,33 @@ public boolean isEnabled() { this.add(new WrapperPanel(newButton), c); } + private void stopEditing() { + TableCellEditor editor = timelineTable.getCellEditor(); + if (editor != null) { + editor.cancelCellEditing(); + } + } + + private void chooseActionIcon(TimelineEntry te) { + stopEditing(); + ActionTable.showChooser(action -> this.safeEditTimelineEntry((ce, actionInfo) -> { + ActionIcon icon = actionInfo.getIcon(); + ce.icon = icon == null ? null : icon.getIconUrl(); + }).accept(te, action) + ); + updateTab(); + } + + private void chooseStatusInfo(TimelineEntry te) { + stopEditing(); + StatusTable.showChooser(status -> this.safeEditTimelineEntry((ce, statusInfo) -> { + StatusEffectIcon icon = statusInfo.getIcon(0); + ce.icon = icon == null ? null : icon.getIconUrl(); + }).accept(te, status) + ); + updateTab(); + } + /** * Wraps the given editing function to make it create an override first if needed * From 491fd8016d46517212921ec338b94d35f3c88cf2 Mon Sep 17 00:00:00 2001 From: xp Date: Wed, 2 Mar 2022 15:35:46 -0800 Subject: [PATCH 3/6] More timeline stuff + settings improvements --- .../xivsupport/gui/LaunchImportedActLog.java | 1 - .../xivsupport/gui/LaunchImportedSession.java | 2 - .../duties/timelines/CustomTimelineEntry.java | 22 ++- .../timelines/TextFileTimelineEntry.java | 5 + .../duties/timelines/TimelineEntry.java | 2 + .../duties/timelines/TimelineProcessor.java | 8 +- .../java/gg/xp/xivsupport/gui/GuiMain.java | 2 +- .../gg/xp/xivsupport/gui/NoCellEditor.java | 1 + .../xivsupport/gui/library/ActionTable.java | 5 +- .../xivsupport/gui/library/ChooserDialog.java | 15 ++- .../xivsupport/gui/library/StatusTable.java | 5 +- .../gui/tables/StandardColumns.java | 46 ++++++- .../gui/tables/TableWithFilterAndDetails.java | 9 +- .../gui/timelines/TimelinesTab.java | 125 ++++++++++++++---- .../PropertiesFilePersistenceProvider.java | 26 +++- .../replay/gui/ReplayControllerGui.java | 11 +- .../xivsupport/gui/GuiWithImportedData.java | 2 - 17 files changed, 235 insertions(+), 52 deletions(-) diff --git a/launcher/src/main/java/gg/xp/xivsupport/gui/LaunchImportedActLog.java b/launcher/src/main/java/gg/xp/xivsupport/gui/LaunchImportedActLog.java index 1b5259006da5..098ca37d7da7 100644 --- a/launcher/src/main/java/gg/xp/xivsupport/gui/LaunchImportedActLog.java +++ b/launcher/src/main/java/gg/xp/xivsupport/gui/LaunchImportedActLog.java @@ -32,7 +32,6 @@ public static void fromEvents(List events, boolean decompress) pico.addComponent(FakeACTTimeSource.class); AutoEventDistributor dist = pico.getComponent(AutoEventDistributor.class); PersistenceProvider pers = pico.getComponent(PersistenceProvider.class); - pers.save("gui.display-predicted-hp", "true"); EventMaster master = pico.getComponent(EventMaster.class); ReplayController replayController = new ReplayController(master, events, decompress); pico.addComponent(replayController); diff --git a/launcher/src/main/java/gg/xp/xivsupport/gui/LaunchImportedSession.java b/launcher/src/main/java/gg/xp/xivsupport/gui/LaunchImportedSession.java index 9228f23d184d..b3ab7f3bef50 100644 --- a/launcher/src/main/java/gg/xp/xivsupport/gui/LaunchImportedSession.java +++ b/launcher/src/main/java/gg/xp/xivsupport/gui/LaunchImportedSession.java @@ -30,8 +30,6 @@ public static void fromEvents(List events, boolean decompress) MutablePicoContainer pico = XivMain.importInit(); AutoEventDistributor dist = pico.getComponent(AutoEventDistributor.class); EventMaster master = pico.getComponent(EventMaster.class); - PersistenceProvider pers = pico.getComponent(PersistenceProvider.class); - pers.save("gui.display-predicted-hp", "true"); ReplayController replayController = new ReplayController(master, events, decompress); pico.addComponent(replayController); pico.getComponent(RawEventStorage.class); diff --git a/xivsupport/src/main/java/gg/xp/xivsupport/events/triggers/duties/timelines/CustomTimelineEntry.java b/xivsupport/src/main/java/gg/xp/xivsupport/events/triggers/duties/timelines/CustomTimelineEntry.java index 8a989490e374..72c662aa4b16 100644 --- a/xivsupport/src/main/java/gg/xp/xivsupport/events/triggers/duties/timelines/CustomTimelineEntry.java +++ b/xivsupport/src/main/java/gg/xp/xivsupport/events/triggers/duties/timelines/CustomTimelineEntry.java @@ -26,11 +26,13 @@ public class CustomTimelineEntry implements TimelineEntry { public @Nullable Double jump; public @Nullable URL icon; private @Nullable TimelineReference replaces; + public boolean enabled; public CustomTimelineEntry() { name = "Name Goes Here"; } + @SuppressWarnings("NegativelyNamedBooleanVariable") @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) public CustomTimelineEntry( @JsonProperty("time") double time, @@ -40,7 +42,8 @@ public CustomTimelineEntry( @JsonProperty("timelineWindow") @NotNull TimelineWindow timelineWindow, @JsonProperty("jump") @Nullable Double jump, @JsonProperty("icon") @Nullable URL icon, - @JsonProperty("replaces") @Nullable TimelineReference replaces) { + @JsonProperty("replaces") @Nullable TimelineReference replaces, + @JsonProperty(value = "disabled", defaultValue = "false") boolean disabled) { this.time = time; this.name = name; this.sync = sync; @@ -50,6 +53,7 @@ public CustomTimelineEntry( this.jump = jump; this.icon = icon; this.replaces = replaces; + this.enabled = !disabled; } @Override @@ -120,7 +124,7 @@ public boolean equals(Object o) { @Override public int hashCode() { - return Objects.hash(time, name, sync, duration, windowStart, windowEnd, jump, icon); + return Objects.hash(time, name, sync, duration, windowStart, windowEnd, jump, icon, replaces, enabled); } @JsonProperty @@ -129,6 +133,17 @@ public int hashCode() { return replaces; } + @Override + public boolean enabled() { + return enabled; + } + + @JsonProperty + @JsonInclude(JsonInclude.Include.NON_DEFAULT) + public boolean disabled() { + return !enabled; + } + public static CustomTimelineEntry overrideFor(TimelineEntry other) { return new CustomTimelineEntry( other.time(), @@ -138,7 +153,8 @@ public static CustomTimelineEntry overrideFor(TimelineEntry other) { other.timelineWindow(), other.jump(), other.icon(), - new TimelineReference(other.time(), other.name()) + new TimelineReference(other.time(), other.name()), + false ); } } diff --git a/xivsupport/src/main/java/gg/xp/xivsupport/events/triggers/duties/timelines/TextFileTimelineEntry.java b/xivsupport/src/main/java/gg/xp/xivsupport/events/triggers/duties/timelines/TextFileTimelineEntry.java index 8bf0cf587938..06a0679f2ab4 100644 --- a/xivsupport/src/main/java/gg/xp/xivsupport/events/triggers/duties/timelines/TextFileTimelineEntry.java +++ b/xivsupport/src/main/java/gg/xp/xivsupport/events/triggers/duties/timelines/TextFileTimelineEntry.java @@ -24,4 +24,9 @@ public String toString() { ", jump=" + jump + '}'; } + + @Override + public boolean enabled() { + return true; + } } diff --git a/xivsupport/src/main/java/gg/xp/xivsupport/events/triggers/duties/timelines/TimelineEntry.java b/xivsupport/src/main/java/gg/xp/xivsupport/events/triggers/duties/timelines/TimelineEntry.java index ef7e880752d0..52373f05f9b0 100644 --- a/xivsupport/src/main/java/gg/xp/xivsupport/events/triggers/duties/timelines/TimelineEntry.java +++ b/xivsupport/src/main/java/gg/xp/xivsupport/events/triggers/duties/timelines/TimelineEntry.java @@ -56,6 +56,8 @@ default double getSyncToTime() { @Nullable Double jump(); + boolean enabled(); + @Override default int compareTo(@NotNull TimelineEntry o) { return Double.compare(this.time(), o.time()); diff --git a/xivsupport/src/main/java/gg/xp/xivsupport/events/triggers/duties/timelines/TimelineProcessor.java b/xivsupport/src/main/java/gg/xp/xivsupport/events/triggers/duties/timelines/TimelineProcessor.java index b8ac0e16d34a..4d0bf14884ed 100644 --- a/xivsupport/src/main/java/gg/xp/xivsupport/events/triggers/duties/timelines/TimelineProcessor.java +++ b/xivsupport/src/main/java/gg/xp/xivsupport/events/triggers/duties/timelines/TimelineProcessor.java @@ -22,6 +22,7 @@ public final class TimelineProcessor { private static final Logger log = LoggerFactory.getLogger(TimelineProcessor.class); private final List entries; + private final List rawEntries; private final IntSetting secondsFuture; private final IntSetting secondsPast; private final BooleanSetting debugMode; @@ -32,7 +33,8 @@ record TimelineSync(ACTLogLineEvent line, double lastSyncTime, TimelineEntry ori } private TimelineProcessor(TimelineManager manager, List entries) { - this.entries = entries; + this.rawEntries = entries; + this.entries = entries.stream().filter(TimelineEntry::enabled).collect(Collectors.toList()); secondsFuture = manager.getSecondsFuture(); secondsPast = manager.getSecondsPast(); debugMode = manager.getDebugMode(); @@ -90,6 +92,10 @@ public List getEntries() { return Collections.unmodifiableList(entries); } + public List getRawEntries() { + return Collections.unmodifiableList(rawEntries); + } + public List getCurrentTimelineEntries() { double effectiveLastSyncTime; if (lastSync == null) { diff --git a/xivsupport/src/main/java/gg/xp/xivsupport/gui/GuiMain.java b/xivsupport/src/main/java/gg/xp/xivsupport/gui/GuiMain.java index 76048db88b84..09a047dec4f9 100644 --- a/xivsupport/src/main/java/gg/xp/xivsupport/gui/GuiMain.java +++ b/xivsupport/src/main/java/gg/xp/xivsupport/gui/GuiMain.java @@ -140,7 +140,7 @@ public GuiMain(EventMaster master, MutablePicoContainer container) { frame.setVisible(true); frame.add(tabPane); if (replay != null) { - frame.add(new ReplayControllerGui(replay).getPanel(), BorderLayout.PAGE_START); + frame.add(new ReplayControllerGui(container, replay).getPanel(), BorderLayout.PAGE_START); } }); SwingUtilities.invokeLater(() -> tabPane.addTab("General", new SystemTabPanel())); diff --git a/xivsupport/src/main/java/gg/xp/xivsupport/gui/NoCellEditor.java b/xivsupport/src/main/java/gg/xp/xivsupport/gui/NoCellEditor.java index 66964ec13f08..0803a3feb5ae 100644 --- a/xivsupport/src/main/java/gg/xp/xivsupport/gui/NoCellEditor.java +++ b/xivsupport/src/main/java/gg/xp/xivsupport/gui/NoCellEditor.java @@ -7,6 +7,7 @@ import java.util.EventObject; public class NoCellEditor implements TableCellEditor { + public static final NoCellEditor INSTANCE = new NoCellEditor(); @Override public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { return null; diff --git a/xivsupport/src/main/java/gg/xp/xivsupport/gui/library/ActionTable.java b/xivsupport/src/main/java/gg/xp/xivsupport/gui/library/ActionTable.java index 7bca20ecaac9..e2078e29aa64 100644 --- a/xivsupport/src/main/java/gg/xp/xivsupport/gui/library/ActionTable.java +++ b/xivsupport/src/main/java/gg/xp/xivsupport/gui/library/ActionTable.java @@ -8,6 +8,7 @@ import gg.xp.xivsupport.gui.tables.filters.IdOrNameFilter; import gg.xp.xivsupport.gui.tables.renderers.ActionAndStatusRenderer; +import java.awt.*; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -50,8 +51,8 @@ public static TableWithFilterAndDetails table() { .build(); } - public static void showChooser(Consumer callback) { + public static void showChooser(Window owner, Consumer callback) { TableWithFilterAndDetails table = table(); - ChooserDialog.showChooser(table, callback); + ChooserDialog.showChooser(owner, table, callback); } } diff --git a/xivsupport/src/main/java/gg/xp/xivsupport/gui/library/ChooserDialog.java b/xivsupport/src/main/java/gg/xp/xivsupport/gui/library/ChooserDialog.java index 4a109cffd4f1..5ea2638764d9 100644 --- a/xivsupport/src/main/java/gg/xp/xivsupport/gui/library/ChooserDialog.java +++ b/xivsupport/src/main/java/gg/xp/xivsupport/gui/library/ChooserDialog.java @@ -11,13 +11,18 @@ public final class ChooserDialog { private ChooserDialog() { } - public static void showChooser(TableWithFilterAndDetails table, Consumer callback) { - JDialog dialog = new JDialog(); + public static void showChooser(Window owner, TableWithFilterAndDetails table, Consumer callback) { + JDialog dialog = new JDialog(owner, "Item Chooser"); Container pane = dialog.getContentPane(); pane.setLayout(new BorderLayout()); pane.add(table, BorderLayout.CENTER); JPanel buttonsPanel = new JPanel(new WrapLayout()); - JButton select = new JButton("Select"); + JButton select = new JButton("Select") { + @Override + public boolean isEnabled() { + return table.getCurrentSelection() != null; + } + }; JButton cancel = new JButton("Cancel"); select.addActionListener(l -> { X selection = table.getCurrentSelection(); @@ -33,10 +38,14 @@ public static void showChooser(TableWithFilterAndDetails table, Consum buttonsPanel.add(cancel); pane.add(buttonsPanel, BorderLayout.SOUTH); dialog.setSize(new Dimension(800, 800)); + dialog.setLocationRelativeTo(owner); dialog.revalidate(); dialog.setModal(true); dialog.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); table.signalNewData(); + table.getMainTable().getSelectionModel().addListSelectionListener(l -> { + select.repaint(); + }); dialog.setVisible(true); } diff --git a/xivsupport/src/main/java/gg/xp/xivsupport/gui/library/StatusTable.java b/xivsupport/src/main/java/gg/xp/xivsupport/gui/library/StatusTable.java index e74f1711be44..6c0cb42839f2 100644 --- a/xivsupport/src/main/java/gg/xp/xivsupport/gui/library/StatusTable.java +++ b/xivsupport/src/main/java/gg/xp/xivsupport/gui/library/StatusTable.java @@ -9,6 +9,7 @@ import gg.xp.xivsupport.gui.tables.filters.TextBasedFilter; import gg.xp.xivsupport.gui.tables.renderers.StatusEffectListRenderer; +import java.awt.*; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -51,8 +52,8 @@ public static TableWithFilterAndDetails table() { .build(); } - public static void showChooser(Consumer callback) { + public static void showChooser(Window frame, Consumer callback) { TableWithFilterAndDetails table = table(); - ChooserDialog.showChooser(table, callback); + ChooserDialog.showChooser(frame, table, callback); } } diff --git a/xivsupport/src/main/java/gg/xp/xivsupport/gui/tables/StandardColumns.java b/xivsupport/src/main/java/gg/xp/xivsupport/gui/tables/StandardColumns.java index 3c9b0327a869..d622fd5673c9 100644 --- a/xivsupport/src/main/java/gg/xp/xivsupport/gui/tables/StandardColumns.java +++ b/xivsupport/src/main/java/gg/xp/xivsupport/gui/tables/StandardColumns.java @@ -50,7 +50,7 @@ public final class StandardColumns { private final SequenceIdTracker sqidTracker; public StandardColumns(PersistenceProvider persist, StatusEffectRepository statuses, SequenceIdTracker sqidTracker) { - showPredictedHp = new BooleanSetting(persist, "gui.display-predicted-hp", false); + showPredictedHp = new BooleanSetting(persist, "gui.display-predicted-hp-2", false); this.statuses = statuses; this.sqidTracker = sqidTracker; } @@ -449,6 +449,50 @@ public Object getCellEditorValue() { return null; } } + + public static class CustomCheckboxEditor extends AbstractCellEditor implements TableCellEditor { + + @Serial + private static final long serialVersionUID = -3743763426515940614L; + private final BiConsumer writer; + + public CustomCheckboxEditor(BiConsumer writer) { + this.writer = writer; + } + + @Override + public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { + CustomTableModel model = (CustomTableModel) table.getModel(); + JCheckBox box = new JCheckBox(); + box.setSelected((boolean) value); + box.addActionListener(l -> { + writer.accept(model.getValueForRow(row), box.isSelected()); + }); + return box; + } + + @Override + public Object getCellEditorValue() { + return null; + } + } + + public static TableCellRenderer checkboxRenderer = new TableCellRenderer() { + private final DefaultTableCellRenderer defaultRenderer = new DefaultTableCellRenderer(); + + @Override + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { + if (value instanceof Boolean boolVal) { + JCheckBox cb = new JCheckBox(); + cb.setSelected(boolVal); + cb.setBackground(defaultRenderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column).getBackground()); + return cb; + } + else { + return null; + } + } + }; } diff --git a/xivsupport/src/main/java/gg/xp/xivsupport/gui/tables/TableWithFilterAndDetails.java b/xivsupport/src/main/java/gg/xp/xivsupport/gui/tables/TableWithFilterAndDetails.java index 5dbd6ef00bf6..54699f6bf87a 100644 --- a/xivsupport/src/main/java/gg/xp/xivsupport/gui/tables/TableWithFilterAndDetails.java +++ b/xivsupport/src/main/java/gg/xp/xivsupport/gui/tables/TableWithFilterAndDetails.java @@ -26,12 +26,13 @@ public final class TableWithFilterAndDetails extends TitleBorderFullsizePanel { private static final Logger log = LoggerFactory.getLogger(TableWithFilterAndDetails.class); - private static final ExecutorService exs = Executors.newSingleThreadExecutor(); + private final ExecutorService exs = Executors.newSingleThreadExecutor(); private final Supplier> dataGetter; private final List> filters; private final CustomTableModel mainModel; private final @Nullable JCheckBox stayAtBottom; + private final JTable table; private volatile X currentSelection; private List dataRaw = Collections.emptyList(); private List dataFiltered = Collections.emptyList(); @@ -73,7 +74,7 @@ private TableWithFilterAndDetails( // Main table - JTable table = new JTable(mainModel); + table = new JTable(mainModel); mainModel.configureColumns(table); table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); ListSelectionModel selectionModel = table.getSelectionModel(); @@ -186,6 +187,10 @@ public CustomTableModel getMainModel() { return mainModel; } + public JTable getMainTable() { + return table; + } + private enum RefreshType { NONE, APPEND, diff --git a/xivsupport/src/main/java/gg/xp/xivsupport/gui/timelines/TimelinesTab.java b/xivsupport/src/main/java/gg/xp/xivsupport/gui/timelines/TimelinesTab.java index 285b26ab5ac0..43e583e693cc 100644 --- a/xivsupport/src/main/java/gg/xp/xivsupport/gui/timelines/TimelinesTab.java +++ b/xivsupport/src/main/java/gg/xp/xivsupport/gui/timelines/TimelinesTab.java @@ -12,6 +12,7 @@ import gg.xp.xivsupport.events.triggers.duties.timelines.TimelineManager; import gg.xp.xivsupport.events.triggers.duties.timelines.TimelineOverlay; import gg.xp.xivsupport.events.triggers.duties.timelines.TimelineProcessor; +import gg.xp.xivsupport.gui.NoCellEditor; import gg.xp.xivsupport.gui.TitleBorderFullsizePanel; import gg.xp.xivsupport.gui.WrapLayout; import gg.xp.xivsupport.gui.WrapperPanel; @@ -35,6 +36,7 @@ import javax.swing.table.DefaultTableCellRenderer; import javax.swing.table.TableCellEditor; import java.awt.*; +import java.text.DecimalFormat; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -146,23 +148,39 @@ public TimelinesTab(TimelineManager backend, TimelineOverlay overlay, XivState s int numColMaxWidth = 200; int numColPrefWidth = 50; + int timeColMinWidth = 80; + int timeColMaxWidth = 200; + int timeColPrefWidth = 80; + timelineModel = CustomTableModel.builder(() -> { TimelineProcessor timeline = currentTimeline; if (timeline == null) { return Collections.emptyList(); } - return timeline.getEntries(); + return timeline.getRawEntries(); }) + .addColumn(new CustomColumn<>("En", TimelineEntry::enabled, col -> { + col.setCellRenderer(StandardColumns.checkboxRenderer); + col.setCellEditor(new StandardColumns.CustomCheckboxEditor<>(safeEditTimelineEntry((entry, value) -> { + entry.enabled = value; + stopEditing(); + commitSettings(); + }))); + col.setMinWidth(22); + col.setMaxWidth(22); + })) .addColumn(new CustomColumn<>("Type", Function.identity(), col -> { + col.setCellEditor(NoCellEditor.INSTANCE); col.setMinWidth(40); col.setMaxWidth(40); col.setCellRenderer(new TimelineEntryTypeRenderer()); })) .addColumn(new CustomColumn<>("Time", TimelineEntry::time, col -> { + col.setCellRenderer(new DoubleTimeRenderer()); col.setCellEditor(StandardColumns.doubleEditorNonNull(safeEditTimelineEntry((item, value) -> item.time = value))); - col.setMinWidth(numColMinWidth); - col.setMaxWidth(numColMaxWidth); - col.setPreferredWidth(numColPrefWidth); + col.setMinWidth(timeColMinWidth); + col.setMaxWidth(timeColMaxWidth); + col.setPreferredWidth(timeColPrefWidth); })) .addColumn(new CustomColumn<>("Icon", TimelineEntry::icon, col -> { col.setCellEditor(StandardColumns.urlEditorEmptyToNull(safeEditTimelineEntry((item, value) -> item.icon = value))); @@ -196,15 +214,17 @@ public TimelinesTab(TimelineManager backend, TimelineOverlay overlay, XivState s col.setPreferredWidth(numColPrefWidth); })) .addColumn(new CustomColumn<>("Win Effective", e -> String.format("%.01f - %.01f", e.getMinTime(), e.getMaxTime()), col -> { + col.setCellEditor(NoCellEditor.INSTANCE); col.setMinWidth(numColMinWidth * 2); col.setMaxWidth(numColMaxWidth * 2); col.setPreferredWidth(numColPrefWidth * 2); })) .addColumn(new CustomColumn<>("Jump", TimelineEntry::jump, col -> { + col.setCellRenderer(new DoubleTimeRenderer()); col.setCellEditor(StandardColumns.doubleEditorEmptyToNull(safeEditTimelineEntry((item, value) -> item.jump = value))); - col.setMinWidth(numColMinWidth); - col.setMaxWidth(numColMaxWidth); - col.setPreferredWidth(numColPrefWidth); + col.setMinWidth(timeColMinWidth); + col.setMaxWidth(timeColMaxWidth); + col.setPreferredWidth(timeColPrefWidth); })) .setItemEquivalence((one, two) -> one == two) .build(); @@ -217,7 +237,8 @@ public TimelinesTab(TimelineManager backend, TimelineOverlay overlay, XivState s e.timelineWindow(), e.jump(), e.icon(), - null + null, + false ))); CustomRightClickOption delete = CustomRightClickOption.forRow("Delete", CustomTimelineEntry.class, this::deleteEntry); @@ -228,25 +249,13 @@ public TimelinesTab(TimelineManager backend, TimelineOverlay overlay, XivState s @Override public boolean isCellEditable(int row, int column) { - return column != 6; + return !(getCellEditor(row, column) instanceof NoCellEditor); } @Override public void editingStopped(ChangeEvent e) { - TimelineEntry selected = timelineModel.getSelectedValue(); super.editingStopped(e); - Long currentZone = TimelinesTab.this.currentZone; - List newCurrentEntries = currentTimeline.getEntries().stream() - .filter(CustomTimelineEntry.class::isInstance) - .map(CustomTimelineEntry.class::cast) - .collect(Collectors.toList()); - currentCust.setEntries(newCurrentEntries); - backend.commitCustomSettings(currentZone); - TimelinesTab.this.updateTab(); - if (selected != null) { - SwingUtilities.invokeLater(() -> timelineModel.setSelectedValue(selected)); - SwingUtilities.invokeLater(() -> scrollRectToVisible(getCellRect(getSelectedRow(), 0, true))); - } + commitSettings(); } }; @@ -302,7 +311,41 @@ public boolean isEnabled() { } addNewEntry(newEntry); }); - this.add(new WrapperPanel(newButton), c); + JButton resetButton = new JButton("Delete All Customizations") { + @Override + public boolean isEnabled() { + return currentCust != null && !currentCust.getEntries().isEmpty(); + } + }; + // TODO: confirmation + resetButton.addActionListener(l -> { + int confirmation = JOptionPane.showConfirmDialog(this, "Are you sure you want to delete all customizations for the currently selected timeline?", "Confirm", JOptionPane.YES_NO_OPTION); + if (confirmation == 0) { + resetAll(); + } + }); + JPanel buttonPanel = new JPanel(new WrapLayout()); + buttonPanel.add(newButton); + buttonPanel.add(resetButton); + + this.add(buttonPanel, c); + } + + private void commitSettings() { + TimelineEntry selected = timelineModel.getSelectedValue(); + Long currentZone = TimelinesTab.this.currentZone; + List newCurrentEntries = currentTimeline.getRawEntries().stream() + .filter(CustomTimelineEntry.class::isInstance) + .map(CustomTimelineEntry.class::cast) + .collect(Collectors.toList()); + currentCust.setEntries(newCurrentEntries); + backend.commitCustomSettings(currentZone); + TimelinesTab.this.updateTab(); + if (selected != null) { + SwingUtilities.invokeLater(() -> timelineModel.setSelectedValue(selected)); + SwingUtilities.invokeLater(() -> scrollRectToVisible(timelineTable.getCellRect(timelineTable.getSelectedRow(), 0, true))); + } + } private void stopEditing() { @@ -314,7 +357,7 @@ private void stopEditing() { private void chooseActionIcon(TimelineEntry te) { stopEditing(); - ActionTable.showChooser(action -> this.safeEditTimelineEntry((ce, actionInfo) -> { + ActionTable.showChooser(SwingUtilities.getWindowAncestor(this), action -> this.safeEditTimelineEntry((ce, actionInfo) -> { ActionIcon icon = actionInfo.getIcon(); ce.icon = icon == null ? null : icon.getIconUrl(); }).accept(te, action) @@ -324,9 +367,9 @@ private void chooseActionIcon(TimelineEntry te) { private void chooseStatusInfo(TimelineEntry te) { stopEditing(); - StatusTable.showChooser(status -> this.safeEditTimelineEntry((ce, statusInfo) -> { - StatusEffectIcon icon = statusInfo.getIcon(0); - ce.icon = icon == null ? null : icon.getIconUrl(); + StatusTable.showChooser(SwingUtilities.getWindowAncestor(this), status -> this.safeEditTimelineEntry((ce, statusInfo) -> { + StatusEffectIcon icon = statusInfo.getIcon(0); + ce.icon = icon == null ? null : icon.getIconUrl(); }).accept(te, status) ); updateTab(); @@ -377,6 +420,14 @@ private void deleteEntry(CustomTimelineEntry toDelete) { updateTab(); } + private void resetAll() { + Long zone = this.currentZone; + TimelineCustomizations stuff = backend.getCustomSettings(zone); + stuff.setEntries(Collections.emptyList()); + backend.commitCustomSettings(currentZone); + updateTab(); + } + @Override public String getTabName() { return "Timelines"; @@ -446,4 +497,24 @@ else if (value instanceof TimelineEntry) { return comp; } } + + private class DoubleTimeRenderer extends DefaultTableCellRenderer { + @Override + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { + if (value instanceof Double time) { + return super.getTableCellRendererComponent(table, formatTimeDouble(time), isSelected, hasFocus, row, column); + } + else { + return super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); + } + } + } + + private static final DecimalFormat truncateTrailingZeroes = new DecimalFormat("00.###"); + + private String formatTimeDouble(double time) { + int minutes = (int) (time / 60); + double seconds = (time % 60); + return String.format("%s (%s:%s)", time, minutes, truncateTrailingZeroes.format(seconds)); + } } diff --git a/xivsupport/src/main/java/gg/xp/xivsupport/persistence/PropertiesFilePersistenceProvider.java b/xivsupport/src/main/java/gg/xp/xivsupport/persistence/PropertiesFilePersistenceProvider.java index c04b66c4fbc7..36b32172c9f5 100644 --- a/xivsupport/src/main/java/gg/xp/xivsupport/persistence/PropertiesFilePersistenceProvider.java +++ b/xivsupport/src/main/java/gg/xp/xivsupport/persistence/PropertiesFilePersistenceProvider.java @@ -1,5 +1,6 @@ package gg.xp.xivsupport.persistence; +import gg.xp.xivsupport.persistence.settings.BooleanSetting; import org.apache.commons.lang3.StringUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -28,7 +29,9 @@ public class PropertiesFilePersistenceProvider extends BaseStringPersistenceProv private final Properties properties; private final File file; private final File backupFile; - private final boolean readOnly; + private final boolean canBeReadOnly; + private final BooleanSetting readOnlySetting; + private boolean readOnly; // TODO: is this threadsafe? public static PropertiesFilePersistenceProvider inUserDataFolder(String baseName) { @@ -46,10 +49,10 @@ public PropertiesFilePersistenceProvider(File file) { this(file, false); } - public PropertiesFilePersistenceProvider(File file, boolean readOnly) { + public PropertiesFilePersistenceProvider(File file, boolean canBeReadOnly) { + this.canBeReadOnly = canBeReadOnly; this.file = file; this.backupFile = Paths.get(file.getParentFile().toPath().toString(), file.getName() + ".backup").toFile(); - this.readOnly = readOnly; Properties properties = new Properties(); try { try (FileInputStream stream = new FileInputStream(file)) { @@ -64,14 +67,25 @@ public PropertiesFilePersistenceProvider(File file, boolean readOnly) { } this.properties = properties; writeChangesToDisk(); + this.readOnlySetting = new BooleanSetting(this, "read-only-settings", false); + this.readOnlySetting.addListener(this::checkReadOnlySetting); + readOnly = this.canBeReadOnly && readOnlySetting.get(); + } + + private void checkReadOnlySetting() { + writeChangesToDisk(); + readOnly = canBeReadOnly && readOnlySetting.get(); + writeChangesToDisk(); } private Future writeChangesToDisk() { if (readOnly) { + log.info("Not Saving Settings - Read Only"); return CompletableFuture.completedFuture(null); } return exs.submit(() -> { try { + log.trace("Saving Persistent Settings"); File parentFile = file.getParentFile(); if (parentFile != null) { parentFile.mkdirs(); @@ -106,7 +120,7 @@ public void flush() { @Override protected void setValue(@NotNull String key, @Nullable String value) { - String truncated = StringUtils.abbreviate(value, 50); + String truncated = StringUtils.abbreviate(value, 200); log.info("Setting changed: {} -> {}", key, truncated); properties.setProperty(key, value); writeChangesToDisk(); @@ -119,6 +133,10 @@ protected void deleteValue(@NotNull String key) { writeChangesToDisk(); } + public BooleanSetting getReadOnlySetting() { + return readOnlySetting; + } + @Override protected @Nullable String getValue(@NotNull String key) { return properties.getProperty(key); diff --git a/xivsupport/src/main/java/gg/xp/xivsupport/replay/gui/ReplayControllerGui.java b/xivsupport/src/main/java/gg/xp/xivsupport/replay/gui/ReplayControllerGui.java index 0d187235e1af..c3278593f471 100644 --- a/xivsupport/src/main/java/gg/xp/xivsupport/replay/gui/ReplayControllerGui.java +++ b/xivsupport/src/main/java/gg/xp/xivsupport/replay/gui/ReplayControllerGui.java @@ -2,7 +2,10 @@ import gg.xp.xivsupport.gui.WrapLayout; import gg.xp.xivsupport.gui.tables.filters.TextFieldWithValidation; +import gg.xp.xivsupport.persistence.PropertiesFilePersistenceProvider; +import gg.xp.xivsupport.persistence.gui.BooleanSettingGui; import gg.xp.xivsupport.replay.ReplayController; +import org.picocontainer.MutablePicoContainer; import javax.swing.*; import javax.swing.border.TitledBorder; @@ -16,7 +19,7 @@ public final class ReplayControllerGui { private final JLabel progressLabel; private int advanceAmount = 1; - public ReplayControllerGui(ReplayController controller) { + public ReplayControllerGui(MutablePicoContainer container, ReplayController controller) { this.controller = controller; panel = new JPanel(); panel.setLayout(new WrapLayout()); @@ -26,6 +29,12 @@ public ReplayControllerGui(ReplayController controller) { controller.addCallback(this::refresh); advanceButton.addActionListener(e -> controller.advanceByAsync(advanceAmount)); progressLabel = new JLabel(); + PropertiesFilePersistenceProvider provider = container.getComponent(PropertiesFilePersistenceProvider.class); + if (provider != null) { + JCheckBox cb = new BooleanSettingGui(provider.getReadOnlySetting(), "Don't Save Settings").getComponent(); + cb.setToolTipText("When using replays, it can be useful to block settings changes, as things like echo commands may change settings.\nAlso useful if you just want to play around while having an easy way to revert."); + panel.add(cb); + } panel.add(textBox); panel.add(advanceButton); panel.add(progressLabel); diff --git a/xivsupport/src/test/java/gg/xp/xivsupport/gui/GuiWithImportedData.java b/xivsupport/src/test/java/gg/xp/xivsupport/gui/GuiWithImportedData.java index ba9933fdd257..c2c6e3c5e4de 100644 --- a/xivsupport/src/test/java/gg/xp/xivsupport/gui/GuiWithImportedData.java +++ b/xivsupport/src/test/java/gg/xp/xivsupport/gui/GuiWithImportedData.java @@ -33,9 +33,7 @@ public static void main(String[] args) throws InterruptedException { } MutablePicoContainer pico = XivMain.testingMasterInit(); AutoEventDistributor dist = pico.getComponent(AutoEventDistributor.class); - PersistenceProvider pers = pico.getComponent(PersistenceProvider.class); EventMaster master = pico.getComponent(EventMaster.class); - pers.save("gui.display-predicted-hp", "true"); long start = System.currentTimeMillis(); List events = EventReader.readEventsFromResource("/testsession5.oos.gz"); long read = System.currentTimeMillis(); From d66d6064e629491f07f5d313b01fdc2d9060e743 Mon Sep 17 00:00:00 2001 From: xp Date: Wed, 2 Mar 2022 15:46:42 -0800 Subject: [PATCH 4/6] Test fix --- .../duties/timelines/CustomTimelineEntry.java | 2 +- .../gg/xp/xivsupport/gui/tables/StandardColumns.java | 12 +++++------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/xivsupport/src/main/java/gg/xp/xivsupport/events/triggers/duties/timelines/CustomTimelineEntry.java b/xivsupport/src/main/java/gg/xp/xivsupport/events/triggers/duties/timelines/CustomTimelineEntry.java index 72c662aa4b16..8c81c6ca2eee 100644 --- a/xivsupport/src/main/java/gg/xp/xivsupport/events/triggers/duties/timelines/CustomTimelineEntry.java +++ b/xivsupport/src/main/java/gg/xp/xivsupport/events/triggers/duties/timelines/CustomTimelineEntry.java @@ -26,7 +26,7 @@ public class CustomTimelineEntry implements TimelineEntry { public @Nullable Double jump; public @Nullable URL icon; private @Nullable TimelineReference replaces; - public boolean enabled; + public boolean enabled = true; public CustomTimelineEntry() { name = "Name Goes Here"; diff --git a/xivsupport/src/main/java/gg/xp/xivsupport/gui/tables/StandardColumns.java b/xivsupport/src/main/java/gg/xp/xivsupport/gui/tables/StandardColumns.java index d622fd5673c9..c061940c8f88 100644 --- a/xivsupport/src/main/java/gg/xp/xivsupport/gui/tables/StandardColumns.java +++ b/xivsupport/src/main/java/gg/xp/xivsupport/gui/tables/StandardColumns.java @@ -25,7 +25,9 @@ import gg.xp.xivsupport.persistence.settings.BooleanSetting; import gg.xp.xivsupport.persistence.settings.DoubleSetting; import gg.xp.xivsupport.persistence.settings.LongSetting; +import gg.xp.xivsupport.replay.ReplayController; import org.jetbrains.annotations.Nullable; +import org.picocontainer.PicoContainer; import javax.swing.*; import javax.swing.table.DefaultTableCellRenderer; @@ -49,8 +51,9 @@ public final class StandardColumns { private final StatusEffectRepository statuses; private final SequenceIdTracker sqidTracker; - public StandardColumns(PersistenceProvider persist, StatusEffectRepository statuses, SequenceIdTracker sqidTracker) { - showPredictedHp = new BooleanSetting(persist, "gui.display-predicted-hp-2", false); + public StandardColumns(PicoContainer container, PersistenceProvider persist, StatusEffectRepository statuses, SequenceIdTracker sqidTracker) { + // Setting is there for legacy reasons (old version of launcher) + showPredictedHp = new BooleanSetting(persist, "gui.display-predicted-hp-2", container.getComponent(ReplayController.class) != null); this.statuses = statuses; this.sqidTracker = sqidTracker; } @@ -205,11 +208,6 @@ public Component getTableCellRendererComponent(JTable table, Object value, boole public static final CustomColumn> fieldDeclaredIn = new CustomColumn<>("Declared In", e -> e.getKey().getDeclaringClass().getSimpleName()); - - public BooleanSetting getShowPredictedHp() { - return showPredictedHp; - } - public static CustomColumn booleanSettingColumn(String name, Function settingGetter, int width, @Nullable BooleanSetting enabledBy) { return new CustomColumn<>(name, settingGetter::apply, col -> { col.setMaxWidth(width); From 34bc7ffea2ffb5a9a5e28d6ebe3cbcf48bbe3cb5 Mon Sep 17 00:00:00 2001 From: xp Date: Wed, 2 Mar 2022 16:25:43 -0800 Subject: [PATCH 5/6] Added option to try to ignore a fatal error --- .../gg/xp/xivsupport/gui/util/CatchFatalError.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/xivsupport/src/main/java/gg/xp/xivsupport/gui/util/CatchFatalError.java b/xivsupport/src/main/java/gg/xp/xivsupport/gui/util/CatchFatalError.java index e35efef9b209..348a00a5a681 100644 --- a/xivsupport/src/main/java/gg/xp/xivsupport/gui/util/CatchFatalError.java +++ b/xivsupport/src/main/java/gg/xp/xivsupport/gui/util/CatchFatalError.java @@ -1,5 +1,6 @@ package gg.xp.xivsupport.gui.util; +import gg.xp.xivsupport.gui.WrapLayout; import gg.xp.xivsupport.persistence.Platform; import org.apache.commons.lang3.exception.ExceptionUtils; import org.slf4j.Logger; @@ -62,7 +63,15 @@ public static void run(Runnable run) { c.fill = GridBagConstraints.NONE; JButton exit = new JButton("Exit"); exit.addActionListener(l -> System.exit(1)); - panel.add(exit, c); + JButton tryContinue = new JButton("Try to Continue Anyway"); + tryContinue.addActionListener(l -> { + frame.setVisible(false); + frame.dispose(); + }); + JPanel buttons = new JPanel(new WrapLayout()); + buttons.add(exit); + buttons.add(tryContinue); + panel.add(buttons, c); frame.add(panel); frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); frame.validate(); From 9927a046348a9d6b0a21a933cf00b1be5afe6a54 Mon Sep 17 00:00:00 2001 From: xp Date: Wed, 2 Mar 2022 16:32:10 -0800 Subject: [PATCH 6/6] Fixed layout --- .../main/java/gg/xp/xivsupport/gui/util/CatchFatalError.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/xivsupport/src/main/java/gg/xp/xivsupport/gui/util/CatchFatalError.java b/xivsupport/src/main/java/gg/xp/xivsupport/gui/util/CatchFatalError.java index 348a00a5a681..805f296d9376 100644 --- a/xivsupport/src/main/java/gg/xp/xivsupport/gui/util/CatchFatalError.java +++ b/xivsupport/src/main/java/gg/xp/xivsupport/gui/util/CatchFatalError.java @@ -68,9 +68,10 @@ public static void run(Runnable run) { frame.setVisible(false); frame.dispose(); }); - JPanel buttons = new JPanel(new WrapLayout()); + JPanel buttons = new JPanel(new WrapLayout(WrapLayout.CENTER)); buttons.add(exit); buttons.add(tryContinue); + c.fill = GridBagConstraints.HORIZONTAL; panel.add(buttons, c); frame.add(panel); frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);