inching towards better class inheritence and multiplayer compatibility

This commit is contained in:
2023-11-15 15:19:40 +11:00
parent f004f64b71
commit 1500c22ccc
204 changed files with 1920 additions and 1951 deletions

View File

@ -1,7 +1,7 @@
extends Node3D
class_name AStarGraph3D
@export var grid_size := Vector2(21, 13)
@export var grid_size := Vector2i(21, 13)
@export var point_gap := 1.2
var non_build_locations = []
var astar := AStar3D.new()

View File

@ -0,0 +1,34 @@
extends Projectile
class_name ExplosiveProjectile
@export var explosion_range := 3.0
func _process(delta: float) -> void:
super._process(delta)
if time_alive >= lifetime:
explode()
func _on_body_entered(_body: Node) -> void:
explode()
func explode():
if is_multiplayer_authority():
for enemy in get_tree().get_nodes_in_group("Enemies"):
if global_position.distance_to(enemy.global_position) <= explosion_range:
hit(enemy)
networked_hit.rpc(get_tree().root.get_path_to(enemy))
networked_kill.rpc()
queue_free()
func hit(target):
target.damage(damage)
@rpc("reliable")
func networked_hit(target_node_path):
var target = get_tree().root.get_node(target_node_path)
hit(target)

View File

@ -0,0 +1,17 @@
extends ExplosiveProjectile
class_name HomingProjectile
var target : Node3D
@export var acceleration := 40.0
@export var max_speed := 14.0
func _physics_process(_delta: float) -> void:
if is_instance_valid(target):
direction = global_position.direction_to(target.global_position)
#apply_central_force(direction * acceleration)
func _integrate_forces(state: PhysicsDirectBodyState3D) -> void:
state.linear_velocity += direction * acceleration * state.step
state.linear_velocity = state.linear_velocity.limit_length(max_speed)

View File

@ -0,0 +1,23 @@
extends RigidBody3D
class_name Projectile
@export var collision_shape : CollisionShape3D
var direction := Vector3.FORWARD
var force := 2.0
var damage := 0.0
var lifetime := 10.0
var time_alive := 0.0
func _process(delta: float) -> void:
time_alive += delta
func _on_body_entered(_body: Node) -> void:
pass # Replace with function body.
@rpc("reliable")
func networked_kill():
queue_free()

View File

@ -0,0 +1,15 @@
extends ExplosiveProjectile
class_name StatusApplyingProjectile
@export var status_stats : StatusStats
func hit(target):
super.hit(target)
target.status_manager.add_effect(build_status_object())
func build_status_object() -> StatusEffect:
var status = StatusEffect.new()
status.stats = status_stats
return status

View File

@ -1,7 +0,0 @@
extends Resource
class_name TowerStats
@export var can_target : Data.TargetType
@export var damage := 10.0
@export var fire_range := 20.0
@export var fire_rate := 1.0

View File

@ -1,6 +0,0 @@
extends Resource
class_name WeaponStats
@export var damage : int
@export var fire_rate : float
@export var fire_range : float

View File

@ -1,13 +1,13 @@
extends Resource
class_name Card
enum Faction {GENERIC}
enum Faction {GENERIC = 0}
@export var title : String
@export var rarity : Data.Rarity
@export var faction : Faction
@export var faction : Faction
@export var sprite : AtlasTexture
@export var turret : PackedScene
@export var weapon : PackedScene
@export var weapon_stats : WeaponStats
@export var tower_stats : TowerStats
@export var turret_scene : PackedScene
@export var weapon_scene : PackedScene
@export var weapon_stats : CardText
@export var tower_stats : CardText

View File

@ -0,0 +1,13 @@
extends Resource
class_name CardText
@export var target_type : Data.TargetType
@export var attributes : Array[StatAttribute]
@export_multiline var text : String
func get_attribute(attribute : String) -> float:
for stat in attributes:
if stat.key == attribute:
return stat.value
return 0.0

View File

@ -0,0 +1,5 @@
extends Resource
class_name StatAttribute
@export var key : String
@export var value : float

View File

@ -1,6 +0,0 @@
extends StatusEffect
class_name StatusOnFire
func proc(affected, stacks, _existing_effects):
affected.damage(stats.potency * stacks)

View File

@ -1,5 +1,5 @@
extends StatusEffect
class_name StatusPoison
class_name StatusDoT
func proc(affected, stacks, _existing_effects):

View File

