Difference between revisions of "Denizen Herding Behavior"

From RogueBasin
Jump to navigation Jump to search
Line 27: Line 27:


finished[x][y][z] = 1
finished[x][y][z] = 1
soundscape[x][y][z][activator] = soundscape[x][y][z][activator] or {} --for moveenemytowards
soundscape[x][y][z][activator] = soundscape[x][y][z][activator] or {} --for moveenemytowards
insert(soundscape[x][y][z][activator],{["strength"] = intensity})
insert(soundscape[x][y][z][activator],{["strength"] = intensity})
local counter = intensity
local counter = intensity
while(counter <= intensity and counter >= 1) do
while(counter <= intensity and counter >= 1) do
counter = counter - 1
counter = counter - 1
working,future = future, {}
working,future = future, {}
local x,y,z
local x,y,z
for k,v in pairs(working) do
for k,v in pairs(working) do
for x = v[1]- 1,v[1] + 1 do
for x = v[1]- 1,v[1] + 1 do
finished[x] = finished[x] or {}
finished[x] = finished[x] or {}
for y = v[2] -1,v[2] + 1 do
for y = v[2] -1,v[2] + 1 do
finished[x][y] = finished[x][y] or {}
finished[x][y] = finished[x][y] or {}
for z = v[3] - 1,v[3] + 1 do
for z = v[3] - 1,v[3] + 1 do
if z < data.amountofzlevels and z >= 1 and (not checkforedgeofmap(x,y)) and (not finished[x][y][z]) and (not tiles[x][y][z]["filled"]) and (not tiles[x][y][z]["open"]) then
 
if z < data.amountofzlevels and z >= 1 and (not checkforedgeofmap(x,y)) and (not finished[x][y][z]) and  
(not tiles[x][y][z]["filled"]) and (not tiles[x][y][z]["open"]) then
 
finished[x][y][z] = 1
finished[x][y][z] = 1
insert(future,{x,y,z})
insert(future,{x,y,z})
soundscape[x][y][z][activator] = soundscape[x][y][z][activator] or {} --for moveenemytowards
soundscape[x][y][z][activator] = soundscape[x][y][z][activator] or {} --for moveenemytowards
insert(soundscape[x][y][z][activator],{["strength"] = counter})
insert(soundscape[x][y][z][activator],{["strength"] = counter})
--the following visualizes the sound data.
--the following visualizes the sound data.
-- slang.gotorc(y+data.mapoffsety,x+data.mapoffsetx)
-- slang.gotorc(y+data.mapoffsety,x+data.mapoffsetx)
-- slang.setcolor(counter)
-- slang.setcolor(counter)
-- slang.writechar("*")
-- slang.writechar("*")
-- slang.refresh()
-- slang.refresh()
end
end
end
end
end
end
end
end
end
end


end
end
end
end


-----------------------
-----------------------
Data about the "soundscape" is stored, for now, just with the data of who made the sound and how intensly it was heard at the square. soundscape[x][y][z][the object that activated it][irreleveant key][strengthofsound] and any other pertinent variables about the noise would also be stored there.
 
Data about the "soundscape" is stored, for now, just with the data of who made the sound and how intensly it was heard at the  
square. soundscape[x][y][z][the object that activated it][irreleveant key][strengthofsound] and any other pertinent variables about  
the noise would also be stored there.


To use this data in pathfinding to get your denizens to herd, simply do the following:
To use this data in pathfinding to get your denizens to herd, simply do the following:


1/x times that the creature activates pathfinding to choose a square with the shortest path, simply weight the RELATIVE VALUE of those squares.  Squares with more noise generated by creatures who are the same species as me should be weighted with an amount relative to the total noise on that square from those animals except myself.  Its a simple matter of using a function like "total_sound_at_square_except_me(me,x,y,z).
1/x times that the creature activates pathfinding to choose a square with the shortest path, simply weight the RELATIVE VALUE of  
those squares.  Squares with more noise generated by creatures who are the same species as me should be weighted with an amount  
relative to the total noise on that square from those animals except myself.  Its a simple matter of using a function like "total_sound_at_square_except_me(me,x,y,z).


