Duel Engine 1.10
by: emjlr3
- A dynamic, fully customizable Duel Engine.
- Supports multiple arenas and includes simple computer acceptance AI.
- Just type -duel to get the party started!
JASS:
scope DuelEngineConfig initializer Init
// Copy all Duel Engine triggers to your map (except for the Post Duel Sample, unless you want to use it
// Go through this trigger, configure the options, and enjoy!
private function Init takes nothing returns nothing
// Time in seconds before first duel can occur (>1.)
set TimeBeforeFirstDuel = 15.
// Whether a duel is forced after a duration within which there were no duels (True or False)
set ForcedDuelMatches = true
// That no duel duration in seconds after which we will force a duel (>30.)
set ForceDuelTime = 90.
// Time in seconds after a duel acceptance that it will begin (>1.)
set DuelStartTime = 10.
// The maximum time in seconds that a duel will last, after which it is forced to end
set MaxDuelTime = 120.
// Time in seconds before a duel demand is automatically canceled
set AFK_Timeout = 12.
// If you have computer heroes, this will add duel acceptance AI (True or False)
// This will accept duels 75% of the time so long as the challenging hero is no more then 2 levels higher then the challengee
set DuelAcceptanceAI = true
// This will change the camera bounds for dueling players so they only see the arena (True or False)
// Remember to change them back once the duel is over
set ChangeCameraBounds = true
// Duel Arena Setup
// Each duel arena should have a region over its entire area
// They should also have two smallers regions, one at each side, for heroes to be placed at the start of a duel
// This is where we will setup the arenas and each arenas start location
set Arenas[1] = gg_rct_Duel // This is my arena
set ArenaSides[1] = GetRectCenter(gg_rct_DuelStart1) // These are the two sides of my arena
set ArenaSides[2] = GetRectCenter(gg_rct_DuelStart2)
// If I wanted to add more, it would simply be
//set Arenas[2] = gg_rct_Name
//set ArenaSides[3] = GetRectCenter(gg_rct_Name)
//set ArenaSides[4] = GetRectCenter(gg_rct_Name)
// It is very important to keep the numbering constant like this, my selection algorithim depends on it!
// This is our total number of arenas available
set TotalArenas = 1
// When a duel is over, the winning hero is put back at their players starting location, so place them accordingly
// This is the trigger you want ran after a duel has finished
//set PostDuelTrigger = gg_trg_Name
// Uncomment the above and write the correct name if you have one
// In this trigger, the duel winner is stored as 'DuelWinner' and the loser as 'DuelLoser' (Players)
// However, if the duel was a tie, DuelIsTie will be true (as opposed to false when it is not a tie)
// Duel playes can then be refered to as DuelPlayers[1] and DuelPlayers[2], respectively
// Needed, don't touch
call DuelEngineInit_Init.execute()
endfunction
endscope
JASS:
scope DuelEngineDuels initializer Init
globals
public trigger Trig = CreateTrigger()
private integer I
private group G1 = CreateGroup()
private group G2 = CreateGroup()
private timer T
private timerdialog TD
endglobals
// Needed
private function Filter_Heroes takes nothing returns boolean
return IsUnitType(GetFilterUnit(), UNIT_TYPE_HERO) == true
endfunction
private function Hide takes nothing returns nothing
if GetLocalPlayer()==GetEnumPlayer() then
call TimerDialogDisplay(TD,false)
endif
endfunction
private function Show takes nothing returns nothing
if GetLocalPlayer()==GetEnumPlayer() then
call TimerDialogDisplay(TD,true)
endif
endfunction
// Duel Over
private function MoveBack takes nothing returns nothing
local unit u = GetEnumUnit()
local player p = GetOwningPlayer(u)
call SetUnitPosition(u,GetPlayerStartLocationX(p),GetPlayerStartLocationY(p))
if GetLocalPlayer()==p then
call SelectUnit(u,true)
call PanCameraTo(GetUnitX(u),GetUnitY(u))
endif
set u = null
endfunction
private function End takes nothing returns boolean
local unit u = GetTriggerUnit()
local group g
if GetTriggerEventId()==EVENT_PLAYER_UNIT_DEATH then
if not IsUnitInGroup(u,G1) and not IsUnitInGroup(u,G2) then
return false
endif
if GetOwningPlayer(u)==DuelPlayers[1] then
call GroupRemoveUnit(G1,u)
if CountUnitsInGroup(G1)==0 then
set DuelWinner = DuelPlayers[2]
set DuelLoser = DuelPlayers[1]
set DuelInProgress = false
endif
else
call GroupRemoveUnit(G2,u)
if CountUnitsInGroup(G2)==0 then
set DuelWinner = DuelPlayers[1]
set DuelLoser = DuelPlayers[2]
set DuelInProgress = false
endif
endif
if not DuelInProgress then
set g = CreateGroup()
call GroupEnumUnitsOfPlayer(g,DuelWinner,Condition(function Filter_Heroes))
call ForGroup(g,function MoveBack)
call ForForce(bj_FORCE_ALL_PLAYERS,function Hide)
call QuestMessageBJ( bj_FORCE_ALL_PLAYERS, bj_QUESTMESSAGE_ALWAYSHINT,GetPlayerName(DuelWinner) + " has defeated " + GetPlayerName(DuelLoser) + " in their duel." )
if PostDuelTrigger!=null then
set DuelIsTie = false
call TriggerExecute(PostDuelTrigger)
endif
call DestroyGroup(g)
set g = null
call PauseTimer(T)
set TimeSinceLastDuel = 0.
call DisableTrigger(Trig)
endif
else
set DuelInProgress = false
call QuestMessageBJ( bj_FORCE_ALL_PLAYERS, bj_QUESTMESSAGE_ALWAYSHINT,"The duel between "+GetPlayerName(DuelPlayers[1]) + " and " + GetPlayerName(DuelPlayers[2]) + " has ended in a tie." )
set g = CreateGroup()
call GroupEnumUnitsOfPlayer(g,DuelPlayers[1],Condition(function Filter_Heroes))
call ForGroup(g,function MoveBack)
call GroupClear(g)
call GroupEnumUnitsOfPlayer(g,DuelPlayers[2],Condition(function Filter_Heroes))
call ForGroup(g,function MoveBack)
call ForForce(bj_FORCE_ALL_PLAYERS,function Hide)
call DestroyGroup(g)
set g = null
if PostDuelTrigger!=null then
set DuelIsTie = true
call TriggerExecute(PostDuelTrigger)
endif
set TimeSinceLastDuel = 0.
call DisableTrigger(Trig)
endif
return false
endfunction
// Duel Begin
private function Move takes nothing returns nothing
local unit u = GetEnumUnit()
if GetWidgetLife(u)<.405 then
call ReviveHeroLoc(u,ArenaSides<i>,true)
else
call SetUnitPositionLoc(u,ArenaSides<i>)
endif
call SetWidgetLife(u,GetUnitState(u,UNIT_STATE_MAX_LIFE))
call SetUnitState(u,UNIT_STATE_MANA,GetUnitState(u,UNIT_STATE_MAX_MANA))
call UnitRemoveBuffs(u,true,true)
call UnitResetCooldown(u)
if GetLocalPlayer()==GetOwningPlayer(u) then
call SelectUnit(u,true)
call PanCameraTo(GetUnitX(u),GetUnitY(u))
endif
set u = null
endfunction
private function Start takes nothing returns nothing
local group g = CreateGroup()
local integer i = GetRandomInt(1,TotalArenas)
if not DuelInProgress then
return
endif
if GetLocalPlayer()==DuelPlayers[1] or GetLocalPlayer()==DuelPlayers[2] and ChangeCameraBounds then
call SetCameraBoundsToRect(Arenas<i>)
endif
call GroupClear(G1)
call GroupClear(G2)
set I = (i*2)-1
call GroupEnumUnitsOfPlayer(g,DuelPlayers[1],Condition(function Filter_Heroes))
call ForGroup(g,function Move)
call GroupAddGroup(g,G1)
call GroupClear(g)
set I = I+1
call GroupEnumUnitsOfPlayer(g,DuelPlayers[2],Condition(function Filter_Heroes))
call ForGroup(g,function Move)
call GroupAddGroup(g,G2)
set I = i
call TimerDialogSetTitle(TD,"Duel Time")
call TimerStart(T,MaxDuelTime,false,function End)
call EnableTrigger(Trig)
call DestroyGroup(g)
set g = null
endfunction
public function Go takes nothing returns nothing
call TimerDialogSetTitle(TD,"Duel In")
call TimerStart(T,DuelStartTime,false,function Start)
call ForForce(bj_FORCE_ALL_PLAYERS,function Show)
endfunction
private function Init takes nothing returns nothing
call TriggerAddCondition(Trig,Condition(function End))
call TriggerRegisterAnyUnitEventBJ(Trig,EVENT_PLAYER_UNIT_DEATH)
call DisableTrigger(Trig)
set T = CreateTimer()
set TD = CreateTimerDialog(T)
endfunction
endscope</i></i></i>
JASS:
scope DuelEngineForced initializer Init
globals
public trigger Trig = CreateTrigger()
real TimeSinceLastDuel = 0.
endglobals
private function Go takes nothing returns nothing
local integer i = 0
local player p1
local player p2
if not DuelInProgress then
set TimeSinceLastDuel = TimeSinceLastDuel + 5.
endif
if TimeSinceLastDuel<ForceDuelTime or DuelInProgress then
return
endif
loop
set p1 = Player(GetRandomInt(0,11))
exitwhen GetPlayerSlotState(p1)==PLAYER_SLOT_STATE_PLAYING
set i = i + 1
exitwhen i>100
endloop
if i>100 then
call DisableTrigger(Trig)
call BJDebugMsg("Forced duels is now disabled, there are not enough players left to duel.")
endif
set i = 0
loop
set p2 = Player(GetRandomInt(0,11))
exitwhen GetPlayerSlotState(p2)==PLAYER_SLOT_STATE_PLAYING and IsPlayerEnemy(p1,p2)
set i = i + 1
exitwhen i>100
endloop
if i>100 then
call DisableTrigger(Trig)
call BJDebugMsg("Forced duels is now disabled, there are not enough players left to duel.")
endif
call QuestMessageBJ( bj_FORCE_ALL_PLAYERS, bj_QUESTMESSAGE_UPDATED, "Long time no duel..." )
set DuelInProgress = true
call TriggerSleepAction(3.)
set DuelPlayers[1] = p1
set DuelPlayers[2] = p2
call QuestMessageBJ( bj_FORCE_ALL_PLAYERS, bj_QUESTMESSAGE_ALWAYSHINT, "Upcoming duel forced between " + GetPlayerName(DuelPlayers[1]) + " and " + GetPlayerName(DuelPlayers[2]) + "." )
call DuelEngineDuels_Go.execute()
endfunction
private function Init takes nothing returns nothing
call TriggerAddAction(Trig,function Go)
call TriggerRegisterTimerEvent(Trig,5.,true)
call DisableTrigger(Trig)
endfunction
endscope
JASS:
scope DuelEngineInit
globals
real TimeBeforeFirstDuel
boolean ForcedDuelMatches
real ForceDuelTime
real DuelStartTime
real MaxDuelTime
boolean DuelAcceptanceAI
boolean ChangeCameraBounds
rect array Arenas
location array ArenaSides
integer TotalArenas
trigger PostDuelTrigger = null
player DuelWinner
player DuelLoser
boolean DuelIsTie = false
real AFK_Timeout = 10.
// Needed
boolean DuelInProgress = false
endglobals
private function Setup takes nothing returns nothing
call TriggerRegisterPlayerChatEvent( DuelEnginePlayers_Trig, GetEnumPlayer(), "-duel", true )
endfunction
private function Start takes nothing returns nothing
call QuestMessageBJ( bj_FORCE_ALL_PLAYERS, bj_QUESTMESSAGE_ALWAYSHINT, "|cff00ccffDuels Available:|r
You may type |cffffcc00-duel|r now to invite opponents to a 1 on 1 duel." )
call ForForce(bj_FORCE_ALL_PLAYERS,function Setup)
if ForcedDuelMatches then
call EnableTrigger(DuelEngineForced_Trig)
endif
call DestroyTrigger(GetTriggeringTrigger())
endfunction
public function Init takes nothing returns nothing
local trigger t = CreateTrigger()
call TriggerRegisterTimerEvent(t,TimeBeforeFirstDuel,false)
call TriggerAddAction(t,function Start)
set t = null
endfunction
endscope
JASS:
scope DuelEnginePlayers initializer Init
globals
public trigger Trig = CreateTrigger()
public trigger Offer = CreateTrigger()
public trigger Take = CreateTrigger()
dialog DuelDialog
dialog TakeDialog
button array DuelButtons
private integer I
player array DuelPlayers
private timer AFK = CreateTimer()
endglobals
private function Filter_Heroes takes nothing returns boolean
return IsUnitType(GetFilterUnit(), UNIT_TYPE_HERO) == true
endfunction
// Timeout
private function Timeout takes nothing returns nothing
local force f = GetForceOfPlayer(DuelPlayers[1])
call QuestMessageBJ( f, bj_QUESTMESSAGE_ALWAYSHINT, "Why go afk right after demanding a duel....?" )
set DuelInProgress = false
call DestroyForce(f)
set f = null
endfunction
// Accept/Decline duel
private function Accept takes nothing returns nothing
call QuestMessageBJ( bj_FORCE_ALL_PLAYERS, bj_QUESTMESSAGE_ALWAYSHINT, "Upcoming duel between " + GetPlayerName(DuelPlayers[1]) + " and " + GetPlayerName(DuelPlayers[2]) + "." )
call DuelEngineDuels_Go.execute()
endfunction
private function Decline takes nothing returns nothing
local force f = GetForceOfPlayer(DuelPlayers[1])
call QuestMessageBJ( f, bj_QUESTMESSAGE_ALWAYSHINT, "Sorry, " + GetPlayerName(DuelPlayers[2]) + " didn039;t want to duel with you at this time." )
call DestroyForce(f)
set f = null
set DuelInProgress = false
endfunction
private function TakeOffer takes nothing returns boolean
call PauseTimer(AFK)
if GetClickedButton() == DuelButtons[2] then
call Decline()
else
call Accept()
endif
return false
endfunction
// Illicit duel offer
private function Offered takes nothing returns boolean
local integer i = 0
local unit u
local group g
local force f
local player p = GetTriggerPlayer()
local player take
local integer lvl
call PauseTimer(AFK)
loop
exitwhen i > 15
exitwhen GetClickedButton() == DuelButtons<i>
set i = i + 1
endloop
if i == 15 then
set f = GetForceOfPlayer(p)
call QuestMessageBJ( f, bj_QUESTMESSAGE_ALWAYSHINT, "Maybe next time..." )
set DuelInProgress = false
call DestroyForce(f)
set f = null
return false
endif
set take = Player(i)
if GetPlayerSlotState(take) != PLAYER_SLOT_STATE_PLAYING then
set f = GetForceOfPlayer(p)
call QuestMessageBJ( f, bj_QUESTMESSAGE_ALWAYSHINT, "It would seem that player has just left the game..." )
set DuelInProgress = false
call DestroyForce(f)
set f = null
return false
endif
call DialogClear( TakeDialog )
set DuelPlayers[2] = take
set g = GetUnitsOfPlayerMatching(p, Condition(function Filter_Heroes))
set u = FirstOfGroup(g)
call DialogSetMessage( TakeDialog, "A duel offer from"+ GetPlayerName(p) + " - " + GetUnitName(u) + " (" + I2S(GetHeroLevel(u)) + ")")
set DuelButtons[1] = DialogAddButton( TakeDialog, "(|cff00ccffY|r) I accept ", 039;Y039;)
set DuelButtons[2] = DialogAddButton( TakeDialog, "(|cff00ccffN|r) No thanks", 039;N039;)
call DialogDisplay( take, TakeDialog, true)
if GetPlayerController(DuelPlayers[2])==MAP_CONTROL_USER then
call TimerStart(AFK,AFK_Timeout,false,function Decline)
endif
// Acceptance AI
if DuelAcceptanceAI and GetPlayerController(DuelPlayers[2])==MAP_CONTROL_COMPUTER then
set g = GetUnitsOfPlayerMatching(DuelPlayers[1], Condition(function Filter_Heroes))
set lvl = GetHeroLevel(FirstOfGroup(g))
call DestroyGroup(g)
set g = GetUnitsOfPlayerMatching(DuelPlayers[2], Condition(function Filter_Heroes))
set u = FirstOfGroup(g)
if lvl<=GetHeroLevel(u)+2 and GetRandomReal(0.,1.)<=.75 then
call Accept()
else
call Decline()
endif
endif
call DestroyGroup(g)
set g = null
set u = null
return false
endfunction
// Try to duel someone
private function MakeList takes nothing returns nothing
local group g
local unit u
if GetPlayerSlotState(GetEnumPlayer()) == PLAYER_SLOT_STATE_PLAYING and IsPlayerEnemy(GetEnumPlayer(),GetTriggerPlayer()) then
set g = GetUnitsOfPlayerMatching(GetEnumPlayer(), Condition(function Filter_Heroes))
set u = FirstOfGroup(g)
set I = I + 1
set DuelButtons[GetPlayerId(GetEnumPlayer())] = DialogAddButton( DuelDialog, "(|cff00ccff" + I2S(I - 039;0039;) + "|r) " + GetPlayerName(GetEnumPlayer()) + " - " + GetUnitName(u) + " (" + I2S(GetHeroLevel(u)) + ")", I)
call DestroyGroup(g)
set g = null
set u = null
endif
endfunction
private function Go takes nothing returns boolean
local force f
local player p = GetTriggerPlayer()
local integer id = GetPlayerId(p)
if DuelInProgress then
set f = GetForceOfPlayer(p)
call QuestMessageBJ( f, bj_QUESTMESSAGE_ALWAYSHINT, "Sorry, a dueling demand is currently in progress.
Please try again later.")
call DestroyForce(f)
set f = null
return false
endif
set DuelInProgress = true
call DialogClear( DuelDialog )
call DialogSetMessage( DuelDialog, "Which player would you like to duel with?" )
set I = 039;0039;
call ForForce(bj_FORCE_ALL_PLAYERS, function MakeList)
set DuelButtons[15] = DialogAddButton(DuelDialog, "|cff00ccffC|rancel", 039;C039;)
call DialogDisplay(p, DuelDialog, true)
set DuelPlayers[1] = p
call TimerStart(AFK,AFK_Timeout,false,function Timeout)
return false
endfunction
private function Init takes nothing returns nothing
call TriggerAddCondition(Trig,Condition(function Go))
set DuelDialog = DialogCreate()
set TakeDialog = DialogCreate()
call TriggerRegisterDialogEvent(Offer,DuelDialog)
call TriggerAddCondition(Offer,Condition(function Offered))
call TriggerRegisterDialogEvent(Take,TakeDialog)
call TriggerAddCondition(Take,Condition(function TakeOffer))
endfunction
endscope</i>
Credits to Acehart for originally writing this system in GUI for Hulks Heroes. I re-wrote it in vJASS, optimized it, and made it configurable.
Broken down into multiple trigger dividers for easier readability.
To come:
- Team Duels
Changelog:
1.10
- Added a message when not enough players are around to force a duel.
- Fixed the map not saving bug
- Minor optimizations
- Fixed a bug where it would always update camera bounds even if turned off
- Fixed afk player bug
- Altered the duel acceptance AI a bit