Universal Clashing System

Started by PotS, November 30, 2024, 06:09:48 PM

Previous topic - Next topic

PotS

Ikemen 0.99 doesn't have the required features to make it work universally.
You can help with Ikemen GO's development by trying out the latest development build and reporting any bugs on GitHub.
My Mugen and Ikemen content can also be found here.

DJMouF


Tabris666

this was how i wanted to implement it as lua on mugen (which can be done, have made several systems lua that work without issue)

-- MUGEN Attack Clashing System
-- Adds universal attack clashing mechanics to all characters

local clashing = {}

-- Configuration
clashing.config = {
    -- Visual effects
    clashEffect = {
        anim = 1000,                       -- Animation number for clash effect
        sound = "data/sounds/clash.wav",   -- Sound effect path
        sparkDuration = 5,                 -- Frames for spark effect to last
        sparkOffset = {x = 0, y = -20}     -- Offset for spark position
    },
   
    -- Clash thresholds
    minimumClashForce = 5,                 -- Minimum attack power to trigger clash
    clashPowerDifferenceThreshold = 10,    -- Power difference where stronger attack wins
   
    -- Gameplay effects
    pushBackDistance = 30,                 -- Distance characters are pushed back
    hitPauseDuration = 5,                  -- Frames of hit pause during clash
    recoveryFrames = 10,                  -- Additional recovery frames after clash
    cancelWindow = 3                      -- Frames window to cancel into special moves
}

-- Clash detection table
clashing.activeClashes = {}

-- Initialize the system
clashing.init = function()
    -- Register the collision callback
    mugen.addcollisioncallback(function(collider, collidee, hitdef)
        clashing.checkForClash(collider, collidee, hitdef)
    end)
   
    -- Register the tick callback
    mugen.addtickcallback(function()
        clashing.updateClashes()
    end)
end

-- Check if two attacks should clash
clashing.checkForClash = function(p1, p2, hitdef)
    -- Only check between different players
    if p1 == p2 or not p1:isplayersprite() or not p2:isplayersprite() then
        return
    end
   
    -- Both must be attacking
    if not p1:instate("attack") or not p2:instate("attack") then
        return
    end
   
    -- Both must have hitdefs that can clash
    if not hitdef.clash or hitdef.noclash then
        return
    end
   
    -- Check if attacks are facing each other
    local p1Facing = p1:facing()
    local p2Facing = p2:facing()
    local facingEachOther = (p1Facing == 1 and p2Facing == -1) or
                          (p1Facing == -1 and p2Facing == 1)
   
    if not facingEachOther then
        return
    end
   
    -- Check attack power meets minimum threshold
    if (hitdef.power or 0) < clashing.config.minimumClashForce then
        return
    end
   
    -- Create a new clash entry
    local clashId = p1:id() .. "_" .. p2:id() .. "_" .. mugen.gameticks()
    clashing.activeClashes[clashId] = {
        players = {p1, p2},
        hitdefs = {hitdef, p2:gethitdef()},
        timer = 0,
        position = p1:pos()
    }
   
    -- Apply initial clash effects
    clashing.applyClashEffects(p1, p2, clashId)
end

-- Apply visual and gameplay effects when clash occurs
clashing.applyClashEffects = function(p1, p2, clashId)
    local clash = clashing.activeClashes[clashId]
   
    -- Play sound effect
    mugen.playsound(clashing.config.clashEffect.sound)
   
    -- Create spark effect
    local sparkPos = {
        x = clash.position.x + clashing.config.clashEffect.sparkOffset.x,
        y = clash.position.y + clashing.config.clashEffect.sparkOffset.y
    }
    mugen.createexplosion(sparkPos.x, sparkPos.y, clashing.config.clashEffect.anim)
   
    -- Apply hit pause
    p1:hitpause(clashing.config.hitPauseDuration)
    p2:hitpause(clashing.config.hitPauseDuration)
   
    -- Cancel current attacks
    p1:changeanim(5) -- Change to stand state
    p2:changeanim(5)
   
    -- Disable physics temporarily
    p1:physicsdisable()
    p2:physicsdisable()
   
    -- Register the clash state
    p1:var_set("clashing", 1)
    p2:var_set("clashing", 1)
    p1:var_set("clash_timer", 0)
    p2:var_set("clash_timer", 0)
    p1:var_set("clash_partner", p2:id())
    p2:var_set("clash_partner", p1:id())
end

-- Update active clashes each frame
clashing.updateClashes = function()
    for clashId, clash in pairs(clashing.activeClashes) do
        clash.timer = clash.timer + 1
       
        local p1 = clash.players[1]
        local p2 = clash.players[2]
       
        -- Check if players are still valid
        if not p1:isplayersprite() or not p2:isplayersprite() then
            clashing.activeClashes[clashId] = nil
            return
        end
       
        -- Handle clash resolution
        if clash.timer >= clashing.config.hitPauseDuration then
            clashing.resolveClash(p1, p2, clash)
            clashing.activeClashes[clashId] = nil
        end
       
        -- Limit the number of active clashes
        if clash.timer > 60 then -- 1 second timeout
            clashing.activeClashes[clashId] = nil
        end
    end
end

-- Determine the outcome of a clash
clashing.resolveClash = function(p1, p2, clash)
    local h1 = clash.hitdefs[1]
    local h2 = clash.hitdefs[2]
   
    -- Calculate power difference
    local powerDiff = (h1.power or 0) - (h2.power or 0)
   
    if math.abs(powerDiff) < clashing.config.clashPowerDifferenceThreshold then
        -- Equal clash - both characters pushed back
        clashing.pushBack(p1, -1) -- Negative direction for p1
        clashing.pushBack(p2, 1)  -- Positive direction for p2
       
        -- Both enter recovery state
        p1:var_set("clash_recovery", clashing.config.recoveryFrames)
        p2:var_set("clash_recovery", clashing.config.recoveryFrames)
    else
        -- Determine winner and loser
        local winner, loser
        if powerDiff > 0 then
            winner = p1
            loser = p2
        else
            winner = p2
            loser = p1
        end
       
        -- Winner continues their attack
        winner:physicsenable()
        winner:var_set("clashing", 0)
        winner:var_set("clash_cancel_window", clashing.config.cancelWindow)
       
        -- Loser is hit
        loser:physicsenable()
        loser:var_set("clashing", 0)
        loser:hitpush(clashing.config.pushBackDistance)
       
        -- Apply hit effects
        local hitAnim = 20 -- Default hit animation
        loser:changeanim(hitAnim)
    end
   
    -- Clear clash state
    p1:var_set("clashing", 0)
    p2:var_set("clashing", 0)
end

-- Push character back after clash
clashing.pushBack = function(player, direction)
    local pushForce = clashing.config.pushBackDistance
    local currentVel = player:vel()
   
    player:velset({
        x = currentVel.x + (pushForce * direction),
        y = currentVel.y - math.abs(pushForce * 0.3) -- Small upward push
    })
end

-- Initialize the system when loaded
clashing.init()

-- Add helper functions to player objects
player._clashing = clashing

return clashing


Sadly is giving me error
Lua initing
data/clash_system.lua:33: attempt to call field 'addcollisioncallback' (a nil value)
System loaded OK
Initialize OK
Set up graphics...timer...OK
So giving up for now unless i think of some way to fix it (or someone finds a way to fix it).
"Thousands of humans falling over like dominoes... ahhh... that sight was so beautiful! I thought my brain was gonna melt..!"