Snippet PlayerAllianceAdv

Nestharus

o-o
Reaction score
84
JASS:

library PlayerAllianceAdv /* v1.0.1.4
*************************************************************************************
*
*   Manages player alliances (like allied and shared vision) between players using alliance
*   flags and counts of the different flags. The highest flag is the flag that is enabled (
*   if 2 players are marked as an enemy and they are also marked as allied, the allied flag
*   will be the one that's used). Alliances can be created and destroyed. As alliances are created, 
*   a counter is incremented for that alliance between the two players. When the alliance is destroyed,
*   the counter is decremented. If the counter reaches 0, the next flag is used. If there are no flags, 
*   a flag of 0 is used (ALLIANCE_UNALLIED).
*
*   Alliances are stored into a composite number (a number made up of numbers that are mashed together).
*
*************************************************************************************
*
*   */uses/*
*   
*       */ PlayerAlliance /*       hiveworkshop.com/forums/submissions-414/snippet-playeralliance-192941/
*
*************************************************************************************
*
*   function GetAllianceId takes integer playerId1, integer playerId2, integer allianceFlag returns PlayerAlliance
*       -   Converts playerId1, playerId2, allianceFlag into a composite number representing the alliance
*   function GetAlliance takes integer playerId1, integer playerId2 returns integer
*       -   Returns current alliance flag between players.
*
*************************************************************************************
*
*   struct PlayerAlliance extends array
*
*       -   Struct that generates alliances between players. Alliances aren't instantiated but rather
*       -   built into an id. The id is then used to increase a counter related to that alliance by 1.
*       -   It will then check the best current alliance against the one just created. If the one created
*       -   is better, it will use it (allied > enemy), otherwise it'll ignore it and possibly use later.
*       -   The id stores the player 1 id, player 2 id, and the alliance flag.
*
*       readonly PlayerAlliance previous
*           -   Used for reading the values stored in the id (returns pointers**)
*           -   alliance flag -> target player -> source player
*       readonly integer value
*           -   Used to read the value inside of a pointer
*       static method create takes player sourcePlayer, player targetPlayer, integer allianceFlag returns PlayerAlliance
*           -   Creates an alliance between a source player and a target player given an alliance flag (see
*           -   player alliance lib for flags). If the alliance passed in is higher than the best current alliance
*           -   between the two players, it will immediately be used (passive -> ally). If it isn't, it'll be ignored
*           -   until later (passive doesn't go to enemy).
*       method destroy takes nothing returns nothing
*           -   Destroys a player alliance. Will decrease the counter of the alliance between the players by 1.
*           -   If the counter was 0, it goes down to the next highest flag (allied might degrade to passive).
*
************************************************************************************/
    globals
        /*************************************************************************************
        *
        *   Converters
        *
        *       Composite number converters (to avoid math) int[16][16][8]
        *
        *************************************************************************************/
        private integer array cp    //previous composite number
        private integer array cv    //value stored at the back of the number
        
        /*************************************************************************************
        *
        *   Player Alliance
        *
        *************************************************************************************/
        private integer array aa    //strength of player alliance (how many times flag was set)
        private integer array ah    //highest flag set on player alliance
    endglobals
    
    /*************************************************************************************
    *
    *   GetAlliance
    *
    *       Retrieves the current alliance flag between two players.
    *
    *       integer pid1:           Source player of alliance
    *       integer pid2:           Target player of alliance
    *
    *       returns:                integer (alliance flag)
    *
    *************************************************************************************/
    function GetAlliance takes integer pid1, integer pid2 returns integer
        return ah[pid1*16+pid2]
    endfunction
    
    /*************************************************************************************
    *
    *   PlayerAlliance
    *
    *       Can take a composite number and loop through the values stored inside of it as well as
    *       split it apart. Also creates player alliances and destroys them. When a player alliance is
    *       created, it increases the counter between those two players for that alliance. If the flag is
    *       higher than the current alliance flag for the two players, that alliance is used. If not, the
    *       alliance is ignored. When alliances are destroyed, the method will search for flags that are
    *       being used and implement those flags (two players might be allied for a short period and then
    *       go back to passive, yes, they stack!).
    *
    *   Fields
    *
    *       readonly PlayerAlliance previous
    *       readonly integer value
    *
    *   Methods
    *
    *       static method create takes player p1, player p2, integer flag returns thistype
    *       method destroy takes nothing returns nothing
    *       method operator previous takes nothing returns thistype
    *       method operator value takes nothing returns integer
    *
    *************************************************************************************/
    struct PlayerAlliance extends array
        /*************************************************************************************
        *
        *   previous
        *
        *       Retrieves the previous node
        *       flag -> player 2 -> player 1
        *
        *************************************************************************************/
        method operator previous takes nothing returns thistype
            return cp[this]
        endmethod
        
        /*************************************************************************************
        *
        *   value
        *
        *       Retrieves the value stored in the current plyer alliance node (player ids or
        *       alliance flag).
        *
        *************************************************************************************/
        method operator value takes nothing returns integer
            return cv[this]
        endmethod
    
        /*************************************************************************************
        *
        *   create
        *
        *       Creates a player alliance and returnst he instance of it (instances are not unique!)
        *       alliances may be created and destroyed multiple times. A counter is stored in the background
        *       that tracks how many times an instance has been created/destroyed. At map init, all instances
        *       are automatically generated (all of the lists).
        *
        *       player p1:              Source ally player
        *       player p2:              Target ally player
        *       integer flag:           Alliance flag (see PlayerAlliance lib)
        *
        *       returns:                PlayerAlliance
        *
        *************************************************************************************/
        static method create takes player p1, player p2, integer flag returns thistype
            local integer pid       //player id of p1
            local integer pid2      //player id of p2
            local thistype i_1      //pid,pid2
            local thistype i_2      //pid2,pid
            local thistype i1       //this 1
            local thistype i2       //this 2
            //if the flag is >= 0 and <= 7, it was a valid alliance
            debug if (flag >= 0 and flag <= 7 and p1 != null and p2 != null and p1 != p2) then
                set pid = GetPlayerId(p1)
                set pid2 = GetPlayerId(p2)
                //retrieve instances
                set i_1 = pid*16+pid2
                set i_2 = pid2*16+pid
                set i1 = i_1*8+flag   //this is a composite number of p1, p2 and flag
                set i2 = i_2*8+flag   //this 2 is a composite number of p2, p1 and flag
                //increment instances usages by 1
                set aa[i1] = aa[i1]+1
                set aa[i2] = aa[i2]+1
                
                //check to see if the current flag is bigger than the highest flag set for the players
                if (flag > ah[i_1]) then
                    //if it is, then update the player alliances
                    set ah[i_1] = flag
                    set ah[i_2] = flag
                    call Ally(p1, p2, flag)
                endif
            debug else
                debug if (p1 == null) then
                    debug call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, "PLAYER ALLIANCE ERROR: NULL PLAYER 1 ")
                debug endif
                debug if (p2 == null) then
                    debug call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, "PLAYER ALLIANCE ERROR: NULL PLAYER 2 ")
                debug endif
                debug if (p1 == p2) then
                    debug call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, "PLAYER ALLIANCE ERROR: PLAYER 1 == PLAYER 2 ")
                debug endif
                debug if (flag < 0 or flag > 7) then
                    debug call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, "PLAYER ALLIANCE ERROR: INVALID ALLIANCE FLAG")
                debug endif
                return 0
            debug endif
            return i1
        endmethod
        
        /*************************************************************************************
        *
        *   destroy
        *
        *       Destroys a player alliance (they may be destroyed as many times as they were created).
        *
        *       returns:                nothing
        *
        *************************************************************************************/
        method destroy takes nothing returns nothing
            //modulo math doesn't need to be used to split the number apart
            //as all of the splits are stored into a list
            //flag -> player 2 -> player 1
            //values are stored into cv
            //i1,i2 are instances, pidc and pidc2 are composite pid numbers (pid*16+pid2 etc)
            //only the first node on the list is a valid composite number, the rest
            //are just pointers
            //thus pidc and pidc2 have to be built, but i1 does not
            local integer i1 = this             //i1 == this
            local integer flag = cv[i1]         //alliance flag
            local integer pidc = cp[i1]         //use initially to get prev
            local integer pidc2
            local integer pid2 = cv[pidc]       //player 2
            local integer pid = cv[cp[pidc]]    //player 1
            local integer i2
            //ensure that the instance is valid*
            debug if (i1 <= 2039 and i1 >= 8) then 
                set pidc = pid*16+pid2
                set pidc2 = pid2*16+pid
                set i2 = pidc2*8+flag       //retrieve other instance
                //make sure instantiated
                debug if (aa[i1] > 0) then
                    set aa[i1] = aa[i1]-1
                    set aa[i2] = aa[i2]-1
                    
                    //if deallocated and flag isn't 0, go to next highest flag
                    if (aa[i1] == 0 and flag > 0) then
                        //loop until found a flag that has count > 0 or no more flags
                        loop
                            set flag = flag - 1
                            //build composite number using current flag
                            set i1 = pidc*8+flag
                            //check to see if there is an alliance in the flag or if the flag is 0
                            exitwhen aa[i1] > 0 or flag == 0
                        endloop
                        //update highest flag
                        set ah[pidc] = flag
                        set ah[pidc2] = flag
                        //ally players using new flag
                        call Ally(Player(pid), Player(pid2), flag)
                    endif
                debug else
                    debug call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, "PLAYER ALLIANE ERROR: ATTEMPTED TO DESTROY NULL ALLIANCE")
                debug endif
            debug else
                debug call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, "PLAYER ALLIANE ERROR: ATTEMPTED TO DESTROY INVALID ALLIANCE")
            debug endif
        endmethod
    endstruct
    
    /*************************************************************************************
    *
    *   GetAllianceId
    *
    *       Retrieves a composite number representing alliance: (pid1*16+pid2)*8+flag
    *       The player order doesn't matter, but there are two ids that represent each
    *       alliance (either can be used).
    *
    *       integer pid1:           Source player of alliance
    *       integer pid2:           Target player of alliance
    *       integer flag:           Alliance flag (PlayerAlliance lib)
    *
    *       returns:                integer (composite number)
    *
    *************************************************************************************/
    function GetAllianceId takes integer pid1, integer pid2, integer flag returns PlayerAlliance
        debug if (pid1 >= 0 and pid1 <= 15 and pid2 >= 0 and pid2 <= 15 and flag >= 0 and flag <= 7 and pid1 != pid2) then
            return (pid1*16+pid2)*8+flag
        debug endif
        debug if (pid1 == pid2) then
            debug call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, "PLAYER ALLIANCE ID ERROR: INVALID PLAYER ALLIANCE")
        debug endif
        debug if (pid1 < 0 or pid1 > 15) then
            debug call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, "PLAYER ALLIANCE ID ERROR: INVALID PLAYER ID 1")
        debug endif
        debug if (pid2 < 0 or pid2 > 15) then
            debug call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, "PLAYER ALLIANCE ID ERROR: INVALID PLAYER ID 2")
        debug endif
        debug if (flag < 0 or flag > 15) then
            debug call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, "PLAYER ALLIANCE ID ERROR: INVALID ALLIANCE FLAG")
        debug endif
        debug return 0
    endfunction
    
    /*************************************************************************************
    *
    *   Initialization
    *
    *************************************************************************************/
    private module Inits
        private static method onInit takes nothing returns nothing
            local integer ic                //current pointer for player 1
            local integer ic2               //current pointer for player 2
            local integer i = 15            //current player 1
            local integer i2 = 14           //current player 2
            local integer f                 //current flag
            local integer v                 //current p1,p2,f
            local integer h = 254           //current p1,p2
            local integer h2 = 239          //current p2,p1
            local integer c = 2039          //current player pointer (pointers 8-2039 used by composites)
            local player p1 = Player(15)    //current player 1
            local player p2                 //current player 2
            
            /*
                Connect all of the lists up
                
                loop through player 1s
                    loop through player 2s
                        see if the players are allied and set alliance flags (for initial teams)
                        loop through flags
            */
            loop
                //store player 1 into pointer c
                set c = c + 1
                set ic = c
                set cv[c] = i
                loop
                    set p2 = Player(i2)
                    set f = 7
                    
                    //make player 2 point back to player 1
                    set c = c + 1
                    set ic2 = c
                    set cv[c] = i2
                    set cp[c] = ic
                    
                    //make player 1 point back to player 2 on another list
                    set c = c + 1
                    set cv[c] = i
                    set cp[c] = ic2
                    
                    //loop through flags and make flag point back to player 2
                    loop
                        //player 1 final list
                        set v = h*8+f
                        set cp[v] = ic2
                        set cv[v] = f
                        
                        //player 2 final list
                        set v = h2*8+f
                        set cp[v] = c
                        set cv[v] = f
                        
                        exitwhen f == 0
                        set f = f - 1
                    endloop
                    exitwhen i2 == 0
                    set i2 = i2 - 1
                    set h = h - 1
                    set h2 = h2 - 16
                endloop
                set i = i - 1
                exitwhen i == 0
                set i2 = i-1
                set p1 = Player(i)
                set h = h - 17 + i //(h-15-2+i)
                set h2 = h - 15
            endloop
        endmethod
    endmodule
    
    private struct Init extends array
        implement Inits
    endstruct
