Skip to content

BaseChar.md

In: res://characters/BaseChar.gd

Inherits: BaseObj

Description

Defines the Fighter class from which every YOMI Hustle character inherits. Contains properties that determine character metadata and variables that are useful as accessors.

Properties

Property Default Value Description
MAX_HEALTH 1000 The health of your fighter.

num_air_movements

2 The amount of air options your fighter has.

character_portrait

N/A The portrait that will show in character select.
you_label $you_label The label that shows over your character when you control them.
actionable_label 1000 The label that shows over your prediction ghost on the frame you will be active.
quitter_label $"%QuitterLabel" The label that shows over your character after you leave, briefly before you blow up. Shame on you.
input_state InputState.new() N/A
color Color.white N/A

player_info_scene

N/A The playerinfo scene your character uses.

player_extra_params_scene

N/A N/A

damage_taken_modifier

"1.0" Multiplier for this fighter's damage taken.
num_feints 2 Presumably the number of free cancels the player can make from neutral.
opponent N/A Reference to the parent node of the enemy fighter; commonly used as an accessor from character states.
queued_action null N/A
queued_data null N/A
queued_extra null N/A

dummy_interruptable

false N/A
game_over false Likely set to true when MAX_HEALTH hits 0.
forfeit false N/A
will_forfeit false N/A
applied_style null N/A
is_color_active false N/A
is_aura_active false N/A
is_style_active null N/A
ivy_effect false ?????

colliding_with_opponent

true Presumably a check for collisionbox overlaps between fighters.

air_movements_left

0 The amount of air options your fighter currently has.
action_cancels {} N/A
ghost_ready_tick null N/A
ghost_ready_set false N/A
got_parried false True if your move was successfully parried by the opponent.
di_enabled true Value determined from pre-match settings; enables DI if true.
turbo_mode true Value determined from pre-match settings; enables Turbo Mode if true.

infinite_resources

true Value determined from pre-match settings; enables infinite resources if true.
one_hit_ko true Value determined from pre-match settings; enables OHKO if true.
burst_enabled true Value determined from pre-match settings; enables bursting if true.

always_perfect_parry

true Value determined from pre-match settings; causes every block to parry regardless of timing if true.
blocked_last_hit true True if you successfully blocked the opponent's last move.
trail_hp MAX_HEALTH Must be int. N/A
hp 0 Must be int. N/A
super_meter 0 Must be int. N/A
supers_available 0 Must be int. N/A
combo_proration 0 Must be int. N/A

parried_last_state

false Most likely set to true if this fighter parried the opponent's last move.

initiative_effect

false N/A
burst_meter 0 Must be int. N/A
bursts_available 0 Must be int. N/A
busy_interrupt false N/A

any_available_actions

false N/A
state_changed false N/A
on_the_ground false True if the fighter is on the ground.
nudge_amount "1.0" N/A
feints 2 Likely tied to the number of free cancels this fighter can use out of neutral.
current_nudge {"x":"0","y":"0"} N/A
current_di {"x":"0","y":"0"} Current direct input vector for this fighter.
last_vel {"x":"0","y":"0"} Likely the last recorded velocity for this fighter.
last_aerial_vel {"x":"0","y":"0"} Likely the last recorded aerial velocity for this fighter.
combo_moves_used {} N/A
reverse_state false A boolean value likely used to track if the move's "reverse" slider is being used.
ghost_reverse false A boolean value likely used to track if the move should be reversed for the prediction ghost. Would'nt recommend touching this value.

nudge_distance_left

0 N/A
can_nudge false Likely a boolean value for if this fighter can nudge.
parried false N/A
initiative false True if this fighter has initiative.
aura_particle null N/A
feinting false True if this fighter is feinting.
last_action 0 N/A
stance "Normal" Current stance for the fighter.
parried_hitboxes [] N/A

grounded_hits_taken

0 N/A
throw_pos_x 16 Likely tied to the X distance this fighter will throw the opponent after a grab.
throw_pos_y -5 Likely tied to the Y distance this fighter will throw the opponent after a grab.
combo_damage 0 Likely tracks the amount of damage this fighter has taken during a combo.
hitlag_applied 0 N/A
forfeit_ticks 0 N/A
lowest_tick 0 N/A

Enumerations

There are no enumerated types for this class.

Constants

