HeroEngine Forums
Welcome, Guest. Please login or Register for HeroCloud Account.

Author Topic: Guild System Tutorial  (Read 1372 times)

Mr_Conflicts

  • General Accounts
  • *
  • Posts: 32
  • Client Programmer
    • View Profile
    • Daniel J. Burkhart
Guild System Tutorial
« on: Aug 03, 16, 09:43:31 AM »

So I have recently been working on Anvil of Honors guild system and thought that it would nice of me to pass along the information about how it can be done. I am hopeful the will help people see the true beauty of the back end of the Hero Engine and how Arbitrary Root Nodes work, enjoy!

NOTE: this tutorial assumes you know the basics of HSL and the DOM editor if not I do recommend looking up the basic HSL and DOM tutorials on the wiki as well as the official Hero Engine YouTube channel.

References:

http://hewiki.heroengine.com/wiki/Arbitrary_Root_Node (aka ARNs or Arbitrary Root Nodes)

Beginning:

To start you will need to create three fields on the client DOM.

  • guildID - type ID and set the description to something like guild id value
  • GuildMemberList - type list of id and description list of guild ids
  • guildName - type string description guild name string value

after creating each of those fields be sure to copy each field to the server where we will create two new fields.

  • GuildList - type lookuplist indexed by id of id description A list of all guilds
  • pendingGuildInfoRequests - type lookuplist indexed by id of lookuplist indexed by id of lookuplist indexed by id of id description pending requests for guild root node

Now for the explanations, the guildID, GuildMemberList, and guildName fields should explain themselves (just one thing, note that GuildMemberList must be ID values, just keep that in mind). However the GuildList and pendingGuildInfoRequests may be a little bit confusing.

The GuildList will be used by the ARN (when you set it up) to store all of the guild IDs within your world.
The pendingGuildInfoRequests will also be used by the ARN, however it is a bit more complicated than that, let me explain. When a call is made to the guild registry server area (we will set it up later on) the request is put into the pendingGuildInfoRequests which takes the Guild ARN ID, the area ID where the request originated, and the instance ID of that area ID where that request originated. All this in turn will be set to the account ID of the user who requested the information. In all, this is just the method of extracting the guild info and displaying it to the player who requested it.

Now that that is explained lets create the GuildRegistryClassMethods script on the server side DOM. To begin create a script called GuildRegistry type data, description Registry that contains all guild info. One that is done you will need to link all the fields we created earlier (remember you should have replicated the fields from the client side). Now that you have that created you can open the empty script and begin to program. However before you do you will need to open the E_WorldClassMethods (S) script which serves as the default override for the world system.

Now this is where it gets complicated. In order to have the guild area always running you will need to create a system area which contains only data. This means that you can not travel there (with a character). This means that you will create it in the area organizer and will then use a function that will be called by the CLI ONCE, emphasis on ONCE. To do this flip back to the GuildRegistryClassMethods (which should be blank) and copy and paste this code:

Code: [Select]
#define debug

function Msg( account as NodeRef, msg as String )
  //ignoring account which isnt valid in a system area
  //left channel blank since it is irrelevant in a system area
  //this will simply "chat" to the chat panel for debugging
  #if debug
  $CHAT.ChatWorld("", msg)
  #endif
.

shared function Guild_Msg( msg as String )
  #if debug
  $Debug.SendNotifyDebugMessage("Guild", SYSTEM.EXEC.CALLEDBYSCRIPT+":"+SYSTEM.EXEC.CALLEDBYFUNCTION+"("+GetAreaName(GetAreaNumber())+")("+GetInstanceNumber()+")-> "+msg )
  #endif
.

shared function Guild_Error( msg as String )
  #if debug
  $Debug.SendNotifyDebugMessage("Guild", SYSTEM.EXEC.CALLEDBYSCRIPT+":"+SYSTEM.EXEC.CALLEDBYFUNCTION+"("+GetAreaName(GetAreaNumber())+")("+GetInstanceNumber()+")[ERROR]-> "+msg )
  #else
  ScriptError( msg )
  #endif
.

// THIS IS WHERE THE CODE WILL GO LATER IN THE TUTORIAL