My code for moving the denizens is at follows.  the table "Distances" is a table of data in the form distances[x][y][z] = amount, where distances[x][y][z] is the distance of a particular square to the destination square.  It's a little hokey but it's pretty simple too:
My code for moving the denizens is at follows.  the table "Distances" is a table of data in the form distances[x][y][z] = amount, where distances[x][y][z] is the distance of a particular square to the destination square.  It's a little hokey but it's pretty simple too:
Line 70: Line 109:


function funcs.totalsoundfromob(ob,x,y,z)
function funcs.totalsoundfromob(ob,x,y,z)
--print("Totaling sound")
--print("Totaling sound")
local soundscape = data.soundscape
local soundscape = data.soundscape
local totalstr = 0
local totalstr = 0
for k2,v2 in pairs(soundscape[x][y][z][ob]) do
for k2,v2 in pairs(soundscape[x][y][z][ob]) do
totalstr = totalstr + v2["strength"]
totalstr = totalstr + v2["strength"]
end
end
return totalstr
return totalstr
end
end


function funcs.totalsoundatspot(ob,x,y,z) --totals sounds at spot from "my species"
function funcs.totalsoundatspot(ob,x,y,z) --totals sounds at spot from "my species"
local soundscape = data.soundscape
local soundscape = data.soundscape
local sound
local sound
local totalstrength = 0
local totalstrength = 0
local numfriends = 0
local numfriends = 0
for k,v in pairs(soundscape[x][y][z]) do
for k,v in pairs(soundscape[x][y][z]) do
if ob["species"] == k["species"] and k ~= ob and (not k["isplayer"]) then --not k isplayer for sanity purposes.
if ob["species"] == k["species"] and k ~= ob and (not k["isplayer"]) then --not k isplayer for sanity purposes.
sound = funcs.totalsoundfromob(k,x,y,z)
sound = funcs.totalsoundfromob(k,x,y,z)
totalstrength = totalstrength + sound
totalstrength = totalstrength + sound
numfriends = numfriends + 1
numfriends = numfriends + 1
end
end
end
end
-- print("total strength "..totalstrength)
-- print("total strength "..totalstrength)
return totalstrength, numfriends
return totalstrength, numfriends
end
end
--ob contains ob.x and ob.y and ob.z, targ contains targ.x,y,z etc.
--ob contains ob.x and ob.y and ob.z, targ contains targ.x,y,z etc.
function funcs.moveenemytowards(ob,targ)
function funcs.moveenemytowards(ob,targ)
-- print("Moving "..ob.name)
-- print("Moving "..ob.name)
-- if targ then print("Towards"..targ.x.." "..targ.y.." "..targ.z.." from "..ob.x.." "..ob.y.." "..ob.z) end
-- if targ then print("Towards"..targ.x.." "..targ.y.." "..targ.z.." from "..ob.x.." "..ob.y.." "..ob.z) end
local numfriends
local numfriends
local soundonhomesquare, numfriends = funcs.totalsoundatspot(ob,ob.x,ob.y,ob.z)
local soundonhomesquare, numfriends = funcs.totalsoundatspot(ob,ob.x,ob.y,ob.z)
local distances
local distances
-- if not targ and ob.activateherding and soundonhomesquare < 0 then --if i dont hear anything and im supposed to be moving randomly
 
-- if not targ and ob.activateherding and soundonhomesquare < 0 then --if i dont hear anything and im supposed to be moving  
randomly
 
if targ and targ.name then print("Targ name "..targ.name) end
if targ and targ.name then print("Targ name "..targ.name) end
-- print("Starting pathfinding")
-- print("Starting pathfinding")
if targ then funcs.pathfindfromto(ob,targ) distances = ob.distances end --gets a path... checks for a new path every so often.
 
if targ then funcs.pathfindfromto(ob,targ) distances = ob.distances end --gets a path... checks for a new path every so  
often.
 