Constant Value Description
MAX_STALES 15 N/A
MIN_STALE_MODIFIER "0.2" N/A
DAMAGE_SUPER_SCALE_DIVISOR 1 Likely the number that super gain from dealing damage is divided by.
DAMAGE_TAKEN_SUPER_GAIN_DIVISOR 3 Likely the number that super gain from taking damage is divided by.
HITLAG_COLLISION_TICKS 4 N/A
PROJECTILE_PERFECT_PARRY_WINDOW 3 Likely the number of ticks that this fighter is alloted to perfectly parry a projectile.
BURST_ON_DAMAGE_AMOUNT 5 N/A
COUNTER_HIT_ADDITIONAL_HITLAG_FRAMES 3 N/A
MAX_GROUNDED_HITS 7 N/A
PARRY_CHIP_DIVISOR 3 N/A
PARRY_KNOCKBACK_DIVISOR "3" N/A
P1_COLOR Color("aca2ff") Likely the color player 1 will display as to the player.
P2_COLOR Color("aca2ff") Likely the color player 2 will display as to the player.
GUTS_REDUCTIONS {"1":"1", "0.70":"0.90", "0.60":"0.80", "0.50":"0.70", "0.40":"0.60", "0.30":"0.55", "0.20":"0.52", "0.10":"0.50", } N/A
MAX_GUTS 10 N/A
MAX_DI_COMBO_ENHANCMENT 15 Likely the max coefficient for directional input influence.
MAX_BURSTS 1 Likely the max amount of bursts this fighter can use from one burst meter.
BURST_BUILD_SPEED 4 Likely the speed at which the burst meter builds.
MAX_BURST_METER 1500 Likely the integer maximum for this fighter's burst meter.
START_BURSTS 1 Likely the amount of bursts this fighter starts with.
MAX_SUPER_METER 125 Likely the integer maximum for this fighter's super meter.
MAX_SUPERS 9 Likely the amount of super levels this fighter can store.
VEL_SUPER_GAIN_DIVISOR 4 N/A
NUDGE_DISTANCE 20 N/A
PARRY_METER 50 N/A
METER_GAIN_MODIFIER "1.0" N/A

Methods

init(pos = null)

Init function

Serves to handle fighter properties when lobby options are changed; i.e. if one hit K.O. is enabled.

returns null

apply_style(style)

No information on this function. Does nothing if not using the Steam release of YOMI Hustle.

returns null

reset_color()

Resets the shader parameters for both fighters.

Returns null

reset_aura()

Resets the "aura" for both fighters. (???)

Returns null

reset_style()

Resets the "style" for both fighters. (???)

Returns null

reapply_style()

Reapplies "style" (???)

Returns null

start_super()

Emits the super_started signal.

Returns null

change_stance_to(stance)

Changes the fighter's stance to a string supplied to the stance argument.

Returns null

show_you_label()

Shows the "you" label.

Returns null

is_you()

Returns your player I.D. if you are in a multiplayer match.

Returns Network.player_id, false

_ready()

Ready function

Sets the fighter's sprite animation to "Wait", does extra work with the state variables array and a couple of signals; please document further.

Returns null

on_state_changed(states_stack)

Likely runs whenever this fighter changes states. Does nothing by default.

Returns null

on_got_hit()

Runs whenever this fighter gets hit. Does nothing by default.

Returns null

gain_burst_meter(amount = null)

Does nothing if bursts arent enabled; adds BURST_BUILD_SPEED to the burst meter if amount is not defined; else, adds amount to the burst meter; if the burst meter is over the limit, gain a burst.

Returns null

copy_to()

N/A

Returns null

gain_burst()

Handles logic for gaining a burst; gives a burst if the fighter has not reached their limit on bursts.

Returns null

use_burst()

Decrements the amount of bursts available by 1. Does nothing if infinite_resources is set to true.

Returns null

use_burst_meter(amount)

Decrements the burst meter by amount.

Returns null

use_super_bar()

Removes 1 super level from the fighter; if the fighter does not have a super level, set the bar to empty. Does nothing if infinite_resources is set to true.

Returns null

use_super_meter(amount)

Removes amount from the fighter's super meter; if the fighter does not have enough super meter, the bar is set to empty. Does nothing if infinite_resources is set to true.

Returns null

stack_move_in_combo(move_name)

N/A

Returns null

gain_super_meter(amount)

Adds post-stale math amount to the fighter's super meter.

Returns null

combo_stale_meter(meter:int)

Handles combo staling math.

Returns null

update_data()

Updates the data variable.

Returns null

get_playback_input

N/A

Returns null

reset_combo()

Resets this fighter's combo.

Returns null

incr_combo()

Increments this fighter's combo counter by 1.

Returns null

is_colliding_with_opponent

Returns true if this fighter is colliding with the opponent, or is grabbed (?).

Returns true, false

thrown_by(hitbox:ThrowBox)

N/A

Returns null

_process(_delta)

N/A

Returns null

debug_text()

Debug function. N/A

Returns null

has_armor()

Returns false by default. Probably used for super armor mechanics.

Returns false

launched_by(hitbox)

N/A

Returns null

hit_by(hitbox)

N/A

Returns null

set_throw_position(x:int, y:int)

Changes the throw position of this fighter's throw.

Returns null

take_damage(damage:int, minimum = 0)

Causes your fighter to take damage, calculates burst gain from damage taken, and gives your opponent super meter based on the damage taken.