@ -1,5 +0,0 @@
extends StatusEffect
class_name StatusRadioactive
func proc(affected, stacks, _existing_effects):
affected.damage(stats.potency * stacks)

View File

@ -1,5 +1,6 @@
extends StatusEffect
class_name StatusCold
class_name StatusSlow
func on_attached(affected, _existing_effects):
affected.movement_speed_penalty -= stats.potency

View File

@ -1,10 +0,0 @@
extends StatusEffect
class_name StatusSticky
func on_attached(affected, _existing_effects):
affected.movement_speed_penalty -= stats.potency
func on_removed(affected, _existing_effects):
affected.movement_speed_penalty += stats.potency

View File

@ -0,0 +1,13 @@
extends Tower
class_name HitscanTower
func shoot():
super.shoot()
if targeted_enemy and is_instance_valid(targeted_enemy) and targeted_enemy.alive:
targeted_enemy.damage(damage)
@rpc("reliable")
func networked_shoot():
super.networked_shoot()

View File

@ -0,0 +1,30 @@
extends Tower
class_name ProjectileTower
@export var projectile_scene : PackedScene
var force := 2.0
var projectile_id := 0
func shoot():
if is_multiplayer_authority():
networked_spawn_projectile.rpc(multiplayer.get_unique_id())
@rpc("reliable")
func networked_shoot():
super.networked_shoot()
shoot()
@rpc("reliable", "call_local")
func networked_spawn_projectile(peer_id):
var projectile = projectile_scene.instantiate() as Projectile
projectile.position = global_position + Vector3.UP
projectile.damage = damage
projectile.direction = -yaw_model.global_transform.basis.z
projectile.force = force
projectile.name = base_name + str(peer_id) + str(projectile_id)
get_tree().root.add_child(projectile)
projectile_id += 1

92
Scripts/Towers/tower.gd Normal file
View File

@ -0,0 +1,92 @@
extends Node3D
class_name Tower
@export var stats : CardText
@export var animator : AnimationPlayer
@export var pitch_model : MeshInstance3D
@export var yaw_model : MeshInstance3D
@export var range_indicator : CSGSphere3D
var base_name
var targeted_enemy
var time_since_firing := 0.0
var time_between_shots := 0.0
var damage := 0.0
var target_range := 0.0
func _ready() -> void:
time_between_shots = stats.get_attribute("Fire Delay")
damage = stats.get_attribute("Damage")
target_range = stats.get_attribute("Range")
range_indicator.radius = target_range
func preview_range(value):
range_indicator.set_visible(value)
func _process(delta: float) -> void:
if !is_multiplayer_authority():
return
if time_since_firing < time_between_shots:
time_since_firing += delta
func _physics_process(_delta: float) -> void:
if !is_multiplayer_authority():
#only doing the graphical sort of stuff but not shoot logic
if targeted_enemy:
if !is_instance_valid(targeted_enemy) or !targeted_enemy.alive or global_position.distance_to(targeted_enemy.global_position) > target_range:
targeted_enemy = null
else:
aim()
return
if !targeted_enemy:
acquire_target()
else:
if !is_instance_valid(targeted_enemy) or !targeted_enemy.alive or global_position.distance_to(targeted_enemy.global_position) > target_range:
targeted_enemy = null
if targeted_enemy:
aim()
if time_since_firing >= time_between_shots:
time_since_firing -= time_between_shots
shoot()
func aim():
yaw_model.look_at(targeted_enemy.global_position)
pitch_model.look_at(targeted_enemy.global_position)
pitch_model.rotation.x = 0.0
func acquire_target():
var most_progressed_enemy = null
for enemy in get_tree().get_nodes_in_group("Enemies"):
if global_position.distance_to(enemy.global_position) > target_range:
continue
var em_1 = enemy.movement_controller as EnemyMovement
var em_2 : EnemyMovement
if most_progressed_enemy != null:
em_2 = most_progressed_enemy.movement_controller as EnemyMovement
if (most_progressed_enemy == null or em_1.distance_remaining < em_2.distance_remaining) and enemy.stats.target_type & stats.target_type:
most_progressed_enemy = enemy
if most_progressed_enemy != null:
targeted_enemy = most_progressed_enemy
networked_acquire_target.rpc(get_tree().root.get_path_to(most_progressed_enemy))
func shoot():
animator.play("shoot")
if is_multiplayer_authority():
networked_shoot.rpc()
@rpc("reliable")
func networked_shoot():
shoot()
@rpc("reliable")
func networked_acquire_target(target_node_path):
targeted_enemy = get_tree().root.get_node(target_node_path)

