diff --git a/specs/magical-objects.allium b/specs/magical-objects.allium new file mode 100644 index 0000000..00c118d --- /dev/null +++ b/specs/magical-objects.allium @@ -0,0 +1,182 @@ +-- allium: 3 +-- allium: magical-objects + +-- Scope: Magical Objects (Healing Objects and Weapons) +-- Includes: MagicalObject base, HealingObject, MagicalWeapon, Character interactions +-- Excludes: +-- - Magical Object to Magical Object interactions (not in story) +-- - Magical Object factions (they are neutral) +-- - Characters healing Magical Objects (forbidden by story) + +------------------------------------------------------------ +-- Entities and Variants +------------------------------------------------------------ + +entity MagicalObject { + health: Health + maxHealth: Health + status: alive | destroyed + + is_alive: status = alive + is_destroyed: status = destroyed + + transitions status { + alive -> destroyed + terminal: destroyed + } +} + +entity HealingObject : MagicalObject { + -- Healing objects transfer health to characters + -- They cannot deal damage +} + +entity MagicalWeapon : MagicalObject { + damage: Integer -- fixed damage amount + owner: Character -- only the owner can use this weapon + -- Weapons cannot give health to characters +} + +------------------------------------------------------------ +-- Rules +------------------------------------------------------------ + +rule HealingObjectHealsCharacter { + when: CharacterUsesHealingObject(character, object, amount) + + requires: object.status = alive + requires: character.status = alive + requires: amount >= 0 + + let objectRemaining = object.health + let characterMax = Level.maxHealthForLevel(character.level) + let characterHeadroom = characterMax - character.health + let actualHeal = min(amount, objectRemaining, characterHeadroom) + + ensures: + if actualHeal > 0: + character.health = character.health + actualHeal + object.health = object.health - actualHeal + if object.health = 0: + object.status = destroyed + else: + object.status = alive + else: + character.health = character.health + object.health = object.health +} + +rule HealingObjectDestroyedCannotHeal { + when: CharacterUsesHealingObject(character, object, amount) + + requires: object.status = destroyed + + ensures: + character.health = character.health + object.health = object.health +} + +rule DeadCannotUseHealingObject { + when: CharacterUsesHealingObject(character, object, amount) + + requires: character.status = dead + + ensures: + character.health = character.health + object.health = object.health +} + +rule HealingObjectZeroHealIsNoOp { + when: CharacterUsesHealingObject(character, object, amount) + + requires: object.status = alive + requires: character.status = alive + requires: amount >= 0 + requires: min(amount, object.health, Level.maxHealthForLevel(character.level) - character.health) = 0 + + ensures: + character.health = character.health + object.health = object.health +} + +rule MagicalWeaponDealsDamage { + when: CharacterUsesWeapon(owner, weapon, target) + + requires: weapon.status = alive + requires: owner.status = alive + requires: owner = weapon.owner + + ensures: + target.health = max(0, target.health - weapon.damage) + if target.health = 0: + target.status = dead + weapon.health = weapon.health - 1 + if weapon.health = 0: + weapon.status = destroyed + else: + weapon.status = alive +} + +rule DeadCannotUseWeapon { + when: CharacterUsesWeapon(owner, weapon, target) + + requires: owner.status = dead + + ensures: + target.health = target.health + weapon.health = weapon.health +} + +rule NonOwnerCannotUseWeapon { + when: CharacterUsesWeapon(thief, weapon, target) + + requires: thief != weapon.owner + + ensures: + target.health = target.health + weapon.health = weapon.health +} + +rule WeaponDestroyedCannotDealDamage { + when: CharacterUsesWeapon(owner, weapon, target) + + requires: weapon.status = destroyed + + ensures: + target.health = target.health + weapon.health = weapon.health +} + +------------------------------------------------------------ +-- Invariants +------------------------------------------------------------ + +invariant MagicalObjectHealthNonNegative { + for m in MagicalObjects: + m.health >= 0 +} + +invariant MagicalObjectHealthNeverExceedsMax { + for m in MagicalObjects: + m.health <= m.maxHealth +} + +invariant MagicalObjectDestroyedAtZeroHealth { + for m in MagicalObjects: + m.health = 0 implies m.status = destroyed +} + +invariant MagicalObjectAliveAtPositiveHealth { + for m in MagicalObjects: + m.health > 0 implies m.status = alive +} + +invariant HealingObjectDoesNotDealDamage { + for h in HealingObjects, t in Characters: + not exists amount: CharacterUsesHealingObject(t, h, amount) implies t.health >= t.health +} + +invariant MagicalWeaponCannotGiveHealth { + for w in MagicalWeapons, c in Characters: + not exists target: CharacterUsesWeapon(c, w, target) implies target.health <= target.health +} diff --git a/user-stories.md b/user-stories.md index b2db9c1..c5c5c90 100644 --- a/user-stories.md +++ b/user-stories.md @@ -50,6 +50,7 @@ This is a description of the business rules we should support in the game engine - Healing Magical Objects cannot deal Damage 3. Characters can deal Damage by using a Magical Weapon. + - A Character can only use a Magical Weapon they own - These Magical Objects deal a fixed amount of damage when they are used - The amount of damage is fixed at the time the weapon is created - Every time the weapon is used, the Health is reduced by 1