Returns null

get_guts()

N/A

Returns current_guts

get_combo_stale(count)

N/A

Returns mod

guts_stale_damage(damage:int)

N/A

Returns damage

can_parry_hitbox(hitbox)

N/A

Returns true, false

set_color(color:Color)

Sets the color of this fighter.

Returns null

release_opponent()

If the opponent is grabbed, sets their state to the falling state.

Returns null

process_extra(extra)

N/A

Returns null

refresh_air_movements()

Sets the number of air options for this fighter back to maximum.

Returns null

refresh_feints()

Sets the number of free cancels for this fighter back to maximum.

Returns null

clean_parried_hitboxes()

N/A

Returns null

get_opponent_dir()

Returns the difference between your fighter's position and the opponent's position.

Returns Utils.int_sign(opponent.get_pos().x - get_pos().x)

get_advantage()

Returns information regarding frame advantage.

Returns advantage

set_lowest_tick()

N/A

Returns null

tick_before()

N/A

Returns null

toggle_quit_graphic(on = null)

Toggles the 'QUITTER" graphic; also handles the sound for quitters. Shame on them.

Returns null

tick()

N/A

Returns null

set_ghost_colors()

Responsible for handling the colors of the prediction ghosts.

Returns null

set_facing(facing:int, force = false)

Causes the fighter to face a set direction based on facing, unless reverse_state is true. Forcibly causes the fighter to face a set direction regardless of reverse_state if force is set to true.

Returns null

update_facing()

Updates the direction this fighter is facing, based on the opponent's location.

Returns null

on_state_interruptable(state)

N/A

Returns null

on_state_hit_cancellable(state)

N/A

Returns null

on_action_selected(action, data, extra)

Called whenever the player selects an action for this fighter.

Returns null

forfeit()

Sets will_forfeit to true.

Returns null

_draw()

Does nothing by default. N/A

Returns null

Signals

action_selected(action, data)

N/A

super_started()

N/A

parried()

N/A

undo()

N/A

forfeit()

N/A

Credits

Info contributed by D00dlenoodles

Source

BaseChar.gd
extends BaseObj

class_name Fighter

signal action_selected(action, data)
signal super_started()
signal parried()
signal undo()
signal forfeit()


var MAX_HEALTH = 1000













const MAX_STALES = 15
const MIN_STALE_MODIFIER = "0.2"

const DAMAGE_SUPER_GAIN_DIVISOR = 1
const DAMAGE_TAKEN_SUPER_GAIN_DIVISOR = 3
const HITLAG_COLLISION_TICKS = 4
const PROJECTILE_PERFECT_PARRY_WINDOW = 3
const BURST_ON_DAMAGE_AMOUNT = 5

const COUNTER_HIT_ADDITIONAL_HITLAG_FRAMES = 3

const MAX_GROUNDED_HITS = 7

const PARRY_CHIP_DIVISOR = 3
const PARRY_KNOCKBACK_DIVISOR = "3"

const P1_COLOR = Color("aca2ff")
const P2_COLOR = Color("ff7a81")

const GUTS_REDUCTIONS = {
    "1":"1", 


    "0.70":"0.90", 
    "0.60":"0.80", 
    "0.50":"0.70", 
    "0.40":"0.60", 
    "0.30":"0.55", 
    "0.20":"0.52", 
    "0.10":"0.50", 
}

const MAX_GUTS = 10

const MAX_DI_COMBO_ENHANCMENT = 15

const MAX_BURSTS = 1
const BURST_BUILD_SPEED = 4
const MAX_BURST_METER = 1500
const START_BURSTS = 1

const MAX_SUPER_METER = 125
const MAX_SUPERS = 9
const VEL_SUPER_GAIN_DIVISOR = 4


const NUDGE_DISTANCE = 20

const PARRY_METER = 50
const METER_GAIN_MODIFIER = "1.0"

export  var num_air_movements = 2

export (Texture) var character_portrait

onready var you_label = $YouLabel
onready var actionable_label = $ActionableLabel
onready var quitter_label = $"%QuitterLabel"

var input_state = InputState.new()

var color = Color.white

export (PackedScene) var player_info_scene
export (PackedScene) var player_extra_params_scene

export  var damage_taken_modifier = "1.0"

export  var num_feints = 2

var opponent

var queued_action = null
var queued_data = null
var queued_extra = null

var dummy_interruptable = false

var game_over = false
var forfeit = false
var will_forfeit = false

var applied_style = null
var is_color_active = false
var is_aura_active = false
var is_style_active = null

var ivy_effect = false

var colliding_with_opponent = true

var air_movements_left = 0

var action_cancels = {
}

var ghost_ready_tick = null
var ghost_ready_set = false
var got_parried = false

