Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Population AI #26

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
57 changes: 57 additions & 0 deletions AI/population/Agent.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
class_name Agent
extends Node
# single population agent

enum JOBS{
LOW,
MIDDLE,
HIGH
}
enum JOB_TYPES {
LIBRARIAN,#1 max
FIREFIGHTER,# 4 max
PARK_RANGER, # 1 max
BUSINESS_OWNER, #1 max
BUSINESS_EMPLOYEE, #15 max
UTILITY_PLANT_OPERATOR,#2 max
HOSPITAL_EMPLOYEE, #8 max
POLICE_OFFICER, #4 max
SEWAGE_WORKER, #2 max
WASTE_TREATMENT_WORKER, #2 max
MUSEUM_WORKER, #1 max
SCHOOL_EMPLOYEE, #8 max
TIDE_SCIENTIST, #1 max
RAIN_SCIENTIST, #1 max
WIND_SCIENTIST #1 max
}
# tile in which agent lives in
var residential_tile = null
# tile in which agent works in
var commercial_tile = null
# type of job agent can have
var job = null
var level = JOBS.LOW
# employment status
var hasJob = false

var unemployed_month = null
var months_passed = 0

func _init(tile):
residential_tile = tile

func change_job(tile):
hasJob = true
commercial_tile = tile

func job_level():
var r = randf()
if r < 0.6:
level = JOBS.LOW
elif r < 0.9:
level = JOBS.MIDDLE
else:
level = JOBS.HIGH

func change_residence(tile):
residential_tile = tile
15 changes: 15 additions & 0 deletions AI/population/Population.tscn
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[gd_scene load_steps=4 format=2]

[ext_resource path="res://AI/population/population.gd" type="Script" id=1]
[ext_resource path="res://addons/behavior_tree/src/blackboard.gd" type="Script" id=2]
[ext_resource path="res://AI/population/population_agent.tscn" type="PackedScene" id=3]

[node name="Population" type="Node"]
script = ExtResource( 1 )

[node name="Blackboard" type="Node" parent="."]
script = ExtResource( 2 )

[node name="BehaviorTree" parent="." instance=ExtResource( 3 )]
_blackboard = NodePath("../Blackboard")
_agent = NodePath("..")
34 changes: 34 additions & 0 deletions AI/population/UpdateAgent.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
extends Node

var ActiveAgents = []
var totalJobs = 0
#above 10% unemployment and people are unhappy
var UNEMPLOYMENT_LIMIT = .10

func add_agent(i, j):
var tile = Global.tileMap[i][j]
var newAgent = load("res://AI/population/Agent.gd")
var currentAgent = newAgent.new(tile)
ActiveAgents.append(currentAgent)

# Called every frame. 'delta' is the elapsed time since the previous frame.
#func _process(delta):
# pass
func total_agents():
#print(ActiveAgents.size())
return ActiveAgents.size()

func can_work():
var p = total_agents()
if p == 0:
return false
if (1-totalJobs/p) > UNEMPLOYMENT_LIMIT:
return true
else:
return false

func increase_total_jobs():
totalJobs = totalJobs + 1;

func decrease_total_jobs():
totalJobs = totalJobs - 1;
15 changes: 15 additions & 0 deletions AI/population/can_find_job.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
extends BTConditional


# A conditional node MUST NOT override _tick but only
# _pre_tick and _post_tick.

# The condition is checked BEFORE ticking. So it should be in _pre_tick.
# Checks if the queue is empty. If it is, do not proceed.
func _pre_tick(agent: Node, blackboard: Blackboard) -> void:
if UpdateAgent.can_work():
#print("passed can_find_job")
verified = true
else:
#print("failed can_find_job")
verified = false
124 changes: 124 additions & 0 deletions AI/population/can_find_job_leaf.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
extends BTLeaf


var BASE_MOVE_CHANCE = 0.01
var rng = RandomNumberGenerator.new()

func _tick(agent: Node, blackboard: Blackboard) -> bool:
var current_agent = blackboard.get_data("queue").pop_front()
var mapHeight = Global.mapHeight
var mapWidth = Global.mapWidth
var foundJob = false
# iterates through the map to find possible working spaces
for i in mapHeight:
for j in mapWidth:
if foundJob == false:
var current_tile = Global.tileMap[i][j]

if current_tile.jobCapacity < current_tile.jobMax:
if current_tile.has_utilities() && current_tile.tileDamage == 0:
if current_tile.zone == Tile.TileZone.COMMERCIAL:
foundJob = true
if current_tile.jobCapacity == 0:
current_agent.level = Agent.JOBS.HIGH
current_agent.job = Agent.JOB_TYPES.BUSINESS_OWNER
else:
current_agent.job_level()
current_agent.job = Agent.JOB_TYPES.BUSINESS_EMPLOYEE
current_tile.jobCapacity += 1
current_agent.change_job(current_tile)

