Repository navigation
Card System Diagrams
Maintained by hezhenyu (Member 1). Diagrams verified against main on 16 September 2026.
The card data layer deserialises each JSON card into one CardConfig, whose ordered effects and optional embedded upgrade are represented by EffectConfig and CardUpgradeConfig. The loader owns file/JSON structure checks and collection-wide duplicate-ID detection, while CardValidator owns per-card validation; a successfully loaded base card therefore has one or more effects and may contain one upgrade, whose own effects are also ordered and non-empty.
classDiagram
direction LR
namespace com_csse3200_game_cards_configs {
class CardConfig {
+String id
+String name
+String description
+int cost
+CardType type
+Rarity rarity
+TargetType target
+EffectConfig[] effects
+String texturePath
+CardUpgradeConfig upgrade
}
class EffectConfig {
+EffectType type
+int value
+int duration
}
class CardUpgradeConfig {
+String name
+String description
+int cost
+Rarity rarity
+EffectConfig[] effects
}
}
namespace com_csse3200_game_cards {
class CardType {
<<enumeration>>
ATTACK
SKILL
POWER
STATUS
CURSE
}
class Rarity {
<<enumeration>>
COMMON
UNCOMMON
RARE
}
class TargetType {
<<enumeration>>
SELF
SINGLE_ENEMY
ALL_ENEMIES
}
class EffectType {
<<enumeration>>
DAMAGE
BLOCK
HEAL
POISON
VULNERABLE
FEEBLE
STRENGTH
SUNDER
ENERGY_GAIN
PIERCE
CLEANSE
+boolean usesDuration()
}
class CardValidator {
<<utility>>
+List~String~ validate(CardConfig card)
+boolean isValid(CardConfig card)
}
class CardConfigLoader {
<<utility>>
+String DEFAULT_CARD_FILE
+List~CardConfig~ loadCards()
+List~CardConfig~ loadCards(String filename)
}
}
CardConfig "1" *-- "1..*" EffectConfig : effects
CardConfig "1" *-- "0..1" CardUpgradeConfig : upgrade
CardUpgradeConfig "1" *-- "1..*" EffectConfig : effects
CardConfig --> CardType : type
CardConfig --> Rarity : rarity
CardConfig --> TargetType : target
EffectConfig --> EffectType : type
CardUpgradeConfig --> Rarity : rarity
CardConfigLoader ..> CardConfig : parses and returns
CardConfigLoader ..> CardValidator : delegates validate(card)
CardValidator ..> CardConfig : validates
note for CardConfig "Package: com.csse3200.game.cards.configs<br/>upgrade is an embedded field, not a separate card entry."
note for CardUpgradeConfig "Package: com.csse3200.game.cards.configs<br/>The upgraded variant inherits id, type, target and texturePath from its containing CardConfig."
note for EffectConfig "Package: com.csse3200.game.cards.configs"
note for CardType "Package: com.csse3200.game.cards"
note for Rarity "Package: com.csse3200.game.cards"
note for TargetType "Package: com.csse3200.game.cards"
note for EffectType "Package: com.csse3200.game.cards<br/>usesDuration() is true for POISON, VULNERABLE and FEEBLE; false for every other constant."
note for CardConfigLoader "Package: com.csse3200.game.cards<br/>Checks required base and upgrade fields; parses JSON into CardConfig; detects duplicate card IDs; delegates each parsed card to CardValidator.validate(card)."
note for CardValidator "Package: com.csse3200.game.cards<br/>Validates one CardConfig independently; collection-level checks such as duplicate IDs belong to the loader or card library."
CardEffectResolver.resolve(...) walks card.effects by index, so unseal_the_breach resolves SUNDER (sequence 0) before DAMAGE (sequence 1). Application therefore strips armour with the SUNDER value before passing the DAMAGE value to takeDamage(value); the diagram does not assert who invokes either application implementation.
sequenceDiagram
actor Caller
participant Resolver as CardEffectResolver
participant Executor as EffectExecutor
participant Resolved as ResolvedCardEffect
participant Application as Effect application
participant Stats as CombatStatsComponent
Caller->>Resolver: resolve(card, context)
Note over Resolver,Resolved: card.effects[0] = SUNDER(value)<br/>card.effects[1] = DAMAGE(value)
loop each effect index i in array order
Resolver->>Executor: resolve(card.id, card.effects[i], card.target, i, context)
alt i = 0: SUNDER
Executor-->>Resolver: ResolvedCardEffect(..., SUNDER, ..., value, 0, 0)
else i = 1: DAMAGE
Executor-->>Resolver: ResolvedCardEffect(..., DAMAGE, ..., value, 0, 1)
end
end
Note over Application,Stats: SUNDER handling is duplicated in<br/>BattleController at line 795 and<br/>Team1EnemyStateAdapter at line 85:<br/>setArmour(getArmour() - effect.value())<br/>See note 2 for their differing effect-set support.
Application->>Stats: getArmour()
Stats-->>Application: armour
Application->>Stats: setArmour(armour - value)
Note over Stats: setArmour(int armour) stores Math.max(armour, 0)
Application->>Stats: takeDamage(value)
Stats->>Stats: absorbDamageWithBlock(value)
Stats->>Stats: absorbDamageWithArmour(afterBlock)
Stats->>Stats: setHealth(Math.max(health - remainingDamage, 0))
-
Ordering:
takeDamage()resolves block, then armour, then health, so[DAMAGE, SUNDER]would let armour absorb the hit before SUNDER stripped it. Commit6cfc7249reordered the array to[SUNDER, DAMAGE]. -
Known divergence: The paths support different effect sets and handle unsupported types differently:
BattleControllersilently ignores them, whileTeam1EnemyStateAdapterthrows anIllegalArgumentException. FEEBLE is accepted by the adapter and silently dropped by the legacy controller. -
PIERCE and SUNDER:
takePiercingDamage()bypasses block and armour and changes health directly, so stripping armour first gives PIERCE no benefit.