View File

@ -0,0 +1,37 @@
extends Weapon
class_name HitscanWeapon
@export var raycast : RayCast3D
@export var range_debug_indicator : CSGSphere3D
var attack_range := 0.0
func _ready() -> void:
super._ready()
attack_range = stats.get_attribute("Range")
raycast.target_position = Vector3(0, 0, -attack_range)
range_debug_indicator.radius = attack_range
raycast.global_position = hero.camera.global_position
func shoot():
super.shoot()
if raycast.is_colliding():
var target = raycast.get_collider()
if target != null:
var target_hitbox = target.shape_owner_get_owner(raycast.get_collider_shape())
if target_hitbox is Hitbox:
hit(target, target_hitbox)
networked_hit.rpc(get_tree().root.get_path_to(target), get_tree().root.get_path_to(target_hitbox))
func hit(_target, target_hitbox : Hitbox):
target_hitbox.damage(damage)
@rpc("reliable")
func networked_hit(target_path : String, target_hitbox_path : String):
var target = get_tree().root.get_node(target_path)
var target_hitbox = get_tree().root.get_node(target_hitbox_path) as Hitbox
hit(target, target_hitbox)

View File

@ -0,0 +1,25 @@
extends Weapon
class_name ProjectileWeapon
@export var projectile_scene : PackedScene
var force := 2.0
var projectile_id := 0
func shoot():
super.shoot()
var projectile = projectile_scene.instantiate() as Projectile
projectile.position = global_position
projectile.damage = damage
projectile.direction = -global_transform.basis.z
projectile.force = force
projectile.name = str(multiplayer.get_unique_id()) + str(projectile_id)
get_tree().root.add_child(projectile)
projectile_id += 1
@rpc("reliable")
func networked_shoot():
super.networked_shoot()
shoot()

View File

@ -0,0 +1,15 @@
extends HitscanWeapon
class_name StatusApplyingWeapon
@export var status_stats : StatusStats
func hit(target, target_hitbox : Hitbox):
super.hit(target, target_hitbox)
target.status_manager.add_effect(build_status_object())
func build_status_object() -> StatusEffect:
var status = StatusEffect.new()
status.stats = status_stats
return status

58
Scripts/Weapons/weapon.gd Normal file
View File

@ -0,0 +1,58 @@
extends Node3D
class_name Weapon
@export var stats : CardText
@export var animator : AnimationPlayer
var hero : Hero
var trigger_held := false
var second_trigger_held := false
var time_since_firing := 0.0
var time_between_shots := 0.0
var damage := 0.0
func _ready() -> void:
time_between_shots = stats.get_attribute("Fire Delay")
damage = stats.get_attribute("Damage")
func set_hero(value):
hero = value
func _process(delta: float) -> void:
if time_since_firing < time_between_shots:
time_since_firing += delta
func _physics_process(_delta: float) -> void:
if trigger_held and time_since_firing >= time_between_shots:
time_since_firing -= time_between_shots
shoot()
networked_shoot.rpc()
func hold_trigger():
trigger_held = true
func release_trigger():
trigger_held = false
func hold_second_trigger():
second_trigger_held = true
func release_second_trigger():
second_trigger_held = false
func shoot():
animator.play("shoot")
@rpc
func networked_shoot():
animator.play("shoot")

View File

@ -3,28 +3,27 @@ class_name CardInHand
var stats : Card
@export var rarity_sprite : Sprite2D
@export var title_text: Label
@export var damage_text_text: Label
@export var damage_text: Label
@export var fire_rate_text_text: Label
@export var fire_rate_text: Label
@export var range_text_text: Label
@export var range_text: Label
@export var title_text : Label
@export var description : RichTextLabel
func set_card(value):
stats = value
title_text.text = stats.title
rarity_sprite.region_rect = Rect2(64 * stats.rarity, 0, 64, 64)
view_weapon()
func process_card_text(card_text : CardText) -> String:
var processed_string = card_text.text
for stat in card_text.attributes:
processed_string = processed_string.replace(stat.key, str(stat.value))
processed_string = processed_string.replace("/", "[color=red]")
processed_string = processed_string.replace("\\", "[color=black]")
return processed_string
func view_weapon():
damage_text.text = str(stats.weapon_stats.damage)
fire_rate_text.text = str(stats.weapon_stats.fire_rate)
range_text.text = str(stats.weapon_stats.fire_rate)
description.text = process_card_text(stats.weapon_stats)
func view_tower():
damage_text.text = str(stats.tower_stats.damage)
fire_rate_text.text = str(stats.tower_stats.fire_rate)
range_text.text = str(stats.tower_stats.fire_rate)
description.text = process_card_text(stats.tower_stats)