-- if not targ then distances = nil end
-- if not targ then distances = nil end
-- print("Finished pathfinding")
-- print("Finished pathfinding")
local soundscape = data.soundscape
local soundscape = data.soundscape
local totalsound
local totalsound
local oldx,oldy,oldz = ob.x,ob.y,ob.z
local oldx,oldy,oldz = ob.x,ob.y,ob.z
--print("MOVING A MONSTER!!")
--print("MOVING A MONSTER!!")
local x local y
local x local y
local z
local z
local lowestdistance = {distance = 1000000 ,x=oldx,y=oldy,z=oldz} --HARDCODED LIMIT
local lowestdistance = {distance = 1000000 ,x=oldx,y=oldy,z=oldz} --HARDCODED LIMIT
local iterations = 0
local iterations = 0


local movementintelligence
local movementintelligence
if ob.movementintelligence then movementintelligence = ob.movementintelligence
if ob.movementintelligence then movementintelligence = ob.movementintelligence
else movementintelligence = 20 end
else movementintelligence = 20 end


while(iterations < movementintelligence) do
while(iterations < movementintelligence) do
iterations = iterations + 1
iterations = iterations + 1
x = math.random(ob["x"]-1,ob["x"]+1)
x = math.random(ob["x"]-1,ob["x"]+1)
y = math.random(ob["y"]-1,ob["y"]+1)
y = math.random(ob["y"]-1,ob["y"]+1)
z = math.random(ob["z"]-1,ob["z"]+1)
z = math.random(ob["z"]-1,ob["z"]+1)
-- for x = ob["x"] -1,ob["x"] + 1 do iterating in this pattern makes us select the upper left corner...
-- for x = ob["x"] -1,ob["x"] + 1 do iterating in this pattern makes us select the upper left corner...
-- for y = ob["y"] -1,ob["y"] + 1 do
-- for y = ob["y"] -1,ob["y"] + 1 do
-- for z = ob["z"] -1,ob["z"] + 1 do
-- for z = ob["z"] -1,ob["z"] + 1 do
if ((not targ) and (not funcs.checkforblockpassageofpath(x,y,z))) or (targ) then
if ((not targ) and (not funcs.checkforblockpassageofpath(x,y,z))) or (targ) then
local relativevalue = 0
local relativevalue = 0
if ob.activateherding then totalsound = funcs.totalsoundatspot(ob,x,y,z) end
if ob.activateherding then totalsound = funcs.totalsoundatspot(ob,x,y,z) end
if targ and not distances then return end --at destination already
if targ and not distances then return end --at destination already
if ((not targ) or (distances and distances[x] and distances[x][y] and distances[x][y][z])) and not funcs.checkforedgeofmap(x,y) then --was and distances[x][y][z]
 
if ((not targ) or (distances and distances[x] and distances[x][y] and distances[x][y][z])) and not  
funcs.checkforedgeofmap(x,y) then --was and distances[x][y][z]
 
if targ then relativevalue = distances[x][y][z] + relativevalue end
if targ then relativevalue = distances[x][y][z] + relativevalue end
--print("SOUND "..soundonhomesquare)
--print("SOUND "..soundonhomesquare)
local soundvariable
local soundvariable
local usesoundvariable = true
local usesoundvariable = true
if ob.tightgroups then soundvariable = ob.prefersoundlevel * numfriends end
if ob.tightgroups then soundvariable = ob.prefersoundlevel * numfriends end
if ob.loosegroups then soundvariable = ob.prefersoundlevel end
if ob.loosegroups then soundvariable = ob.prefersoundlevel end
if ob.prefersoundlevel == 0 then usesoundvariable = false end  
if ob.prefersoundlevel == 0 then usesoundvariable = false end  


if (not targ) and ob.activateherding and soundonhomesquare == 0 then --if i dont hear anything and have no target, choose a random square.
 
 
if (not targ) and ob.activateherding and soundonhomesquare == 0 then --if i dont hear anything and  
have no target, choose a random square.
 