var di_enabled = true
var turbo_mode = false
var infinite_resources = false
var one_hit_ko = false
var burst_enabled = true
var always_perfect_parry = false
var blocked_last_hit = false

var trail_hp:int = MAX_HEALTH
var hp:int = 0
var super_meter:int = 0
var supers_available:int = 0
var combo_proration:int = 0

var parried_last_state = false
var initiative_effect = false

var burst_meter:int = 0
var bursts_available:int = 0

var busy_interrupt = false
var any_available_actions = true

var state_changed = false
var on_the_ground = false
var nudge_amount = "1.0"

var feints = 2


var current_nudge = {
    "x":"0", 
    "y":"0", 
}

var current_di = {
    "x":"0", 
    "y":"0", 
}

var last_vel = {
    "x":"0", 
    "y":"0", 
}

var last_aerial_vel = {
    "x":"0", 
    "y":"0", 
}

var combo_moves_used = {}

var reverse_state = false
var ghost_reverse = false

var nudge_distance_left = 0

var can_nudge = false
var parried = false

var initiative = false
var aura_particle = null

var feinting = false

var last_action = 0

var stance = "Normal"

var parried_hitboxes = []

var grounded_hits_taken = 0

var throw_pos_x = 16
var throw_pos_y = - 5

var combo_damage = 0
var hitlag_applied = 0
var forfeit_ticks = 0

var lowest_tick = 0

class InputState:
    var name
    var data

func init(pos = null):
    .init(pos)
    if not is_ghost:
        Network.player_objects[id] = self
    feints = num_feints
    if one_hit_ko:
        MAX_HEALTH = 1
    hp = MAX_HEALTH
    game_over = false
    show_you_label()
    if burst_enabled:
        for i in range(START_BURSTS):
            gain_burst()
    for state in state_machine.get_children():
        if state is CharacterState:
            for category in state.interrupt_from:
                if not action_cancels.has(category):
                    action_cancels[category] = []
                if not (state in action_cancels[category]):
                    action_cancels[category].append(state)
    if infinite_resources:
        supers_available = MAX_SUPERS
        super_meter = MAX_SUPER_METER

func apply_style(style):
    if not SteamYomi.STARTED:
        return 
    if style != null and not is_ghost:
        is_color_active = true
        is_style_active = true
        applied_style = style
        if Global.enable_custom_colors and style.has("character_color") and style.character_color != null:
            set_color(style.character_color)
            Custom.apply_style_to_material(style, sprite.get_material())
        if Global.enable_custom_particles and not is_ghost and style.show_aura and style.has("aura_settings"):
            reset_aura()
            is_aura_active = true
            aura_particle = preload("res://fx/CustomTrailParticle.tscn").instance()
            particles.add_child(aura_particle)
            aura_particle.load_settings(style.aura_settings)
            aura_particle.position = hurtbox_pos_float()
            aura_particle.start_emitting()
            if aura_particle.show_behind_parent:
                aura_particle.z_index = - 1
        if style.has("hitspark"):
            custom_hitspark = load(Custom.hitsparks[style.hitspark])
            for hitbox in hitboxes:
                hitbox.HIT_PARTICLE = custom_hitspark





func reset_color():
    is_color_active = false
    sprite.get_material().set_shader_param("color", P1_COLOR if id == 1 else P2_COLOR)
    sprite.get_material().set_shader_param("use_outline", false)

func reset_aura():
    is_aura_active = false
    if is_instance_valid(aura_particle):
        aura_particle.queue_free()
    aura_particle = null

func reset_style():
    reset_color()
    reset_aura()
    is_style_active = false

func reapply_style():
    apply_style(applied_style)

func start_super():
    emit_signal("super_started")

func change_stance_to(stance):
    self.stance = stance

func show_you_label():
    if is_you():
        you_label.show()

func is_you():
    if Network.multiplayer_active:
        return id == Network.player_id
    return false

func _ready():
    sprite.animation = "Wait"
    state_variables.append_array(
        ["current_di", "current_nudge", "feinting", "feints", "lowest_tick", "is_color_active", "blocked_last_hit", "combo_proration", "state_changed", "nudge_amount", "initiative_effect", "reverse_state", "combo_moves_used", "parried_last_state", "initiative", "last_vel", "last_aerial_vel", "trail_hp", "always_perfect_parry", "parried", "got_parried", "parried_this_frame", "grounded_hits_taken", "on_the_ground", "hitlag_applied", "combo_damage", "burst_enabled", "di_enabled", "turbo_mode", "infinite_resources", "one_hit_ko", "dummy_interruptable", "air_movements_left", "super_meter", "supers_available", "parried", "parried_hitboxes", "burst_meter", "bursts_available"]
    )
    add_to_group("Fighter")
    connect("got_hit", self, "on_got_hit")
    state_machine.connect("state_changed", self, "on_state_changed")

func on_state_changed(states_stack):

    pass

func on_got_hit():
    pass

