246 lines
7 KiB
C
246 lines
7 KiB
C
/*
|
|
* This software is licensed under the terms of the MIT License.
|
|
* See COPYING for further information.
|
|
* ---
|
|
* Copyright (c) 2011-2024, Lukas Weber <laochailan@web.de>.
|
|
* Copyright (c) 2012-2024, Andrei Alexeyev <akari@taisei-project.org>.
|
|
*/
|
|
|
|
#pragma once
|
|
#include "taisei.h"
|
|
|
|
#include "move.h"
|
|
#include "taisei.h"
|
|
|
|
#include "aniplayer.h"
|
|
#include "color.h"
|
|
#include "coroutine/taskdsl.h"
|
|
#include "difficulty.h"
|
|
#include "entity.h"
|
|
#include "resource/resource.h"
|
|
|
|
#define BOSS_HURT_RADIUS 16
|
|
#define BOSS_MAX_ATTACKS 24
|
|
|
|
enum {
|
|
ATTACK_START_DELAY = 60,
|
|
ATTACK_START_DELAY_EXTRA = 150,
|
|
ATTACK_END_DELAY = 20,
|
|
ATTACK_END_DELAY_MOVE = 0,
|
|
ATTACK_END_DELAY_SPELL = 60,
|
|
ATTACK_END_DELAY_SURV = 20,
|
|
ATTACK_END_DELAY_EXTRA = 150,
|
|
ATTACK_END_DELAY_PRE_EXTRA = 60,
|
|
BOSS_DEATH_DELAY = 120,
|
|
};
|
|
|
|
typedef struct Attack Attack;
|
|
typedef struct AttackInfo AttackInfo;
|
|
|
|
typedef void (*BossRule)(Boss*, int time) attr_nonnull(1);
|
|
|
|
typedef enum AttackType {
|
|
AT_Normal,
|
|
AT_Move,
|
|
AT_Spellcard,
|
|
AT_SurvivalSpell,
|
|
AT_ExtraSpell,
|
|
} AttackType;
|
|
|
|
#define ATTACK_IS_SPELL(a) ((a) == AT_Spellcard || (a) == AT_SurvivalSpell || (a) == AT_ExtraSpell)
|
|
|
|
DEFINE_TASK_INTERFACE(BossAttack, {
|
|
BoxedBoss boss;
|
|
Attack *attack;
|
|
});
|
|
|
|
typedef TASK_INDIRECT_TYPE(BossAttack) BossAttackTask;
|
|
typedef TASK_IFACE_ARGS_TYPE(BossAttack) BossAttackTaskArgs;
|
|
typedef TASK_IFACE_ARGS_SIZED_PTR_TYPE(BossAttack) BossAttackTaskCustomArgs;
|
|
|
|
struct AttackInfo {
|
|
/*
|
|
HOW TO SET UP THE IDMAP FOR DUMMIES
|
|
|
|
The idmap is used as a template to generate spellstage IDs in stage_init_array().
|
|
It looks like this:
|
|
|
|
{id_easy, id_normal, id_hard, id_lunatic}
|
|
|
|
Where each of those IDs are in range of 0-127, and are unique within each stage-specific AttackInfo array.
|
|
A special value of -1 can be used to not generate a spellstage for specific difficulties.
|
|
This is useful if your spell is only available on Hard and Lunatic, or has a difficulty-dependent name, etc.
|
|
|
|
IDs of AT_ExtraSpell attacks may overlap with those of other types.
|
|
|
|
Most importantly DO NOT CHANGE ESTABLISHED IDs unless you absolutely must.
|
|
Doing so is going to break replays, progress files, and anything that stores stage IDs permanently.
|
|
Stage IDs are an internal detail invisible to the player, so they don't need to have any kind of fancy ordering.
|
|
*/
|
|
schar idmap[NUM_SELECTABLE_DIFFICULTIES + 1];
|
|
|
|
AttackType type;
|
|
char *name;
|
|
float timeout;
|
|
float hp;
|
|
|
|
TASK_INDIRECT_TYPE(BossAttack) task;
|
|
BossRule draw_rule;
|
|
|
|
cmplx pos_dest;
|
|
int bonus_rank;
|
|
};
|
|
|
|
struct Attack {
|
|
const char *name;
|
|
|
|
AttackType type;
|
|
|
|
int starttime;
|
|
int endtime;
|
|
int endtime_undelayed;
|
|
int failtime;
|
|
int timeout;
|
|
|
|
float bonus_base;
|
|
float maxhp;
|
|
float hp;
|
|
|
|
BossRule draw_rule;
|
|
|
|
COEVENTS_ARRAY(
|
|
initiated, // before start delay
|
|
started, // after start delay
|
|
finished, // before end delay
|
|
completed // after end delay
|
|
) events;
|
|
|
|
AttackInfo *info; // NULL for attacks created directly through boss_add_attack
|
|
};
|
|
|
|
static inline bool attack_has_started(const Attack *atk) {
|
|
return atk->events.started.num_signaled;
|
|
}
|
|
|
|
static inline bool attack_has_finished(const Attack *atk) {
|
|
return atk->events.finished.num_signaled;
|
|
}
|
|
|
|
static inline bool attack_was_failed(const Attack *atk) {
|
|
return atk->failtime > 0;
|
|
}
|
|
|
|
static inline bool attack_is_active(const Attack *atk) {
|
|
return attack_has_started(atk) && !attack_has_finished(atk);
|
|
}
|
|
|
|
DEFINE_ENTITY_TYPE(Boss, {
|
|
cmplx pos;
|
|
|
|
Attack attacks[BOSS_MAX_ATTACKS];
|
|
Attack *current;
|
|
|
|
const char *name;
|
|
|
|
int acount;
|
|
|
|
AniPlayer ani;
|
|
Sprite portrait; // Used in spellcard intros
|
|
|
|
Color zoomcolor;
|
|
Color shadowcolor;
|
|
Color glowcolor;
|
|
|
|
int failed_spells;
|
|
int lastdamageframe; // used to make the boss blink on damage taken
|
|
int birthtime;
|
|
|
|
MoveParams move;
|
|
|
|
// These are publicly accessible damage multipliers *you* can use to buff your spells.
|
|
// Just change the numbers. global.shake_view style. 1.0 is the default.
|
|
// If a new attack starts, they are reset. Nothing can go wrong!
|
|
float bomb_damage_multiplier;
|
|
float shot_damage_multiplier;
|
|
|
|
bool in_background;
|
|
float background_transition;
|
|
|
|
float damage_to_power_accum;
|
|
|
|
struct {
|
|
float opacity;
|
|
float fill_total;
|
|
float fill_alt;
|
|
Color fill_color;
|
|
Color fill_altcolor;
|
|
} healthbar;
|
|
|
|
struct {
|
|
Attack *a_prev;
|
|
Attack *a_cur;
|
|
Attack *a_next;
|
|
float global_opacity;
|
|
float spell_opacity;
|
|
float plrproximity_opacity;
|
|
float attack_timer;
|
|
} hud;
|
|
|
|
COEVENTS_ARRAY(defeated) events;
|
|
});
|
|
|
|
Boss *create_boss(const char *name, char *ani, cmplx pos) attr_nonnull(1, 2) attr_returns_nonnull;
|
|
void free_boss(Boss *boss) attr_nonnull(1);
|
|
void process_boss(Boss **boss) attr_nonnull(1);
|
|
|
|
void draw_extraspell_bg(Boss *boss, int time) attr_nonnull(1);
|
|
void draw_boss_background(Boss *boss) attr_nonnull(1);
|
|
void draw_boss_overlay(Boss *boss) attr_nonnull(1);
|
|
void draw_boss_fake_overlay(Boss *boss) attr_nonnull(1);
|
|
|
|
Attack *boss_add_attack(Boss *boss, AttackType type, const char *name, float timeout, int hp, BossRule draw_rule)
|
|
attr_nonnull(1) attr_returns_nonnull;
|
|
Attack *boss_add_attack_task(Boss *boss, AttackType type, const char *name, float timeout, int hp, BossAttackTask task, BossRule draw_rule)
|
|
attr_nonnull(1) attr_returns_nonnull;
|
|
Attack *boss_add_attack_task_with_args(
|
|
Boss *boss, AttackType type, const char *name, float timeout, int hp,
|
|
BossAttackTask task, BossRule draw_rule, BossAttackTaskCustomArgs args)
|
|
attr_nonnull(1) attr_returns_nonnull;
|
|
Attack *boss_add_attack_from_info(Boss *boss, AttackInfo *info, bool move)
|
|
attr_nonnull(1, 2) attr_returns_nonnull;
|
|
Attack *boss_add_attack_from_info_with_args(Boss *boss, AttackInfo *info, BossAttackTaskCustomArgs args)
|
|
attr_nonnull(1, 2) attr_returns_nonnull;
|
|
void boss_set_attack_bonus(Attack *a, int rank) attr_nonnull(1);
|
|
|
|
void boss_set_portrait(Boss *boss, const char *name, const char *variant, const char *face) attr_nonnull(1);
|
|
|
|
void boss_engage(Boss *b) attr_nonnull(1);
|
|
void boss_start_next_attack(Boss *b, Attack *a) attr_nonnull(1, 2);
|
|
void boss_finish_current_attack(Boss *boss) attr_nonnull(1);
|
|
|
|
bool boss_is_dying(Boss *boss) attr_nonnull(1); // true if the last attack is over but the BOSS_DEATH_DELAY has not elapsed.
|
|
bool boss_is_fleeing(Boss *boss) attr_nonnull(1);
|
|
bool boss_is_vulnerable(Boss *boss) attr_nonnull(1);
|
|
bool boss_is_player_collision_active(Boss *boss) attr_nonnull(1);
|
|
|
|
cmplx boss_get_sprite_offset(Boss *boss);
|
|
|
|
void boss_death(Boss **boss) attr_nonnull(1);
|
|
|
|
void boss_reset_motion(Boss *boss) attr_nonnull(1);
|
|
|
|
void boss_preload(ResourceGroup *rg);
|
|
|
|
#define BOSS_DEFAULT_SPAWN_POS (VIEWPORT_W * 0.5 - I * VIEWPORT_H * 0.5)
|
|
#define BOSS_DEFAULT_GO_POS (VIEWPORT_W * 0.5 + 200.0*I)
|
|
#define BOSS_NOMOVE (-3142-39942.0*I)
|
|
|
|
Boss *_init_boss_attack_task(const BossAttackTaskArgs *args)
|
|
attr_nonnull_all attr_returns_nonnull;
|
|
void _begin_boss_attack_task(const BossAttackTaskArgs *args)
|
|
attr_nonnull_all;
|
|
#define INIT_BOSS_ATTACK(_args) _init_boss_attack_task(_args)
|
|
#define BEGIN_BOSS_ATTACK(_args) _begin_boss_attack_task(_args)
|
|
|
|
int attacktype_start_delay(AttackType t) attr_const;
|
|
int attacktype_end_delay(AttackType t) attr_const;
|