endlibrary
 

emjlr3

Change can be a good thing
Reaction score
395
how is this easier then the blizzard.j alliance functions?
 

Risen

New Member
Reaction score
4
What's up with the weird variable names? Is it an attempt to show off your code by making it unreadable or is it just an alternative to avoid making up correct names for certain variables?
 

Nestharus

o-o
Reaction score
84
Efficiency. Variables with smaller var names can be read/written to a little faster than variables with longer names. I typically use 2 chars now, the first being the category and the second being the name (cv = composite value)
 

Bribe

vJass errors are legion
Reaction score
67
Like optimizer doesn't ruin native declarations, ExecuteFunc and TriggerRegisterVariableEvent.
 
Reaction score
456
Like you optimize everything by yourself, just because you can't use native declarations (there's a workaround for every useful native I believe), ExecuteFunc (obsolete), TriggerRegisterVariableEvent (never used).
 

tooltiperror

Super Moderator
Reaction score
231
Code:
Alliances are stored into a composite number (a number made up of numbers that are mashed together).
7 is not composite, but it is made up of 5 and 2 mashed together. Did you go to elementary school? A composite number is any natural number (positive integer) that has more than two factors, in the exception of positive 1.

Like you optimize everything by yourself, just because you can't use native declarations (there's a workaround for every useful native I believe), ExecuteFunc (obsolete), TriggerRegisterVariableEvent (never used).
Yeah, quote for truth. I think there should be a standard function ([LJASS]function IsUnitAlive[/LJASS]) that can return whatever method is desired, but no one asks me.
 

