ConflictSystem

From Legends of Aria Admin and Modding Wiki
Revision as of 01:28, 28 January 2018 by Gizmo (talk | contribs) (AdvanceConflictRelation)
Jump to: navigation, search

 welcome this will discuss the contents of the globals\helpers\conflict.lua
 this documentation is correct as of version 6.0 PRECB1 Release 

this documentation was added by Gizmo

ConflictRelations Table

 ConflictRelations = {
   Warning = {
       Name = "Warning",
       Warning = true,
   },
   BeenWarned = {
       Name = "BeenWarned",
       Warning = true,
   },
   Aggressor = {
       Name = "Aggressor",
   },
   Victim = {
       Name = "Victim"
   },
   Defender = {
       Name = "Defender"
   }
 }

Conflict Functions

AdvanceConflictRelation

 -- Advance mobileA against mobileB in conflict, will also refresh any current 
    conflicts between the two.
 -- @param mobileA(mobileObj)
 -- @param mobileB(mobileObj)
 -- @param noWarning(boolean)(optional) if true, warning will be skipped and they will 
    go directly to aggressor
 -- @param karmaAction(luaTable)(optional) KarmaAction that will be executed on mobileA 
    when they become an aggressor. (default: KarmaActions.Negative.Attack) Setting this 
    to false will not execute any karma actions.
 -- @return none
 function AdvanceConflictRelation(mobileA, mobileB, noWarning, karmaAction)
   if not( karmaAction == false ) then
       karmaAction = karmaAction or KarmaActions.Negative.Attack
   end
   -- when Warnings are not enabled, we force noWarning to true.
   if ( ServerSettings.Conflict.WarningEnabled ~= true ) then
       noWarning = true
   end
   -- reassign mobileA and mobileB to pet owners, if applicable
   if ( IsPet(mobileA) ) then
       mobileA = mobileA:GetObjectOwner() or mobileA
   end
   if ( mobileB ~= nil and IsPet(mobileB) ) then
       mobileB = mobileB:GetObjectOwner() or mobileB
   end
   -- conflict don't care what you do to yourself or your pets
   if ( mobileA == mobileB ) then return end
   local aToBRelation = GetConflictRelation(mobileA, mobileB)
   local bToARelation = GetConflictRelation(mobileB, mobileA)
   local refreshA = true
   local refreshB = true
   -- when one side has a relation but the other side does not, we count neither side 
      as having a relation.
   -- this is so we can clear a conflict relation on either side and not need to clear 
      both involved parties (the other involved could be in a different region at time 
      of clearing!)
   if ( noWarning ~= true and (aToBRelation == nil or bToARelation == nil) ) then
       UpdateConflictRelation(mobileA, mobileB, ConflictRelations.Warning)
       UpdateConflictRelation(mobileB, mobileA, ConflictRelations.BeenWarned)
       refreshA = false
   elseif ( (noWarning == true and (aToBRelation == nil or bToARelation == nil))
       or ConflictEquals(aToBRelation, ConflictRelations.Warning) ) then
       -- A becomes aggressor, B becomes victim.
       UpdateConflictRelation(mobileA, mobileB, ConflictRelations.Aggressor, true)
       UpdateConflictRelation(mobileB, mobileA, ConflictRelations.Victim)
       refreshA = false
       refreshB = false
       -- Karma action for becoming the aggressor
       if ( karmaAction ) then
           ExecuteKarmaAction(mobileA, karmaAction, mobileB)
       end
       if ( IsInitiate(mobileB) and IsPlayerCharacter(mobileA) ) then 
           EndInitiate(mobileB) 
       end
       elseif ( ConflictEquals(aToBRelation, ConflictRelations.Victim) ) then
       -- A was a victim, now A is a defender.
       UpdateConflictRelation(mobileA, mobileB, ConflictRelations.Defender)
       refreshA = false
   end
   --refresh the conflict expiration.
   -- Possible optimization, only refresh every other one/every third one?
   if ( aToBRelation ~= nil and refreshA ) then
       if ( karmaAction and ConflictEquals(aToBRelation, ConflictRelations.Aggressor) ) 
         then
           ExecuteKarmaAction(mobileA, karmaAction, mobileB)
       end
       UpdateConflictRelation(mobileA, mobileB, ConflictRelations[aToBRelation], true)
   end
   if ( bToARelation ~= nil and refreshB ) then
       UpdateConflictRelation(mobileB, mobileA, ConflictRelations[bToARelation])
   end
 end

