#pragma once #include "input.h" #include "trinket.h" #include "world.h" constexpr float CRAWL_STRIDE = 5; constexpr float ROLL_RADIUS = 10; constexpr float STEP_SPEED = 40; constexpr float WALK_STRIDE = 7; struct Attack; struct Sim; constexpr int ECHO_MAX = 64; constexpr int MAX_FIGHTERS = SPAWN_MAX; constexpr float BOUNCE_KEEP = 0.75f; constexpr float SECONDARY_SHADE = 0.5f; constexpr int COLOR_COUNT = 16; constexpr int COLOR_UNASSIGNED = -1; // The colours fighters can wear, in the order fighters without one are given them. constexpr Color PALETTE[COLOR_COUNT] = { {1, 0.35f, 0.35f, 1}, {0.4f, 0.6f, 1, 1}, {1, 0.9f, 0.3f, 1}, {0.4f, 1, 0.45f, 1}, {1, 0.6f, 0.2f, 1}, {0.7f, 0.45f, 1, 1}, {0.3f, 0.95f, 1, 1}, {1, 0.5f, 0.8f, 1}, {0.75f, 1, 0.3f, 1}, {0.2f, 0.75f, 0.7f, 1}, {1, 0.3f, 1, 1}, {0.6f, 0.85f, 1, 1}, {1, 0.78f, 0.6f, 1}, {0.8f, 0.75f, 1, 1}, {0.75f, 0.5f, 0.3f, 1}, {0.75f, 0.78f, 0.85f, 1}, }; constexpr const char* COLOR_NAMES[COLOR_COUNT] = {"Red", "Blue", "Yellow", "Green", "Orange", "Purple", "Cyan", "Pink", "Lime", "Teal", "Magenta", "Sky", "Peach", "Lavender", "Brown", "Silver"}; constexpr float BOUNCE_MIN = 120; constexpr float BOX_HEIGHT = 24; constexpr float BOX_WIDTH = 12; constexpr float JUMP_SPEED = 400; constexpr float KNOCK_DECAY = 1.2f; constexpr float LAUNCH_END_SPEED = 200; constexpr float RUN_SPEED = 150; constexpr float RACE_KNOCK = 0.5f; constexpr float SLAM_SPEED = 400; constexpr float CHARGE_TIME = 1.0f; constexpr float CHARGE_VISIBLE = 0.15f; // Shock Absorber takes off this share of what it takes off weapon movement from knockback. constexpr float ABSORB_KNOCK_SHARE = 2 / 3.0f; // A platforming fighter driven by Controls: movement, aiming, the shared weapon charge, and everything it carries. // Loadouts hold weapon and trinket numbers (-1 for empty), with only their first slots counts in use (the rest stay // empty). The same item can sit in several slots, each copy with its own level (slot levels); item levels are what a // copy gets when equipped, and what an unequipped item is described at. Copies always stack: weapon copies are separate // weapons with their own cooldowns (Dual Wield can hold two of one), and trinket copies add up. Cooldowns (per weapon // key, see weapon_key), Doubler echoes and the kick animation belong to its weapons. Index is its place in the game's // fighter list, which its attacks record. Hitstop freezes it (and its attacks) after landing or taking a heavy hit. A // big hit launches it: it flies, bouncing off whatever it hits, tumbling, with no say in where it goes until it slows // down. Crouching on the ground lowers it, slows it to a waddle and halves the knockback it takes. Spinning a flail // lightens its gravity. Hard bounces hurt, credited to the launcher, whoever landed the last hit. In a race, hits only // knock it about (half as hard), and touching a pit puts it back on the last ground it stood squarely on. Slopes slow // it climbing and speed it up descending. Crouching on one slides it down, rolling, and the slide carries on over flat // ground until it slows. class Fighter { friend class Scene; public: Body body; Controls controls; Vec2 aim = {1, 0}; FixedList echoes; float charge = 0; float cooldowns[SLOT_MAX] = {}; float flash = 0; float health = 0; float hitstop = 0; float kickFrom = 0; float kickTime = 1; float kickTo = 0; float scale = 1; float shockCooldown = 0; float contactCooldown = 0; int index = 0; int launcher = 0; int offSlot = -1; int slot = 0; int trinketItemLevels[TRINKET_COUNT] = {}; int trinketSlotLevels[SLOT_MAX] = {}; int trinketLoadout[SLOT_MAX]; int trinketSlots = SLOTS_DEFAULT; int weaponItemLevels[WEAPON_COUNT] = {}; int weaponSlotLevels[SLOT_MAX] = {}; int weaponLoadout[SLOT_MAX]; int weaponSlots = SLOTS_DEFAULT; bool isAlive = true; bool isBot = false; bool isCrouching = false; bool isSpinning = false; bool isLaunched = false; // In the sandbox, whether it fights other fighters (both need it on); without it, it's drawn darkened. bool isPvp = false; bool isSliding = false; void init(Sim& sim, int number); void state_reset(Sim& sim, float maxHealth); void position_reset(Sim& sim); void position_set(Sim& sim, Vec2 feet); void slots_set(int weapons, int trinkets); void damage_take(Sim& sim, Damage damage); void update(Sim& sim); void blend(float alpha); void impulse_add(Vec2 impulse, bool isDamped = true); void teleport_to(Vec2 centre); void burst_add(Vec2 impulse); void charge_cancel(Sim& sim); int weapon_get(); int weapon_key(int weaponSlot); int off_slot_get(); int off_weapon_get(); int slot_level_get(int weaponSlot); const LevelDef& level_get(int weaponSlot); void item_set(bool isTrinket, int itemSlot, int item); void levels_fill(int weaponLevel, int trinketLevel); float trinket_get(TrinketId trinket, float neutral); float trinket_best_get(TrinketId trinket, float neutral); float trinket_sum_get(int trinket, const float* table); int trinket_level_get(int trinket); int trinket_count(int trinket); float trinket_cooldown_get(int trinket); float knock_absorb_get(); float pound_area_get(); Vec2 center_get(); Vec2 hand_get(); Vec2 offhand_get(); bool is_any_pressed(); bool is_down(Control control); bool is_pressed(Control control); bool is_released(Control control); bool is_trinket_equipped(int trinket); int wall_get(Sim& sim); bool is_crawling(); bool is_left(); private: Vec2 cornerPrevious; Vec2 safe; float coyoteTimer = 0; float dashCooldown = 0; float dashTimer = 0; float jumpBuffer = 0; float kickTimer = 0; float kickPose = 0; float slideTimer = 0; float stepDistance = 0; float wallMemory = 0; float roll = 0; float rollSpin = 0; float tumble = 0; float wallSpeed = 0; int slideWall = 0; Uint16 buttonsPrevious = 0; bool isCharging = false; bool isFireBlocked = false; bool isFacingLeft = false; bool isJumping = false; void slot_update(Sim& sim); void safe_update(Sim& sim); void size_update(); void dash_update(Sim& sim, bool isJumpPressed); void climb_update(Sim& sim); Vec2 wall_point_get(int wall); void wall_push(Sim& sim, int wall, Vec2 base); void bounce_resolve(Sim& sim, Vec2 before); void run_update(Attack* hook); void rope_update(Attack& hook); void charge_update(Sim& sim); };