func gain_burst_meter(amount = null):
    if not burst_enabled:
        return 
    if bursts_available < MAX_BURSTS:
        burst_meter += BURST_BUILD_SPEED if amount == null else amount
        if burst_meter > MAX_BURST_METER:
            gain_burst()

func copy_to(f):
    .copy_to(f)
    f.update_data()
    f.set_facing(get_facing_int(), true)
    f.update_data()


func gain_burst():
    if bursts_available < MAX_BURSTS:
        bursts_available += 1
        burst_meter = 0

func use_burst():
    if infinite_resources:
        return 
    bursts_available -= 1
    refresh_air_movements()

func use_burst_meter(amount):
    if infinite_resources:
        return 
    if bursts_available > 0:
        bursts_available = 0
        burst_meter = MAX_BURST_METER
    burst_meter -= amount

func use_super_bar():
    if infinite_resources:
        return 
    supers_available -= 1
    if supers_available < 0:
        supers_available = 0
        super_meter = 0

func use_super_meter(amount):
    if infinite_resources:
        return 
    super_meter -= amount
    if super_meter < 0:
        if supers_available > 0:
            super_meter = MAX_SUPER_METER + super_meter
            use_super_bar()
        else :
            super_meter = 0

func stack_move_in_combo(move_name):
    if combo_moves_used.has(move_name):
        combo_moves_used[move_name] += 1
    else :
        combo_moves_used[move_name] = 1

func gain_super_meter(amount):
    amount = combo_stale_meter(amount)
    super_meter += amount
    if super_meter >= MAX_SUPER_METER:
        if supers_available < MAX_SUPERS:
            super_meter -= MAX_SUPER_METER
            supers_available += 1
        else :
            super_meter = MAX_SUPER_METER

func combo_stale_meter(meter:int):
    var staling = get_combo_stale(combo_count)
    return fixed.round(fixed.mul(fixed.mul(str(meter), staling), METER_GAIN_MODIFIER))

func update_data():
    data = get_data()
    obj_data = data["object_data"]
    data["state_data"] = {
        "state":current_state().state_name, 
        "frame":current_state().current_tick, 
        "combo count":combo_count, 
    }

func get_playback_input():
    if ReplayManager.playback:
        if get_frames().has(current_tick):
            return get_frames()[current_tick]

func get_global_throw_pos():
    var pos = get_pos()
    pos.x += throw_pos_x * get_facing_int()
    pos.y += throw_pos_y
    return pos

func reset_combo():
    combo_count = 0
    combo_damage = 0
    combo_proration = 0
    combo_moves_used = {}
    opponent.grounded_hits_taken = 0
    opponent.trail_hp = opponent.hp

func incr_combo():
    combo_count += 1

func is_colliding_with_opponent():
    return (colliding_with_opponent or (current_state() is CharacterHurtState and (hitlag_applied - hitlag_ticks) < HITLAG_COLLISION_TICKS) and current_state().state_name != "Grabbed")

func thrown_by(hitbox:ThrowBox):
    emit_signal("got_hit")
    state_machine._change_state("Grabbed")

func hitbox_from_name(hitbox_name):
        var hitbox_props = hitbox_name.split("_")
        var obj_name = hitbox_props[0]
        var hitbox_id = int(hitbox_props[ - 1])
        var obj = objs_map[obj_name]
        if obj:
            return objs_map[obj_name].hitboxes[hitbox_id]

func _process(_delta):
    update()
    if invulnerable:
        if (Global.current_game.real_tick / 1) % 2 == 0:
            var transparent = color
            self_modulate.a = 0.5

        else :
            self_modulate.a = 1.0

    else :
        self_modulate.a = 1.0
    if is_instance_valid(aura_particle):
        aura_particle.visible = Global.enable_custom_particles
        aura_particle.position = hurtbox_pos_float()
        aura_particle.facing = get_facing_int()

    if is_style_active:
        if applied_style and not is_color_active and Global.enable_custom_colors:
            apply_style(applied_style)
        if applied_style and not is_aura_active and Global.enable_custom_particles:
            apply_style(applied_style)
    if is_color_active and not Global.enable_custom_colors:
        reset_color()
    if is_aura_active and not Global.enable_custom_particles:
        reset_aura()

func debug_text():
    .debug_text()
    debug_info(
        {
            "lowest_tick":lowest_tick, 
            "initiative":initiative, 
        }
    )

func has_armor():
    return false