relativevalue = relativevalue - (math.random(1,1000) * 10)
relativevalue = relativevalue - (math.random(1,1000) * 10)
elseif ob.activateherding and (usesoundvariable and (soundonhomesquare < (soundvariable)) or not usesoundvariable) and soundonhomesquare > 0 then --if sound is lower than threshhold, herd HALF the time..
 
if math.random(1,ob.herdingtendency) == 1 then relativevalue = relativevalue - (totalsound * 10) end --herd according to a fraction of times ob.herdingtendency should be 10 or 20 or 5 or whatever.  If im beyond the sound range by not herding, i wont herd from that point until i hear more sound..
elseif ob.activateherding and (usesoundvariable and (soundonhomesquare < (soundvariable)) or not  
usesoundvariable) and soundonhomesquare > 0 then --if sound is lower than threshhold, herd HALF the time..
 
if math.random(1,ob.herdingtendency) == 1 then relativevalue = relativevalue - (totalsound * 10)  
end --herd according to a fraction of times ob.herdingtendency should be 10 or 20 or 5 or whatever.  If im beyond the sound range  
by not herding, i wont herd from that point until i hear more sound..
 
elseif (not targ) then --if i dont care about sound, move randomly.
elseif (not targ) then --if i dont care about sound, move randomly.
relativevalue = relativevalue - (math.random(1,1000) * 10) --was +
relativevalue = relativevalue - (math.random(1,1000) * 10) --was +
end
end
if relativevalue < lowestdistance["distance"] then --go towards high strength sound
if relativevalue < lowestdistance["distance"] then --go towards high strength sound
lowestdistance = {["distance"] = relativevalue,["x"]=x,["y"]=y,["z"]=z}
lowestdistance = {["distance"] = relativevalue,["x"]=x,["y"]=y,["z"]=z}
end
end
  end
  end
  end --ends if not targ...
  end --ends if not targ...
-- end
-- end
-- end
-- end
-- end
-- end
end --ends iterations over intelligence...
end --ends iterations over intelligence...
funcs.moveobject(ob,lowestdistance["x"],lowestdistance["y"],lowestdistance["z"])
funcs.moveobject(ob,lowestdistance["x"],lowestdistance["y"],lowestdistance["z"])
-- funcs.drawspotsimple(oldx,oldy,oldz,nil,ob)
-- funcs.drawspotsimple(oldx,oldy,oldz,nil,ob)
end
end
----------------------------------------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------------------------------------
--so as you can see the "relative value" of the square gets changed depending on how much sound is detected nearby.
--so as you can see the "relative value" of the square gets changed depending on how much sound is detected nearby.
Hope this helps someone!  Check out my game Ascii Wilderness, which is open source LUA.
Hope this helps someone!  Check out my game Ascii Wilderness, which is open source LUA.
http://asciiwilderness.blogspot.com/p/ascii-wilderness.html
http://asciiwilderness.blogspot.com/p/ascii-wilderness.html
In this game, the deer properly herd together based on their internal variables.  This code also depends on some internal herding variables from the participants such as "activateherding = 1" and "tightgroups" or "loosegroups" = 1 and "prefersoundlevel" = amount
 
In this game, the deer properly herd together based on their internal variables.  This code also depends on some internal herding  
variables from the participants such as "activateherding = 1" and "tightgroups" or "loosegroups" = 1 and "prefersoundlevel" = amount
and also "herdingtendency" = 3- 10
and also "herdingtendency" = 3- 10


Using this code, my ghouls will wait up for other nearby ghouls before closing in for the attack.  Sweet!
Using this code, my ghouls will wait up for other nearby ghouls before closing in for the attack.  Sweet!

Revision as of 01:42, 25 December 2011

This article is by Russell Ackerman. I thought I would share my success at getting my deer to herd in my roguelike, I did it by doing this: This article relates to the LUA programming language Sound propogates out from a square, dropping informatino about itself. The function is as follows:

function funcs.makesound(intensity,activator,x,y,z)

