diff options
| author | Anson Bridges <bridges.anson@gmail.com> | 2022-10-11 00:15:48 -0400 |
|---|---|---|
| committer | Anson Bridges <bridges.anson@gmail.com> | 2022-10-11 00:15:48 -0400 |
| commit | e7fb9bacf3ebb5209f90f412757c35276af51e85 (patch) | |
| tree | 2dfac9d1273bf5efa1da5cfe82b4d8e64ae0bf3a /godot/scripts/characters | |
| parent | 7dbec964a375598d454e04719576eb6c469a5d7b (diff) | |
ai cannon-manning state machine
Diffstat (limited to 'godot/scripts/characters')
| -rw-r--r-- | godot/scripts/characters/Idle.gd | 2 | ||||
| -rw-r--r-- | godot/scripts/characters/ManCannon.gd | 107 | ||||
| -rw-r--r-- | godot/scripts/characters/NetworkedCharacter.gd | 14 | ||||
| -rw-r--r-- | godot/scripts/characters/player_controller_new.gd | 164 |
4 files changed, 246 insertions, 41 deletions
diff --git a/godot/scripts/characters/Idle.gd b/godot/scripts/characters/Idle.gd new file mode 100644 index 0000000..4be00e4 --- /dev/null +++ b/godot/scripts/characters/Idle.gd @@ -0,0 +1,2 @@ +extends State + diff --git a/godot/scripts/characters/ManCannon.gd b/godot/scripts/characters/ManCannon.gd new file mode 100644 index 0000000..b4d111c --- /dev/null +++ b/godot/scripts/characters/ManCannon.gd @@ -0,0 +1,107 @@ +extends State + +enum ManCannonStates { INITIAL, OPERATE_CANNON, MOVE_TO_CANNON, FIND_CANNONBALL } +var ai_substate: int +var path_requested: bool = false +var retry_timer: float = 0.0 +var searching_for: String + +var cannon: NetMachine + +# Corresponds to the `_process()` callback. +func update(delta: float) -> void: + retry_timer -= delta + +# Virtual function. Corresponds to the `_physics_process()` callback. +func physics_update(_delta: float) -> void: + match ai_substate: + ManCannonStates.INITIAL: + _state_initial() + ManCannonStates.MOVE_TO_CANNON: + _state_move_to_cannon() + ManCannonStates.FIND_CANNONBALL: + _state_find_cannonball() + ManCannonStates.OPERATE_CANNON: + _state_operate_cannon() + +func _state_initial() -> void: + if cannon.loaded: + change_substate(ManCannonStates.MOVE_TO_CANNON) + else: + change_substate(ManCannonStates.FIND_CANNONBALL) + +func _state_move_to_cannon() -> void: + if !path_requested: + #CharacterAIManager.request_find_path(owner, Vector3.ZERO, true, cannon.get_node("AINavPoint")) + owner.ai_set_path_target(cannon.get_node("AINavPoint").global_transform.origin) + owner.ai_set_look_status(cannon.steer_area, "TRACK") + path_requested = true + if (owner.global_transform.origin - cannon.get_node("AINavPoint").global_transform.origin).length_squared() > 4: + owner.ai_set_path_target(cannon.get_node("AINavPoint").global_transform.origin) + if retry_timer <= 0 and owner.use_ray.is_colliding(): + if owner.use_ray.get_collider() == cannon.steer_area: + owner.use_held = false + owner.initiate_use() + if !cannon.loaded: + owner.use_held = true + retry_timer = 3.1 + + if owner.machine == cannon: + owner.ai_set_look_status(null, "NONE") + retry_timer = 1.0 + change_substate(ManCannonStates.OPERATE_CANNON) + +func _state_find_cannonball() -> void: + #CHECK FOR BALL BOX FIRST! /setaistate Player ManCannon + if !path_requested: + CharacterAIManager.request_find_object(owner, "BallBarrel", 50, true) + searching_for = "BallBarrel" + path_requested = true + #if !path_requested: + # CharacterAIManager.request_find_object(owner, "Cannonball", 50, false) + # searching_for = "Cannonball" + # path_requested = true + if owner.use_ray.is_colliding(): + if owner.use_ray.get_collider().name.begins_with("UseArea") and owner.use_ray.get_collider().get_parent().name.begins_with(searching_for): + owner.initiate_use() + + #cannonball found + if owner.inventory["cannonball"] > 0: + change_substate(ManCannonStates.MOVE_TO_CANNON) + +func _state_operate_cannon() -> void: + if owner.machine != cannon: #if control lost + if is_instance_valid(cannon): + change_substate(ManCannonStates.MOVE_TO_CANNON) + else: + state_machine.transition_to("Idle") + return + if retry_timer <= 0.0: + cannon.attack1() + owner.initiate_use() + change_substate(ManCannonStates.FIND_CANNONBALL) + +func change_substate(to: int): + path_requested = false + ai_substate = to + +func enter(msg := {}) -> void: + retry_timer = 0 + ai_substate = ManCannonStates.INITIAL + if "cannon" in msg.keys(): + cannon = msg["cannon"] + else: + if owner.ai_target_machine != null: + cannon = owner.ai_target_machine + return + var bodies = owner.get_node("AISearchArea").get_overlapping_bodies() + for body in bodies: + if body.name.begins_with("Cannon"): + cannon = body + return + #if cannon not found + print("cannon not found") + state_machine.transition_to("Idle") + +func exit() -> void: + pass diff --git a/godot/scripts/characters/NetworkedCharacter.gd b/godot/scripts/characters/NetworkedCharacter.gd index c8e90a4..c14d46f 100644 --- a/godot/scripts/characters/NetworkedCharacter.gd +++ b/godot/scripts/characters/NetworkedCharacter.gd @@ -15,14 +15,21 @@ var world: Spatial = null var carrying: bool = false var carrying_object: NetObject = null +var loading: bool = false +var load_target: RigidBody = null +var load_ammo: String + # Control var controlling_machine: bool = false #whether character is riding/controlling something var machine: RigidBody = null -export var is_player: bool = false #whether character is currently controlled by a player +var is_player: bool = false #whether character is currently controlled by a player var ladder_m: Spatial = null +const inventory_caps: Dictionary = {"cannonball" : 1} +remote var inventory: Dictionary = {"cannonball" : 0} # + func get_init_info() -> Dictionary: - return {"linear_velocity" : linear_velocity, "angular_velocity" : angular_velocity, "controlling_machine" : controlling_machine, "team" : team, "health" : health, "nametag" : $Nametag.text} + return {"linear_velocity" : linear_velocity, "angular_velocity" : angular_velocity, "controlling_machine" : controlling_machine, "team" : team, "health" : health, "inventory": inventory, "nametag" : $Nametag.text} func mp_init(init_info: Dictionary): for variable in init_info.keys(): @@ -60,11 +67,12 @@ func take_control_of_machine(slave_machine: RigidBody): controlling_machine = true func lose_machine(): - if is_network_master(): world.cam.attach(self, "FIRSTPERSON", "./Neck/Head") + if is_network_master() and get_network_master() != 1: world.cam.attach(self, "FIRSTPERSON", "./Neck/Head") controlling_machine = false machine = null remotesync func damage(dmg_amt: int, _type: String, shooter: Array, extra: String): + print(dmg_amt) health -= dmg_amt if health <= 0 and is_network_master(): if shooter[0] != get_network_master() and shooter[0] != 1: world.rpc_id(shooter[0], "game_killsound") diff --git a/godot/scripts/characters/player_controller_new.gd b/godot/scripts/characters/player_controller_new.gd index 6e2a28b..b2af75c 100644 --- a/godot/scripts/characters/player_controller_new.gd +++ b/godot/scripts/characters/player_controller_new.gd @@ -7,6 +7,7 @@ var mouse_axis: Vector2 = Vector2.ZERO onready var head: Spatial = $Neck/Head onready var neck: Spatial = $Neck onready var melee_ray: RayCast = $"%MeleeRay" +onready var use_ray: RayCast = $"%UseRay" onready var gun_ray: RayCast = $Neck/Head/GunRay onready var carry_point: Position3D = $Neck/Head/CarryPoint onready var viewmodel: Spatial = $Neck/Head/VIEWMODEL_ARMS @@ -17,6 +18,7 @@ var direction := Vector3() var move_axis := Vector2() var floorspeed := Vector3() var jumping:bool = false +var use_held:bool = false var can_jump:bool = true onready var nav: NavigationAgent = $NavigationAgent @@ -37,10 +39,17 @@ var c_friction:float = 4.0 var air_control:float = 0.3 #ai -var ai_state: String = "IDLE" +onready var ai_state_machine:StateMachine = get_node("AIStateMachine") +enum AIStates { IDLE, MAN_CANNON } +export(AIStates) var ai_state = AIStates.IDLE + +export var ai_target_machine_path := NodePath() +var ai_target_machine: NetMachine = null var ai_should_pathfind: bool = false var ai_path_target_global: Vector3 = Vector3.ZERO +var ai_path_array: Array = [] +const AI_PATH_PROXIM_DIST_SQ: float = 1.0 var ai_look_target: Vector3 = Vector3.ZERO var ai_should_look: bool = false @@ -63,6 +72,15 @@ func _ready() -> void: $"%MeleeRay".add_exception(self) $"%UseRay".add_exception($AreaDetect) $"%MeleeRay".add_exception($AreaDetect) + + if ai_target_machine_path: + ai_target_machine = get_node(ai_target_machine_path) + if get_tree().get_network_unique_id() != 1: #only server needs ai processing + get_node("AIStateMachine").queue_free() + + #for playerbody in get_tree().get_nodes_in_group("player"): + # if playerbody.team == team and playerbody != self: + # add_collision_exception_with(playerbody) func add_weapon_vm(weapon_vm: Spatial, trfrm: Transform): viewmodel.get_node("Skeleton/GunBone").add_child(weapon_vm) @@ -70,10 +88,12 @@ func add_weapon_vm(weapon_vm: Spatial, trfrm: Transform): # Called every frame. 'delta' is the elapsed time since the previous frame func _process(_delta: float) -> void: - if is_player and !world.is_chatting: + if is_network_master() and !is_player: + pass + elif is_player and !world.is_chatting: + use_held = Input.is_action_pressed("use") if Input.is_action_just_pressed("use"): initiate_use() - if controlling_machine: if Input.is_action_just_pressed("fire"): machine.attack1() @@ -121,26 +141,57 @@ func initiate_use(): carrying_object.rpc("set_nm",1) carrying_object = null return - if $"%UseRay".is_colliding(): - var area_c = $"%UseRay".get_collider() - match area_c.name: - "SteerArea": #must be a networkedmachine - if area_c.get_parent().controllable: world.rpc_id(1, "_call_on_server", "_client_request_control_vehicle", {"id" : world.client_id, "machine_path" : area_c.get_parent().get_path(), "char_name" : name}) - "LadderArea": - mount_ladder(area_c.get_parent()) - "TugArea": - pass - "CarryArea": - if weapon.name == "HANDS" and weapon.can_pickup(): - carrying = true - carrying_object = area_c.get_parent() - carrying_object.rpc("set_nm",get_network_master()) - carry_point.global_transform.origin = carrying_object.global_transform.origin - _: - pass + + #interact with world + if !use_ray.is_colliding(): return + var area_c = use_ray.get_collider() + match area_c.name: + "SteerArea": #must be a networkedmachine + if area_c.get_parent().controllable: world.rpc_id(1, "_call_on_server", "_client_request_control_vehicle", {"id" : world.client_id, "machine_path" : area_c.get_parent().get_path(), "char_name" : name}) + elif !area_c.get_parent().loaded and inventory[area_c.get_parent().ammo_type] > 0: + loading = true + load_target = area_c.get_parent() + load_ammo = area_c.get_parent().ammo_type + "LadderArea": + mount_ladder(area_c.get_parent()) + "TugArea": + pass + "UseArea": #must have method use_generic + area_c.get_parent().use_generic(self) + "PickupArea": #must be a networkedprojectile. must have inventory_name property + var type: String = area_c.get_parent().inventory_name + if inventory[type] < inventory_caps[type]: + area_c.get_parent().rpc("net_remove") + inventory[type] += 1 + rset("inventory", inventory) + "CarryArea": + if weapon.name == "HANDS" and weapon.can_pickup(): + carrying = true + carrying_object = area_c.get_parent() + carrying_object.rpc("set_nm",get_network_master()) + carry_point.global_transform.origin = carrying_object.global_transform.origin + _: + pass + +func load_process(delta) -> void: + if use_ray.is_colliding() and use_ray.get_collider().get_parent() == load_target and use_held: + var progress: float =load_target.increase_load(delta) + if progress < 0: + if get_network_master() != 1: world.hud.hide_progress() + load_target.reset_load() + loading = false + load_target = null + inventory[load_ammo] -= 1 + else: + if get_network_master() != 1: world.hud.set_progress(progress) + else: + if get_network_master() != 1: world.hud.hide_progress() + load_target.reset_load() + loading = false + load_target = null func carry_process() -> void: - if !is_instance_valid(carrying_object): + if !is_instance_valid(carrying_object) or carrying_object.get_network_master() != get_network_master(): carrying_object = null carrying = false return @@ -157,6 +208,8 @@ func carry_process() -> void: # Called every physics tick. 'delta' is constant func _physics_process(delta: float) -> void: if is_network_master(): + if loading: load_process(delta) + move_dir_process() if !is_player: #ai behavior if ai_should_look: ai_look_at() if carrying: @@ -168,6 +221,21 @@ func _physics_process(delta: float) -> void: else: walk(delta) is_on_floor = false #reset whether is on floor in between frames + + +func move_dir_process(): + direction = Vector3() + if is_player: + var aim: Basis = head.get_global_transform().basis + direction += -move_axis.x * aim.z + move_axis.y * aim.x + direction.y = 0 + direction = direction.normalized() + else: + if ai_should_pathfind: + if !ai_path_process(): return + direction = ai_path_target_global - global_transform.origin + direction.y = 0 + direction = direction.normalized() # called each physics frame func on_floor_test() -> bool: @@ -198,19 +266,14 @@ func _integrate_forces(state: PhysicsDirectBodyState) -> void: break if i == player_state.get_contact_count() - 1: friction = 1 - nav.set_velocity(velocity) + rpc("set_phys_transform", transform, linear_velocity) if global_transform.origin.y < -30: rpc("damage", 500000, "drown", [1, "Davy Jones"], "") + + nav.set_velocity(velocity) func walk(_delta: float) -> void: - # Input - direction = Vector3() - var aim: Basis = head.get_global_transform().basis - direction += -move_axis.x * aim.z + move_axis.y * aim.x - direction.y = 0 - direction = direction.normalized() - if floor_normal != Vector3.UP: direction = direction.rotated(floor_normal.cross(Vector3.UP).normalized(), Vector3.UP.angle_to(floor_normal)) # Jump @@ -306,13 +369,17 @@ remotesync func play_weapon_sound(filepath) -> void: remotesync func add_rocket_to_scene(pos, dir, id): var rocket = preload("res://scenes/ballistics/Rocket.tscn").instance() world.get_node("BALLISTICS").add_child(rocket, true) - rocket.shooter = name + " (" + world.players_info[get_network_master()][0] + ")" + rocket.shooter = name + if get_network_master() != 1: rocket.shooter+= " (" + world.players_info[get_network_master()][0] + ")" rocket.shooter_id = id rocket.global_transform.origin = pos rocket.global_transform.basis = Basis(-1*dir.z, dir.y, dir.x) rocket.add_collision_exception_with(self) -func set_look_status(target, type="STATIC"): +func ai_set_state(state: String): + ai_state_machine.transition_to(state) + +func ai_set_look_status(target, type:String="STATIC"): if type == "STATIC": ai_look_target = target ai_should_look = true @@ -321,11 +388,9 @@ func set_look_status(target, type="STATIC"): ai_should_look = true ai_should_track = true else: + ai_should_look = false ai_should_track = false -func set_path_target(target): - ai_path_target_global = target - func ai_look_at(): if ai_should_track: if is_instance_valid(ai_track_object): @@ -335,10 +400,33 @@ func ai_look_at(): ai_should_track = false var p_neck: float = Vector3(ai_look_target.x - neck.global_transform.origin.x, 0, ai_look_target.z - neck.global_transform.origin.z).signed_angle_to(-neck.global_transform.basis.z, neck.global_transform.basis.y ) var p_head: float = Vector3(ai_look_target.x - neck.global_transform.origin.x, ai_look_target.y - head.global_transform.origin.y, ai_look_target.z - neck.global_transform.origin.z).signed_angle_to( -head.global_transform.basis.z, head.global_transform.basis.x) - if abs(p_neck) > 0.01: - neck.rotation_degrees.y = lerp(neck.rotation_degrees.y, neck.rotation_degrees.y-p_neck, AI_LOOK_SPEED+abs(p_neck/3.1415)) - if abs(p_head) > 0.01: - head.rotation_degrees.x = lerp(head.rotation_degrees.x, head.rotation_degrees.x-p_head, AI_LOOK_SPEED+abs(p_head/3.1415)) + neck.rotation_degrees.y = lerp(neck.rotation_degrees.y, neck.rotation_degrees.y-p_neck, AI_LOOK_SPEED+abs(p_neck/3.1415)) + head.rotation_degrees.x = lerp(head.rotation_degrees.x, head.rotation_degrees.x-p_head, AI_LOOK_SPEED+abs(p_head/3.1415)) if abs(p_neck) <= 0.01 and abs(p_head) <= 0.01 and !ai_should_track: ai_should_look = false + +func ai_set_path_target(target: Vector3): + ai_should_pathfind = true + nav.set_target_location(target) + #CharacterAIManager.request_find_path(self, target, true) + +func ai_set_path_array(arr: PoolVector3Array): + world.draw_path(arr) + ai_should_pathfind = true + ai_path_array = arr + ai_path_target_global = ai_path_array[0] + +func ai_path_process() -> bool: +# if (global_transform.origin - ai_path_array[0]).length_squared() < AI_PATH_PROXIM_DIST_SQ: +# ai_path_array.remove(0) +# if len(ai_path_array) == 0: #destination reached +# ai_should_pathfind = false +# return false +# else: ai_path_target_global = ai_path_array[0] +# return true + if nav.is_navigation_finished(): + ai_should_pathfind = false + return false + ai_path_target_global = nav.get_next_location() + return true |