Nestharus

o-o
Reaction score
84
7 is a composite number in this. It is made up of 2 numbers, and 5+2 is an impossible combination. 2 and 1 would be a possible combo. Hexadecimal colors are composite numbers. Save/load codes are (should be) composite numbers. =D

If you are using the formal math defn, then no, 7 isn't a composite number. However, for all intents and purposes, 7 is a composite number for this lib. 8 is the minimum composite number that this lib can generate and 2039 is the max. The 2031 pointers in this are represent every single possible alliance between every single possible player (excluding a player with itself). There are an additional 255 pointers that represent every combination of every player (16*15+15). The pointers 0-7 are never used because they are out of bounds (0, 1, 2, 3, 4, 5, 6, and 7 are impossible alliances).

Like you optimize everything by yourself, just because you can't use native declarations (there's a workaround for every useful native I believe), ExecuteFunc (obsolete), TriggerRegisterVariableEvent (never used).

Ahem, I use TriggerRegisterVariableEvent for custom events that can have triggers register to them >.>.


The docs don't go into this, but in the init, I generate every single possible composite number for the library and build a stack for each one (to avoid mod arithmetic when breaking the numbers apart). This library is like the best way I know of to managed stacked prioritized alliances ;D. It's useful for things like alliance systems, but it's even more useful for things like teams (teams with alliances set for the players in them and alliances between teams).
 