-- print("Making a sound .."..intensity.." from "..activator["name"].." X "..x.." Y "..y.." Z "..z)

local objectloc = data.objectloc

local working, future = {}, Template:X,y,z

local soundscape = data.soundscape

local finished = {}

local insert = table.insert

local tiles = data.tiles

local checkforedgeofmap = funcs.checkforedgeofmap

finished[x] = {}

finished[x][y] = {}

finished[x][y][z] = 1

soundscape[x][y][z][activator] = soundscape[x][y][z][activator] or {} --for moveenemytowards

insert(soundscape[x][y][z][activator],{["strength"] = intensity})

local counter = intensity

while(counter <= intensity and counter >= 1) do

counter = counter - 1

working,future = future, {}

local x,y,z

for k,v in pairs(working) do

for x = v[1]- 1,v[1] + 1 do

finished[x] = finished[x] or {}

for y = v[2] -1,v[2] + 1 do

finished[x][y] = finished[x][y] or {}

for z = v[3] - 1,v[3] + 1 do

if z < data.amountofzlevels and z >= 1 and (not checkforedgeofmap(x,y)) and (not finished[x][y][z]) and (not tiles[x][y][z]["filled"]) and (not tiles[x][y][z]["open"]) then

finished[x][y][z] = 1

insert(future,{x,y,z})

soundscape[x][y][z][activator] = soundscape[x][y][z][activator] or {} --for moveenemytowards

insert(soundscape[x][y][z][activator],{["strength"] = counter})

--the following visualizes the sound data.

-- slang.gotorc(y+data.mapoffsety,x+data.mapoffsetx)

-- slang.setcolor(counter)

-- slang.writechar("*")

-- slang.refresh()

end

end

end

end

end


end

end



Data about the "soundscape" is stored, for now, just with the data of who made the sound and how intensly it was heard at the square. soundscape[x][y][z][the object that activated it][irreleveant key][strengthofsound] and any other pertinent variables about the noise would also be stored there.

To use this data in pathfinding to get your denizens to herd, simply do the following:

1/x times that the creature activates pathfinding to choose a square with the shortest path, simply weight the RELATIVE VALUE of those squares. Squares with more noise generated by creatures who are the same species as me should be weighted with an amount relative to the total noise on that square from those animals except myself. Its a simple matter of using a function like "total_sound_at_square_except_me(me,x,y,z).

My code for moving the denizens is at follows. the table "Distances" is a table of data in the form distances[x][y][z] = amount, where distances[x][y][z] is the distance of a particular square to the destination square. It's a little hokey but it's pretty simple too:


function funcs.totalsoundfromob(ob,x,y,z)

--print("Totaling sound")

local soundscape = data.soundscape

local totalstr = 0

for k2,v2 in pairs(soundscape[x][y][z][ob]) do

totalstr = totalstr + v2["strength"]

end

return totalstr

end


function funcs.totalsoundatspot(ob,x,y,z) --totals sounds at spot from "my species"

local soundscape = data.soundscape

local sound

local totalstrength = 0

local numfriends = 0

for k,v in pairs(soundscape[x][y][z]) do

if ob["species"] == k["species"] and k ~= ob and (not k["isplayer"]) then --not k isplayer for sanity purposes.

sound = funcs.totalsoundfromob(k,x,y,z)

totalstrength = totalstrength + sound

numfriends = numfriends + 1

end

end

-- print("total strength "..totalstrength)

return totalstrength, numfriends

end


--ob contains ob.x and ob.y and ob.z, targ contains targ.x,y,z etc. function funcs.moveenemytowards(ob,targ)

-- print("Moving "..ob.name)

-- if targ then print("Towards"..targ.x.." "..targ.y.." "..targ.z.." from "..ob.x.." "..ob.y.." "..ob.z) end

local numfriends

local soundonhomesquare, numfriends = funcs.totalsoundatspot(ob,ob.x,ob.y,ob.z)

local distances

-- if not targ and ob.activateherding and soundonhomesquare < 0 then --if i dont hear anything and im supposed to be moving randomly