Used In File Function Used In
base_mobile HandleApplyDamage(damager, damageAmount, damageType, isCrit, wasBlocked)
globals\helpers\karma CheckKarmaLoot(player, container)

ClearConflictTable

 -- Clear the conflict table of a mobile
 -- @param mobile(mobileObj)
 -- @return none
 function ClearConflictTable(mobile, isPlayer)
   if ( mobile == nil or not mobile ) then
       LuaDebugCallStack("[Conflict] Invalid mobile provided.")
       return
   end
   if ( mobile:HasObjVar("Conflicts") ) then
       mobile:DelObjVar("Conflicts")
   end
   if ( isPlayer == true ) then
       InitializeClientConflicts(mobile)
   end
 end

ConflictEquals

 -- Convenience function to make checking conflicts more readable
 -- @param strRelation(string) The string Name of the relation to check for
 -- @param tableRelation(luaTable) The ConflictRelations entry to check against
 -- @return true or false
 function ConflictEquals(strRelation, tableRelation)
   return ( strRelation == tableRelation.Name )
 end

ForeachAggressor

 -- Perform a function on each aggressor of a mobile
 -- @param mobile(mobileObj)
 -- @param callback(function(id))
 -- @return none
 function ForeachAggressor(mobile, callback)
   local conflictTable = GetConflictTable(mobile)
   for mobileB,relation in pairs(conflictTable) do
       if ( mobileB:IsValid() ) then
           if (( ConflictEquals(relation[1], ConflictRelations.Victim) or ConflictEquals(relation[1], ConflictRelations.Defender )) and ValidConflictRelationTable(relation)) then
               if ( ConflictEquals(GetConflictRelation(mobileB, mobile), ConflictRelations.Aggressor) ) then
                   callback(mobileB)
               end
           end
       end
   end
 end

FreezeConflictTable

 --- Freeze the conflict table on a mobile, optionally saving the frozen table on a 
 different object. Will clear all conflicts for the mobile if target does not equal 
 mobile.
 -- @param mobile(mobileObj)
 -- @param target(gameObj)(optional) the gameObj the conflict table will be saved to
 -- @return none
 function FreezeConflictTable(mobile, target)
   target = target or mobile
   local conflictTable = GetConflictTable(mobile)
   for mobileId,conflict in pairs(conflictTable) do
       -- set all the expires to true, meaning they never expire
       conflictTable[mobileId][2] = true
   end
   -- save the frozen table on the target
   SetConflictTable(target, conflictTable)
 end