func launched_by(hitbox):


    hitlag_ticks = hitbox.victim_hitlag + (COUNTER_HIT_ADDITIONAL_HITLAG_FRAMES if hitbox.counter_hit else 0)
    hitlag_applied = hitlag_ticks

    if objs_map.has(hitbox.host):
        var host = objs_map[hitbox.host]
        if host.hitlag_ticks < hitbox.hitlag_ticks:
            host.hitlag_ticks = hitbox.hitlag_ticks

    if hitbox.rumble:
        rumble(hitbox.screenshake_amount, hitbox.victim_hitlag if hitbox.screenshake_frames < 0 else hitbox.screenshake_frames)

    nudge_amount = hitbox.sdi_modifier

    if hitbox.ignore_armor or not has_armor():
        var state
        if is_grounded():
            state = hitbox.grounded_hit_state
        else :
            state = hitbox.aerial_hit_state

        if state == "HurtGrounded":
            grounded_hits_taken += 1
            if grounded_hits_taken >= MAX_GROUNDED_HITS:
                state = "HurtAerial"
                grounded_hits_taken = 0

        state_machine._change_state(state, {"hitbox":hitbox})
        if hitbox.disable_collision:
            colliding_with_opponent = false

        busy_interrupt = true
        can_nudge = true

        if opponent.combo_count == 0:
            opponent.combo_proration = hitbox.damage_proration

        var host = objs_map[hitbox.host]
        var projectile = not host.is_in_group("Fighter")

        if not projectile:
            refresh_feints()
            opponent.refresh_feints()

        if hitbox.increment_combo:
            opponent.incr_combo()

    emit_signal("got_hit")
    take_damage(hitbox.damage, hitbox.minimum_damage)
    state_tick()

func hit_by(hitbox):
    if parried:
        return 
    if hitbox.name in parried_hitboxes:
        return 
    if not hitbox.hits_otg and is_otg():
        return 
    if hitbox.throw and not is_otg():
        return thrown_by(hitbox)

    if not can_parry_hitbox(hitbox):

        match hitbox.hitbox_type:
            Hitbox.HitboxType.Normal:
                launched_by(hitbox)
            Hitbox.HitboxType.Flip:
                set_facing(get_facing_int() * - 1, true)
                var vel = get_vel()
                set_vel(fixed.mul(vel.x, "-1"), vel.y)
                for hitbox in hitboxes:
                    hitbox.facing = get_facing()
                    pass
                emit_signal("got_hit")
                take_damage(hitbox.damage, hitbox.minimum_damage)
            Hitbox.HitboxType.ThrowHit:
                emit_signal("got_hit")
                take_damage(hitbox.damage, hitbox.minimum_damage)
                opponent.incr_combo()
    else :
        opponent.got_parried = true

        var host = objs_map[hitbox.host]
        var projectile = not host.is_in_group("Fighter")
        var perfect_parry
        if not projectile:
            perfect_parry = always_perfect_parry or opponent.current_state().feinting or opponent.feinting or (initiative and not blocked_last_hit) or parried_last_state
            opponent.feinting = false
            opponent.current_state().feinting = false
        else :

            perfect_parry = always_perfect_parry or parried_last_state or (current_state().current_tick < PROJECTILE_PERFECT_PARRY_WINDOW and host.has_projectile_parry_window)
        if perfect_parry:
            parried_last_state = true
        else :
            blocked_last_hit = true


        parried = true

        hitlag_ticks = 0
        parried_hitboxes.append(hitbox.name)
        var particle_location = current_state().get("particle_location")
        particle_location.x *= get_facing_int()

        if not particle_location:
            particle_location = hitbox.get_overlap_center_float(hurtbox)
        var parry_meter = PARRY_METER if hitbox.parry_meter_gain == - 1 else hitbox.parry_meter_gain

        current_state().parry(perfect_parry)
        if not perfect_parry:
            take_damage(hitbox.damage / PARRY_CHIP_DIVISOR)
            apply_force_relative(fixed.div(hitbox.knockback, fixed.mul(PARRY_KNOCKBACK_DIVISOR, "-1")), "0")
            gain_super_meter(parry_meter / 3)
            opponent.gain_super_meter(parry_meter / 3)
            if not projectile:
                current_state().anim_length = opponent.current_state().anim_length
                current_state().endless = opponent.current_state().endless
                current_state().iasa_at = opponent.current_state().iasa_at



            spawn_particle_effect(preload("res://fx/FlawedParryEffect.tscn"), get_pos_visual() + particle_location)
            parried = false
            play_sound("Block")
            play_sound("Parry")
        else :
            spawn_particle_effect(preload("res://fx/ParryEffect.tscn"), get_pos_visual() + particle_location)
            gain_super_meter(parry_meter)
            play_sound("Parry2")
            play_sound("Parry")
            emit_signal("parried")

func set_throw_position(x:int, y:int):
    throw_pos_x = x
    throw_pos_y = y

