Combat system - mganzarcik/fabulae GitHub Wiki

When combat is initiated, either manually by the player or when spotted by the enemy, the game enters a combat mode. When in combat mode, enemies and the player take turns – opposing teams move together and in any order. There is no initiative.

During combat, grid is visible.

During combat, only one character can occupy a given tile, which is in contrast with what can happen outside of combat. If the combat is initiated and some participants occupy the same grid, they move to neighboring grids before combat starts. Enemy combatants use different AI scripts based on their class and race. The AI scripts can range from very simple (attack closest enemy until dead, move to next enemy) to quite complex (attack spellcasters first, then already hurt characters, run away if below 20% HP, cast healing on hurt allies).

Combat on local maps

Combat start

Combat starts whenever a hostile NPC gets spotted by any player character. At combat start, all members of the enemy character’s group are alerted to the location of the player character that spotted him. All enemies who are not members of the group, but are within 8 tiles of the enemy, are also alerted. Once this happens, combat “officially” starts, with the player always moving first.

Combat end

The end combat button becomes available to the player if no hostile characters can be seen by any of the PCs. The player can either use the button and end the combat immediately, or he can remain in the turn based mode. If no hostiles appear during the next 3 turns, combat ends automatically.

AI Script example:

if (!character.isAwareOfEnemyPosition()) {
    return null;
}

GameCharacter target = character.getNearestEnemyInSight();
if (target != null) {
	return new Attack(target);
}
return new MoveTo(character.getLastKnownEnemyPosition());

Combat on the overland map

Combat is not directly possible on an overland map. Characters move on the overland map as groups and not as individuals (there still might be groups of 1 though) and only engage in combat if both are hostile and on the same tile.

If an enemy group is spotted by the player, combat does not start immediately. Instead, the enemy group, depending on its AI logic, might start moving towards the player group. Once they occupy the same tile, the map switches to a combat map.

Overland maps can also trigger random encounters based on their hostility and how dangerous they are. The check is performed each time the player character group changes a tile. See the Random encounters section for more details.

Combat maps

Each Game Location can have a list of combat maps defined. One of these is then randomly selected each time combat should occur in the game location and the game location either is, or belongs to an overland map.

The placement of the characters on combat maps is semi-random. Each combat map must have two locations defined, one for the player and one for the adversary. The locations can be different in sizes, at least 1x1 tile though. At the beginning, a tile is picked at random from the location and all player characters are placed around that tile based on their current formation, with the leader being placed directly on the tile. The group will be rotated to face the tile selected for the enemy. The enemy placement follows the same logic. If a tile is blocked when a character should be placed on it, a closest free tile is used instead. If no such tile can be found, an error is logged and the character will not participate in combat at all.

Is is not possible to save the game on combat maps.

Combat maps should also have an escape zone defined. See Running away for more details.

Combat phases

A combat turn consists of two phases

  1. Player's phase – the player gains control and can move his characters. Characters can be moved in any order until all of their APs are depleted. The player can switch between characters as he pleases – he can move with one for 3 APs, then cast a spell with another, and then finish moving with the first one and attack. The player can at any point end his turn using the end turn button.

  2. Computer phase – the computer gathers all characters on the map (including those not aware of the player’s presence). Computer then moves every character separately by executing its combat AI Script and then checking whether the character has any APs left. If it does, it keeps executing the script until no more APs remain. Once that happens, it moves on to the next character.

At the end of each turn, the action points of all game characters are restored to their maximum and effects with duration (such as spells that last several turns) have their remaining turns reduced by one.

Action points

Every action in combat consumes action points. Once the character runs out of action points, he cannot move that turn anymore. Characters can take actions in any sequence as long as they have enough APs.

Action AP cost
Move 1
Attack 5 (might be lower with higher skill)
Use item in the inventory or quick use slot 4
Use item in the game world (open / close / pick up) 4
Use skill (lockpick, disarm trap) 4
Open inventory 3 (only paid once per turn per character)
Cast a spell Depends on the spell
Use a perk Depends on the perk

APs are replenished on the start of every turn. Any APs that were not used the previous turn are not carried over.

