• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

Duel System

This bundle is marked as useful / simple. Simplicity is bliss, low effort and/or may contain minor bugs.
  • Like
Reactions: Amigurumi
This is my MUI Duel System like the one known from Warhammer Mark of Chaos...

When a hero attacks another one, a circle of flames appears around them.
the two will fight till one of them is dead.
they can't leave the circle and other units can't enter it.
in addition the both fighters can't be attacked or attack a unit outside...
targeted spells won't work from outside, too...
the winner of a duel will be fully healed.

it contains two new events and some functions:
  • call TriggerRegisterDuelEndEvent( trigger t )
    will fire the trigger when a duel ends because of the death of a fighter
  • call TriggerRegisterDuelBeginEvent( trigger t )
    will fire when a duel is started (use GetAttacker and GetTriggerUnit to refer to the units)
  • call GetWinner() returns unit
    will return the unit that won the duel when responding to TriggerRegisterDuelEndEvent
  • call GetLoser() returns unit
    will return the unit that lost the duel when responding to TriggerRegisterDuelEndEvent
  • call ForceDuel( unit a, unit b, real x, real y )
    will force a duel between unit a and b with the center of the duel circle at x,y
  • call ForceDuelLoc( unit a, unit b, location p )
    same like ForceDuel but with a location instead of coordinates
  • call EnableWinnerHeal( boolean flag )
    simply turns the feature on or off that the winner of a duel is fully healed


Update v.2.0: cleared the code of some unused locals :D
added possibility to force a duel

Update v.3.0 added a new function and optimized the code

Update v.4.0 heavy bug solved
I advise to download this version!! the others had a big bug

Update v.5.0 major code changings and a improved test map


JASS:
library DuelSystem initializer Init

//This is my MUI Duel System like the one known from Warhammer Mark of Chaos...
//
//When a hero attacks another one, a circle of flames appears around them.
//The two will fight till one of them is dead.
//They can't leave the circle and other units can't enter it.
//In addition the both fighters can't be attacked or attack a unit outside...
//Targeted spells won't work from outside, too...
//The winner of a duel will be fully healed.
//
//included functions:
// TriggerRegisterDuelEndEvent takes trigger t returns boolean
//  will fire the trigger when a duel ends because of the death of a fighter
//
// TriggerRegisterDuelBeginEvent takes trigger t returns boolean
//  will fire when a duel is started (use GetAttacker and GetTriggerUnit to refer to the units)
//
// GetWinner takes nothing returns unit
//  will return the unit that won the duel when responding to TriggerRegisterDuelEndEvent
//
// GetLoser takes nothing returns unit
//  will return the unit that lost the duel when responding to TriggerRegisterDuelEndEvent
//
// ForceDuel takes unit a, unit b, real x, real y returns boolean
//  will force a duel between unit a and b with the center of the duel circle at x,y
//
// ForceDuelLoc takes unit a, unit b, location p returns boolean
//  same like ForceDuel but with a location instead of coordinates
// 
// EnableWinnerHeal takes boolean flag returns nothing
//  simply turns on or off the feature that the winner of a duel is fully healed

// The Setup Part
globals
    // the effect model which makes the circle
    private constant string FLAME_SFX = "Abilities\\Spells\\Other\\BreathOfFire\\BreathOfFireDamage.mdl"
    
    // the effect created when a duel is won
    private constant string WIN_SFX = "Abilities\\Spells\\Other\\Awaken\\Awaken.mdl"
    
    // the effect created when a duel is aborted
    private constant string ABORT_SFX = "Abilities\\Spells\\Human\\FlakCannons\\FlakTarget.mdl"
    
    // the unit spinning around the circle
    private constant integer EFFECT_UNIT = 'h000'
    
    // the radius of the circle              
    private constant real RADIUS = 400
    
    // the interval of the timer
    private constant real INTERVAL = 0.01
    
    // if true the winner of a duel is healed
    private boolean heal = true
endglobals

// end of Setup Part (dont change anything below)
//---------------------------------------------

private struct data
unit u1
unit u2
unit d1
unit d2
effect array sfx[37]
real x
real y
real r = 0
integer c = 0
integer s = 0
endstruct

