• 🏆 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!

Alliance System

Requirements: DialogUtils, Table

Description:
This is a system that handles making alliances ingame.
The players can build alliances using the commands '-ally' and '-war'.
Actually it is a very simple thing, but I think there are more things necessary to make the ally system be a good ally system that appears well and does not fail under certain circumstances. Following you can see a list with the features that are useful in my opinion.

Features

  • Players can't change initial alliances when forbidden by a constant. This means they can not declare war on initial allies.
  • The messages that appear when players ally appear well (colored player names, correct english (I hope))
  • A player declares automatically war on a player he allied that did not ally him but attacks him (my experience says that players may attack each other if they allied each other. Useful for games e.g. where you conquer things by killing them)
  • The system supports numbers, color names and playernames in the commands.
  • I think most players are going to use colors: Colors have extra support. Different names for the same color is possible (like grey and gray or lightblue and light blue)
  • If a player a allies another player b there is a peace time: A period in which the player a can't declare war manually on player b
  • If allying fails, the player that tried to ally gets explicit error messages
  • Players have an ally limit that forbids building too big alliances
  • Not all players can be in the same team. There is always struggle
  • You can forbid partial alliances. If partial alliances are forbidden, a player sends an ally request to another player if he wants to ally him. If the other player confirms the request, then both players ally each other. If a player declares war on the other player, the alliance dissolves for both sides.

Testing
This has been tested intensively. Everything works as it should.
So you can trust to the system.


JASS:
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


Keywords:
Ally, Alliance, war, unally, system, strategy
Contents

Ally System v1.01 (Map)

Reviews
23:08, 17th Jun 2010 The_Reborn_Devil: The coding looks good and it's definitely useful. Status: Approved Rating: Recommended

Moderator

M

Moderator

23:08, 17th Jun 2010
The_Reborn_Devil:

The coding looks good and it's definitely useful.


Status: Approved
Rating: Recommended
 
Level 11
Joined
Apr 29, 2007
Messages
826
just one question, do you really need to put those methods inside the structs, I mean can't you just make them functions?
There's only a mere difference between methods and functions.
Methods are much better to handle than functions because they are members in those structs and not just some random function which use a struct.

Example:
JASS:
struct A
    method msgA takes nothing returns nothing
        call BJDebugMsg("A")
    endmethid
endstruct

struct B
    method msgB takes nothing returns nothing
        call BJDebugMsg("B")
    endmethod
endstruct

Now guess how it would look if you use functions.
JASS:
struct A
endstruct

struct B
endstruct

function msgA takes A data returns nothing
    call BJDebugMsg("A")
endfunction

function msgB takes B data returns nothing
    call BJDebugMsg("B")
endfunction
Only a small example of how bugging using functions instead of methods is.
The more struct members you have, the more overcrowded your code gets.
 
Level 3
Joined
May 31, 2010
Messages
35
another way to make enemies of another player is just make them enemies when one attacks the other with normal attack, only bug with this is that spells dont work
 
Level 8
Joined
Oct 1, 2010
Messages
408
I've been trying this but I'm having a small problem. When I try to set partial alliances to false I get this error when I try to save the map.

attachment.php


This happens when I use these settings
JASS:
private constant boolean ALLOW_CHANGING_INITIAL_ALLIANCES = true
private constant boolean ALLOW_PARTIAL_ALLIANCES          = false
private constant integer MAX_ALLY_COUNT                   = 1

Those were the only things I changed in the trigger.

Thank you for any help.
 
Top