elif current_tile.zone == Tile.TileZone.PUBLIC_WORKS:
if current_tile.inf == Tile.TileInf.PARK:
foundJob = true
current_tile.jobCapacity += 1
current_agent.job = Agent.JOB_TYPES.PARK_RANGER
current_agent.change_job(current_tile)
current_agent.job_level()
elif current_tile.inf == Tile.TileInf.FIRE_STATION:
foundJob = true
current_tile.jobCapacity += 1
current_agent.job = Agent.JOB_TYPES.FIREFIGHTER
current_agent.change_job(current_tile)
current_agent.job_level()
elif current_tile.inf == Tile.TileInf.HOSPITAL:
foundJob = true
current_tile.jobCapacity += 1
current_agent.job = Agent.JOB_TYPES.HOSPITAL_EMPLOYEE
current_agent.change_job(current_tile)
current_agent.job_level()
elif current_tile.inf == Tile.TileInf.POLICE_STATION:
foundJob = true
current_tile.jobCapacity += 1
current_agent.job = Agent.JOB_TYPES.POLICE_OFFICER
current_agent.change_job(current_tile)
current_agent.job_level()
elif current_tile.inf == Tile.TileInf.LIBRARY:
foundJob = true
current_tile.jobCapacity += 1
current_agent.job = Agent.JOB_TYPES.LIBRARIAN
current_agent.change_job(current_tile)
current_agent.job_level()
elif current_tile.inf == Tile.TileInf.MUSEUM:
foundJob = true
current_tile.jobCapacity += 1
current_agent.job = Agent.JOB_TYPES.MUSEUM_WORKER
current_agent.change_job(current_tile)
current_agent.job_level()
elif current_tile.inf == Tile.TileInf.SCHOOL:
foundJob = true
current_tile.jobCapacity += 1
current_agent.job = Agent.JOB_TYPES.SCHOOL_EMPLOYEE
current_agent.change_job(current_tile)
current_agent.job_level()
elif current_tile.inf == Tile.TileInf.UTILITIES_PLANT:
foundJob = true
current_tile.jobCapacity += 1
current_agent.job = Agent.JOB_TYPES.UTILITY_PLANT_OPERATOR
current_agent.change_job(current_tile)
current_agent.job_level()
elif current_tile.inf == Tile.TileInf.SEWAGE_FACILITY:
foundJob = true
current_tile.jobCapacity += 1
current_agent.job = Agent.JOB_TYPES.SEWAGE_WORKER
current_agent.change_job(current_tile)
current_agent.job_level()
elif current_tile.inf == Tile.TileInf.WASTE_TREATMENT:
foundJob = true
current_tile.jobCapacity += 1
current_agent.job = Agent.JOB_TYPES.WASTE_TREATMENT_WORKER
current_agent.change_job(current_tile)
current_agent.job_level()
# if living space is suitable and chance of moving allows for move to happen, move agent
#if (selectTile > rng.randf_range(0, maxRange)):
# UpdateAgent.ActiveAgents.find(current_agent).residential_tile = current_tile
# moved = true
# break
if foundJob == true:
current_agent.unemployed_month = null
current_agent.months_passed = 0
UpdateAgent.increase_total_jobs()
else:
if current_agent.unemployed_month == null:
current_agent.unemployed_month = UpdateDate.month
elif current_agent.unemployed_month != UpdateDate.month:
current_agent.unemployed_month = UpdateDate.month
current_agent.months_passed +=1

if current_agent.months_passed > 6:
var currTile = current_agent.residential_tile
currTile.remove_people(1)
UpdateAgent.ActiveAgents.erase(current_agent)

# updates queue
check_empty(blackboard)
#print("succeeded can_find_job")
#succeeds, if ticked
return succeed()

# Checks to see if the last item in the queue was consumed. Stops the AI
func check_empty(blackboard: Blackboard) -> void:
var empty = blackboard.get_data("queue").empty()
#print(str("empty",empty))
if empty:
blackboard.set_data("queue_empty", true)
15 changes: 15 additions & 0 deletions AI/population/cannot_find_job.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
extends BTConditional


# A conditional node MUST NOT override _tick but only
# _pre_tick and _post_tick.