globals
    private integer total = 0
    private data array DATAS
    private group g = CreateGroup()
    private group gg = CreateGroup()
    private unit winner
    private unit loser
    private trigger array triggers
    private integer total_triggers = 0
    private trigger array triggers2
    private integer total_triggers2 = 0
    private timer tim = CreateTimer()
    private hashtable h = InitHashtable()
    private data Temp
endglobals

private function DistanceBetweenUnits takes unit a, unit b returns real
    local real dx = GetUnitX(b) - GetUnitX(a)
    local real dy = GetUnitY(b) - GetUnitY(a)
    return SquareRoot(dx * dx + dy * dy)
endfunction

private function AngleBetweenCoords takes real x, real y, real xx, real yy returns real
    return Atan2(yy - y, xx - x)
endfunction

private function IsUnitDead takes unit u returns boolean
    return (IsUnitType(u,UNIT_TYPE_DEAD) or GetUnitTypeId(u) == 0 )
endfunction

function GetDuelWinner takes nothing returns unit
    return winner
endfunction

function GetDuelLoser takes nothing returns unit
    return loser
endfunction

function EnableWinnerHeal takes boolean flag returns nothing
    set heal = flag
endfunction

function TriggerRegisterDuelEndEvent takes trigger t returns boolean
    local integer i = 0
    if LoadInteger(h, GetHandleId(t),0) == 1 then
        return false
    endif
    set triggers[total_triggers] = t
    set total_triggers = total_triggers + 1
    call SaveInteger(h, GetHandleId(t),0,1)
    return true
endfunction

function TriggerRegisterDuelBeginEvent takes trigger t returns boolean
    local integer i = 0
    if LoadInteger(h, GetHandleId(t),1) == 1 then
        return false
    endif
    set triggers2[total_triggers2] = t
    set total_triggers2 = total_triggers2 + 1
    call SaveInteger(h, GetHandleId(t),1,1)
    return true
endfunction

function EndDuel takes unit u returns nothing
    local integer ii = 0
    local integer i = LoadInteger(h, GetHandleId(u),0)
    local real x
    local real y
    local data dat = DATAS[i]
    if IsUnitInGroup(u,g) then
        call FlushChildHashtable(h,GetHandleId(dat.u1))
        call FlushChildHashtable(h,GetHandleId(dat.u2))
        loop
            exitwhen ii == 36
            call DestroyEffect(dat.sfx[ii])
            set x = dat.x + RADIUS * Cos(ii*10 * bj_DEGTORAD)
            set y = dat.y + RADIUS * Sin(ii*10 * bj_DEGTORAD)
            call DestroyEffect(AddSpecialEffect(ABORT_SFX,x,y))
            set ii = ii + 1
        endloop
        call RemoveUnit(dat.d1)
        call RemoveUnit(dat.d2)
        call GroupRemoveUnit(g,dat.u1)
        call GroupRemoveUnit(g,dat.u2)          
        set total = total - 1
        set DATAS[i] = DATAS[total]
        call SaveInteger(h, GetHandleId(DATAS[i].u1),0,i)
        call SaveInteger(h, GetHandleId(DATAS[i].u2),0,i)
        call dat.destroy()
        if total == 0 then
            call PauseTimer(tim)
        endif
    endif
endfunction

private function PortAway takes nothing returns nothing
    local real x
    local real y
    local real r
    set r = AngleBetweenCoords(Temp.x,Temp.y,GetUnitX(GetEnumUnit()),GetUnitY(GetEnumUnit()))
    set x = Temp.x + (RADIUS + 51) * Cos(r)
    set y = Temp.y + (RADIUS + 51) * Sin(r)
    call SetUnitPosition(GetEnumUnit(),x,y)
    call IssueImmediateOrder(GetEnumUnit(),"stop")
endfunction