Used In File Function Used In
base_mobile DoMobileDeath(damager)
base_player_death RegisterEventHandler(EventType.CreatedObject, "created_corpse", function (success, objRef)

GetConflictRelation

 -- Get the relation of conflict mobileA is to mobileB.
 -- @param mobileA(mobileObj)
 -- @param mobileB(mobileObj)
 -- @param mobileAConflictTable(optional) return value from GetConflictTable()
 -- @return One of ConflictRelations, nil if not-found/expired.
 function GetConflictRelation(mobileA, mobileB, mobileAConflictTable)
   mobileAConflictTable = mobileAConflictTable or GetConflictTable(mobileA) or {}
   -- only valid, frozen/non-expired, will make the cut.
   if ( ValidConflictRelationTable(mobileAConflictTable[mobileB]) ) then
       return mobileAConflictTable[mobileB][1]
   end
   return nil
 end

GetConflictTable

 -- Get the conflict table for a mobile
 -- @param mobile(mobileObj)
 -- @return luaTable containing all conflicts for this mobile
 function GetConflictTable(mobile)
   if ( mobile == nil or not mobile ) then
       LuaDebugCallStack("[Conflict] Invalid mobile provided.")
       return {}
   end
   return mobile:GetObjVar("Conflicts") or {}
 end

Used In File Function Used In
globals/helpers/conflict This is used as a tool throughout the conflict file. You will find it used in the FreezContactTable, ValidConflictRelationTable, ForeachAggressor, IsAggressor, InheritAggressivePlayerConflicts and InitializeClientConflicts functions.

GetNearbyTaggedMobiles

 -- Get all mobiles that are tagged to a mobile and are also nearby
 -- @param mobile
 -- @return list of nearby mobiles (luaArray)
 function GetNearbyTaggedMobiles(mobile)
   local validMobiles = {}
   if ( mobile ) then
       local taggedMobiles = mobile:GetObjVar("Tag") or {}
       local loc = mobile:GetLoc()
       for id,val in pairs(taggedMobiles) do
           local obj = GameObj(id)
           if ( obj ~= nil and obj:IsValid() and obj:GetLoc():Distance(loc) <= ServerSettings.Misc.GroupAutolootRange ) then
               table.insert(validMobiles, obj)
           end
       end
   end
   return validMobiles
 end

InheritAggressivePlayerConflicts

 
 -- Inherit Aggressor/Defender conflict relations from playerA onto playerB, DOES NOT ENFORCE IsPlayerCharacter()
 -- @param playerA(playerObj) The player whos conflicts are being inherited
 -- @param playerB(playerObj) The player who is inheriting the conflicts
 -- @return none
 function InheritAggressivePlayerConflicts(playerA, playerB)
   if ( playerA == playerB ) then return end
   local conflictsA = GetConflictTable(playerA)
   -- loop all of playerA's conflicts
   for mobileC,aToCRelationTable in pairs(conflictsA) do
       if ( mobileC ~= playerB and mobileC:IsValid() ) then
           -- using this variable to keep the if nesting from going too deep
           local keepgoing = false
           -- if A to C is aggressor
           if ( 
               ConflictEquals(aToCRelationTable[1], ConflictRelations.Aggressor)
               and
               ValidConflictRelationTable(aToCRelationTable)
           ) then
               local cToARelation = GetConflictRelation(mobileC, playerA)
               -- and C to A is Victim/Defender
               if ( 
                   cToARelation ~= nil
                   and
                   (
                       ConflictEquals(cToARelation, ConflictRelations.Victim)
                       or
                       ConflictEquals(cToARelation, ConflictRelations.Defender)
                   )
               ) then
                   keepgoing = true
               end
           end
           if ( keepgoing ) then
               local alreadyAggressive = false
               local bToCRelation = GetConflictRelation(playerB, mobileC)
               -- if B to C is aggressor
               if (
                   bToCRelation ~= nil
                   and
                   ConflictEquals(bToCRelation, ConflictRelations.Aggressor)
               ) then
                   local cToBRelation = GetConflictRelation(mobileC, playerB)
                   -- and C to B is Victim/Defender
                   if ( 
                       cToBRelation ~= nil
                       and
                       (
                           ConflictEquals(cToBRelation, ConflictRelations.Victim)
                           or
                           ConflictEquals(cToBRelation, ConflictRelations.Defender)
                       )
                   ) then
                       alreadyAggressive = true
                   end
               end
               if ( alreadyAggressive ) then
                   -- refresh it
                   UpdateConflictRelation(playerB, mobileC, conflictsB[mobileC], true)
                   UpdateConflictRelation(mobileC, playerB, cRelationTable[mobileB])
               else -- otherwise
                   -- inherit it.
                   -- set playerB's relation: aggressor to mobileC.
                   UpdateConflictRelation(playerB, mobileC, ConflictRelations.Aggressor, true)
                   -- and set mobileC's relation: victim to playerB
                   UpdateConflictRelation(mobileC, playerB, ConflictRelations.Victim)
               end
           end
       end
   end
 end

InitializeClientConflicts

 -- Refresh the client with conflict status on login/region change
 -- @param mobile
 function InitializeClientConflicts(mobile)  
   local conflictTable = GetConflictTable(mobile)
   for mobileB,relation in pairs(conflictTable) do
       if ( mobileB:IsValid() ) then -- if the mobile is in same region currently.
           local bToARelation = GetConflictRelation(mobileB, mobile) -- if they have a valid relation with us
           if ( bToARelation ~= nil ) then
               if ((
                       ConflictEquals(relation[1], ConflictRelations.Victim) 
                       or
                       ConflictEquals(relation[1], ConflictRelations.Defender)
                   )
                   and
                   ConflictEquals(bToARelation, ConflictRelations.Aggressor)
                   and
                   ValidConflictRelationTable(relation)
               ) then
                   local timeRemaining = relation[2] - DateTime.UtcNow
                   mobile:SendClientMessage("UpdateMobileConflictStatus",{mobileB,"Aggressed",timeRemaining.TotalSeconds})
                   if ( IsPlayerCharacter(mobileB) ) then
                       mobileB:SendClientMessage("UpdateMobileConflictStatus",{mobile,"Aggressor",timeRemaining.TotalSeconds})
                   end
               elseif( 
                   ConflictEquals(relation[1], ConflictRelations.Aggressor)
                   and
                   (
                       ConflictEquals(bToARelation, ConflictRelations.Victim) 
                       or
                       ConflictEquals(bToARelation, ConflictRelations.Defender)
                   )
                   and
                   ValidConflictRelationTable(relation)
               ) then 
                   local timeRemaining = relation[2] - DateTime.UtcNow
                   mobile:SendClientMessage("UpdateMobileConflictStatus",{mobileB,"Aggressor",timeRemaining.TotalSeconds})
                   if ( IsPlayerCharacter(mobileB) ) then
                       mobileB:SendClientMessage("UpdateMobileConflictStatus",{mobile,"Aggressed",timeRemaining.TotalSeconds})
                   end
               elseif( 
                   ConflictEquals(relation[1], ConflictRelations.Warning)
                   and
                   ConflictEquals(bToARelation, ConflictRelations.BeenWarned)
                   and
                   ValidConflictRelationTable(relation)
               ) then
                   local timeRemaining = relation[2] - DateTime.UtcNow
                   mobile:SendClientMessage("UpdateMobileConflictStatus",{mobileB,"Warning",timeRemaining.TotalSeconds})
               end
           end
       end
   end
 end

IsAggressor

 --- Determine if a mobile is an aggressor
 -- @param mobile(mobileObj)
 -- @param guardIgnore(boolean)(optional) if true, ignore any relation that's tagged guard ignore. (this is so guards ignore when someone is an aggressor against an outcast for example)
 -- @return true if mobile is an aggressor
 function IsAggressor(mobile, guardIgnore)
   for mobileB,conflict in pairs(GetConflictTable(mobile)) do
       if ( 
           guardIgnore ~= true or 
           (guardIgnore == true) and 
           conflict[3] ~= true and 
           ConflictEquals(conflict[1], ConflictRelations.Aggressor) and 
           ValidConflictRelationTable(conflict)
          ) then
           if ( mobileB:IsValid() ) then
               local bToARelation = GetConflictRelation(mobileB, mobile)
               if ( bToARelation ~= nil ) then
                   if ( ConflictEquals(bToARelation, ConflictRelations.Victim) or ConflictEquals(bToARelation, ConflictRelations.Defender) ) then return true end
               end
           end
       end
   end
   return false
 end

IsMobTaggedBy

 -- Check to see if a mobile(npc) is tagged by a player
 -- @param mobile(mobileObj) DOES NOT force this to be an NPC ( but it should be )
 -- @param player(playerObj) DOES NOT force this to be an Player ( but it should be )
 -- @return true or false or nil (nil if the mobile had no conflicts)
 function IsMobTaggedBy(mobile, player)
   local tag = mobile:GetObjVar("Tag") or {}
   return ( player ~= nil and tag[player.Id] == true )
 end

SetConflictTable

 -- Set the conflict table for a mobile
 -- @param mobile(mobileObj)
 -- @param data(luaTable)
 -- @return none
 function SetConflictTable(mobile, data)
   mobile:SetObjVar("Conflicts", data)
 end

TagMob

 -- Uses the damage list to determine what Single Player/Collective Group did the most damage and sets the ObjVar Tag with the mobiles that fit the bill
 -- @param mobile(mobileObj) the NPC that has died
 function TagMob(mobile)
   local damagers = mobile:GetObjVar("Damagers")
   if ( damagers ~= nil ) then
       -- get a list of all groups/solos involved in all the damage
       local groups = {}
       local solos = {}
       for damager,data in pairs(damagers) do
           if ( damager ~= nil and damager:IsValid() ) then
               local gid = damager:GetObjVar("Group")
               if ( gid ~= nil ) then
                   if ( groups[gid] == nil ) then groups[gid] = {} end
                   table.insert(groups[gid], {damager,data.Amount})
               else
                   -- we have to set it as damager.Id because guilds keep track of members by their Id
                   table.insert(solos, {damager.Id,data.Amount})
               end
           end
       end
       -- calculate the damage for each group
       local groupsDamage = {}
       for gid,data in pairs(groups) do
           for i,d in pairs(groups[gid]) do
               if ( groupsDamage[gid] == nil ) then groupsDamage[gid] = 0 end
               groupsDamage[gid] = groupsDamage[gid] + d[2]
           end
       end
       -- finally determine who's the winner
       local most = {nil,0}
       -- first check solos
       for mobileId,data in pairs(solos) do
           if ( data[2] > most[2] ) then
               most = data
           end
       end
       local isGroup = false
       -- then check collective groups
       for gid,amount in pairs(groupsDamage) do
           if ( amount > most[2] ) then
               most = {gid,amount}
               -- solos were checked first
               isGroup = true
           end
       end
       local tag = {}
       if ( isGroup ) then
           -- add every mobile in the group that won
           local groupRecord = GetGroupRecord(most[1]) or {}
           groupRecord.Members = groupRecord.Members or {}
           for id,data in pairs(groupRecord.Members) do
               tag[id] = true
           end
       else
           -- add the solo mobile that won
           tag[most[1]] = true
       end
       mobile:SetObjVar("Tag", tag)
   end
 end

UpdateConflictRelation

 -- Update the conflict relation between two mobiles, also cleans mobileA's conflict 
    table of any expired.
 -- @param mobileA(mobileObj)
 -- @param mobileB(mobileObj)
 -- @param newRelation(luaTable) the entry from ConflictRelations
 -- @param guardCheck(boolean) If true, this update will check for guard protection, 
     this way you can refresh a conflict on both sides but only one will cares about 
     guards.
 -- @return none
 function UpdateConflictRelation(mobileA, mobileB, newRelation, guardCheck)
   if ( newRelation == nil ) then
       LuaDebugCallStack("[Conflict] Nil conflict relation table provided")
       return
   end
   if ( newRelation.Name == nil ) then
       LuaDebugCallStack("[Conflict] Invalid conflict relation table, missing Name")
       return
   end
   if ( mobileA == nil ) then
       LuaDebugCallStack("[Conflict] Nil mobileA provided.")
       return
   end
   if ( mobileB == nil ) then
       LuaDebugCallStack("[Conflict] Nil mobileB provided.")
       return
   end
   local tableA = GetConflictTable(mobileA)
   local wasGuardIgnored = false
   if ( not guardCheck and tableA[mobileB] ~= nil and tableA[mobileB][3] ) then
       wasGuardIgnored = true
   end
   -- set/update the conflict
   local now = DateTime.UtcNow
   tableA[mobileB] = {newRelation.Name}
   -- when the conflict should end
   if ( newRelation.Warning == true ) then
       tableA[mobileB][2] = now + ServerSettings.Conflict.WarningDuration
   else
       tableA[mobileB][2] = now + ServerSettings.Conflict.RelationDuration
   end
   -- cleanse table of any expired.
   for k,v in pairs(tableA) do
       if ( now >= v[2] ) then
           -- it's expired
           tableA[k] = nil
       end
   end
   if ( ConflictEquals(newRelation.Name, ConflictRelations.Aggressor) and 
        IsPlayerCharacter(mobileA) and IsPlayerCharacter(mobileB)) then
       -- mobileB has zero karma concequences against mobileA
       -- Turn mobileA AGGRESSIVE on mobileB's client (so B is looking at an aggressive 
       A)
       mobileA:SendClientMessage("UpdateMobileConflictStatus",
       {mobileB,"Aggressor",ServerSettings.Conflict.RelationDuration.TotalSeconds})
       mobileB:SendClientMessage("UpdateMobileConflictStatus",
       {mobileA,"Aggressed",ServerSettings.Conflict.RelationDuration.TotalSeconds})
       -- 100
   elseif ( ConflictEquals(newRelation.Name, ConflictRelations.Warning) and 
            IsPlayerCharacter(mobileA) and IsPlayerCharacter(mobileB) ) then
       -- mobileA is nearly an aggressor to mobileB
       -- Turn mobileB YELLOW on mobileA's client (so A is looking at a mobile they 
           know if they hit again they are an aggressor)
       mobileA:SendClientMessage("UpdateMobileConflictStatus",
       {mobileB,"Warning",ServerSettings.Conflict.WarningDuration.TotalSeconds})
   end
   --[[
       Handle Guard protection triggers for aggressive actions.
   ]]
   -- if mobileA's relation is aggressor
   if ( guardCheck and ConflictEquals(newRelation.Name, ConflictRelations.Aggressor) ) 
     then
       local karmaBLevel = GetKarmaLevel(GetKarma(mobileB))
       local guardIgnore = true
       -- if mobileB's karma level is guard protected
       if ( karmaBLevel.GuardProtectPlayer or karmaBLevel.GuardProtectNPC ) then
           local isPlayerB = IsPlayerCharacter(mobileB)
           if ( (isPlayerB and karmaBLevel.GuardProtectPlayer)
           or (not isPlayerB and karmaBLevel.GuardProtectNPC) ) then
               -- make the guards protect B from A
               GuardProtect(mobileB, mobileA)
               guardIgnore = false
           end
       end
       -- if guards don't protect mobileB's karma level, add an ignore guard entry
       -- (This is so, for example, players can be aggressors against an outcast, but 
          guards won't attack them for being aggressors against outcasts)
       if ( guardIgnore == true ) then
           tableA[mobileB][3] = true
       end
   end
   -- when skipping guard protect this value would be ignored and overwritten if not 
      cached from previous data.
   if ( wasGuardIgnored ) then
       tableA[mobileB][3] = true
   end
   -- save the updated conflict table for the mobile
   SetConflictTable(mobileA, tableA)
 end

ValidConflictRelationTable

 --- Validate a conflict relation table (make sure it's not expired)
 -- @param conflictRelationTable(luaTable) A single entry from return value 
    GetConflictTable()
 -- @return true if valid, false if not
 function ValidConflictRelationTable(conflictRelationTable)
   return (
       conflictRelationTable ~= nil
       and
       (
           -- frozen
           conflictRelationTable[2] == true
           or
           -- or non-expired
           DateTime.UtcNow < conflictRelationTable[2]
       )
   )
 end

Server Settings

 welcome this will discuss the contents of the globals\server_settings\conflict.lua
 this documentation is correct as of version 6.0 PRECB1 Release 

 ServerSettings.Conflict = {
   -- Do guards attack non-guard protected karma levels who are aggressors?
   GuardsKillAggressors = true,
   -- The duration of the Aggressor/Victim/Defender relation. 
   -- These must be locked in the same timespan so we can do reverse checks. 
   -- For example: Determine all aggressors by checking who a mobile is a Victim/Defender to.
   RelationDuration = TimeSpan.FromSeconds(100),
   -- Does the first hit count as a warning? 
   -- If this is disabled, all hits will immediately jump to aggressor.
   WarningEnabled = true,
   WarningDuration = TimeSpan.FromSeconds(50),
 }

Conflict Table

ServerSettings.Conflict Table Description
GuardsKillAggressors true/false
   -- Do guards attack non-guard protected karma levels who are aggressors?
RelationDuration Duration of 100 seconds
WarningEnabled true/false
   -- Does the first hit count as a warning? 
   -- If this is disabled, all hits will immediately jump to aggressor.
WarningDuration Duration of 50 seconds