if targ and targ.name then print("Targ name "..targ.name) end

-- print("Starting pathfinding")

if targ then funcs.pathfindfromto(ob,targ) distances = ob.distances end --gets a path... checks for a new path every so often.

-- if not targ then distances = nil end

-- print("Finished pathfinding")

local soundscape = data.soundscape

local totalsound

local oldx,oldy,oldz = ob.x,ob.y,ob.z

--print("MOVING A MONSTER!!")

local x local y

local z

local lowestdistance = {distance = 1000000 ,x=oldx,y=oldy,z=oldz} --HARDCODED LIMIT

local iterations = 0


local movementintelligence

if ob.movementintelligence then movementintelligence = ob.movementintelligence

else movementintelligence = 20 end


while(iterations < movementintelligence) do

iterations = iterations + 1

x = math.random(ob["x"]-1,ob["x"]+1)

y = math.random(ob["y"]-1,ob["y"]+1)

z = math.random(ob["z"]-1,ob["z"]+1)

-- for x = ob["x"] -1,ob["x"] + 1 do iterating in this pattern makes us select the upper left corner...

-- for y = ob["y"] -1,ob["y"] + 1 do

-- for z = ob["z"] -1,ob["z"] + 1 do

if ((not targ) and (not funcs.checkforblockpassageofpath(x,y,z))) or (targ) then

local relativevalue = 0

if ob.activateherding then totalsound = funcs.totalsoundatspot(ob,x,y,z) end

if targ and not distances then return end --at destination already

if ((not targ) or (distances and distances[x] and distances[x][y] and distances[x][y][z])) and not funcs.checkforedgeofmap(x,y) then --was and distances[x][y][z]

if targ then relativevalue = distances[x][y][z] + relativevalue end

--print("SOUND "..soundonhomesquare)

local soundvariable

local usesoundvariable = true

if ob.tightgroups then soundvariable = ob.prefersoundlevel * numfriends end

if ob.loosegroups then soundvariable = ob.prefersoundlevel end

if ob.prefersoundlevel == 0 then usesoundvariable = false end


if (not targ) and ob.activateherding and soundonhomesquare == 0 then --if i dont hear anything and have no target, choose a random square.

relativevalue = relativevalue - (math.random(1,1000) * 10)

elseif ob.activateherding and (usesoundvariable and (soundonhomesquare < (soundvariable)) or not usesoundvariable) and soundonhomesquare > 0 then --if sound is lower than threshhold, herd HALF the time..

if math.random(1,ob.herdingtendency) == 1 then relativevalue = relativevalue - (totalsound * 10) end --herd according to a fraction of times ob.herdingtendency should be 10 or 20 or 5 or whatever. If im beyond the sound range by not herding, i wont herd from that point until i hear more sound..

elseif (not targ) then --if i dont care about sound, move randomly.

relativevalue = relativevalue - (math.random(1,1000) * 10) --was +

end

if relativevalue < lowestdistance["distance"] then --go towards high strength sound

lowestdistance = {["distance"] = relativevalue,["x"]=x,["y"]=y,["z"]=z}

end

end

end --ends if not targ...

-- end

-- end

-- end

end --ends iterations over intelligence...

funcs.moveobject(ob,lowestdistance["x"],lowestdistance["y"],lowestdistance["z"])

-- funcs.drawspotsimple(oldx,oldy,oldz,nil,ob)

end


--so as you can see the "relative value" of the square gets changed depending on how much sound is detected nearby.

Hope this helps someone! Check out my game Ascii Wilderness, which is open source LUA.

http://asciiwilderness.blogspot.com/p/ascii-wilderness.html

In this game, the deer properly herd together based on their internal variables. This code also depends on some internal herding variables from the participants such as "activateherding = 1" and "tightgroups" or "loosegroups" = 1 and "prefersoundlevel" = amount and also "herdingtendency" = 3- 10


Using this code, my ghouls will wait up for other nearby ghouls before closing in for the attack. Sweet!