private function execute takes nothing returns nothing
    local data dat
    local integer i = 0
    local integer ii
    local real x
    local real y
    local real r
    local unit u
    loop
        exitwhen i >= total
        set dat = DATAS[i]
        //*****circling*****
        set x = dat.x + RADIUS * Cos(dat.r * bj_DEGTORAD)
        set y = dat.y + RADIUS * Sin(dat.r * bj_DEGTORAD)
        call SetUnitPosition(dat.d1,x,y)
        set x = dat.x + RADIUS * Cos((dat.r + 180) * bj_DEGTORAD)
        set y = dat.y + RADIUS * Sin((dat.r + 180) * bj_DEGTORAD)
        call SetUnitPosition(dat.d2,x,y)
        set dat.r = dat.r + 1
        //*****creating sfx*****
        set dat.s = dat.s + 1
        if dat.s == 10 and dat.sfx[35] == null then
            set dat.s = 0
            set dat.sfx[dat.c] = AddSpecialEffect(FLAME_SFX,GetUnitX(dat.d1),GetUnitY(dat.d1))
            set dat.sfx[dat.c+1] = AddSpecialEffect(FLAME_SFX,GetUnitX(dat.d2),GetUnitY(dat.d2))
            set dat.c = dat.c + 2
        endif
        //*****port fighters back into circle*****
        call GroupEnumUnitsInRange(gg,dat.x,dat.y,RADIUS-50,null)      
        if not IsUnitInGroup(dat.u1,gg) then
            set r = AngleBetweenCoords(dat.x,dat.y,GetUnitX(dat.u1),GetUnitY(dat.u1))
            set x = dat.x + (RADIUS - 50) * Cos(r)
            set y = dat.y + (RADIUS - 50) * Sin(r)
            call SetUnitPosition(dat.u1,x,y)
        endif
        if not IsUnitInGroup(dat.u2,gg) then
            set r = AngleBetweenCoords(dat.x,dat.y,GetUnitX(dat.u2),GetUnitY(dat.u2))
            set x = dat.x + (RADIUS - 50) * Cos(r)
            set y = dat.y + (RADIUS - 50) * Sin(r)
            call SetUnitPosition(dat.u2,x,y)
        endif
        call GroupClear(gg) 
        //*****port other units away from the circle***** 
        call GroupEnumUnitsInRange(gg,dat.x,dat.y,RADIUS + 50,null)        
        call GroupRemoveUnit(gg,dat.u1)
        call GroupRemoveUnit(gg,dat.u2)
        set Temp = dat
        call ForGroup(gg, function PortAway)
        call GroupClear(gg) 
        //*****Is a fighter dead?*****  
        if IsUnitDead(dat.u1) or IsUnitDead(dat.u2) then
            if IsUnitDead(dat.u1) then 
                set winner = dat.u2    //if unit 1 is dead unit 2 is the winner
                set loser = dat.u1
            else
                set winner = dat.u1    //else the winner is unit 1
                set loser = dat.u2
            endif
            if heal then
                call SetWidgetLife(winner,9999999999)
            endif
            call SetUnitAnimation(winner,"stand victory")
            call EndDuel(dat.u1)
            set ii = 0
            loop
                exitwhen ii >= total_triggers
                if triggers[ii] != null and TriggerEvaluate(triggers[ii]) then
                    call TriggerExecute(triggers[ii])
                endif
                set ii = ii + 1
            endloop
        endif   
        //*****prepare next*****  
        set i = i + 1
    endloop
    set u = null
endfunction

function ForceDuel takes unit a, unit b, real x, real y returns boolean
    local data dat
    local integer i = 0
    if IsUnitInGroup(a,g) or IsUnitInGroup(b,g) or IsUnitAlly(a,GetOwningPlayer(b)) then
        return false
    endif
    set dat = data.create()
    set dat.u1 = a
    set dat.u2 = b
    call SaveInteger(h, GetHandleId(a),0,total)
    call SaveInteger(h, GetHandleId(b),0,total)
    set dat.x = x
    set dat.y = y
    call GroupAddUnit(g,a)
    call GroupAddUnit(g,b)
    set dat.d1 = CreateUnit(Player(15),EFFECT_UNIT,0,0,0)
    call UnitAddAbility(dat.d1,'Aloc')
    call SetUnitPathing(dat.d1,false)
    call PauseUnit(dat.d1,true)
    set dat.d2 = CreateUnit(Player(15),EFFECT_UNIT,0,0,0)
    call UnitAddAbility(dat.d2,'Aloc')
    call SetUnitPathing(dat.d2,false)
    call PauseUnit(dat.d2,true)
    set dat.sfx[35] = null
    
    set DATAS[total] = dat
    set total = total + 1  
    
    if total == 1 then
        call TimerStart(tim, INTERVAL, true, function execute)
    endif                                                                    
    loop
        exitwhen i >= total_triggers2
        if TriggerEvaluate(triggers2[i]) then
            call TriggerExecute(triggers2[i])
        endif
        set i = i + 1
    endloop
    return true
