Welcome to the Achaea Forums! Please be sure to read the Forum Rules.

Multiclass Coding Structure?

JacenJacen Posts: 2,187 @@ - Legendary Achaean
So I'm playing again, and coding-wise, I'm starting mostly from scratch - I never coded much for dragon, and I stopped playing soon after I went from monk to serpent.

I'm curious about what the coders here have come up with as far as your scripts for multiclass. I'm not asking for your dragon aliases, but more like flow and structure of your scripts that deal with multiclass. For example, before I went dormant, I coded my hotkeys to limb attacks. Hotkey NUMPAD_4 would call a generic left arm attack function. That function would check my class, then either call a dragon or monk left arm attack function, which then sent the commands to the server. 

That setup was very function heavy, but it kept me from having multiple hotkeys for the same keys. Performance-wise, I'm not sure which is better: Groups of identical hotkeys/aliases/triggers that are turned off/on on class change, or a single group of each that checks your class whenever activated.

Anyways, I'm interested to hear what solutions you've come up with!
image

Comments

  • AustereAustere TennesseePosts: 1,904 @@ - Legendary Achaean
    I use groups that are enabled and disabled on class change. Triggers, aliases, gui displays. ...everything changes over.  I do have a tendency to reuse alias names for abilities that function similarly. For example,  ham is hammer tattoo and raze depending on class.
  • AntoniusAntonius Posts: 3,879 @@ - Legendary Achaean
    I'll post some code when I get home, but here's a brief rundown of how my stuff is structured (it's very, very code intensive):

    I use aliases, rather than hotkeys, for my offense, but the general gist of it is:
    aliases to select affliction combinations (actually one alias, it just recognises a lot of different inputs and works out the afflictions accordingly)
    aliases to select targetted limb
    a single attack alias (atk) with an optional modifier (differs depending on class)

    The attack alias calls a core offense function that does four main things:
    1. generates a list of generic commands (stand, summon mount, vault, handle parrying, etc) that aren't dependent on class
    2. calls a class-specific offense function that generates and returns the list of commands to run for the actual attack (each class has its own namespace, so these functions will be: antonius.serpent.attack, antonius.depthswalker.attack, etc)
    3. creates an in-game alias with all of those commands (controlled by a script - only actually updates the alias is the set of commands has changed)
    4. queues said in-game alias (controlled by a script - only queues if not already queued, will replace anything already queued that uses balance)

    Then in a fight, I'll be doing things like:
    'curkal' - set afflictions to use to paralysis and asthma
    'atk' - tries to attack; if I'm a dual cutting Paladin or Runewarden, this will doubleslash with curare and kalmia; if I'm a Serpent, it will doublestab with curare and kalmia; if I'm alchemist, will truewrack with paralysis and asthma; if I'm a Bard it will (try to) find a venom and song combination that allows me to give those afflictions; etc
    'atkd' - tries to attack, plus do whatever the d modifier does; if I'm Depthswalker, it's attack (reap) + attune degeneration; if I'm Paladin, it's attack (depends on weaponmastery spec) plus lay rite of demons; etc
    Iniar
  • BannBann Posts: 339 ✭✭✭ - Distinguished
    If you're talking about sheer efficiency, having a bunch of stuff that turns off/on depending on class is probably going to be the 'fastest'. If you're looking for something that might be easier for you, having a function that checks your class and then does whatever would probably be easiest, because all of your shit will be in that one place and if something breaks, bam. You know exactly where to go and depending on your class where it probably broke at. Also don't have to make 15 different keybinds/aliases every time, you just keep the same ones and add/remove stuff to the function.

  • NazihkNazihk Posts: 486 ✭✭✭✭ - Eminent
    Realistically speaking, any modern PC should be able to handle the additional overhead of the class-check-functions without any sort of tangible drop in performance. If adding a variable lookup and some basic selection logic to your attacks causes you any sort of noticeable problem, something is wrong.

    So just roll with whatever you like, really. 
    Bann
  • OmorOmor Posts: 756 ✭✭✭✭ - Eminent
    I use an if-then that checks my class with gmcp. It's definitely not the fastest, because it has to read the gmcp data, but the slowdown is small enough to not effect me.
    Omor Ceberek - Targossas

    got gud
  • ZahanZahan Posts: 187 ✭✭✭ - Distinguished
    Omor said:
    I use an if-then that checks my class with gmcp. It's definitely not the fastest, because it has to read the gmcp data, but the slowdown is small enough to not effect me.

    Gmcp data is read once, when it's received, then it's stuck into a variable.  All you're doing [probably] is checking that variable that is already set.  If you localize that gmcp variable inside your function, it'll go even faster, but just checking lua variables should never slow it down to any noticeable degree without extreme profiling.

    Unless you're sending a gmcp packet on every function, waiting for a return packet, evaluating that packet, then acting accordingly.  You'd definitely have lag then, just in ping for the gmcp request/reception.  

    Are you noticing a lag in processing from your functions?
    Click here for Nexus packages
    Currently available: Abs, Cnote, Keepalive, Lootpet, Mapmod
  • OmorOmor Posts: 756 ✭✭✭✭ - Eminent
    @Zahan I only understood some of those words. I was mainly referencing my bashing, which is.. who cares. Heh. I'll copy my bashing button here though.

    if gmcp.Char.Status.class == "Runewarden" then
    send("queue add eqbal stand;queue add eqbal doublewhirl;onslaught;collide")
    elseif gmcp.Char.Status.class == "Monk" then
    send("queue add eqbal stand;queue add eqbal combo sdk ucp ucp;sbp;tnk")
    elseif gmcp.Char.Status.class == "Depthswalker" then
    send("queue add eqbal stand;queue add eqbal shadow reap;shadow drain;shadow lash")
    elseif gmcp.Char.Status.class == "Silver Dragon" then
    send("queue add eqbal stand;queue add eqbal gut;queue add eqbal incantation;clearqueue all;dragonspark;overwhelm")
    end

    Omor Ceberek - Targossas

    got gud
  • AtalkezAtalkez Posts: 3,564 @@ - Legendary Achaean
    Doing a gmcp check on the class like that is no different than checking a variable, was what he was saying. Essentially, that's exactly what you're doing. Instead, though, the variable is stored globally on gmcp than just being a system local variable.

    That's what I personally use in all of my stuff. The only time I notice any real performance hit is when I'm spamming the hell out of something, which is honestly to be expected.


    You hug Aurora compassionately.
    Zahan
  • OmorOmor Posts: 756 ✭✭✭✭ - Eminent
    Ah, thanks Atalkez. The buzzwords and whatnot go over my head, usually. Like I've yet to figure out exactly what a 'function' is.
    Omor Ceberek - Targossas

    got gud
  • VessilVessil Posts: 51 ✭✭ - Stalwart
    edited November 2016
    If you're looking to get into best practices, if you're using a global(like those checks of the gmcp variables) many times it is best to pop it into a local variable. I don't know how many uses of that variable makes it more efficient to use a local variable. I do this with gmcp things a lot just because of the way you can shorten some code by replacing 15 calls to "gmcp.Char.Status.class" with a local called "class". For instance, I'd rewrite that alias the following way:


    local class = gmcp.Char.Status.class
    if class == "Runewarden" then
    send("queue add eqbal stand;queue add eqbal doublewhirl;onslaught;collide")
    elseif class == "Monk" then
    send("queue add eqbal stand;queue add eqbal combo sdk ucp ucp;sbp;tnk")
    elseif class == "Depthswalker" then
    send("queue add eqbal stand;queue add eqbal shadow reap;shadow drain;shadow lash")
    elseif class == "Silver Dragon" then
    send("queue add eqbal stand;queue add eqbal gut;queue add eqbal incantation;clearqueue all;dragonspark;overwhelm")
    end

    (Reference: http://stackoverflow.com/questions/9132288/why-are-local-variables-accessed-faster-than-global-variables-in-lua)

    As @Nazihk says though, in Mudlet if you're not doing something many times a second, a relatively modern computer isn't going to see issues crop up with inefficient code, you just have to watch out for things where you're doing a complicated thing many times a second, like if you're doing some sort of for loop 100+ times and you do a call to expandAlias instead of using a function, or if you're doing something that takes up a large amount of memory, like keeping a table of player information that has every player you have ever seen on WHO along with a bunch of text information that you're storing about them. Before I moved to an SSD, I was having seconds of lag when I would save the table that I used to hold player info, since it was 1000+ entries long and each entry also had their full description and basically most of the information from HONOURS.

    Another place to be attentive is when making your triggers. If you find want to try and minimize the amount of complicated regex comparing you do, you can take your regex triggers and modify them so that they match a "beginning of line substring" of the first few words to make sure you're avoiding the regex option unless the line almost certainly matches, since the substring and exact match methods of matching triggers are much faster than regex parsing. (I think this is something that Vadi did in svo if I'm not mistaken, but I don't actually use it so I've not seen the code since it was first made open source)


    On the original topic though, I generally have a big folder that contains smaller folders for every class I build an offense/defense for, and I have them toggled when my class changes, using things like enableAlias() and enableTrigger(). I find that after having used Mudlet for as many years as I did in another IRE(in which I had a system for at one point more than 1/3 of the classes in the game), I build something like my first classes offense and defense in whatever way I think will make it easiest to add in new classes at a future date. Keeping stuff modular(able to be expanded by adding small new parts that you already have a framework for) tends to feel much better to me than making one set of aliases/triggers/keys that has a bunch of if/then statements to handle every class.
    SiduriZahan
  • VessilVessil Posts: 51 ✭✭ - Stalwart
    Since the time during which you can edit stuff is so short here:

    Example of what I was mentioning that svo does:


  • DunnDunn The great Buffalo tundraPosts: 5,230 @@ - Legendary Achaean
    I just have a function that checks gmcp and returns true on all of my class specific scripts.

    if isClass("Depthswalker") then shadowShit() end 

    Then I map all common utility to the same aliases/keys and go from there depending on what's comfortable.

    I find it's easiest to keep things straight if I put as much of it as I can on the same map (I have 4 knight specs, magi, alchemist, depthswalker, serpent, bard, BM, shaman, occultist. RIP Lucrescent nuts).


    BannShirszaeVessilValaria
  • IniarIniar Posts: 45 ✭✭ - Stalwart
    @Jacen

    For macros, I began with the traditional if x then y  structure, but quickly outgrew that.

    Migrated to function calls for classes, still using an if class then function approach.
      - allows us to avoid duplicate macros which are a PitA to figure out at times if folders aren't turned on/off properly like you said
      - allows us to streamline free responses (stand, wield, what have you), but done differently to Antonius'; these things are calculated at the end of construction and pre-pended to the outgoing line via an alternate send function, reducing code for each function but is technically less efficient then Antonius'
      - easier to swap functions during testing

    Final macro structure was a bit overconstructed...
      - all keys were bound to a single function passing the combination as the argument (macro.fire("s"), macro.fire("shft-backslash")
      - the function would attempt to execute a class-specific function call, failing which, would call the module-specific function call (macro[arg]() might map to Indorani.s() or macro.s()), after a lookup
      - this absolutely obviated the need to navigate to the Keys folder to do anything else ever again, and access to the macro structure was rarely required. Simply creating specifically named functions or replacing them allowed us to re-write offences without deleting old functions.

    The thing I was working on was a method to integrate class-class synergy in a group fight while reducing access time within a UI-centric client, before my IRE game sputtered and dissipated (hi @Vessil). And then I became an Achaean bum. #bumlyf

    I do like Antonius' on-the-fly construction.
    KiskanVessil
  • KiskanKiskan Posts: 157 ✭✭✭ - Distinguished
    Iniar said:
    The thing I was working on was a method to integrate class-class synergy in a group fight while reducing access time within a UI-centric client, before my IRE game sputtered and dissipated (hi @Vessil). And then I became an Achaean bum. #bumlyf
    I wish Achaea was the right game for me, but I know it isn't.  Still, at this point, I wouldn't invest time/money in any of the games other than this one unless you truly have it to burn.  This is the game that is healthy.  This is the game that gets real dev love.  This is the IRE game that has a future.  Period.
  • JacenJacen Posts: 2,187 @@ - Legendary Achaean
    @Iniar I really like the idea of getting macros and such into functions as quickly as possible. It reminds me of a friends MUSHclient system, where even the macros are set with lua functions. I just wasn't sure if it was efficient performance-wise, or how lua's performance degrades as ... lua instance? Grows larger in size.

    I think that's the way I'm gonna take it though!
    image
  • ZahanZahan Posts: 187 ✭✭✭ - Distinguished
    edited November 2016
    Jacen said:
    @Iniar I really like the idea of getting macros and such into functions as quickly as possible. It reminds me of a friends MUSHclient system, where even the macros are set with lua functions. I just wasn't sure if it was efficient performance-wise, or how lua's performance degrades as ... lua instance? Grows larger in size.

    I think that's the way I'm gonna take it though!
    That's where profiling comes in handy.

    Here's the profiling for "SetMacro" function in MUSHclient:

                     Function        Count     Total(s)  Average(ms)
                  Accelerator           39       0.0097       0.2495


    You can see that loading almost 40 macros, took less than 1/100th of a second.  Might not want to do it with 800 macros but drawing just a couple dozen nets a near immeasurable (with variances in ping) amount of lag.


    This is the profiling script I use for mush, if anyone is interested:
    http://gammon.com.au/forum/?id=10279
    Click here for Nexus packages
    Currently available: Abs, Cnote, Keepalive, Lootpet, Mapmod
    Iniar
Sign In to Comment.