//###########################
//           CLI
//###########################
remote method GuildRegistry_CreateGuildRegistry( account as ID )
  // - Called by the CLI to create a new post office node.
  var q = QueryAssociation( GetRootNode(), "AreaRootToWeatherRegistry", None )
  println("attempting to create new Guild Registry")
  foreach a in q
    freg as NodeRef of Class GuildRegistry = a.target
    if freg != None
      RemoveMailBoxForNode( freg )
      #if debug
      println("removed registry mailbox")
      #endif
      $CHAT.ChatPlayer( account, "", "Removed Guild registry ." + freg + "$RDestroying node " + freg + " make sure to change the nodeID in the Guild ClassMethods script." )
      DestroyNode( freg )
    .
  .
  reg as NodeRef = CreatePersistedNodeFromClass( "GuildRegistry" )
  AddAssociation( GetRootNode(), "AreaRootToGuildRegistry", reg )
  CreateMailBoxForNode( reg )
  #if debug
  println("created registry mailbox " + reg)
  #endif
  //call area 0 instance 0 $GuildRegistry.GuildRegistry_KeepUp( account )
  $CHAT.ChatPlayer( account, "", "Created a new GuildSystem root " + reg + " .$RThe GuildSystem root is now a Guild Registry.$RRegistered this systemarea" )
  UpdateAreaLastEdit()
.

shared function GetGuildRegistryNode() as NodeRef of Class GuildRegistry
  reg as NodeRef of Class GuildRegistry
  var q = QueryAssociation(GetRootNode(), "AreaRootToGuildRegistry", None )
  foreach a in q
    var f = a.target
    where f is kindof GuildRegistry
      reg = f
    .
  .
  return reg
.

method _RootNodeLoadedNotification( rootID as ID )
  if GetGuildRegistryNode().pendingGuildInfoRequests has rootID
    //there is a info request for this arn, so lets get it:
    //we're going to call back to the area/instance/member this request came from:
    area as ID
    instance as ID
    member as ID
    foreach areaID in GetGuildRegistryNode().pendingGuildInfoRequests[rootID]
      area = areaID
      foreach instanceID in GetGuildRegistryNode().pendingGuildInfoRequests[rootID][areaID]
        instance = instanceID
        member = GetGuildRegistryNode().pendingGuildInfoRequests[rootID][areaID][instanceID]
        //now we have all the info needed to send the data back to the member:
        //we specify that this node is of class Guild so that we can access the fields we need
        guild as NodeRef of Class Guild = rootID
        if guild != None
          call area area instance instance $AREA.ReceiveGuildInfoForMember(guild.guildName, guild.guildID, guild.GuildMemberList, member)
        else
          $CHAT.ChatWorld("world", " oops this guild didnt exist!")
        .
        //now below is the receiveing method in the E_AreaClassMEthods script
      .     
    .
    //if there are no further requests for this ARN, lets unload it for now
    UnloadRootNode( rootID, me )
  .
.

method _RootNodeLoadFailedNotification( rootID as ID, areaID as ID, areaInstance as ID, failureReason as String )
  Guild_Error("Failed to load " + failureReason )
.

method _RootNodeUnloadedNotification( rootID as ID )
  Guild_Error("Unloaded.")
.

And compile, (if you can not compile then please leave a forum post below and I will address the issue keep in mind I am pretty much reversing the steps  of how I created it as well as working on a world that has been very customized to suit the projects needs) Assuming you have compiled successfully, you will need to now use the CLI to create the system and and get the id. To do this use the following command and enter it into the console:

Code: [Select]
\cpfc GuildRegistry GuildRegistry
This means that you can now access the functions/methods on the server using the $GuildRegistry keyword. Now lets create a chat command within the Hero Script editor. To do this open the editor (CTRL-H) and then server script and type CMDGuild.  Now within CMDGuild copy and paste the following.

Code: [Select]
//#define debug

shared function HE_ProcessCommandInput( account as NodeRef, input as String )
  // The Command Handler calls this shared function based on a mapping stored on the COMMANDHANDLER prototype
  //   which maps a /command to a script to call

  args as List of String
  Tokenize( input, args )
  if args.length < 2
    HE_CommandUsage( account, input )
    return
  .
 
  partialMatch toLower( args[2] )
    to "create"
      println("trying to create new guild registry")
      call area GuildRegistryClassMethods:GetGuildSystemAreaID() instance 0 $GuildRegistry.GuildRegistry_CreateGuildRegistry( account )
    .
    default
      HE_CommandUsage( account, input )
      return
    .
  .
.

shared function HE_CommandUsage( account as NodeRef of Class E_playerAccount, input as String )
  // NOTE, this style is much more efficient on the client than doing a seperate Msg() for each line.
  // This is because only a single label gets created in chat, rather than one per line.
 
  msg as String = "/Guild <parameter>$R" 
  msg = msg + " - For script referance only$R"
  Msg( account, msg )
.

function Msg( account as NodeRef, msg as String )
  $CHAT.CHATPLAYER( account, "", msg )
.

and then in the console

Code: [Select]
/REGISTER add /guild script="CMDGuild"
Then using the chat type /guild create after you have registered the chat command. This should output an ID which you MUST copy and save to put it back into code this ID is the area ID for the guild registry and is very important, so do not lose it. Now back to the E_WorldClassMethods script!