func take_damage(damage:int, minimum = 0):
    if opponent.combo_count == 0:
        trail_hp = hp
    if damage == 0:
        return 
    gain_burst_meter(damage / BURST_ON_DAMAGE_AMOUNT)
    damage = Utils.int_max(guts_stale_damage(combo_stale_damage(damage)), 1)
    damage = Utils.int_max(damage, minimum)
    hp -= damage
    opponent.combo_damage += damage
    opponent.gain_super_meter(damage / DAMAGE_SUPER_GAIN_DIVISOR)
    gain_super_meter(damage / DAMAGE_TAKEN_SUPER_GAIN_DIVISOR)
    if hp < 0:
        hp = 0

func get_guts():
    var current_guts = "1"
    for level in GUTS_REDUCTIONS:
        var hp_level = fixed.div(str(hp), str(MAX_HEALTH))
        if fixed.le(hp_level, level):
            current_guts = GUTS_REDUCTIONS[level]
    return current_guts

func get_combo_stale(count):
    var ratio = fixed.div(fixed.sub(str(MAX_STALES), str(Utils.int_min(count, MAX_STALES))), str(MAX_STALES))
    var mod = fixed.mul(fixed.sub("1", MIN_STALE_MODIFIER), fixed.powu(ratio, 2))
    mod = fixed.add(mod, MIN_STALE_MODIFIER)
    return mod

func guts_stale_damage(damage:int):
    var guts = get_guts()
    damage = fixed.round(fixed.mul(str(damage), guts))
    return damage

func combo_stale_damage(damage:int):
    var staling = get_combo_stale(Utils.int_max(opponent.combo_count + (opponent.combo_proration if opponent.combo_count > 1 else 0) - 1, 0))
    return fixed.round(fixed.mul(str(damage), staling))

func can_parry_hitbox(hitbox):
    if not current_state() is ParryState:
        return false
    if hitbox.hitbox_type == Hitbox.HitboxType.Flip:
        return false
    return current_state().can_parry_hitbox(hitbox)

func set_color(color:Color):
    if color != null:
        sprite.get_material().set_shader_param("color", color)
        self.color = color

func release_opponent():
    if opponent.current_state().state_name == "Grabbed":
        opponent.change_state("Fall")

func process_extra(extra):
    if "DI" in extra:
        if di_enabled:
            var di = extra["DI"]
            current_nudge = xy_to_dir(di.x, di.y, str(NUDGE_DISTANCE))
            current_di = xy_to_dir(di.x, di.y, fixed.add("1.0", fixed.mul("1.0", fixed.div(str(Utils.int_min(MAX_DI_COMBO_ENHANCMENT, opponent.combo_count)), "5"))))
        else :
            current_di = FixedVec2String.new(0, 0)
    if "reverse" in extra:
        reverse_state = extra["reverse"]
        if reverse_state:
            ghost_reverse = true
    if "feint" in extra:
        feinting = extra.feint
        if feinting:
            feints -= 1
    else :
        feinting = false

func refresh_air_movements():
    air_movements_left = num_air_movements

func refresh_feints():
    feints = num_feints

func clean_parried_hitboxes():


    if not parried_hitboxes:
        return 
    var hitboxes_to_refresh = []
    for hitbox_name in parried_hitboxes:
        var hitbox = hitbox_from_name(hitbox_name)
        if hitbox:
            if not hitbox.enabled or not hitbox.active:
                hitboxes_to_refresh.append(hitbox)

    for hitbox in hitboxes_to_refresh:
        parried_hitboxes.erase(hitbox.name)

func get_opponent_dir():
    return Utils.int_sign(opponent.get_pos().x - get_pos().x)

func get_advantage():
    if opponent == null:
        return true

    var minus_modifier = 0
    var advantage = (opponent and opponent.lowest_tick <= lowest_tick) or parried_last_state


    if state_interruptable and opponent.state_interruptable:
        advantage = true
    if current_state().state_name == "WhiffInstantCancel" or (previous_state() and previous_state().state_name == "WhiffInstantCancel" and current_state().has_hitboxes):
        advantage = false
    if opponent.current_state().state_name == "WhiffInstantCancel" or (opponent.previous_state() and opponent.previous_state().state_name == "WhiffInstantCancel" and opponent.current_state().has_hitboxes):
        advantage = false
    return advantage

func set_lowest_tick(tick):
    if lowest_tick == null or tick < lowest_tick:
        lowest_tick = tick

func update_advantage():
    var new_adv = get_advantage()
    if new_adv and not initiative:
        initiative_effect = true
    initiative = new_adv