# The condition is checked BEFORE ticking. So it should be in _pre_tick.
# Checks if the queue is empty. If it is, do not proceed.
func _pre_tick(agent: Node, blackboard: Blackboard) -> void:
if UpdateAgent.can_work():
#print("failed cannot_find_job")
verified = false
else:
#print("passed cannot_find_job")
verified = true
31 changes: 31 additions & 0 deletions AI/population/cannot_find_job_leaf.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
extends BTLeaf


var BASE_MOVE_CHANCE = 0.01
var rng = RandomNumberGenerator.new()

func _tick(agent: Node, blackboard: Blackboard) -> bool:
var current_agent = blackboard.get_data("queue").pop_front()
if current_agent.unemployed_month == null:
current_agent.unemployed_month = UpdateDate.month
elif current_agent.unemployed_month != UpdateDate.month:
current_agent.unemployed_month = UpdateDate.month
current_agent.months_passed +=1

if current_agent.months_passed > 6:
var currTile = current_agent.residential_tile
currTile.remove_people(1)
UpdateAgent.ActiveAgents.erase(current_agent)

# updates queue
check_empty(blackboard)
#print("succeeded cannot_find_job")
#succeeds, if ticked
return succeed()

# Checks to see if the last item in the queue was consumed. Stops the AI
func check_empty(blackboard: Blackboard) -> void:
var empty = blackboard.get_data("queue").empty()
#print(str("empty",empty))
if empty:
blackboard.set_data("queue_empty", true)
18 changes: 18 additions & 0 deletions AI/population/happy.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
extends BTLeaf

var rng = RandomNumberGenerator.new()

func _tick(agent: Node, blackboard: Blackboard) -> bool:
var current_agent = blackboard.get_data("queue").pop_front()
print("geere")
# updates queue
check_empty(blackboard)

#succeeds, if ticked
return succeed()

# Checks to see if the last item in the queue was consumed. Stops the AI
func check_empty(blackboard: Blackboard) -> void:
var empty = blackboard.get_data("queue").empty()
if empty:
blackboard.set_data("queue_empty", true)
17 changes: 17 additions & 0 deletions AI/population/has_job.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
extends BTConditional


# A conditional node MUST NOT override _tick but only
# _pre_tick and _post_tick.


# The condition is checked BEFORE ticking. So it should be in _pre_tick.
# Checks if the queue is empty. If it is, do not proceed.
func _pre_tick(agent: Node, blackboard: Blackboard) -> void:
var current_agent = blackboard.get_data("queue").front()
if (current_agent.hasJob == false):
#print("failed has_job")
verified = false
else:
#print("passed has_job")
verified = true
17 changes: 17 additions & 0 deletions AI/population/has_no_job.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
extends BTConditional


# A conditional node MUST NOT override _tick but only
# _pre_tick and _post_tick.


# The condition is checked BEFORE ticking. So it should be in _pre_tick.
# Checks if the queue is empty. If it is, do not proceed.
func _pre_tick(agent: Node, blackboard: Blackboard) -> void:
var current_agent = blackboard.get_data("queue").front()
if (current_agent.hasJob == false):
#print("passed no_job")
verified = true
else:
#print("failed no_job")
verified = false
31 changes: 31 additions & 0 deletions AI/population/is_happy.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
extends BTConditional


# A conditional node MUST NOT override _tick but only
# _pre_tick and _post_tick.

var BASE_LEAVE_CHANCE = 0.01
var BASE_STAY_CHANCE = 0.01
var NO_UTILITIES_UNHAPPINESS = 10
var DAMAGE_UNHAPPINESS = 10
var SEVERE_DAMAGE_UNHAPPINESS = 30
# The condition is checked BEFORE ticking. So it should be in _pre_tick.
# Checks if the queue is empty. If it is, do not proceed.
func _pre_tick(agent: Node, blackboard: Blackboard) -> void:
var current_agent = blackboard.get_data("queue").front()
var currTile = current_agent.residential_tile
var leaveChance = 0
var status = currTile.get_status()
var selectTile = BASE_STAY_CHANCE * (currTile.landValue + currTile.happiness)
if (!currTile.has_utilities()):
leaveChance += NO_UTILITIES_UNHAPPINESS
if (status == Tile.TileStatus.LIGHT_DAMAGE || status == Tile.TileStatus.MEDIUM_DAMAGE):
leaveChance += DAMAGE_UNHAPPINESS
elif (status == Tile.TileStatus.HEAVY_DAMAGE):
leaveChance += SEVERE_DAMAGE_UNHAPPINESS
if (selectTile * leaveChance > 0.5):
#print("failed is_happy")
verified = false
else:
#print("passed is_happy")
verified = true
Loading