Within the KeepUpArea method add the following lines of code:

Code: [Select]
is "guild"
      KeepAreaUp( YOUR-GUILD-SYSTEM-AREA-ID, 0 )
      println("keeping guild up" + $WORLD._SpinUpState(YOUR-GUILD-SYSTEM-AREA-ID, 0))
    .

this is under the when system statement. Now within the LoadSystemAreas method add the following lines:

Code: [Select]
if KeepAreaUp( YOUR-GUILD-SYSTEM-AREA-ID, 0 )                      // - Guild Area
    $CHAT.ChatWorld("World", "Guild System Loading")
  .

This in turn should keep these areas up at all times within your world, if they are not being kept up at all times the guild will not work because the area is not loaded. Now back into the GuildRegistryClassMethods script!

now within that script add the following where I said the rest for the functions/methods would go earlier in the tutorial.

Code: [Select]
shared function GetGuildSystemAreaID() as ID
  return YOUR-GUILD-SYSTEM-AREA-ID
.

shared function GetGuildRootNodeID() as ID 
  return YOUR-GUILD-ROOT-NODE-ID // I will explain later on
.

shared function GetGuildRootNode() as NodeRef of Class GuildRegistry
  n as NodeRef = GetGuildRootNodeID()
  //if the id was null.. thats odd, but lets get it by association instead
  if n = None
    var glist = QueryAssociation( GetRootNode(), "AreaRootToGuildRegistry", 0 )
    foreach gAssoc in glist
      //since there can only be one of these, we'll simply take the first one it comes to
      n = gAssoc.target //and target is the node we want
    .
  .
  return n
.

remote method GetGuildInfoForMember(areaID as ID, instanceID as ID, accountID as ID, guildID as ID)
  mylist as LookupList indexed by ID of ID = GetGuildRegistryNode().GuildList
  var reg = GetGuildRegistryNode()
  if not (reg.GuildList has guildID)
    return
  .
  guildArnID as ID = reg.GuildList[guildID]
  reg.pendingGuildInfoRequests[guildArnID][areaID][instanceID] = accountID
  if mylist has guildID
    RequestRootNodeLoad(guildArnID, me) //me means to call back to the registries class script when loaded
  .
.

// info as LookupList indexed by ID of LookupList indexed by String of LookupList indexed by String of List of String
// info contains guild: ID, NAME, RANK, MEMBERLIST
remote method GuildInfo(guildID as ID, accountID as ID, areaID as ID, instanceID as ID)
  var reg = GetGuildRegistryNode()
  if not (reg.GuildList has guildID)
    return
  .
  guildArnID as ID = reg.GuildList[guildID]
  reg.pendingGuildInfoRequests[guildArnID][areaID][instanceID] = accountID
  var guild = GetGuildRegistryNode().GuildList[guildID] 
  RequestRootNodeLoad( guild, me )
.

// For GMs ONLY!
// Clears the guild list and resets all guilds in the game
#if debug
remote method ClearGuilds()
  var reg = GetGuildRegistryNode()
 
  clear reg.GuildList
.
#else
remote method ClearGuilds()
  $CHAT.ChatPlayer(SYSTEM.REMOTE.CLIENT, "Info", "This function is only applicable in debug mode.")
.
#endif

remote method CreateNewGuild(guildID as ID, guildName as String, accountID as ID, areaID as ID, instanceID as ID)
  // guildID would be 0 if player is not in a guild
  // however if they are in a guild they shouldnt be creating a new one
  if guildID != None
    #if debug
    println("already in guild")
    #endif
    //return
  .
 
  //guilds as lookuplist indexed by guildID of guildARNID
  var reg = GetGuildRegistryNode()
  if reg = None
    println("there is no Guild Registry in the System area")
    return
  .
  guilds as LookupList indexed by ID of ID = reg.GuildList
  if guilds has guildID
    #if debug
    println( "guild already exists by this players ID" )
    #endif
    return
  else
    //guilds must be created from a class other than the registry class
    //created a new class Guild, with script and added the guildID and guildName fields
    leader as NodeRef of Class E_playerCharacter = SYSTEM.REMOTE.CLIENT
    //must be created as a persisted node
    newGuild as NodeRef of Class Guild = CreatePersistedNodeFromClass("Guild")
    newGuild.guildID = newGuild //changed from leaders ID
    newGuild.guildName = guildName
    //be sure to store the newGuild ID as the ARN id in a list in the registry
    GetGuildRegistryNode().GuildList[newGuild] = newGuild
    //now we've stored the id of the ARN indexed by the guilds id, we can create the ARN
   
    error as String = "oops something went wrong"
    if CreateRootNode(newGuild, GetRootNode(), error)
      #if debug
      println( "Success" )
      #endif
      call area areaID instance instanceID $AREA.GuildCreated(accountID, newGuild)
    else
      #if debug
      println("failed to create guild")
      #endif
    .
  .
