=><=
''(print: $player's name)''
vs.
''(print: $opponent's name)''
<==
($healthText: $opponent) ($healthText: $player)
(set: _healthRatio to $opponent's health / $opponent's maxHealth - $player's health / $player's maxHealth)\
(if: _healthRatio < -0.2 and $opponent's losingText is not (a:))[\
(move: $opponent's losingText's 1st into _t)(print: _t)
](else-if: _healthRatio > 0.2 and $opponent's winningText is not (a:))[\
(move: $opponent's winningText's 1st into _t)(print: _t)
]\
($poseText: $opponent)
=><=
|==
(t8n-arrive:"instant")+(link-reveal-goto: "Attack", "Combat Move")[(set:$player's move to "A")]
=|=
(t8n-arrive:"instant")+(link-reveal-goto: "Parry", "Combat Move")[(set:$player's move to "P")]
==|
(t8n-arrive:"instant")+(link-reveal-goto: "Grapple", "Combat Move")[(set:$player's move to "G")]
|==|
(set:_shake to (animate:?passage,'rumble'))\
($moveText: $player)
(click:?page)[=\
($moveText: $opponent)
(click:?page)[=\
(if: ($move:$player) is "A")[\
(if: ($move:$opponent) is "A")[\
Both attacks hit!
_shake\
(set:
$opponent's health to it - $player's attack,
$player's health to it - $opponent's attack,
)](else-if: ($move:$opponent) is "P")[\
Your attack was deflected!
(print:$opponent's attackText)
You take the hit!
_shake\
(set:$player's health to it - $opponent's attack)\
](else-if: ($move:$opponent) is "G")[\
You score a hit, stopping (print:$opponent's them) in (print:$opponent's their) tracks!
_shake\
(set:$opponent's health to it - $player's attack)\
]](else-if: ($move:$player) is "P")[\
(if: ($move:$opponent) is "A")[\
You deflect (print:$opponent's their) attack!
(print: $player's attackText)
You score a hit!
_shake\
(set: $opponent's health to it - $player's attack)\
](else-if: ($move:$opponent) is "P")[\
Neither of you attack.
](else-if: ($move:$opponent) is "G")[\
You get knocked aside!
(print:$opponent's attackText)
You take the hit!
_shake\
(set:$player's health to it - $opponent's attack)\
]](else-if: ($move:$player) is "G")[\
(if: ($move:$opponent) is "A")[\
You get hit!
_shake\
(set:$player's health to it - $opponent's attack)\
](else-if: ($move:$opponent) is "P")[\
You knock (print:$opponent's them) aside!
(print: $player's attackText)
You score a hit!
_shake\
(set: $opponent's health to it - $player's attack)\
](else-if: ($move:$opponent) is "G")[\
You knock each other back!
]]
(if: $opponent's health < 1)[\
(print: $opponent's loseText)
(click-goto:?page, $winCombatPassage)
](else-if: $player's health < 1)[\
(print: $player's loseText)
(click-goto:?page, $loseCombatPassage)
](else:)[
(set:$opponent's pattern to (str: ...(rotated: -1, ...it)))
(t8n-arrive:"instant")(click-goto:?page, "Combat Menu")
](set:
$move to (macro: dm-type _x, [
This produces "A", "G", or "P" based on the move of the passed-in fighter. Because opponents choose their move from the first character in their pattern, and players choose their move manually, two different approaches are needed here.
(set:_out to '')
(if:_x contains "pattern")[
(set:_out to _x's pattern's 1st)
](else:)[
(set: _out to _x's move)
]
(out-data:_out)
]),
$poseText to (macro: dm-type _x, [
This produces the appropriate text for the passed-in fighter's current pose, which is based on their current move. Note that player fighters do not have poses.
(out-data: _x's ((cond:
($move:_x) is "A", "attackPose",
($move:_x) is "P", "parryPose",
"grapplePose",
)))
]),
$moveText to (macro: dm-type _x, [
This produces the appropriate text for the passed-in fighter's current move.
(out-data: _x's ((cond:
($move:_x) is "A", "attackText",
($move:_x) is "P", "parryText",
"grappleText",
)))
]),
$healthText to (macro: dm-type _x, [
This produces the appropriate text for the passed-in fighter's current health, based on their maximum health. This chooses between the five entries in the array using the following method:
100% - 1st
99% to 75% - 2nd
75% to 50% - 3rd
50% to 25% - 4th
25% to 0% - 5th
(out-data: _x's healthText's (
(ceil: (1 - (_x's health / _x's maxHealth)) * 4) + 1
))
]),
$turnBasedCombat to (macro: dm-type _player, dm-type _opponent, str-type _winCombat, str-type _loseCombat, [
This macro begins turn-based combat, with the player fighting the opponent, and then sending the player to one of two different rooms based on whether they won or lost the battle.
(set:
$player to _player,
$opponent to _opponent,
$winCombatPassage to _winCombat,
$loseCombatPassage to _loseCombat)
(out-data:(goto:"Combat Menu"))
])
)(set:
_allOpponents to (dm:
"Angy", (dm:
"name", "Angy Agnes, Daughter of Violence",
"they", "she",
"them", "her",
"their", "her",
"health", 6,
"maxHealth", 6,
"attack", 3,
"pattern", "AAAAGP",
"attackPose", 'Angy shouts "ATTACK!!!"',
"attackText", "Angy (either:'whirls her fists','kicks rapidly', 'swings a rock', 'bites furiously')!",
"parryPose", 'Angy shouts "ATTACK!"',
"parryText", "Angy raises a stick in front of her!",
"grapplePose", 'Angy shouts "ATTACK!!"',
"grappleText", "Angy squats and shoves your legs!",
"healthText", (a:
"Angy is grinning.",
"Angy is scowling.",
"Angy is frowning and limping.",
"Angy's face is bloody.",
"Angy is holding back tears of pain.",
),
"winningText", (a:
'Angy hoots "HA! HA! No one beats me!"',
'Angy mutters "Gotta finish this!"',
),
"losingText", (a:
'Angy wails "BAH! Stop fighting dirty!!"',
'Angy gasps "Come on, me!! YAAGH!!"',
),
"loseText", 'Angy screams "UGHH! FORGET IT!!" and runs away.',
),
"Foxy", (dm:
"name", "Foxy Merrytail, the Steel at the Throat",
"they", "she",
"them", "her",
"their", "her",
"health", 5,
"maxHealth", 5,
"attack", 2,
"pattern", "PPAGAA",
"attackPose", "Foxy leans forward.",
"attackText", "Foxy stabs!",
"parryPose", "Foxy leans back.",
"parryText", "Foxy raises her buckler!",
"grapplePose", "Foxy stands straight.",
"grappleText", "Foxy shoves with her elbow!",
"healthText", (a:
"Foxy looks proud.",
"Foxy looks angry.",
"Foxy is sweating.",
"Foxy is in pain.",
"Foxy looks about to fall over.",
),
"winningText", (a:
'Foxy spits "Heh! You move like a rock, too."',
'Foxy smirks "You\'re pretty soft for a rock."',
),
"losingText", (a:
'Foxy mutters "Damn hunk o\'rock..."',
'Foxy snorts "Can\'t lose to a damn rock..."',
),
"loseText", "A knife clatters to the ground! Foxy just surrendered!",
),
"Moss", (dm:
"name", "Moss Knight, the Forest's Spectre",
"they", "it",
"them", "it",
"their", "its",
"health", 8,
"maxHealth", 8,
"attack", 1,
"pattern", "GPPGPGPA",
"attackPose", "The Moss Knight raises its blade.",
"attackText", "The Moss Knight chops!",
"parryPose", "The Moss Knight points its blade at you.",
"parryText", "The Moss Knight waves its blade in front of you!",
"grapplePose", "The Moss Knight creaks.",
"grappleText", "The Moss Knight lunges forward!",
"healthText", (a:
"The Moss Knight's armour is thickly-coated with moss.",
"The Moss Knight's moss is squashed.",
"The Moss Knight is shedding clumps of moss.",
"The Moss Knight's moss is full of holes.",
"The Moss Knight is a complete mess.",
),
"winningText", (a:),
"losingText", (a:),
"loseText", "Your foe sinks to its knees in defeat!",
),
),
_playerBaseStats to (dm:
"name", "The Marble Angel",
"attack", 1,
"health", 7,
"maxHealth", 7,
"attackText", "You swing your stone arms!",
"parryText", "You raise your stone palms!",
"grappleText", "You try to push your opponent over!",
"healthText", (a:
"Your stone skin is pristine and perfect.",
"Your stone skin is scratched.",
"Your stone limbs are chipped.",
"Your body is covered in cracks.",
"Chunks of your body are breaking off.",
),
"loseText", "Your arms suddenly snap off, and your legs soon follow. The battle is over.",
)
)\
=><=
#The Basics of TBC
//(turn-based combat)//
<==
This is a demonstration story for a turn-based combat engine, such as those seen in computer RPGs. This engine has the following mechanics:
* The only moves available to the player and their opponent are ''attack'', ''parry'', and ''grapple'', which beat one another in a rock-paper-scissors triangle. Attacking into a parry causes the parryer to get hit, as does parrying into a grapple and grappling into an attack. Attacking into an attack will cause both to hit.
* Fighters have ''health points'', but these are not numerically displayed. Instead, one of five strings showing the fighter's appearance, based on their depleted health, are displayed at the start of each turn.
* Fighters also have ''attack points'', determining how much health points their attacks deplete. This is also not numerically displayed.
* The opponent will assume a ''pose'' at the start of each turn, which hints at their upcoming move.
* The opponent does not choose moves randomly, but cycles through a fixed pattern of moves.
* When both player and opponent lose their last health point, the player wins.
Additionally, the engine's implementation has these features:
* Two passages are used, plus a setup passage.
* Nine story-wide variables used: five convenient custom macros, `$move, $poseText, $moveText, $healthText and $turnBasedCombat`, and four data variables, `$opponent, $player, $winCombatPassage and $loseCombatPassage`.
* Turn-based combat sections of a story is initiated by calling the custom macro `$turnBasedCombat`, which accepts two datamaps holding the player's stats and the opponent's stats, as well as two passages names to transport the player to after they win or lose the battle.
In this demonstration, you play ''the Marble Angel'', a living statue who fights bare-handed. You may choose your opponent:
* (link:"Fight Angy Agnes (a goblin brawler)")[($turnBasedCombat: _playerBaseStats, _allOpponents's Angy, "Win Room", "Lose Room")]
* (link:"Fight Foxy Merrytail (a rogue)")[($turnBasedCombat: _playerBaseStats, _allOpponents's Foxy, "Win Room", "Lose Room")]
* (link:"Fight the Moss Knight (an undead knight)")[($turnBasedCombat: _playerBaseStats, _allOpponents's Moss, "Win Room", "Lose Room")]
Oh my! It seems that ''(print: $opponent's name)'' wins this match!
Don't worry, the Marble Angel's stone flesh can flow and knit back together. This isn't the end. Now, [[Start<-get up!]]
^^Written by Leon Arnott, April 2021.^^Ahhh! The ''Marble Angel'' won that bout!
So, you seem to have a grasp of the basics. Excellent! Truly excellent!
Feel free to use this engine's code in your own projects. Now, [[Start<-back to the title screen]] with you!
^^Written by Leon Arnott, April 2021.^^