Here’s the first version of the code that might possibly be sufficient for use in a game. Most of the new code is the general (let $Aggressor attack $Victim) predicate family, which both the player and monster use. There are also some sensible (prevent [attack $]) rules, and if a ($ gives chase) flag is set for a monster it will chase the player through the dungeon if the player leaves the room.
First, the dice:
(roll 0 into 0)
(roll $D into $N)
(random from 0 to 2 into $X)
($D minus 1 into $Y)
(roll $Y into $Z)
($X plus $Z into $N)
Next, armor is a kind of wearable and weapons are a kind of item:
(item $Weapon) *(weapon $Weapon)
(wearable $Armor) *(armor $Armor)
Here’s the big predicate, which handles combat between any two entities:
(let $Aggressor attack $Victim using aim $AimDice and force $ForceDice)
(collect $A)
*($A is #wornby $Victim)
(armor $A)
(into $Armors)
(length of $Armors into $ArmorCount)
(random from 0 to $ArmorCount into $ArmorIndex)
(if) ($ArmorIndex = 0) (then)
($Victim Evasion $EvasionDice)
($Victim Mitigation $MitigationDice)
(else)
(nth $Armors $ArmorIndex $Armor)
($Armor Evasion $EvasionDice)
($Armor Mitigation $MitigationDice)
(endif)
(roll $AimDice into $AimRoll)
(roll $EvasionDice into $EvasionRoll)
(if) ($AimRoll > $EvasionRoll) (then)
(roll $ForceDice into $ForceRoll)
(roll $MitigationDice into $MitigationRoll)
(if) ($ForceRoll > $MitigationRoll) (then)
($ForceRoll minus $MitigationRoll into $Damage)
(if) (current player $Aggressor) (then)
You
(else)
(The $Aggressor)
(endif)
strike(s $Aggressor), dealing $Damage damage to
(if) (current player $Victim) (then)
you!
(else)
(the $Victim)!
(endif)
($Victim Health $OldHealth)
(if) ($OldHealth > $Damage) (then)
($OldHealth minus $Damage into $NewHealth)
(now) ($Victim Health $NewHealth)
(else)
(line)
(if) (current player $Victim) (then)
You have died!
(else)
(The $Aggressor) (has $Aggressor)
killed (the $Victim)!
(endif)
(now) ($Victim Health 0)
(endif)
(else)
(if) ($ArmorIndex = 0) (then)
(if) ($MitigationDice = 0) (then)
The
(if) (plural $Aggressor) (then)
attacks are
(else)
attack is
(endif)
ineffective!
(else)
(if) (current player $Victim) (then)
You
(else)
(The $Victim)
(endif)
(is $Victim) protected by
(if) (current player $Victim) (then)
your
(else)
(its $Victim)
(endif)
(skin $Victim)!
(endif)
(else)
(if) (plural $Aggressor) (then)
They are
(else)
It is
(endif)
deflected by (a $Armor)!
(endif)
(endif)
(else)
(if) (current player $Aggressor) (then)
You miss
(elseif) (plural $Aggressor) (then)
They miss
(else)
(It $Aggressor) misses
(endif)
(if) (current player $Victim) (then)
you!
(else)
(the $Victim)!
(endif)
(endif)
(line)
(if) (current player $Aggressor) ~($Victim is hostile) (then)
(The $Victim) become(s $Victim) hostile!
(now) ($Victim is hostile)
(endif)
(par)
This is the code that gets called if an attacker is using a weapon…
(let $Aggressor attack $Victim with $Weapon)
(if) (current player $Aggressor) (then)
You
(else)
(The $Aggressor)
(endif)
attack(s $Aggressor)
(if) (current player $Victim) (then)
you
(else)
(the $Victim)
(endif)
with (a $Weapon)!
(line)
($Weapon Aim $AimDice)
($Weapon Force $ForceDice)
(let $Aggressor attack $Victim using aim $AimDice and force $ForceDice)
…and this is used in the case of an unarmed attack.
(let $Aggressor attack $Victim unarmed)
(if) (current player $Aggressor) (then)
You
(else)
(The $Aggressor)
(endif)
swing(s $Aggressor)
(if) ~(plural $Aggressor) (then) a (endif)
(fist $Aggressor) at
(if) (current player $Victim) (then)
you!
(else)
(the $Victim)!
(endif)
(line)
($Aggressor Aim $AimDice)
($Aggressor Force $ForceDice)
(let $Aggressor attack $Victim using aim $AimDice and force $ForceDice)
One of those two actions gets called depending on whether the aggressor has a weapon:
(let $Aggressor attack $Victim)
(par)
(collect $W)
*($W is #heldby $Aggressor)
(weapon $W)
(into $Weapons)
(if) (empty $Weapons) (then)
(let $Aggressor attack $Victim unarmed)
(else)
(randomly select $Weapon from $Weapons)
(let $Aggressor attack $Victim with $Weapon)
(endif)
Here’s the player-specific code for attacking (or preventing the attack):
(prevent [attack $Corpse])
($Corpse Health 0)
(The $Corpse) is already dead!
(prevent [attack $Object])
(if) ~($Object Vitality $) (then)
Attacking (the $Object) would accomplish little.
(else)
(fail)
(endif)
(prevent [attack $Player])
(current player $Player)
You shouldn't attack yourself!
(perform [attack $Victim])
(current player $Player)
(let $Player attack $Victim)
Here’s the code for monsters attacking and chasing (note, order of predicates matters here, if you swap them I think the monster gets a free attack on you after following you into a room):
(on every tick in $Room)
(current player $Player)
*($Aggressor is hostile)
($Aggressor is in room $Room)
($Aggressor Health $Health)
($Health > 0)
(let $Aggressor attack $Player)
(on every tick in $Room)
(current player $Player)
*($Aggressor is hostile)
~($Aggressor is in room $Room)
($Aggressor gives chase)
($Aggressor Health $Health)
($Health > 0)
($Aggressor is in room $Elsewhere)
(first step from $Elsewhere to $Room is $Dir)
(let $Aggressor go $Dir)
That’s it for the library so far!
Here’s a small world to test things out in:
#room1
(room *)
#room2
(room *)
#door
(name *)
door
(door *)
(singleton *)
(* is open)
(openable *)
(* is unlocked)
(lockable *)
(from #room1 go #east through #door to #room2)
(from #room2 go #west through #door to #room1)
#player
(* is #in #room1)
(current player *)
(fist *)
(select) fist (or) punch (or) elbow (or) kick (purely at random)
(skin *)
tough skin
(animate *)
(proper *)
(* Health 8)
(* Vitality 8)
(* Aim 1)
(* Force 1)
(* Evasion 1)
(* Mitigation 0)
#whip
(name *)
your whip
(* is #heldby #player)
(weapon *)
(proper *)
(* Aim 2)
(* Force 2)
#leatherjacket
(name *)
your leather jacket
(* is #wornby #player)
(armor *)
(proper *)
(* Evasion $X)
(* is #wornby $Actor)
($Actor Evasion $X)
(* Evasion 0)
(* Mitigation $X)
(* is #wornby $Actor)
($Actor Mitigation $Y)
($Y plus 1 into $X)
(* Mitigation 1)
#goblin
(name *)
goblin
(* is #in #room1)
(fist *)
claw
(skin *)
tough green skin
(* gives chase)
(animate *)
(singleton *)
(* Health 4)
(* Vitality 4)
(* Aim 1)
(* Force 1)
(* Evasion 1)
(* Mitigation 1)
#spear
(name *)
crude spear
(* is #heldby #goblin)
(weapon *)
(* Aim 2)
(* Force 2)
#shield
(name *)
iron shield
(* is #wornby #goblin)
(armor *)
(an *)
(* Evasion 0)
(* Mitigation $X)
(* is #wornby $Actor)
($Actor Mitigation $Y)
($Y plus 2 into $X)
(* Mitigation 2)
I kind of like that you can either have armor / weapons give dice roll bonuses to the actor’s base stats or overwrite them (for example with the iron shield, which gives good mitigation but is heavy so it interferes with evasion).
One thing I don’t like is that you can’t yet run through a door and slam it shut behind you to escape a chasing monster; as soon as you go through the door the monster follows. I think I’ll handle this by adding an (after [go $Dir]) to close (and lock if you have the key) the door you just went through; then I’ll let certain monsters have the ability to open doors (and unlock doors if they have the key). That maybe will motivate interesting puzzles, like say going into a jail, stealing the jailer’s keys, hitting the jailer to get them to follow you into the jail cell, then rushing back out and locking them in.
EDIT: Here’s code to attack with a specified weapon:
(prevent [attack $ with $Object])
~(weapon $Object)
(The $Object) isn't a weapon!
(prevent [attack $Corpse with $Weapon])
($Corpse Health 0)
You poke (the $Corpse) with (the $Weapon).
(It $Corpse) (is $Corpse) definitely dead.
(prevent [attack $Object with $Weapon])
(if) ~($Object Vitality $) (then)
Attacking (the $Object) with (the $Weapon) is pointless.
(else)
(fail)
(endif)
(prevent [attack $Player with $Weapon])
(current player $Player)
Turning (the $Weapon) on yourself is a bad idea.
(perform [attack $Victim with $Weapon])
(current player $Player)
(let $Player attack $Victim with $Weapon)
And attacking unarmed:
(understand [attack/break/smash/hit/slap/kick/fight/torture/wreck/crack/destroy/murder/kill/punch/thump | $Words] as [attack $Victim unarmed])
(split $Words by [unarmed] into $VictimWords and [])
*(understand $VictimWords as non-all object $Victim)
(prevent [attack $Corpse unarmed])
($Corpse Health 0)
There's no need; (the $Corpse) is dead.
(prevent [attack $Object unarmed])
(if) ~($Object Vitality $) (then)
There's no sense in attacking (the $Object).
(else)
(fail)
(endif)
(prevent [attack $Player unarmed])
(current player $Player)
Stop hitting yourself!
(perform [attack $Victim unarmed])
(current player $Player)
(let $Player attack $Victim unarmed)
Edit2: Here’s the code to close and lock doors behind you:
(after [go $Dir])
(current room $NewRoom)
(from $ go $Dir through $Door to $NewRoom)
(openable $Door)
Would you like to close (the $Door) behind you?
(line)
(if) (yesno) (then)
(try [close $Door])
(endif)
(if)
($Door is closed)
(current player $Player)
($Key unlocks $Door)
($Key is #heldby $Player)
(then)
Would you like to lock (the $Door) behind you?
(line)
(if) (yesno) (then)
(try [lock $Door])
(endif)
(endif)