.


Some of the following may not work yet as the E_AreaClassMethods script still needs to be tied into the guild registry. I will explain now how that is done.

First copy and paste the following into the the script:

Code: [Select]
// Guild System
// Gets guild information for member area
remote method ReceiveGuildInfoForMember(guildName as String, guildID as ID, memberList as List of ID, memberID as ID)
  account as NodeRef of Class E_playerAccount = memberID
  member as NodeRef of Class E_playerCharacter = account.GetMyChar()
  if member = None
    return
    #if debug
    println( "character must have logged during the transfer..." )
    #endif
  .
  member.guildName = guildName
  member.guildID = guildID
  member.GuildMemberList = memberList
  //$CHAT.ChatPlayer(account, "Info", "Your guild member list is updated!")
  //here you would call your client (account) with the info to populate the guild panel
.


method GetGuildInfoForMemberArea(member as NodeRef of Class E_playerCharacter)
  //now we call the registry, which is in another area like so:
  guildID as ID = member.guildID
  call area GuildRegistryClassMethods:GetGuildSystemAreaID() instance 0 $GuildRegistry.GetGuildInfoForMember(GetAreaNumber(), GetInstanceNumber(), member.GetMyAccount().GetID(), guildID)
.

untrusted function RemoteGetGuildInfo()
  account as NodeRef of Class E_playerAccount = SYSTEM.REMOTE.CLIENT
  char as NodeRef of Class E_playerCharacter = account.GetMyChar()
  guildID as ID = char.guildID
  call area GuildRegistryClassMethods:GetGuildSystemAreaID() instance 0 $GuildRegistry.GuildInfo(guildID, account, GetAreaNumber(), GetInstanceNumber())
.

untrusted function RemoteCreateGuild(guildID as ID, guildName as String)
  call area GuildRegistryClassMethods:GetGuildSystemAreaID() instance 0 $GuildRegistry.CreateNewGuild(guildID, guildName, SYSTEM.REMOTE.CLIENT, GetAreaNumber(), GetInstanceNumber())
.

remote method GuildCreated(accountID as ID, guildID as ID)
  account as NodeRef of Class E_playerAccount = accountID
  if account != None
    char as NodeRef of Class E_playerCharacter = account.GetMyChar()
    if char != None
      char.guildID = guildID
      add back char to char.GuildMemberList
      $CHAT.ChatPlayer( account, "Guild", "Guild Created" )
    .
  .
.

OK now for some explanations on why this is necessary. When the client side makes a call to the server its going to call the E_AreaClassMethods script. The methods/functions it calls within that script then send the data needed to be sent into the GuildRegistyClassMethods script. The reason the area must send the data is because you player does not exist within that area thus you need to supply it with the necessary IDs which then get stored and can be used later when you have a guild panel and need to get the data from the guild registry.

Now that the server-side basics are done I will leave the client side to you. Keep in mind that you still will need to add more functionality in order to create a full guild system (like adding other players and disbanding for example) however using the information you have here you should be able to create the client side code as well as add the remaining functionality.

Feel free to message me with questions and I will reply as soon as I can. Also if anyone wants to add anything please do, I may have overlooked something or misunderstood a concept thus not explaining it correctly. However this should be informative to most people. Also don't just copy, try to fully understand what it is I am doing and read everything because in the end it will make you a better HSL programmer due to the fact that this touches almost all aspects of the Hero Engine and shows just how much you can do with it, hope you enjoyed it.
« Last Edit: Aug 03, 16, 02:50:35 PM by Mr_Conflicts »
Logged
Anvil of Honor - Client Programmer

Mr_Conflicts

  • General Accounts
  • *
  • Posts: 32
  • Client Programmer
    • View Profile
    • Daniel J. Burkhart
Edit Notes: Guild System Tutorial
« Reply #1 on: Aug 03, 16, 02:51:35 PM »

First things first, KeepUpArea method in the E_WorldClassMethods script is custom however you should be able to figure it out based on what I have given you. Also I fixed a problem in the tutorial where I stated that you do not use the area panel to create the instance when in fact you do, and that is how you get the Area Instance ID, the registry ID is obtained through the CLI. If anyone finds anything else I would really like to know so I can correct it.
Logged
Anvil of Honor - Client Programmer

Archwind

  • General Accounts
  • *
  • Posts: 16
    • View Profile
Re: Guild System Tutorial
« Reply #2 on: Aug 03, 16, 04:47:43 PM »

Thanks for putting this together and all your hard work. Someday I may get off my lazy *** and do something. LOL
Logged