func tick_before():
    if queued_action == "Forfeit":
        if forfeit:
            queued_action = "Continue"

    dummy_interruptable = false
    clean_parried_hitboxes()
    busy_interrupt = false
    update_grounded()
    if ReplayManager.playback:
        var input = get_playback_input()
        if input:
            queued_action = input["action"]
            queued_data = input["data"]
            queued_extra = input["extra"]

            if queued_action == "Forfeit":

                forfeit = true
                Global.current_game.forfeit(id)
    else :
        if queued_action:

            if queued_action == "Undo":
                queued_action = null
                queued_data = null
                return 
            if queued_action == "Forfeit":
                forfeit = true

            if not is_ghost:
                ReplayManager.frames[id][current_tick] = {
                    "action":queued_action, 
                    "data":queued_data, 
                    "extra":queued_extra, 
                }
    var feinted_last = feinting
    var pressed_feint = false
    if queued_extra:
        process_extra(queued_extra)
        pressed_feint = feinting
    if queued_action:
        if queued_action in state_machine.states_map:

            if feinted_last:
                var particle_pos = get_hurtbox_center_float()
                spawn_particle_effect(preload("res://fx/FeintEffect.tscn"), particle_pos)
            state_machine._change_state(queued_action, queued_data)
            if pressed_feint:
                feinting = true
                current_state().feinting = true
    queued_action = null
    queued_data = null
    queued_extra = null
    lowest_tick = current_state().current_real_tick

func toggle_quit_graphic(on = null):
    if on == null:
        quitter_label.visible = not quitter_label.visible
        if quitter_label.visible:
            play_sound("QuitterSound")
    else :
        quitter_label.visible = on
        if on:
            play_sound("QuitterSound")

func tick():
    if hitlag_ticks > 0:
        if can_nudge:
            if fixed.round(fixed.mul(fixed.vec_len(current_nudge.x, current_nudge.y), "100.0")) > 1:
                current_nudge = fixed.vec_mul(current_nudge.x, current_nudge.y, nudge_amount)
                spawn_particle_effect(preload("res://fx/NudgeIndicator.tscn"), Vector2(get_pos().x, get_pos().y + hurtbox.y), Vector2(current_di.x, current_di.y).normalized())
                move_directly(current_nudge.x, current_nudge.y if not is_grounded() else "0")
            can_nudge = false
        hitlag_ticks -= 1
        if hitlag_ticks == 0:
            if state_hit_cancellable:
                state_interruptable = true
                can_nudge = false
    else :
        if parried:

            parried = false
            if current_state().get("parry_active"):
                current_state().parry_active = false
            var prev = previous_state()
            if prev and prev.get("parry_active"):
                prev.parry_active = false
        var minus_offset = 0 if id == 1 else 1
        state_tick()

        if state_hit_cancellable:
            state_interruptable = true
            can_nudge = false
        if not current_state() is ThrowState:
            chara.apply_pushback(get_opponent_dir())
        if is_grounded():
            refresh_air_movements()
        current_tick += 1
        if not (current_state().is_hurt_state) and not (opponent.current_state().is_hurt_state):
            var x_vel_int = chara.get_x_vel_int()
            if Utils.int_sign(x_vel_int) == Utils.int_sign(opponent.get_pos().x - get_pos().x):
                gain_super_meter(Utils.int_max(Utils.int_abs(x_vel_int) / VEL_SUPER_GAIN_DIVISOR, 1))



    if state_interruptable:
        update_grounded()
    gain_burst_meter()
    update_data()
    for particle in particles.get_children():
        particle.tick()
    any_available_actions = true
    last_vel = get_vel()
    if not is_grounded():
        last_aerial_vel = last_vel
    if not (previous_state() is ParryState) or not (current_state() is ParryState):
        parried_last_state = false

    if forfeit:
        forfeit_ticks += 1
    if forfeit and forfeit_ticks > 2:
        change_state("ForfeitExplosion")
        forfeit = false

func set_ghost_colors():
    if not ghost_ready_set and (state_interruptable or dummy_interruptable):
        ghost_ready_set = true
        ghost_ready_tick = current_tick
        if opponent.ghost_ready_tick == null or opponent.ghost_ready_tick == ghost_ready_tick:
            set_color(Color.green)
            if opponent.current_state().interruptible_on_opponent_turn or opponent.feinting:
                opponent.ghost_ready_set = true
                opponent.set_color(Color.green)
        elif opponent.ghost_ready_tick < ghost_ready_tick:
            set_color(Color.orange)

func set_facing(facing:int, force = false):
    if reverse_state and not force:
        facing *= - 1
    .set_facing(facing)

func update_facing():
    if obj_data.position_x < opponent.obj_data.position_x:
        set_facing(1)
    elif obj_data.position_x > opponent.obj_data.position_x:
        set_facing( - 1)
    if initialized:
        update_data()

func on_state_interruptable(state):
    if not dummy:
        state_interruptable = true
    else :
        dummy_interruptable = true

func on_state_hit_cancellable(state):
    if not dummy:
        state_hit_cancellable = true

func on_action_selected(action, data, extra):


    if you_label.visible:
        you_label.hide()
    queued_action = action
    queued_data = data
    queued_extra = extra
    state_interruptable = false
    state_hit_cancellable = false
    if action == "Undo":
        emit_signal("undo")


    emit_signal("action_selected", action, data, extra)

func forfeit():
    will_forfeit = true

func _draw():




    pass