endfunction

function ForceDuelLoc takes unit a, unit b, location p returns boolean
    return ForceDuel(a, b, GetLocationX(p), GetLocationY(p))
endfunction

private function start takes nothing returns boolean
    local unit a = GetTriggerUnit()
    local unit b = GetAttacker()
    local real x = (GetUnitX(b) + GetUnitX(a))/2
    local real y = (GetUnitY(b) + GetUnitY(a))/2
    if IsUnitType(a,UNIT_TYPE_HERO) and IsUnitType(b,UNIT_TYPE_HERO) then
        call ForceDuel(a,b,x,y)
    endif
    set b = null
    set a = null
    return false
endfunction

private function PreventAttacking takes nothing returns boolean
    if (IsUnitInGroup(GetTriggerUnit(),g) and not IsUnitInGroup(GetAttacker(),g)) or (IsUnitInGroup(GetAttacker(),g) and not IsUnitInGroup(GetTriggerUnit(),g)) then
        call IssueImmediateOrder(GetAttacker(),"stop")
    endif
    return false
endfunction

private function PreventCasting takes nothing returns boolean
    if not IsUnitInGroup(GetTriggerUnit(),g) and IsUnitInGroup(GetSpellTargetUnit(),g) then
        call IssueImmediateOrder(GetTriggerUnit(),"stop")
    endif
    return false
endfunction

private function Init takes nothing returns nothing
    local trigger t = CreateTrigger()
    call TriggerAddCondition(t,Condition(function start))
    call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_ATTACKED)
    
    set t = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_ATTACKED)
    call TriggerAddCondition(t,Condition(function PreventAttacking))
    
    set t = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_CAST)
    call TriggerAddCondition(t,Condition(function PreventCasting))
endfunction
endlibrary

Keywords:
duel, fight, battle, war, death, bloody, vjass, mui, warhammer, mark, of, chaos, honor
Contents

Duel System (Map)

Reviews
7 November 2015 Bribe: Rejecting due to the status of this resource being "needs fix" for years. 22:18, 30th Nov 2009 TriggerHappy: Well... 0.01 timer event period is too low. Anything above 0.02 should be fine. I honestly don't...

Moderator

M

Moderator

7 November 2015
Bribe: Rejecting due to the status of this resource being "needs fix" for years.

22:18, 30th Nov 2009
TriggerHappy:

Well...

  • 0.01 timer event period is too low. Anything above 0.02 should be fine.
  • I honestly don't understand the point of this block, the condition right above it does the exact same thing. It's like a double check.
 
Level 17
Joined
Mar 17, 2009
Messages
1,349
Hahaha cool :p and about your question... well what I've got in mind is: Event - Unit is attacked, Condition: AttackingUnit() != whichUnit, Actions: you order the unit to stop...

That would sort of work... but you also need to include events such as: Event - Unit begins casting an ability...

Yet, one problem is AoE effects... :s
 
Level 10
Joined
Feb 7, 2005
Messages
409
Damn Witcher, you never fail to amaze me, this is exactly what I was looking for.... again!

Edit: not sure if it does this, but if it doesn't could you make it so that it forces surrounding units out of the circle, that if a hero leaves the circle (via mirror image or blink) it will stop the duel, and that it wont force units out of the circle after the initial force (so summons can be used).

I haven't uploaded it to my map yet so I can't say for sure whether or not it does this, but it would be greatly preferred if it did!

also could you make it so the trigger has an option that makes it so it can be initiated via a specific ability as well / instead of attacking? that way I could make a "duel!" ability that will challenge an opposing unit.

And finally, if you feel like it, it would be cool if the units forced out will surround the circle and watch the fight, and should a hero try to leave the surrounding units would push it back in.
 
Level 10
Joined
Feb 7, 2005
Messages
409
sorry lol I got ahead of myself, thinking of all the things I could use this for.

