Jump to content

Recommended Posts

Been trying to work outside my comfort zone by experimenting with entity behavior / brains. It's not really something I have experience with.

I've made Rockjaws act a little more menacing by circling their target / their target's boat before attacking, but when they leap off the boat it breaks their stalking behavior for some reason and I can't figure out why.

Also their wandering behavior doesn't work at all...?


Experimental Rockjaw Brain:

Spoiler
require "behaviours/wander"
require "behaviours/chaseandattack"
require "behaviours/attackwall"
require "behaviours/minperiod"
require "behaviours/leash"
require "behaviours/faceentity"
require "behaviours/doaction"
require "behaviours/standstill"
require("behaviours/runaway")
local BrainCommon = require("brains/braincommon")
----------------------------------------------------------------------------------------------------------------------------------------------------
local RESET_COMBAT_DELAY = 10

local MIN_STALKING_TIME = 2
local MAX_STALKING_CHASE_TIME = 4

local RUN_AWAY_DIST = 6
local STOP_RUN_AWAY_DIST = 6
local HUNTER_PARAMS =
{
	tags = { "_combat" },
	notags = { "INLIMBO", "playerghost", "invisible", "hidden", "flight", "shadowcreature" },
	oneoftags = { "character", "monster", "largecreature", "shadowminion" },
	fn = function(ent, inst)
		--Don't run away from non-hostile animals unless they are attacking us
		return ent.components.combat:TargetIs(inst)
			or ent:HasTag("character")
			or ent:HasTag("monster")
			or ent:HasTag("shadowminion")
	end,
}

local SharkBrain = Class(Brain, function(self, inst)
	Brain._ctor(self, inst)
end)

----------------------------------------------------------------------------------------------------------------------------------------------------

local function isOnWater(inst)
    return not inst:GetCurrentPlatform() and not TheWorld.Map:IsVisualGroundAtPoint(inst.Transform:GetWorldPosition())
end

local function Leap(inst)
	inst:PushEvent("leap")
	inst.components.timer:StartTimer("leap_cd", TUNING.MINOTAUR_LEAP_CD * 10)
end

local function ShouldChase_Boat(inst)
	return inst.components.combat:HasTarget() and inst.components.combat.target:GetCurrentPlatform() 
	and not inst.components.combat:InCooldown() and not inst.components.timer:TimerExists("leap_cd")
end

local function Attack(inst)
    inst:PushEvent("dobite")
end


--==================--
--==oO STALKING Oo==--
--==================--
local function GetHomePos(inst)
	return inst.components.knownlocations:GetLocation("prison")
end

local function ShouldStalk(inst)
	return inst:IsStalking() and (inst.components.combat:InCooldown() or not inst.components.combat:HasTarget())
end

local function ShouldCircle(inst)
	return inst.components.combat:HasTarget() and inst.components.combat:InCooldown() and not inst:IsStalking()
end

local function ShouldChase_Water(inst)
	return inst.components.combat:HasTarget() and not inst.components.combat.target:GetCurrentPlatform() and not inst.components.combat:InCooldown()
end

local function DoStalking(inst)
	--print()
	--print("------------------------")
	local target = inst:GetStalking()
	if target ~= nil then
		
		if target:GetCurrentPlatform() ~= nil then
			--print("Target is on a boat")
			target = target:GetCurrentPlatform()
		end
		
		--print("TARGET	:	"..target)
		
		local x, y, z = inst.Transform:GetWorldPosition()
		local x1, y1, z1 = target.Transform:GetWorldPosition()
		local dx = x1 - x
		local dz = z1 - z
		local dist = math.sqrt(dx * dx + dz * dz)
		--print("DIST	:	"..dist)

		
		local strafe_angle = Remap(math.clamp(dist, 4, 8), 4, 8, 135, 75)
		--print("STRAFE_ANGLE	:	"..strafe_angle)


		local rot = inst.Transform:GetRotation()
		--print("ROTATION	:	"..rot)
		
		
		local rot1 = math.atan2(-dz, dx) * RADIANS
		--print("ROT_1	:"..rot1)
		
		
		local rota = rot1 - strafe_angle
		local rotb = rot1 + strafe_angle
		--print("ROT_A	:	"..rota)
		--print("ROT_A	:	"..rotb)
		
		
		if DiffAngle(rot, rota) < 30 then
			rot1 = rota
		elseif DiffAngle(rot, rotb) < 30 then
			rot1 = rotb
		else
			rot1 = math.random() < 0.5 and rota or rotb
		end
		rot1 = rot1 * DEGREES
		--print("NEW ROT_1	:	"..rot1)
		
		--print("VECTOR3	:	"..(x + math.cos(rot1) * 10, 0, z - math.sin(rot1) * 10))
		--print("------------------------")
		return Vector3(x + math.cos(rot1) * 10, 0, z - math.sin(rot1) * 10)
	end