During combat, no armor may be equipped or unequipped. However, weapons may be swapped / equipped and quick item slots may be populated.

Attacking

Each character has two modes of attack – they can attack in a melee, or they can use a ranged weapon. In order to attack using melee, they must be adjacent to the tile of the enemy, either diagonally or directly. When using ranged attacks, the target must be in the line of sight of the attacker. Ranged attacks cannot be used against targets that are adjacent to the attacker. Ranged attacks do not use ammo.

Number of attacks per turn is only limited by the APs.

When a character attacks, his Chance to Hit is calculated and modified by the target’s chance to Dodge or Parry. The resulting Actual Chance to Hit is then used to determine randomly whether or not the attack connects.

If the attack connects, damage dealt is calculated from the attacker’s skill and weapon and then modified by target’s Armor Rating. Damage dealt is never less than 1. The resulting damage is then subtracted from target’s HP and in case it has reached zero, the target is immediately killed and removed from combat.

Chance to Hit

If attacking with a weapon:

Chance to Hit (CtH) = (Weapon Skill Level + Weapon bonuses (if any) + 10) * 5

If attacking unarmed:

Chance to Hit (CtH) = (Unarmed Skill Level *1.5 + 10) * 5

CtH has a bonus of 10% if attacking from a side and 20% if attacking from the back (both values are configurable). The bonuses only apply for melee attacks.

Chance to Dodge or Parry (CtDP) = (Weapon Skill Level + Dodge skill level) * 5

If attacking unarmed and the opponent is not unarmed, is final CtDP is halved.

For both CtH and CtDP, further modifiers might be applied due to worn items, survival effects, magic, etc.

Actual Chance to Hit (ACtH) = CtH – Enemy CtDP

Ranged skills are ignored on defense and ignore weapon skills on offense.

If ACtH becomes greater than 99%, it is set to 99%. If it is lower than 1%, it is set to 1%.

Example:

Axe skill 3, Axe +1 attacks Dagger skill 2, Dodge skill 1
CtH= (3 + 1 + 10) * 5= 70%
CtDP = (2 + 1) * 5 = 15%
ACtH = 70 – 15 = 55%

Damage

Damage is calculated as: ((Weapon Damage + Weapon Bonus + Weapon Skill) / 100) * (100-Enemy Armor Rating)

If Damage < 1, then it is 1.

Example:

2D4 Sword + 3 with Sword Skill 4 attacks an enemy with Armor Rating 48%
2D4 returns 6
Damage = ((6 + 3 + 4) / 100) * (100-48) = (13 /100) * 52 = 6,76

Running away

On local maps

In order to run away, none of the PCs that are alive must be visible to enemy (this also means no enemy units will be visible to the player). Once that happens, the End Combat button will become available. Clicking it will end the combat mode.

Enemies will continue moving towards the last known location of the PCs they saw after combat ends. After they enter the spot, they will wander around in a small radius simulating search and then return back to original positions. Map transitions are disabled during combat, which means the player CAN become cornered and slaughtered if not careful.

AI pseudocode for searching:

if (s_shouldReturnAfterCombat || (getLastKnownEnemyPosition() != null && s_shouldSearchAfterCombat)) {
	ChainAction chain = new ChainAction(this);
	if (getLastKnownEnemyPosition() != null && s_shouldSearchAfterCombat) {
		// wander in 5 tile radius with 60 chance of moving for 30 seconds around the last known enemy position
		chain.addAction(new WanderAction(this, getLastKnownEnemyPosition(), 5, 60, 30));
	}
	if (s_shouldReturnAfterCombat) {
		// return back to where I was at start of combat
		chain.addAction(new MoveToAction(this, (int)s_combatStartX, (int)s_combatStartY));
	}
	if (chain.size() > 0) {
		combatEndAction = chain;
	}
} 

On combat maps

The player must place all of his surviving characters into an escape zone in order to run away. The escape zone can be placed anywhere on the map (location fixed, defined by the designer).

After entering the escape zone with all surviving characters, the combat ends automatically. All items left on the map will be lost. Any enemy characters will be returned back to the world map.

The player character group will be placed next to the tile where the combat originated, with the game paused.

⚠️ **GitHub.com Fallback** ⚠️