phantasia

Phantasia - 2D SDL3 RPG prototype.
git clone git://git.beep.wimdupont.com/phantasia.git
Log | Files | Refs | README | LICENSE

battle.c (8700B)


      1 #include "game/battle.h"
      2 
      3 #include <stdio.h>
      4 #include <string.h>
      5 
      6 #include "config.h"
      7 
      8 #define LEN(a) (sizeof(a) / sizeof(0[a]))
      9 
     10 #if PH_BATTLE_CTB_BASE <= 0 || PH_BATTLE_RESOLVE_PERCENT < 0 || \
     11 	PH_BATTLE_RESOLVE_PERCENT > 100
     12 #error "invalid CTB configuration"
     13 #endif
     14 
     15 static int
     16 ph_battle_actor_delay(const PhWorld *world, int actor)
     17 {
     18 	PhStats stats = ph_world_entity_stats(world, &world->entities[actor]);
     19 	int delay = PH_BATTLE_CTB_BASE / (stats.agility > 0 ? stats.agility : 1);
     20 
     21 	return delay > 0 ? delay : 1;
     22 }
     23 
     24 static void
     25 ph_battle_start_turn(PhBattle *battle, const PhWorld *world, int actor)
     26 {
     27 	battle->current_actor = actor;
     28 	battle->action_delay = ph_battle_actor_delay(world, actor);
     29 	battle->resolved = 0;
     30 	if (actor == world->player_index) {
     31 		battle->phase = PH_BATTLE_COMMAND;
     32 		battle->timer = 0.0f;
     33 	} else {
     34 		battle->phase = PH_BATTLE_ENEMY_ACTION;
     35 		battle->timer = PH_BATTLE_ACTION_SECONDS;
     36 	}
     37 }
     38 
     39 static void
     40 ph_battle_next_turn(PhBattle *battle, const PhWorld *world)
     41 {
     42 	int previous = battle->current_actor;
     43 	int next;
     44 
     45 	if (previous == world->player_index) battle->player_ctb += battle->action_delay;
     46 	else battle->enemy_ctb += battle->action_delay;
     47 	if (battle->player_ctb < battle->enemy_ctb) next = world->player_index;
     48 	else if (battle->enemy_ctb < battle->player_ctb) next = battle->enemy_index;
     49 	else next = previous == world->player_index ? battle->enemy_index : world->player_index;
     50 	ph_battle_start_turn(battle, world, next);
     51 }
     52 
     53 static int
     54 ph_battle_item(const PhWorld *world)
     55 {
     56 	const PhEntity *player = ph_world_player(world);
     57 	PhStats stats = ph_world_entity_stats(world, player);
     58 	int i;
     59 
     60 	for (i = 0; i < world->item_def_count; ++i)
     61 		if (world->inventory[i] > 0 && player &&
     62 				((world->item_defs[i].use == PH_ITEM_USE_HEAL &&
     63 				player->hp < stats.max_hp) ||
     64 				(world->item_defs[i].use == PH_ITEM_USE_RESTORE_MP &&
     65 				player->mp < stats.max_mp))) return world->item_defs[i].id;
     66 	return 0;
     67 }
     68 
     69 const PhSpellDef *
     70 ph_spell_def(int id)
     71 {
     72 	size_t i;
     73 
     74 	for (i = 0; i < LEN(ph_spell_defs); ++i)
     75 		if (ph_spell_defs[i].id == id) return &ph_spell_defs[i];
     76 	return NULL;
     77 }
     78 
     79 const PhSpellDef *
     80 ph_battle_spell(const PhBattle *battle)
     81 {
     82 	if (battle->spell < 0 || (size_t)battle->spell >= LEN(ph_player_spells))
     83 		return NULL;
     84 	return ph_spell_def(ph_player_spells[battle->spell]);
     85 }
     86 
     87 void
     88 ph_battle_begin(PhBattle *battle, const PhWorld *world, int enemy_index)
     89 {
     90 	const PhEntity *enemy = &world->entities[enemy_index];
     91 	const PhEntityDef *def = ph_world_entity_def(world, enemy->type_id);
     92 
     93 	memset(battle, 0, sizeof(*battle));
     94 	battle->enemy_index = enemy_index;
     95 	battle->current_actor = world->player_index;
     96 	battle->enemy_ctb = ph_battle_actor_delay(world, enemy_index);
     97 	battle->timer = PH_BATTLE_TRANSITION_SECONDS;
     98 	if (def) {
     99 		battle->xp_reward = def->xp_reward;
    100 		battle->loot_item_id = def->loot_item_id;
    101 		battle->loot_amount = def->loot_amount;
    102 		snprintf(battle->message, sizeof(battle->message), "%s approaches!", def->name);
    103 	}
    104 }
    105 
    106 void
    107 ph_battle_select(PhBattle *battle, PhWorld *world)
    108 {
    109 	if (battle->phase != PH_BATTLE_COMMAND || !ph_world_player(world) ||
    110 			battle->current_actor != world->player_index) return;
    111 	if (battle->command == PH_BATTLE_MAGICK) {
    112 		battle->spell = 0;
    113 		battle->phase = PH_BATTLE_MAGIC;
    114 		snprintf(battle->message, sizeof(battle->message), "Choose a spell.");
    115 		return;
    116 	}
    117 	if (battle->command == PH_BATTLE_ITEM &&
    118 			!(battle->item_id = ph_battle_item(world))) {
    119 		snprintf(battle->message, sizeof(battle->message), "No usable item.");
    120 		return;
    121 	}
    122 	battle->phase = PH_BATTLE_PLAYER_ACTION;
    123 	battle->timer = PH_BATTLE_ACTION_SECONDS;
    124 	battle->resolved = 0;
    125 	battle->action_delay = ph_battle_actor_delay(world, world->player_index);
    126 }
    127 
    128 void
    129 ph_battle_cast(PhBattle *battle, PhWorld *world)
    130 {
    131 	const PhEntity *player = ph_world_player(world);
    132 	const PhSpellDef *spell = ph_battle_spell(battle);
    133 
    134 	if (battle->phase != PH_BATTLE_MAGIC || !player || !spell) return;
    135 	if (player->mp < spell->mp_cost) {
    136 		snprintf(battle->message, sizeof(battle->message), "Not enough MP.");
    137 		return;
    138 	}
    139 	battle->phase = PH_BATTLE_PLAYER_ACTION;
    140 	battle->timer = PH_BATTLE_ACTION_SECONDS;
    141 	battle->resolved = 0;
    142 	battle->action_delay = ph_battle_actor_delay(world, world->player_index) *
    143 		spell->delay_percent / 100;
    144 	if (battle->action_delay < 1) battle->action_delay = 1;
    145 }
    146 
    147 void
    148 ph_battle_cancel(PhBattle *battle)
    149 {
    150 	if (battle->phase == PH_BATTLE_MAGIC) battle->phase = PH_BATTLE_COMMAND;
    151 }
    152 
    153 void
    154 ph_battle_order(const PhBattle *battle, const PhWorld *world,
    155 	int order[PH_BATTLE_ORDER_COUNT])
    156 {
    157 	int player_ctb = battle->player_ctb;
    158 	int enemy_ctb = battle->enemy_ctb;
    159 	int actor = battle->current_actor;
    160 	int i;
    161 
    162 	for (i = 0; i < PH_BATTLE_ORDER_COUNT; ++i) {
    163 		int delay = ph_battle_actor_delay(world, actor);
    164 		const PhSpellDef *spell = ph_battle_spell(battle);
    165 
    166 		order[i] = actor;
    167 		if (i == 0 && actor == world->player_index) {
    168 			if (battle->phase == PH_BATTLE_PLAYER_ACTION)
    169 				delay = battle->action_delay;
    170 			else if (spell && battle->phase == PH_BATTLE_MAGIC)
    171 				delay = delay * spell->delay_percent / 100;
    172 			if (delay < 1) delay = 1;
    173 		}
    174 		if (actor == world->player_index) player_ctb += delay;
    175 		else enemy_ctb += delay;
    176 		if (player_ctb < enemy_ctb) actor = world->player_index;
    177 		else if (enemy_ctb < player_ctb) actor = battle->enemy_index;
    178 		else actor = actor == world->player_index ?
    179 			battle->enemy_index : world->player_index;
    180 	}
    181 }
    182 
    183 static void
    184 ph_battle_resolve(PhBattle *battle, PhWorld *world)
    185 {
    186 	const PhEntityDef *def;
    187 	const PhSpellDef *spell = NULL;
    188 	int damage;
    189 
    190 	if (battle->phase == PH_BATTLE_PLAYER_ACTION) {
    191 		def = ph_world_entity_def(world, world->entities[battle->enemy_index].type_id);
    192 		if (battle->command == PH_BATTLE_FIGHT) {
    193 			damage = ph_world_physical_attack(world, world->player_index,
    194 				battle->enemy_index);
    195 		} else if (battle->command == PH_BATTLE_MAGICK) {
    196 			spell = ph_battle_spell(battle);
    197 			if (!spell) return;
    198 			damage = ph_world_magic_attack(world, world->player_index,
    199 				battle->enemy_index, spell->mp_cost, spell->power);
    200 			if (damage >= 0 && spell->accuracy_penalty > 0 &&
    201 					spell->accuracy_turns > 0)
    202 				ph_world_apply_accuracy_penalty(world, battle->enemy_index,
    203 					spell->accuracy_penalty, spell->accuracy_turns);
    204 		} else {
    205 			const PhItemDef *item = ph_world_item_def(world, battle->item_id);
    206 
    207 			damage = ph_world_use_inventory_item(world, world->player_index,
    208 				battle->item_id) < 0 ? -1 : 0;
    209 			snprintf(battle->message, sizeof(battle->message), "Used %s.",
    210 				item && item->name ? item->name : "item");
    211 			return;
    212 		}
    213 		if (battle->command == PH_BATTLE_MAGICK && damage >= 0)
    214 			snprintf(battle->message, sizeof(battle->message),
    215 				"%s deals %d damage.", spell->name, damage);
    216 		else
    217 			snprintf(battle->message, sizeof(battle->message), damage > 0 ?
    218 				"%s takes %d damage." : "The attack misses.",
    219 				def && def->name ? def->name : "Enemy", damage);
    220 		return;
    221 	}
    222 
    223 	def = ph_world_entity_def(world, world->entities[battle->enemy_index].type_id);
    224 	damage = ph_world_physical_attack(world, battle->enemy_index, world->player_index);
    225 	ph_world_advance_effects(world, battle->enemy_index);
    226 	snprintf(battle->message, sizeof(battle->message), damage > 0 ?
    227 		"%s deals %d damage." : "%s misses.",
    228 		def && def->name ? def->name : "Enemy", damage);
    229 }
    230 
    231 PhBattleResult
    232 ph_battle_update(PhBattle *battle, PhWorld *world, float dt, int transition)
    233 {
    234 	if (transition) {
    235 		battle->timer -= dt;
    236 		if (battle->timer > 0.0f) return PH_BATTLE_CONTINUE;
    237 		battle->timer = 0.0f;
    238 		ph_battle_start_turn(battle, world, world->player_index);
    239 		return PH_BATTLE_READY;
    240 	}
    241 	if (battle->phase == PH_BATTLE_COMMAND || battle->phase == PH_BATTLE_MAGIC)
    242 		return PH_BATTLE_CONTINUE;
    243 	battle->timer -= dt;
    244 	if (!battle->resolved && battle->timer <= PH_BATTLE_ACTION_SECONDS *
    245 			PH_BATTLE_RESOLVE_PERCENT / 100.0f) {
    246 		ph_battle_resolve(battle, world);
    247 		battle->resolved = 1;
    248 	}
    249 	if (battle->timer > 0.0f) return PH_BATTLE_CONTINUE;
    250 	if (battle->phase == PH_BATTLE_PLAYER_ACTION &&
    251 			world->entities[battle->enemy_index].hp <= 0) {
    252 		if (ph_world_finish_encounter(world, battle->enemy_index) == 0)
    253 			return PH_BATTLE_VICTORY;
    254 		return PH_BATTLE_CONTINUE;
    255 	}
    256 	if (battle->phase == PH_BATTLE_ENEMY_ACTION &&
    257 			world->entities[world->player_index].hp <= 0)
    258 		return PH_BATTLE_DEFEAT;
    259 	ph_battle_next_turn(battle, world);
    260 	return PH_BATTLE_CONTINUE;
    261 }
    262 
    263 float
    264 ph_battle_lunge(const PhBattle *battle, PhBattlePhase phase)
    265 {
    266 	float progress;
    267 
    268 	if (battle->phase != phase || PH_BATTLE_ACTION_SECONDS <= 0.0f) return 0.0f;
    269 	progress = 1.0f - battle->timer / PH_BATTLE_ACTION_SECONDS;
    270 	return (progress < 0.5f ? progress : 1.0f - progress) *
    271 		PH_BATTLE_LUNGE_DISTANCE;
    272 }