end

local function IsStalkingFar(inst)
	local target = inst:GetStalking()
	return target ~= nil and not inst:IsNear(target, 8)
end

local function IsStalkingTooClose(inst)
	local target = inst:GetStalking()
	return target ~= nil and inst:IsNear(target, TUNING.DAYWALKER_ATTACK_RANGE)
end

local function IsStalkingTooFar(inst)
	local target = inst:GetStalking()
	return target ~= nil and not inst:IsNear(target, TUNING.DAYWALKER_ATTACK_RANGE *3)
end

local function GetFaceTargetFn(inst)
	return inst.components.combat.target
end

local function KeepFaceTargetFn(inst, target)
	return inst.components.combat:TargetIs(target)
end

--===============--
--==oO CLAMP Oo==-- Unused for now.
--===============--
local function PushClamp(inst, target)
	inst:PushEvent("clamp", {target = self.inst.components.combat.target:GetCurrentPlatform()})
end

--================--
--==oO WANDER Oo==--
--================--
local function removefood(inst, target)
	if inst._removefood ~= nil then
		inst.foodtoeat = nil
		inst:RemoveEventCallback("onremove", inst._removefood, target)
		inst:RemoveEventCallback("onpickup", inst._removefood, target)
		inst._removefood = nil
	end
end

local function isfoodnearby(inst)
    local target = FindEntity(inst, SEE_DIST, function(item) return inst.components.eater:CanEat(item) and not item:GetCurrentPlatform() and not TheWorld.Map:IsVisualGroundAtPoint(item.Transform:GetWorldPosition()) end)

    -- don't target food if its too close..ironically
    if target and target:GetDistanceSqToInst(inst) < 6*6 then
        return nil
    end

	if inst.foodtoeat ~= target then
		removefood(inst)
		if target then
			inst.foodtoeat = target
			inst._removefood = function() removefood(inst, target) end
			inst:ListenForEvent("onremove", inst._removefood, target)
			inst:ListenForEvent("onpickup", inst._removefood, target)

			return BufferedAction(inst, target, ACTIONS.EAT)
		end
	end
end

local function EatFishAction(inst)
    if not inst.components.timer:TimerExists("gobble_cooldown") then
        local target = FindEntity(inst, SEE_FOOD_DIST, function(food)
                return TheWorld.Map:IsOceanAtPoint(inst.Transform:GetWorldPosition())
            end,
            nil,
            nil,
            {"oceanfish"})

        if target then
            inst.foodtarget = target
            local targetpos = Vector3(target.Transform:GetWorldPosition())

            local act = BufferedAction(inst, target, ACTIONS.EAT)
            act.validfn = function() return not (target.components.inventoryitem and target.components.inventoryitem:IsHeld()) end
            return act
        end
    end

    return nil
end

local WANDER_TIMES = {minwalktime=2, randwalktime=0.5, minwaittime=0.0, randwaittime=0.0}

local function GetWanderPoint(inst)
    local target = inst:GetNearestPlayer(true)
    return target ~= nil and target:GetPosition() or nil
end