Sevion

The DIY Ninja
Reaction score
413
TriggerRegisterVariableEvent (never used)

Actually, there have been places where this is the only solution (or best solution).

Anyhow, I still don't see how this is useful.

I've never seen any map have any use for this type of thing.
 

Nestharus

o-o
Reaction score
84
You'll see its usefulness when I complete my next script. You guys were talking about how horrible and useless BigInt was, but look at it now.


Here is a thought: let's say that 2 players are on a team, meaning that they are allied. One of these players is also on another team that is passive to the first team.

Team 1 passive to Team 2
Team 1- player 1, player 2
Team 2- player 2, player 3

Let's say that player 2 leaves team 1. Using this system, player 1 and player 2 are now passive*. If player 2 left team 2, player 1 and player 2 would be enemies.

Let's give another example. Let's say that normally player 1 and player 2 are neutral to each other. They decide to help each other out, so they become allies. The ally flag is destroyed, so they go back to neutral.

This system makes all of the above very painless and very easy =).

This system was created with teams in mind (precursor to my Team Manager system).

Also notice how allied takes precedence over passive.
enemy -> enemy w/ sight -> passive -> passive w/ sight -> ally -> ally w/ sight -> ally w/ control -> ally w/ adv contol


Enemy is flag 0, which also represents no alliance flags being set. Having no flags at all makes 2 players enemies no? : P

