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 }