local function getdirectionFn(inst)
    local DIST = 6
    local theta = inst.Transform:GetRotation() * DEGREES
    local offset = Vector3(DIST * math.cos( theta ), 0, -DIST * math.sin( theta ))
    local x,y,z = inst.Transform:GetWorldPosition()

    local r = math.random() * 2 - 1
    local newdir = (inst.Transform:GetRotation() + r*r*r * 60) * DEGREES

    if TheWorld.Map:IsVisualGroundAtPoint(x+offset.x,0,z+offset.z) then
        newdir =  newdir + PI
    end

    return newdir
end



function SharkBrain:OnStart()
	local root = PriorityNode({
		WhileNode(function() return not self.inst.sg:HasStateTag("jumping") end, "NotJumpingBehaviour",
			PriorityNode({
				
				--IF NOT IN WATER, PUSH AOE ATTACK. No need for any other check, Sharks automatically jump off towards nearest ocean tile.
				WhileNode(function() return not isOnWater(self.inst) end, "NOT on water",
					PriorityNode({
						DoAction(self.inst, Attack, "attack", true),
						self.inst:StartAttackCooldown()
					})),
				
				--IF NO TARGET, WANDER AND EAT FISH. This doesn't work at all.
				WhileNode(function() return isOnWater(self.inst) and not self.inst.components.combat:HasTarget() end, "on water",
					PriorityNode({
						BrainCommon.PanicTrigger(self.inst),
						DoAction(self.inst, isfoodnearby, "gotofood", true),
						DoAction(self.inst, EatFishAction, "eat fish", true),

						Wander(self.inst, GetWanderPoint, 40, WANDER_TIMES, getdirectionFn)
					})),
				
				--CHECK IF WE SHOULD CIRCLE TARGET.
				WhileNode(function() return ShouldCircle(self.inst) end, "Set Circle Target",
					PriorityNode({
						NotDecorator(ActionNode(function()
							if self.inst.canstalk and not self.inst.components.timer:TimerExists("stalk_cd") then
								self.inst:SetStalking(self.inst.components.combat.target)
							end
							
							if self.inst:IsStalking() then
								self.inst:StartAttackCooldown()
							else
								self.inst.components.combat:ResetCooldown()
							end
						end)),
					})),
					
				--CIRLCE TARGET. IF TARGET TOO FAR, THEN BYPASS ATTACK COOLDOWN.
				WhileNode(function() return ShouldStalk(self.inst) end, "Stalking",
					ParallelNode{
						SequenceNode{
							ParallelNodeAny{
								WaitNode(MIN_STALKING_TIME),
								ConditionWaitNode(function() return IsStalkingFar(self.inst) end),
							},
							ConditionWaitNode(function() return IsStalkingTooFar(self.inst) end),
							ActionNode(function() self.inst.components.combat:ResetCooldown() end),
						},
						Leash(self.inst, DoStalking, 0, 0, false),
					}),
					
				--ATTACK TARGET IF COMBAT COOLDOWN TIMER RUNS OUT.
				--TODO: IF TARGET ON BOAT, SWAP BETWEEN CLAMPING DOWN ON BOAT(like crabking_claw) / RAMMING INTO BOAT / JUMPING ABOARD.
				--IF OUR TARGET IS IN WATER, CHASE AND ATTACK.
				WhileNode(function() return ShouldChase_Water(self.inst) end, "Chase",
					PriorityNode({
						WhileNode(function() return self.inst:IsStalking() end, "Stalking Chase",
							ParallelNodeAny{
								SequenceNode{
									WaitNode(MAX_STALKING_CHASE_TIME),
									ActionNode(function() self.inst:SetStalking(nil) end),
								},
								ChaseAndAttack(self.inst, nil, nil, nil, nil, true),
							}),
						ChaseAndAttack(self.inst),
					})),
				
				
				--IF TARGET ON BOAT AND NO LEAP TIMER, LEAP ABOARD BOAT. This stops stalking behaviour after for some reason...?
				WhileNode(function() return ShouldChase_Boat(self.inst) end, "Leap To Boat",
					ActionNode(function() Leap(self.inst) end)
				),

			})),
	})

	self.bt = BT(self.inst, root)
end

return SharkBrain

 

 

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
  • Create New...