• Reference
  • LUA API
  • Example
  • Behavior Tree

Behavior Tree

In RPGs, we can often see a number of monsters gathering around their territory and wandering around, and in the middle of the territory there is usually a boss or leader monster that is protected by all the other monsters. So how does this work? Here is a simple example that may give you some insight into how this works.

Brief description

  1. when the monster is low on blood, it will automatically run to the boss
  2. When the boss doesn't attack, it will restore its own blood level first
  3. If the boss is at a healthy level, it will restore the unit with the lowest blood level among the minions
  4. when the player does not enter the boss's territory, the monsters will patrol the territory continuously
  5. when the player enters the territory, the monsters automatically attack the player
  6. when the player leaves the territory, the monsters return to their constant patrol status

Code

Methods

do
	local playerId = 1
	local unitLead -- lead actor
	local unitHealer -- healer monster
	--[[
		Function
	--]]
	-- Game initialisation
	function gameInit()
		unitLead = cli.actor_unit(7)
		unitHealer = cli.actor_unit(6)
		local player = cli.player(playerId)
		player:camera_focus(unitLead)
		player:select(unitLead)
		msgPro()
	end
	
	-- Message Processor
	function msgPro()
		local player = cli.player(31)
		local monsterUnitGroup = player._base.get_all_unit_id() -- get player owner's all unit's id
		local guardKey = 134230223 -- unit guard key
		local healerKey = 134257924 -- unit healer key
		cli.loop(2, function(timer)
			monsterUnitGroup = player._base.get_all_unit_id() -- refresh hostile group
			for _, v in Python.enumerate(monsterUnitGroup) do
				local unitTemp = cli.actor_unit(v)
				if unitTemp:get_key() == guardKey then
					guardActionAI(unitTemp)
				end
				if unitTemp:get_key() == healerKey then
					healerActionAI(unitTemp)
				end
			end
		end)
	end
	
	-- Behaviour AI of the Near Guard
	function guardActionAI(unitGuard)
		local unitHealerPoint = unitHealer:get_point()
		local unitLeadPoint = unitLead:get_point()
		local distance = unitHealerPoint * unitLeadPoint
		if distance >= 300 then
			local curGuardHp = unitGuard:get("hp_cur") -- get real current HP property
			local maxGuardHp = unitGuard:get("hp_max") -- get real max HP property
			if curGuardHp > 0.5 * maxGuardHp then
				local torritoryCenterPoint = cli.actor_point(1000000002)
				local torritoryRange = 800
				if torritoryCenterPoint * unitLeadPoint < torritoryRange then
					unitGuard:attack(unitLead)
				else
					local randAngle = math.random() * 360
					local randRadius = math.random() * torritoryRange
					local x, y = torritoryCenterPoint:offset(randAngle, randRadius * 100):get()
					local randTargetPoint = cli.point(x / 100, y / 100, 0)
					unitGuard:move(randTargetPoint)
				end
			else
				unitGuard:move(unitHealer:get_point()) -- make guard run to healer's position
			end
		else
			unitGuard:attack(unitHealer:get_point())
		end
	end
	
	-- Change unit blood volume
	local function changeHp(unit, Hp)
		if Hp == 0 then return end
		local lowestHpUnitCurHp = unit:get("hp_cur")
		local lowestHpUnitMaxHp = unit:get("hp_max")
		local lowestHpUnitHpTemp = (lowestHpUnitCurHp + Hp) > lowestHpUnitMaxHp and lowestHpUnitMaxHp or (lowestHpUnitCurHp + Hp)
		unit:set("hp_cur", lowestHpUnitHpTemp)
		local lowestHpUnitPoint = unit:get_point()
		local harmTextType
		if Hp > 0 then
			harmTextType = "Recover"
		else
			harmTextType = "Phy"
		end
		Hp = Hp >> 31 == 0 and Hp or ~Hp + 1 -- get abs
		if not unit:is_alive() then return end
		cli.create_harm_text(lowestHpUnitPoint, harmTextType, tostring(Hp))
	end
	
	-- Behavioural AI for treatment soldiers
	function healerActionAI(unitHealer)
		local curHealerHp = unitHealer:get("hp_cur")
		local maxHealerHp = unitHealer:get("hp_max")
		if maxHealerHp == curHealerHp then
			local lowestHpUnit = getLowestHpUnit()
			if not lowestHpUnit then
				local torritoryCenterPoint = cli.actor_point(1000000002)
				local unitLeadPoint = unitLead:get_point()
				if torritoryCenterPoint * unitLeadPoint <= 200 then
					unitHealer:attack(unitLead)
				else
					unitHealer:move(torritoryCenterPoint)
				end
			else
				unitHealer:stop()
				local direction = lowestHpUnit:get_point() / unitHealer:get_point() -- Calculate direction
				unitHealer:set_facing(direction, 0)
				changeHp(lowestHpUnit, 20)
				unitHealer:add_animation({
					name = "ability_30001",
					speed = 1,
					init_time = 0,
					end_time = -1,
					loop = false
				}) -- Casting action
			end
		else
			unitHealer:stop()
			changeHp(unitHealer, 20)
			unitHealer:add_animation({
				name = "ability_30001",
				speed = 1,
				init_time = 0,
				end_time = -1,
				loop = false
			}) -- Casting action
		end
	end
	
	-- Get the friendly unit with the lowest blood count
	function getLowestHpUnit()
		local lowestHpUnit = nil
		local monsterUnitGroup = cli.player(31)._base.get_all_unit_id()
		local lowestHpPercentage = 1
		for _, v in Python.enumerate(monsterUnitGroup) do
			local lowestHpUnitTemp = cli.actor_unit(v)
			local lowestHpUnitCurHp = lowestHpUnitTemp:get("hp_cur")
			local lowestHpUnitMaxHp = lowestHpUnitTemp:get("hp_max")
			if lowestHpUnitCurHp / lowestHpUnitMaxHp < lowestHpPercentage then
				lowestHpPercentage = lowestHpUnitCurHp / lowestHpUnitMaxHp
				lowestHpUnit = lowestHpUnitTemp
			end
		end
		return lowestHpUnit
	end

Event

	cli.game:event("Game-Init", gameInit)
end

Download

link unvailable for now.