View File

@ -10,9 +10,9 @@ var player_keymap : PlayerKeymap
var wall_cost := 4
var printer_cost := 10
enum TargetType {LAND = 1, AIR = 2, BOTH = 3}
enum EnemyType {LAND = 1, AIR = 2}
enum Rarity {COMMON, UNCOMMON, RARE, EPIC, LEGENDARY}
enum TargetType {UNDEFINED = 0, LAND = 1, AIR = 2, BOTH = 3}
enum EnemyType {UNDEFINED = 0, LAND = 1, AIR = 2}
enum Rarity {COMMON = 0, UNCOMMON = 1, RARE = 2, EPIC = 3, LEGENDARY = 4}
var rarity_weights = {
"COMMON" = 50,
"UNCOMMON" = 30,
@ -32,18 +32,27 @@ func _ready() -> void:
characters.append(preload("res://PCs/Green/green.tres"))
characters.append(preload("res://PCs/Blue/blue.tres"))
#Common
cards.append(preload("res://PCs/Universal/ClassCards/Assault/card_assault.tres"))
cards.append(preload("res://PCs/Universal/ClassCards/BombLauncher/card_grenade_launcher.tres"))
cards.append(preload("res://PCs/Universal/ClassCards/Sniper/card_sniper.tres"))
cards.append(preload("res://PCs/Universal/ClassCards/BombLauncher/card_bomb_launcher.tres"))
cards.append(preload("res://PCs/Universal/ClassCards/Gatling/card_gatling.tres"))
cards.append(preload("res://PCs/Universal/ClassCards/GlueLauncher/card_glue_launcher.tres"))
cards.append(preload("res://PCs/Universal/ClassCards/RocketLauncher/card_rocket_launcher.tres"))
cards.append(preload("res://PCs/Universal/ClassCards/Flamethrower/card_flamethrower.tres"))
cards.append(preload("res://PCs/Universal/ClassCards/Blowdart/card_blowdart.tres"))
cards.append(preload("res://PCs/Universal/ClassCards/Fireball/card_fireball.tres"))
cards.append(preload("res://PCs/Universal/ClassCards/Icicle/card_icicle.tres"))
cards.append(preload("res://PCs/Universal/ClassCards/Reactor/card_reactor.tres"))
cards.append(preload("res://PCs/Universal/ClassCards/Refridgerator/card_refridgerator.tres"))
#Uncommon
#cards.append(preload("res://PCs/Universal/ClassCards/Blowdart/card_blowdart.tres"))
#cards.append(preload("res://PCs/Universal/ClassCards/Refridgerator/card_refridgerator.tres"))
#cards.append(preload("res://PCs/Universal/ClassCards/GlueLauncher/card_glue_launcher.tres"))
#Rare
#cards.append(preload("res://PCs/Universal/ClassCards/Flamethrower/card_flamethrower.tres"))
#cards.append(preload("res://PCs/Universal/ClassCards/DamageEnhancer/card_damage_enhancer.tres"))
#cards.append(preload("res://PCs/Universal/ClassCards/SpeedEnhancer/card_speed_enhancer.tres"))
#Epic
#cards.append(preload("res://PCs/Universal/ClassCards/Icicle/card_icicle.tres"))
#cards.append(preload("res://PCs/Universal/ClassCards/Fireball/card_fireball.tres"))
#cards.append(preload("res://PCs/Universal/ClassCards/GammaLaser/card_gamma_laser.tres"))
#Legendary
cards.append(preload("res://PCs/Universal/ClassCards/Sniper/card_sniper.tres"))
#cards.append(preload("res://PCs/Universal/ClassCards/Reactor/card_reactor.tres"))
#cards.append(preload("res://PCs/Universal/ClassCards/Lightning/card_lightning.tres"))
enemies.append(preload("res://Worlds/GreenPlanet/Enemies/dog.tres"))
enemies.append(preload("res://Worlds/GreenPlanet/Enemies/dog_fast.tres"))

View File

@ -93,7 +93,7 @@ func spawn_tower_preview():
last_tower_base = ray_collider
var card = inventory.selected_item
last_card = card
tower_preview = card.turret.instantiate() as Tower
tower_preview = card.turret_scene.instantiate() as Tower
tower_preview.stats = card.tower_stats
tower_preview.preview_range(true)
ray_collider.add_child(tower_preview)

View File

@ -23,7 +23,7 @@ var enemies := 0
var objective_health := 120
var wave := 0
var upcoming_wave
var pot : int
var pot : float
var UILayer : CanvasLayer
@ -97,7 +97,7 @@ func set_upcoming_wave():
if is_multiplayer_authority():
var spawn_power = WaveManager.calculate_spawn_power(wave + 1, connected_players_nodes.size())
var new_wave = WaveManager.generate_wave(spawn_power, level.enemy_pool)
networked_set_upcoming_wave.rpc(new_wave, 6 + (spawn_power / 100))
networked_set_upcoming_wave.rpc(new_wave, 6 + floori(spawn_power / 100))
@rpc("reliable", "call_local")
@ -158,6 +158,9 @@ func remove_player(peer_id):
func start_game():
game_active = true
enemies = 0
objective_health = 100
wave = 0
level.a_star_graph_3d.make_grid()
level.a_star_graph_3d.find_path()
set_upcoming_wave()

View File

@ -1,2 +0,0 @@
extends Node3D
class_name GroundEnemyController

58
Scripts/tower.gd Normal file
View File

@ -0,0 +1,58 @@
extends Node3D
class_name OldTower
@export var model : Node3D
@export var range_sphere : CSGSphere3D
@export var minimap_range_sphere : CSGSphere3D
var targeted_enemy
var cooldown := 0.0
var other_cooldown := 0.0
func _ready() -> void:
cooldown = 1.0 / stats.fire_rate
range_sphere.radius = stats.fire_range
minimap_range_sphere.radius = stats.fire_range
#minimap_range_sphere.set_visible(true)
func preview_range(value):
range_sphere.set_visible(value)
minimap_range_sphere.set_visible(value)
func _process(delta: float) -> void:
other_cooldown -= delta
if !targeted_enemy:
acquire_target()
else:
if model.global_position.distance_to(targeted_enemy.global_position) > stats.fire_range:
targeted_enemy = null
if targeted_enemy:
aim()
if other_cooldown <= 0:
shoot()
other_cooldown = cooldown
func shoot():
targeted_enemy.damage(stats.damage)
func aim():
model.look_at(targeted_enemy.global_position)
func acquire_target():
var most_progressed_enemy = null
for enemy in get_tree().get_nodes_in_group("Enemies"):
if model.global_position.distance_to(enemy.global_position) > stats.fire_range:
continue
var em_1 = enemy.movement_controller as EnemyMovement
var em_2 : EnemyMovement
if most_progressed_enemy != null:
em_2 = most_progressed_enemy.movement_controller as EnemyMovement
if (most_progressed_enemy == null or em_1.distance_remaining < em_2.distance_remaining) and enemy.stats.target_type & stats.can_target:
most_progressed_enemy = enemy
if most_progressed_enemy != null:
targeted_enemy = most_progressed_enemy

View File

@ -36,9 +36,11 @@ func toggle_collision():
@rpc("reliable", "call_local", "any_peer")
func networked_spawn_tower():
tower = inventory.selected_item.turret.instantiate() as Tower
tower = inventory.selected_item.turret_scene.instantiate() as Tower
tower.stats = inventory.selected_item.tower_stats
tower.name = "tower"
tower.base_name = name
tower.position = Vector3.UP
minimap_icon.modulate = Color.RED
add_child(tower)

View File

@ -7,15 +7,15 @@ func calculate_spawn_power(wave_number : int, number_of_players : int) -> int:
func generate_wave(spawn_power : int, spawn_pool : Array[Enemy]) -> Dictionary:
var wave = {}
#var sp_used = 0
var sp_used = 0
var enemy_types = randi_range(1, 5)
var enemy_choices = spawn_pool.duplicate()
var sp_allotment = spawn_power / enemy_types
var sp_allotment = floori(spawn_power / enemy_types)
for x in enemy_types:
var choice = enemy_choices.pick_random()
enemy_choices.erase(choice)
if sp_allotment / choice.spawn_power > 0:
wave[Data.enemies.find(choice)] = sp_allotment / choice.spawn_power
#sp_used += wave[choice] * choice.spawn_power
#print("tried to generate wave with " + str(spawn_power) + " spawn power, used " + str(sp_used))
sp_used += wave[Data.enemies.find(choice)] * choice.spawn_power
print("tried to generate wave with " + str(spawn_power) + " spawn power, used " + str(sp_used))
return wave