One last suggestion, can you add a condition that checks the hp / mana of the target?

Like, an option the mapper can turn on to make the spell check and see if the hp / mana of the target is within a certain percentage of the caster / attackers hp/mana. (thus ensuring a fair fight)
 
Level 6
Joined
Jun 4, 2009
Messages
91
well
JASS:
function TriggerRegisterDuelEndEvent takes trigger t returns boolean
    local integer i = 0
    loop
        exitwhen i >= total_triggers
        if triggers[i] == t then
            return false
        endif
    endloop
    set triggers[total_triggers] = t
    set total_triggers = total_triggers + 1
    return true
endfunction

it works for only one trigger !

Here is an easy event interface : http://www.hiveworkshop.com/forums/spells-569/roc-event-manager-v1-0e2-1-24-vjass-required-139849/
provides event registering as well as unregistering.
uses strings, so you can generate strings for specific events (as samples shows)

If you need any help, ask me.
 
well
JASS:
function TriggerRegisterDuelEndEvent takes trigger t returns boolean
    local integer i = 0
    loop
        exitwhen i >= total_triggers
        if triggers[i] == t then
            return false
        endif
    endloop
    set triggers[total_triggers] = t
    set total_triggers = total_triggers + 1
    return true
endfunction
it works for only one trigger !

Here is an easy event interface : http://www.hiveworkshop.com/forums/spells-569/roc-event-manager-v1-0e2-1-24-vjass-required-139849/
provides event registering as well as unregistering.
uses strings, so you can generate strings for specific events (as samples shows)

If you need any help, ask me.

well you already showed me your system and as all the other users i told you that its overcomplicated and that i didn't understand it!!

my workaround works (since the newest version 4.0) for as much triggers as you want and requires not that much code!

@ Amigurumi
the circles merge because people could use the feature that the circles take distance for moving to places they can't...

but in a further update i will include this feature with a boolean to turn it on or off
 
Level 25
Joined
Jun 5, 2008
Messages
2,572
May i ask why are you using angles when you can directly use radians? :p

Since you do this:
  • you call a function that normally returns a radian
  • you convert it to a angle
  • you further down the code convert the angle to radian
  • you use the radian

So did i miss the point of so much bj_DEGTORAD / bj_RADTODEG usage?
 
May i ask why are you using angles when you can directly use radians? :p

Since you do this:
  • you call a function that normally returns a radian
  • you convert it to a angle
  • you further down the code convert the angle to radian
  • you use the radian

So did i miss the point of so much bj_DEGTORAD / bj_RADTODEG usage?

as always you're right :thumbs_up:
UPDATE: changed that
 
What if I have a suicide spell, and it kills the enemy and me. Who wins?

the unit which was attacked to start the duel will win !

a bug! when a hero tries to leave the arena, he will get stuck there and cannot move. the hero that won wont ge healed fully, he just disappeared. weird.

He's not disapperaing he is moved to the tribune a little bit up to the arena and gets of course fully healed!!
 
Level 1
Joined
Mar 8, 2008
Messages
1
Great System The_Witcher!

I am very sorry to bother you; but I got One question:
-Is it possible to lock a certain player out? For example: If I want to make duels available for player 1 to 10, but not player 11 and 12.

I am creating RPG map, or at least sort of. Both player 11 and 12 is bosses which will require more than 1 player, but since this system makes only one person able to attack at once it will ruin the game play.

Therefore, is ist possible to make certian player not react to the system?

And yet again: Great system, it's excellent! :aa:
 
Great System The_Witcher!

I am very sorry to bother you; but I got One question:
-Is it possible to lock a certain player out? For example: If I want to make duels available for player 1 to 10, but not player 11 and 12.

I am creating RPG map, or at least sort of. Both player 11 and 12 is bosses which will require more than 1 player, but since this system makes only one person able to attack at once it will ruin the game play.

Therefore, is ist possible to make certian player not react to the system?

And yet again: Great system, it's excellent! :aa:

Well I'm glad that it helps you :D
if I got you right you want a way to let a unit join a duel and a function to wxclude players from starting a duel...

well that wouldn't be a that huge problem... maybe in next update ;D
 
Top