library AllianceSystem initializer Init requires DialogUtils, Table
//===========================================================================
// Information:
//==============
//
// I thought clearly about the things I wanted to make and that'd be good for an
// ally system before I made this;
// - You can't change initial alliances when permitted
// - A player is auto-warred if he attacks a player he did not ally that allied him.
// - Supports different ways of allying: player numbers, player names and color names
// - Supports different names for the same color (like lightblue and light blue or grey and gray)
// - If a player a allies another player b there is a peace time: Player a can't
// declare war on player b during that time.
// - If allying fails, exact information why it fails are displayed to the player who tried to ally.
// - Players have an ally-limit that permits making too big forces.
// - Not all players can be allied with each other.
//
//===========================================================================
// Implementation:
//===============
//
// 1. Download a tool called 'JassNewGen', and unpack it somewhere. You need that
// edit to use this tool. The 'JassNewGen' is used very commonly and offers other
// nice features. You can find it at:
// [url]http://www.wc3c.net/showthread.php?t=90999[/url]
//
// 2. Make a new trigger, and convert it to custom text. Insert everything
// the library contains into that trigger.
//
// 3. Download a system called 'Table' from this link:
// [url]http://www.wc3c.net/showthread.php?t=101246[/url]
// Do the same installation stuff for 'Table' as for this system.
//
// 4. Save your map and enjoy :-)
//
//===========================================================================
globals
// If this is set to false, players can't declare war on players
// they had an initial alliance with.
private constant boolean ALLOW_CHANGING_INITIAL_ALLIANCES = true
// If you do not allow partial alliance and a player a wants to ally a player b,
// player b gets a request in form of a dialog (Do you want to ally bla?).
// If player b rejects the request, he is not allied.
// When they built an alliance and one of the player declares war, the
// other player declares war automatically, too.
// This permits building partial alliances; Only full alliances are
// allowed if this is set to false.
private constant boolean ALLOW_PARTIAL_ALLIANCES = true
// Players can't have more than MAX_ALLY_COUNT allies and they can't
// ally everybody. So if there are just two players left, they can't ally
// each other.
private constant integer MAX_ALLY_COUNT = 2
// If a player allies another player he has to wait PEACE_TIME seconds in
// order to declare war.
private constant real PEACE_TIME = 120.
endglobals
globals
private trigger AllyTrigger = CreateTrigger()
private trigger WarTrigger = CreateTrigger()
private trigger AttackTrigger = CreateTrigger()
private trigger LeaveTrigger = CreateTrigger()
private timer array Wartimer
private boolean array DeclareWarOnExpire
private boolean array InitialAlly
private integer array AllyCount
private string array ColorString
private integer Playercount = 0
private HandleTable TimerData
private constant integer SIZE = 12
endglobals
// I use colorcodes of a blizz map.
// The ARGB Playercolors totally suck. Vexorian should update them,
// they don't look natural. Teal e.g looks like green.
private function ColorPlayer takes player p returns string
return ColorString[GetPlayerId(p)]+GetPlayerName(p)+"|r"
endfunction
private struct UserMethods
// These both methods are used if ALLOW_PARTIAL_ALLIANCES is true
static method onAlly takes player allying, player allied returns nothing
call DisplayTextToPlayer(GetLocalPlayer(),0,0,ColorPlayer(allying)+" has allied "+ColorPlayer(allied))
endmethod
static method onWar takes player warring, player warred returns nothing
call DisplayTextToPlayer(GetLocalPlayer(),0,0,ColorPlayer(warring)+" has declared war on "+ColorPlayer(warred))
endmethod
// These both methods are used if ALLOW_PARTIAL_ALLIANCES is false
static method onAllyEx takes player allying, player allied returns nothing
call DisplayTextToPlayer(GetLocalPlayer(),0,0,ColorPlayer(allying)+" and "+ColorPlayer(allied)+" have built an alliance.")
endmethod
static method onWarEx takes player warring, player warred returns nothing
call DisplayTextToPlayer(GetLocalPlayer(),0,0,"The alliance between "+ColorPlayer(warring)+" and "+ColorPlayer(warred)+" has dissolved.")
endmethod
endstruct
// Makes a single index from two indexes, where a has a certain size.
// We need this to store Data that are relevant between two players.
// We can turn the index back to the two integers the index was created of.
private function Index2D takes integer a, integer b returns integer
return a + b*SIZE
endfunction
private function IndexA takes integer Matrize returns integer
return ModuloInteger(Matrize,SIZE)
endfunction
private function IndexB takes integer Matrize returns integer
return (Matrize/SIZE)
endfunction
function GetLabelPlayer takes string label returns player
local integer i = 0
// integers
if S2I(label) != 0 then
if S2I(label)-1 < 12 and S2I(label)-1 > 0 then
return Player(S2I(label)-1)
endif
endif
set label = StringCase(label,false)
// Colors
if label == "red" then
return Player(0)
elseif label == "blue" then
return Player(1)
elseif label == "teal" then
return Player(2)
elseif label == "purple" then
return Player(3)
elseif label == "yellow" then
return Player(4)
elseif label == "orange" then
return Player(5)
elseif label == "green" then
return Player(6)
elseif label == "pink" then
return Player(7)
elseif label == "grey" then
return Player(8)
elseif label == "gray" then
return Player(8)
elseif label == "lightblue" then
return Player(9)
elseif label == "light blue" then
return Player(9)
elseif label == "light-blue" then
return Player(9)
elseif label == "darkgreen" then
return Player(10)
elseif label == "dark green" then
return Player(10)
elseif label == "dark-green" then
return Player(10)
elseif label == "brown" then
return Player(11)
endif
//Playernames
loop
exitwhen i > 11
if label == StringCase(GetPlayerName(Player(i)),false) then
return Player(i)
endif
set i = i + 1
endloop
return null
endfunction
private function onWartimerExpire takes nothing returns nothing
local timer t = GetExpiredTimer()
local integer index = TimerData[t]
if DeclareWarOnExpire[index] then
// Ahahaha
// Mwaahahaha I'm a genius. Really.
call SetAlliance.execute(Player(IndexA(index)),Player(IndexB(index)),false)
endif
endfunction
function interface SetAllianceInterface takes player from, player towards, boolean ally returns nothing
// This is a very important function. It sets alliance conditions, starts the wartimers,
// handles the allycount and the usermethods.
function SetAlliance takes player from, player towards, boolean ally returns nothing
local integer index = Index2D(GetPlayerId(from),GetPlayerId(towards))
if ally then
call SetPlayerAlliance(from,towards,ALLIANCE_PASSIVE,true)
call SetPlayerAlliance(from,towards,ALLIANCE_SHARED_VISION,true)
set AllyCount[GetPlayerId(from)] = AllyCount[GetPlayerId(from)] + 1
static if ALLOW_PARTIAL_ALLIANCES then
call UserMethods.onAlly(from,towards)
if Wartimer[index] == null then
set Wartimer[index] = CreateTimer()
set TimerData[Wartimer[index]] = index
endif
set DeclareWarOnExpire[index] = false
// Restarting the timer = Refreshing period.
call TimerStart(Wartimer[index],PEACE_TIME,false,function onWartimerExpire)
elseif GetPlayerAlliance(towards,from,ALLIANCE_PASSIVE) == true then
call UserMethods.onAllyEx(from,towards)
endif
else
call SetPlayerAlliance(from,towards,ALLIANCE_PASSIVE,false)
call SetPlayerAlliance(from,towards,ALLIANCE_SHARED_VISION,false)
set AllyCount[GetPlayerId(from)] = AllyCount[GetPlayerId(from)] - 1
static if ALLOW_PARTIAL_ALLIANCES then
call UserMethods.onWar(from,towards)
else
set AllyCount[GetPlayerId(towards)] = AllyCount[GetPlayerId(towards)] - 1
call SetPlayerAlliance(towards,from,ALLIANCE_PASSIVE,false)
call SetPlayerAlliance(towards,from,ALLIANCE_SHARED_VISION,false)
call UserMethods.onWarEx(from,towards)
endif
endif
endfunction
function ConfirmAlly takes integer value, Dialog confirm returns nothing
local player p2 = Player(confirm.GetData())
if value == 1 then
call SetAlliance(GetClickingPlayer(),p2,true)
call SetAlliance(p2,GetClickingPlayer(),true)
else
call DisplayTextToPlayer(p2,0,0,ColorPlayer(GetClickingPlayer())+" has refused your request.")
endif
endfunction
// OnAlly and onWar are a mess, I know, but all the safety things are neccessary and useful
private function OnAlly takes nothing returns nothing
local player ally = GetLabelPlayer(SubString(GetEventPlayerChatString(),6,StringLength(GetEventPlayerChatString())))
local integer index = 0
local Dialog D
set index = Index2D(GetPlayerId(GetTriggerPlayer()),GetPlayerId(ally))
if ally == null then
call DisplayTextToPlayer(GetTriggerPlayer(),0,0,"|cffff0000Error:|r This player does not exist.")
return
elseif AllyCount[GetPlayerId(GetTriggerPlayer())] == MAX_ALLY_COUNT then
call DisplayTextToPlayer(GetTriggerPlayer(),0,0,"|cffff0000Error:|r You can't have more than "+I2S(MAX_ALLY_COUNT)+" allies.")
return
elseif ally == GetTriggerPlayer() then
call DisplayTextToPlayer(GetTriggerPlayer(),0,0,"|cffff0000Error:|r You can't ally yourself.")
return
// May seem strange that I use -2.
// We have to subtract one because the allying player is not valid.
// And another one because We are GOING to have everybody-allied who if it was -1
// 4 Players. A player may have 2 allies. If red has three allies, he allied everybody.
elseif AllyCount[GetPlayerId(GetTriggerPlayer())] >= Playercount-2 then
call DisplayTextToPlayer(GetTriggerPlayer(),0,0,"|cffff0000Error:|r You can't ally everybody.")
return
elseif GetPlayerSlotState(ally) != PLAYER_SLOT_STATE_PLAYING then
call DisplayTextToPlayer(GetTriggerPlayer(),0,0,"|cffff0000Error:|r "+ColorPlayer(ally)+" is not playing.")
return
elseif GetPlayerController(ally) != MAP_CONTROL_USER then
call DisplayTextToPlayer(GetTriggerPlayer(),0,0,"|cffff0000Error:|r You can't ally Computer Players.")
return
elseif GetPlayerAlliance(GetTriggerPlayer(),ally,ALLIANCE_PASSIVE) == true then
call DisplayTextToPlayer(GetTriggerPlayer(),0,0,"|cffff0000Error:|r You already allied "+ColorPlayer(ally)+".")
return
endif
static if ALLOW_PARTIAL_ALLIANCES then
call SetAlliance(GetTriggerPlayer(),ally,true)
else
set D = Dialog.create("Would you like to ally "+ColorPlayer(GetTriggerPlayer())+"?",ConfirmAlly,0.)
call D.AddOption("Yes",1)
call D.AddOption("No",2)
call D.SetData(GetPlayerId(ally))
call D.ShowFor(ally)
endif
endfunction
private function OnWar takes nothing returns nothing
local player ally
local integer index
if SubString(GetEventPlayerChatString(),0,4) == "-war" then
set ally = GetLabelPlayer(SubString(GetEventPlayerChatString(),5,StringLength(GetEventPlayerChatString())))
else
set ally = GetLabelPlayer(SubString(GetEventPlayerChatString(),8,StringLength(GetEventPlayerChatString())))
endif
set index = Index2D(GetPlayerId(GetTriggerPlayer()),GetPlayerId(ally))
if ally == null then
call DisplayTextToPlayer(GetTriggerPlayer(),0,0,"|cffff0000Error:|r This player does not exist.")
return
elseif ally == GetTriggerPlayer() then
call DisplayTextToPlayer(GetTriggerPlayer(),0,0,"|cffff0000Error:|r You can't declare war on yourself.")
return
elseif GetPlayerSlotState(ally) != PLAYER_SLOT_STATE_PLAYING then
call DisplayTextToPlayer(GetTriggerPlayer(),0,0,"|cffff0000Error:|r "+ColorPlayer(ally)+" is not playing.")
return
elseif InitialAlly[index] == true then
call DisplayTextToPlayer(GetTriggerPlayer(),0,0,"|cffff0000Error:|r Changing initial alliances is not allowed.")
return
elseif GetPlayerAlliance(GetTriggerPlayer(),ally,ALLIANCE_PASSIVE) == false then
call DisplayTextToPlayer(GetTriggerPlayer(),0,0,"|cffff0000Error:|r You are not an ally of "+ColorPlayer(ally)+"s.")
return
elseif TimerGetRemaining(Wartimer[index]) > 0. then
set DeclareWarOnExpire[index] = true
call DisplayTextToPlayer(GetTriggerPlayer(),0,0,"|cffff0000Error:|r You can't declare war on "+ColorPlayer(ally)+" yet. War will be automatically declared in "+I2S(R2I(TimerGetRemaining(Wartimer[index])))+" seconds.")
return
endif
call SetAlliance(GetTriggerPlayer(),ally,false)
endfunction
private function OnAttack takes nothing returns nothing
local integer index
local player p1 = GetOwningPlayer(GetTriggerUnit())
local player p2 = GetOwningPlayer(GetAttacker())
if GetPlayerAlliance(p1,p2,ALLIANCE_PASSIVE) == true then
if GetPlayerAlliance(p2,p1,ALLIANCE_PASSIVE) == false then
set index = Index2D(GetPlayerId(p1),GetPlayerId(p2))
set DeclareWarOnExpire[index] = false
// Refresh the timeout.
call PauseTimer(Wartimer[index])
call SetAlliance(p1,p2,false)
endif
endif
endfunction
// This prevents that everybody is in a team.
// It's a slow function but it's fired rarely.
// Speed bonuses are neglegible
private function OnLeave takes nothing returns nothing
local integer i = 0
local integer i2
local integer allyCount
local integer array allies
local integer random
set Playercount = Playercount - 1
loop
exitwhen i > 11
// Player i allied everybody. BÖP!
if GetPlayerAlliance(Player(i),GetTriggerPlayer(),ALLIANCE_PASSIVE) == true then
set AllyCount[i] = AllyCount[i] - 1
endif
if AllyCount[i] == Playercount-1 then
set i2 = 0
set allyCount = 0
loop
exitwhen i2 > 11
if GetPlayerAlliance(Player(i),Player(i2),ALLIANCE_PASSIVE) == true then
// We store the allies to pick a random ally and make him be an enemy
set allyCount = allyCount + 1
set allies[allyCount] = i2
endif
set i2 = i2 + 1
endloop
set random = allies[GetRandomInt(1,allyCount)]
call DisplayTextToPlayer(GetLocalPlayer(),0,0,ColorPlayer(Player(i))+" declares automatically war on "+ColorPlayer(Player(random))+", because "+ColorPlayer(Player(i))+" allied everybody.")
call SetAlliance(Player(i),Player(random),false)
endif
set i = i + 1
endloop
endfunction
private function Init takes nothing returns nothing
local integer i = 0
local integer i2
local integer index
loop
exitwhen i > 11
if GetPlayerSlotState(Player(i)) == PLAYER_SLOT_STATE_PLAYING and GetPlayerController(Player(i)) == MAP_CONTROL_USER then
set Playercount = Playercount + 1
call TriggerRegisterPlayerChatEvent( AllyTrigger , Player(i), "-ally" , false )
call TriggerRegisterPlayerChatEvent( WarTrigger , Player(i), "-unally", false )
call TriggerRegisterPlayerChatEvent( WarTrigger , Player(i), "-war" , false )
call TriggerRegisterPlayerUnitEvent( AttackTrigger, Player(i), EVENT_PLAYER_UNIT_ATTACKED, null)
call TriggerRegisterPlayerEvent ( LeaveTrigger , Player(i), EVENT_PLAYER_LEAVE)
call TriggerRegisterPlayerEvent ( LeaveTrigger , Player(i), EVENT_PLAYER_DEFEAT)
set i2 = 0
loop
exitwhen i2 > 11
if i2 != i then
if IsPlayerAlly(Player(i),Player(i2)) then
set InitialAlly[Index2D(i,i2)] = true
set Allies[i] = Allies[i] + 1
endif
endif
set i2 = i2 + 1
endloop
endif
set i = i + 1
endloop
call TriggerAddAction(AllyTrigger,function OnAlly)
call TriggerAddAction(WarTrigger,function OnWar)
call TriggerAddAction(LeaveTrigger,function OnLeave)
static if ALLOW_PARTIAL_ALLIANCES then
call TriggerAddAction(AttackTrigger,function OnAttack)
else
call DestroyTrigger(AttackTrigger)
endif
set TimerData = Table.create()
// PlayerColors
set ColorString[0] = "|cffff0000" //red
set ColorString[1] = "|cff0000ff" //blue
set ColorString[2] = "|cff00f5ff" //Teal
set ColorString[3] = "|cff551A8B" //Purple
set ColorString[4] = "|cffffff00" //Yellow
set ColorString[5] = "|cffEE9A00" //Orange
set ColorString[6] = "|cff00CD00" //Green
set ColorString[7] = "|cffFF69B4" //Pink
set ColorString[8] = "|cffC0C0C0" //Gray
set ColorString[9] = "|cffB0E2FF" //Light Blue
set ColorString[10] = "|cff006400" //Dark Green
set ColorString[11] = "|cff8B4513" //Brown
endfunction
endlibrary