If players were to be allies and the passive flag was set to true, they'd still be allies. If the alliance was destroyed, they'd go down to passive rather than down to enemies. Make sense? : )

Because each instance represents the player ids and the flag being set, you don't have to store the instances. So long as you know the 2 players and the alliance flag, you can retrieve the instance.

edit
Fixed a mess up that made highest set flag retrieval impossible and added a new function to retrieve highest flag set.

edit
Initialization doctored up a bit (I really studied the numbers to make sure all of the math was right because TeamManager was exploding with the same algorithm ^)^, apparently this onInit was bugged).

GetAllianceId now returns a PlayerAlliance rather than an integer (had to typecast every time, so figured returning a PlayerAlliance was smerter).
 

Narks

Vastly intelligent whale-like being from the stars
Reaction score
90
If you go to the length of manually shortening all your variable names, you might as well use optimizer, then use an MPQ editor to modify the war3map.j (or was it script.j? I can't remember) to fix the ruined natives.

ExecuteFunc is obsolete, TriggerRegisterVariableEvent is obscure.
 

Risen

New Member
Reaction score
4
Efficiency. Variables with smaller var names can be read/written to a little faster than variables with longer names. I typically use 2 chars now, the first being the category and the second being the name (cv = composite value)

JassHelper automatically makes your variable names into names like "SYSTEMNAME__VARNAME" so I don't think having 20 characters in place of 22 characters is much different (on the nanosecond level.)

I think it's just a poor excuse to show off your code, seriously, the gain on the efficiency is nothing.

Proof:
JASS:
 //globals from PlayerAllianceAdv:
constant boolean LIBRARY_PlayerAllianceAdv=true
        
integer array PlayerAllianceAdv___cp
integer array PlayerAllianceAdv___cv
        
        
integer array PlayerAllianceAdv___aa
integer array PlayerAllianceAdv___ah
//endglobals from PlayerAllianceAdv

//JASSHelper struct globals:
constant integer si__PlayerAlliance=1
constant integer si__PlayerAllianceAdv___Init=2

endglobals
JASS:
//library PlayerAllianceAdv:
    
    
    function GetAlliance takes integer pid1,integer pid2 returns integer
        return PlayerAllianceAdv___ah[pid1 * 16 + pid2]
    endfunction
    
    
        
        function s__PlayerAlliance__get_previous takes integer this returns integer
            return PlayerAllianceAdv___cp[this]
        endfunction
        
        
        function s__PlayerAlliance__get_value takes integer this returns integer
            return PlayerAllianceAdv___cv[this]
        endfunction

Stop the silly name-efficiency convention, it's stupid. Find another way to brag about your code instead of intentionally making it unreadable.
 

Nestharus

o-o
Reaction score
84
Stop the silly name-efficiency convention, it's stupid. Find another way to brag about your code instead of intentionally making it unreadable.

I already explained why I do it. Me saying one thing doesn't magically mean another ^_^. I got into the habit of short variable names when I wrote TimerQueue, and in that one I went so far as to rename the lib Tq =).
 

tooltiperror

Super Moderator
Reaction score
231
I agree with everything Risen said.

Shame on Bribe for approving unreadable code on THW.
 

Nestharus

o-o
Reaction score
84
Well, I'm never going to change it because changing it would be, in my opinion, crappier. That's how I code in JASS now, and I started that practice as I keep saying with Tq when I saw it double in speed with short var names.
 

Risen

New Member
Reaction score
4
You are helpless.

How do you intend on others learning from your code? Exactly; you don't.

With Optimizer, the variable names only reach > 4 characters if the map has at least 20,000 lines of code, so most variable names are generally around 2-3 characters. You're telling me that it's a good idea to stick with piece of shit names like av or ag (compiled to PlayerAllianceAdx__av / PlayerAllianceAdx__ag) when the optimizer renames them into shorter names anyways? I don't care how efficient your system is before someone uses the optimizer on it, nobody releases any maps without that optimizer anyways so what's the point?

So stop the bullshit variable names, if you want people to think you're good at JASS coding (honestly, nobody really cares,) then release a system that's actually complicated, with honest, readable coding.
 
General chit-chat
Help Users
  • No one is chatting at the moment.
  • The Helper The Helper:
    Actually I was just playing with having some kind of mention of the food forum and recipes on the main page to test and see if it would engage some of those people to post something. It is just weird to get so much traffic and no engagement
  • The Helper The Helper:
    So what it really is me trying to implement some kind of better site navigation not change the whole theme of the site
  • Varine Varine:
    How can you tell the difference between real traffic and indexing or AI generation bots?
  • The Helper The Helper:
    The bots will show up as users online in the forum software but they do not show up in my stats tracking. I am sure there are bots in the stats but the way alot of the bots treat the site do not show up on the stats
  • Varine Varine:
    I want to build a filtration system for my 3d printer, and that shit is so much more complicated than I thought it would be
  • Varine Varine:
    Apparently ABS emits styrene particulates which can be like .2 micrometers, which idk if the VOC detectors I have can even catch that
  • Varine Varine:
    Anyway I need to get some of those sensors and two air pressure sensors installed before an after the filters, which I need to figure out how to calculate the necessary pressure for and I have yet to find anything that tells me how to actually do that, just the cfm ratings
  • Varine Varine:
    And then I have to set up an arduino board to read those sensors, which I also don't know very much about but I have a whole bunch of crash course things for that
  • Varine Varine:
    These sensors are also a lot more than I thought they would be. Like 5 to 10 each, idk why but I assumed they would be like 2 dollars
  • Varine Varine:
    Another issue I'm learning is that a lot of the air quality sensors don't work at very high ambient temperatures. I'm planning on heating this enclosure to like 60C or so, and that's the upper limit of their functionality
  • Varine Varine:
    Although I don't know if I need to actually actively heat it or just let the plate and hotend bring the ambient temp to whatever it will, but even then I need to figure out an exfiltration for hot air. I think I kind of know what to do but it's still fucking confusing
  • The Helper The Helper:
    Maybe you could find some of that information from AC tech - like how they detect freon and such
  • Varine Varine:
    That's mostly what I've been looking at
  • Varine Varine:
    I don't think I'm dealing with quite the same pressures though, at the very least its a significantly smaller system. For the time being I'm just going to put together a quick scrubby box though and hope it works good enough to not make my house toxic
  • Varine Varine:
    I mean I don't use this enough to pose any significant danger I don't think, but I would still rather not be throwing styrene all over the air
  • The Helper The Helper:
    New dessert added to recipes Southern Pecan Praline Cake https://www.thehelper.net/threads/recipe-southern-pecan-praline-cake.193555/
  • The Helper The Helper:
    Another bot invasion 493 members online most of them bots that do not show up on stats
  • Varine Varine:
    I'm looking at a solid 378 guests, but 3 members. Of which two are me and VSNES. The third is unlisted, which makes me think its a ghost.
    +1
  • The Helper The Helper:
    Some members choose invisibility mode
    +1
  • The Helper The Helper:
    I bitch about Xenforo sometimes but it really is full featured you just have to really know what you are doing to get the most out of it.
  • The Helper The Helper:
    It is just not easy to fix styles and customize but it definitely can be done
  • The Helper The Helper:
    I do know this - xenforo dropped the ball by not keeping the vbulletin reputation comments as a feature. The loss of the Reputation comments data when we switched to Xenforo really was the death knell for the site when it came to all the users that left. I know I missed it so much and I got way less interested in the site when that feature was gone and I run the site.
  • Blackveiled Blackveiled:
    People love rep, lol
    +1

      The Helper Discord

      Staff online

      Members online

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top