waited far too long for an initial commit but here we are
This commit is contained in:
97
Scripts/AStarGraph3D.gd
Normal file
97
Scripts/AStarGraph3D.gd
Normal file
@ -0,0 +1,97 @@
|
||||
extends Node3D
|
||||
class_name AStarGraph3D
|
||||
|
||||
@export var grid_size := Vector2(21, 13)
|
||||
@export var point_gap := 1.2
|
||||
var non_build_locations = []
|
||||
var astar := AStar3D.new()
|
||||
|
||||
#TODO generalize this better
|
||||
@export var start : Node3D
|
||||
@export var end : Node3D
|
||||
@export var spawner : EnemySpawner
|
||||
@export var visualized_path : VisualizedPath
|
||||
var tower_base_scene = load("res://Scenes/tower_base.tscn")
|
||||
var tower_frame_scene = load("res://Scenes/tower_frame.tscn")
|
||||
var tower_bases = []
|
||||
|
||||
|
||||
func toggle_point(point_id):
|
||||
networked_toggle_point.rpc(point_id)
|
||||
|
||||
|
||||
func point_is_build_location(point_id):
|
||||
return !non_build_locations.has(point_id)
|
||||
|
||||
|
||||
func test_path_if_point_toggled(point_id):
|
||||
if astar.is_point_disabled(point_id):
|
||||
astar.set_point_disabled(point_id, false)
|
||||
else:
|
||||
astar.set_point_disabled(point_id, true)
|
||||
var result = find_path()
|
||||
if astar.is_point_disabled(point_id):
|
||||
astar.set_point_disabled(point_id, false)
|
||||
else:
|
||||
astar.set_point_disabled(point_id, true)
|
||||
return result
|
||||
|
||||
|
||||
@rpc("reliable", "any_peer", "call_local")
|
||||
func networked_toggle_point(point_id):
|
||||
if astar.is_point_disabled(point_id):
|
||||
astar.set_point_disabled(point_id, false)
|
||||
else:
|
||||
astar.set_point_disabled(point_id, true)
|
||||
var base = tower_base_scene.instantiate()
|
||||
base.position = astar.get_point_position(point_id)
|
||||
tower_bases.append(base)
|
||||
add_child(base)
|
||||
find_path()
|
||||
|
||||
|
||||
func find_path() -> bool:
|
||||
var path = astar.get_point_path(astar.get_point_count() - 2, astar.get_point_count() - 1)
|
||||
if !path.is_empty():
|
||||
var curve = Curve3D.new()
|
||||
for point in path:
|
||||
curve.add_point(point)
|
||||
spawner.path.curve = curve
|
||||
spawner.path.spawn_visualizer_points()
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
func make_grid():
|
||||
for x in grid_size.x:
|
||||
for y in grid_size.y:
|
||||
var point_position = Vector3((x - floori(grid_size.x / 2)) * point_gap, 0.5, (y - floori(grid_size.y / 2)) * point_gap)
|
||||
astar.add_point(int(x * grid_size.y + y), point_position)
|
||||
var frame = tower_frame_scene.instantiate()
|
||||
frame.position = point_position
|
||||
add_child(frame)
|
||||
|
||||
for x in grid_size.x:
|
||||
for y in grid_size.y:
|
||||
var point_id = grid_size.y * x + y
|
||||
if x > 0:
|
||||
var north_point_id = grid_size.y * (x - 1) + y
|
||||
astar.connect_points(point_id, north_point_id, false)
|
||||
if x < grid_size.x - 1:
|
||||
var south_point_id = grid_size.y * (x + 1) + y
|
||||
astar.connect_points(point_id, south_point_id, false)
|
||||
if y > 0:
|
||||
var east_point_id = grid_size.y * x + (y - 1)
|
||||
astar.connect_points(point_id, east_point_id, false)
|
||||
if y < grid_size.y - 1:
|
||||
var west_point_id = grid_size.y * x + (y + 1)
|
||||
astar.connect_points(point_id, west_point_id, false)
|
||||
|
||||
non_build_locations.append(astar.get_point_count())
|
||||
astar.add_point(astar.get_point_count(), start.global_position)
|
||||
for x in grid_size.y:
|
||||
astar.connect_points(int(astar.get_point_count() - 1), x)
|
||||
non_build_locations.append(astar.get_point_count())
|
||||
astar.add_point(astar.get_point_count(), end.global_position)
|
||||
for x in grid_size.y:
|
||||
astar.connect_points(astar.get_point_count() - 1, int(grid_size.y * (grid_size.x - 1) + x))
|
42
Scripts/DebugMesh.gd
Normal file
42
Scripts/DebugMesh.gd
Normal file
@ -0,0 +1,42 @@
|
||||
@tool
|
||||
extends MeshInstance3D
|
||||
class_name DebugMesh
|
||||
|
||||
|
||||
func _ready():
|
||||
var mat = StandardMaterial3D.new()
|
||||
mesh = ImmediateMesh.new()
|
||||
mat.no_depth_test = true
|
||||
mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
|
||||
mat.vertex_color_use_as_albedo = true
|
||||
mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
|
||||
set_material_override(mat)
|
||||
|
||||
|
||||
func clear():
|
||||
mesh.clear_surfaces()
|
||||
|
||||
|
||||
func draw_line(begin_pos: Vector3, end_pos: Vector3, color: Color = Color.RED) -> void:
|
||||
mesh.surface_begin(Mesh.PRIMITIVE_LINES)
|
||||
mesh.surface_set_color(color)
|
||||
mesh.surface_add_vertex(begin_pos)
|
||||
mesh.surface_add_vertex(end_pos)
|
||||
mesh.surface_end()
|
||||
|
||||
|
||||
func draw_sphere(center: Vector3, radius: float = 1.0, color: Color = Color.RED) -> void:
|
||||
var step: int = 30
|
||||
var sppi: float = 2 * PI / step
|
||||
var axes = [
|
||||
[Vector3.UP, Vector3.RIGHT],
|
||||
[Vector3.RIGHT, Vector3.FORWARD],
|
||||
[Vector3.FORWARD, Vector3.UP]
|
||||
]
|
||||
for axis in axes:
|
||||
mesh.surface_begin(Mesh.PRIMITIVE_LINE_STRIP)
|
||||
mesh.surface_set_color(color)
|
||||
for i in range(step + 1):
|
||||
mesh.surface_add_vertex(center + (axis[0] * radius)
|
||||
.rotated(axis[1], sppi * (i % step)))
|
||||
mesh.surface_end()
|
78
Scripts/HUD.gd
Normal file
78
Scripts/HUD.gd
Normal file
@ -0,0 +1,78 @@
|
||||
extends CanvasLayer
|
||||
class_name HUD
|
||||
|
||||
var last_lives_count = 120
|
||||
@export var wave_count : Label
|
||||
@export var lives_count : Label
|
||||
@export var enemy_count : Label
|
||||
@export var currency_count : Label
|
||||
@export var crosshair : TextureRect
|
||||
@export var minimap : TextureRect
|
||||
@export var minimap_cam : MinimapCamera3D
|
||||
@export var minimap_viewport : SubViewport
|
||||
@export var fps_label : Label
|
||||
var minimap_anchor : Node3D
|
||||
@export var enemy_sprites : Array[TextureRect]
|
||||
@export var enemy_counts : Array[Label]
|
||||
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
fps_label.text = "FPS: " + str(Engine.get_frames_per_second())
|
||||
|
||||
|
||||
func set_wave_count(value):
|
||||
wave_count.text = str(value)
|
||||
|
||||
|
||||
func set_lives_count(value):
|
||||
lives_count.text = str(value)
|
||||
for x in last_lives_count - value:
|
||||
$LivesBar.take_life()
|
||||
last_lives_count = value
|
||||
|
||||
|
||||
func set_enemy_count(value):
|
||||
enemy_count.text = "Enemies Remaining: " + str(value)
|
||||
|
||||
|
||||
func set_upcoming_wave(value):
|
||||
var frame_count = 0
|
||||
for x in enemy_sprites.size():
|
||||
enemy_sprites[x].set_visible(false)
|
||||
enemy_counts[x].set_visible(false)
|
||||
for enemy in value:
|
||||
enemy_sprites[frame_count].texture = enemy.icon
|
||||
enemy_counts[frame_count].text = str(value[enemy])
|
||||
enemy_sprites[frame_count].set_visible(true)
|
||||
enemy_counts[frame_count].set_visible(true)
|
||||
frame_count += 1
|
||||
|
||||
|
||||
func set_currency_count(value):
|
||||
currency_count.text = str(value)
|
||||
|
||||
|
||||
func set_crosshair_visible(value : bool):
|
||||
crosshair.set_visible(value)
|
||||
|
||||
|
||||
func maximise_minimap(anchor):
|
||||
minimap_cam.anchor = anchor
|
||||
minimap.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
minimap.offset_bottom = -40
|
||||
minimap.offset_top = 40
|
||||
minimap.offset_left = 40
|
||||
minimap.offset_right = -40
|
||||
minimap_viewport.size = Vector2(1840, 1000)
|
||||
minimap_cam.size = 30
|
||||
|
||||
|
||||
func minimize_minimap(anchor):
|
||||
minimap_cam.anchor = anchor
|
||||
minimap.set_anchors_and_offsets_preset(Control.PRESET_TOP_RIGHT)
|
||||
minimap.offset_right = -40
|
||||
minimap.offset_top = 40
|
||||
minimap.offset_left = -256
|
||||
minimap.offset_bottom = 256
|
||||
minimap_viewport.size = Vector2(256, 256)
|
||||
minimap_cam.size = 15
|
7
Scripts/Resources/TowerStats.gd
Normal file
7
Scripts/Resources/TowerStats.gd
Normal file
@ -0,0 +1,7 @@
|
||||
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
|
6
Scripts/Resources/WeaponStats.gd
Normal file
6
Scripts/Resources/WeaponStats.gd
Normal file
@ -0,0 +1,6 @@
|
||||
extends Resource
|
||||
class_name WeaponStats
|
||||
|
||||
@export var damage : int
|
||||
@export var fire_rate : float
|
||||
@export var fire_range : float
|
13
Scripts/Resources/card.gd
Normal file
13
Scripts/Resources/card.gd
Normal file
@ -0,0 +1,13 @@
|
||||
extends Resource
|
||||
class_name Card
|
||||
|
||||
enum Faction {GENERIC}
|
||||
|
||||
@export var title : String
|
||||
@export var rarity : Data.Rarity
|
||||
@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
|
12
Scripts/Resources/enemy.gd
Normal file
12
Scripts/Resources/enemy.gd
Normal file
@ -0,0 +1,12 @@
|
||||
extends Resource
|
||||
class_name Enemy
|
||||
|
||||
@export var title = "dog"
|
||||
@export var target_type : Data.EnemyType
|
||||
@export var icon : Texture
|
||||
@export var sprite : AtlasTexture
|
||||
@export var spawn_power := 10
|
||||
@export var health = 100
|
||||
@export var penalty = 10
|
||||
@export var movement_speed = 0.5
|
||||
@export var spawn_cooldown = 1.0
|
6
Scripts/Resources/hero_class.gd
Normal file
6
Scripts/Resources/hero_class.gd
Normal file
@ -0,0 +1,6 @@
|
||||
extends Resource
|
||||
class_name HeroClass
|
||||
|
||||
@export var hero_name : String = "Default"
|
||||
@export var texture : AtlasTexture
|
||||
@export var deck : Array[Card]
|
63
Scripts/Resources/player_keymap.gd
Normal file
63
Scripts/Resources/player_keymap.gd
Normal file
@ -0,0 +1,63 @@
|
||||
extends Resource
|
||||
class_name PlayerKeymap
|
||||
|
||||
const SAVE_PATH := "user://keymap.tres"
|
||||
|
||||
@export var title : String
|
||||
|
||||
@export var move_forward : InputEventKey
|
||||
@export var move_backward : InputEventKey
|
||||
@export var move_left : InputEventKey
|
||||
@export var move_right : InputEventKey
|
||||
@export var jump : InputEventKey
|
||||
@export var sprint : InputEventKey
|
||||
@export var interact : InputEventKey
|
||||
@export var open_text_chat : InputEventKey
|
||||
@export var ready : InputEventKey
|
||||
@export var pause : InputEventKey
|
||||
@export var equip_card_in_gauntlet : InputEventKey
|
||||
@export var view_map : InputEventKey
|
||||
|
||||
|
||||
func apply():
|
||||
replace_action_event("Move Forward", move_forward)
|
||||
replace_action_event("Move Backward", move_backward)
|
||||
replace_action_event("Move Left", move_left)
|
||||
replace_action_event("Move Right", move_right)
|
||||
replace_action_event("Jump", jump)
|
||||
replace_action_event("Sprint", sprint)
|
||||
replace_action_event("Interact", interact)
|
||||
replace_action_event("Open Text Chat", open_text_chat)
|
||||
replace_action_event("Ready", ready)
|
||||
replace_action_event("Pause", pause)
|
||||
replace_action_event("Equip In Gauntlet", equip_card_in_gauntlet)
|
||||
replace_action_event("View Map", view_map)
|
||||
|
||||
|
||||
func replace_action_event(action_string, event):
|
||||
InputMap.action_erase_events(action_string)
|
||||
InputMap.action_add_event(action_string, event)
|
||||
|
||||
|
||||
func get_current_input_map():
|
||||
move_forward = InputMap.action_get_events("Move Forward")[0]
|
||||
move_backward = InputMap.action_get_events("Move Backward")[0]
|
||||
move_left = InputMap.action_get_events("Move Left")[0]
|
||||
move_right = InputMap.action_get_events("Move Right")[0]
|
||||
jump = InputMap.action_get_events("Jump")[0]
|
||||
sprint = InputMap.action_get_events("Sprint")[0]
|
||||
interact = InputMap.action_get_events("Interact")[0]
|
||||
open_text_chat = InputMap.action_get_events("Open Text Chat")[0]
|
||||
ready = InputMap.action_get_events("Ready")[0]
|
||||
pause = InputMap.action_get_events("Pause")[0]
|
||||
equip_card_in_gauntlet = InputMap.action_get_events("Equip In Gauntlet")[0]
|
||||
view_map = InputMap.action_get_events("View Map")[0]
|
||||
|
||||
|
||||
func save_profile_to_disk():
|
||||
get_current_input_map()
|
||||
ResourceSaver.save(self, SAVE_PATH)
|
||||
static func load_profile_from_disk() -> PlayerKeymap:
|
||||
if ResourceLoader.exists(SAVE_PATH):
|
||||
return ResourceLoader.load(SAVE_PATH)
|
||||
return Data.keymaps[0]
|
42
Scripts/Resources/player_preferences.gd
Normal file
42
Scripts/Resources/player_preferences.gd
Normal file
@ -0,0 +1,42 @@
|
||||
extends Resource
|
||||
class_name PlayerPreferences
|
||||
|
||||
const SAVE_PATH := "user://preferences.tres"
|
||||
|
||||
@export var mouse_sens := 28.0
|
||||
@export var invert_lookY := false
|
||||
@export var invert_lookX := false
|
||||
@export var toggle_sprint := false
|
||||
@export var vsync_mode := 1
|
||||
@export var aa_mode := 0
|
||||
@export var windowed_mode := 0
|
||||
@export var hfov := 100.0
|
||||
|
||||
|
||||
func apply_graphical_settings(viewport):
|
||||
DisplayServer.window_set_vsync_mode(vsync_mode)
|
||||
match aa_mode:
|
||||
0:
|
||||
viewport.use_taa = false
|
||||
viewport.screen_space_aa = Viewport.SCREEN_SPACE_AA_DISABLED
|
||||
1:
|
||||
viewport.use_taa = false
|
||||
viewport.screen_space_aa = Viewport.SCREEN_SPACE_AA_FXAA
|
||||
2:
|
||||
viewport.use_taa = true
|
||||
viewport.screen_space_aa = Viewport.SCREEN_SPACE_AA_DISABLED
|
||||
match windowed_mode:
|
||||
0:
|
||||
DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED)
|
||||
1:
|
||||
DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_FULLSCREEN)
|
||||
2:
|
||||
DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_EXCLUSIVE_FULLSCREEN)
|
||||
|
||||
|
||||
func save_profile_to_disk():
|
||||
ResourceSaver.save(self, SAVE_PATH)
|
||||
static func load_profile_from_disk() -> PlayerPreferences:
|
||||
if ResourceLoader.exists(SAVE_PATH):
|
||||
return ResourceLoader.load(SAVE_PATH)
|
||||
return PlayerPreferences.new()
|
47
Scripts/Resources/player_profile.gd
Normal file
47
Scripts/Resources/player_profile.gd
Normal file
@ -0,0 +1,47 @@
|
||||
extends Resource
|
||||
class_name PlayerProfile
|
||||
|
||||
signal display_name_changed(old_name, new_name)
|
||||
signal preferred_class_changed(old_class, new_class)
|
||||
|
||||
const SAVE_PATH := "user://profile.tres"
|
||||
|
||||
@export var display_name := "Charlie"
|
||||
@export var preferred_class := 0
|
||||
|
||||
func to_dict() -> Dictionary:
|
||||
var dict = {}
|
||||
dict["display_name"] = display_name
|
||||
dict["preferred_class"] = preferred_class
|
||||
return dict
|
||||
static func from_dict(dict) -> PlayerProfile:
|
||||
var output = PlayerProfile.new()
|
||||
output.display_name = dict["display_name"]
|
||||
output.preferred_class = dict["preferred_class"]
|
||||
return output
|
||||
|
||||
func set_display_name(new_display_name):
|
||||
if new_display_name == display_name:
|
||||
return
|
||||
var old_name = display_name
|
||||
display_name = new_display_name
|
||||
save_profile_to_disk()
|
||||
display_name_changed.emit(old_name, display_name)
|
||||
func get_display_name() -> String:
|
||||
return display_name
|
||||
|
||||
func set_preferred_class(new_preferred_class):
|
||||
if new_preferred_class == preferred_class:
|
||||
return
|
||||
var old_class = preferred_class
|
||||
preferred_class = new_preferred_class
|
||||
preferred_class_changed.emit(old_class, preferred_class)
|
||||
func get_preferred_class() -> int:
|
||||
return preferred_class
|
||||
|
||||
func save_profile_to_disk():
|
||||
ResourceSaver.save(self, SAVE_PATH)
|
||||
static func load_profile_from_disk() -> PlayerProfile:
|
||||
if ResourceLoader.exists(SAVE_PATH):
|
||||
return ResourceLoader.load(SAVE_PATH)
|
||||
return PlayerProfile.new()
|
8
Scripts/Resources/status_stats.gd
Normal file
8
Scripts/Resources/status_stats.gd
Normal file
@ -0,0 +1,8 @@
|
||||
extends Resource
|
||||
class_name StatusStats
|
||||
|
||||
@export var unique := false
|
||||
@export var proc_frequency := 0.0
|
||||
@export var duration := 1.0
|
||||
@export var potency := 1.0
|
||||
@export var icon : Texture
|
36
Scripts/StatusEffector.gd
Normal file
36
Scripts/StatusEffector.gd
Normal file
@ -0,0 +1,36 @@
|
||||
extends Node3D
|
||||
class_name StatusEffector
|
||||
|
||||
@export var hbox : HBoxContainer
|
||||
|
||||
var icon_scene = preload("res://Scenes/status_icon.tscn")
|
||||
var effects : Array[StatusEffect]
|
||||
var icons : Array[TextureRect]
|
||||
|
||||
|
||||
func add_effect(new_effect : StatusEffect):
|
||||
var icon_present = false
|
||||
for effect in effects:
|
||||
if effect.stats == new_effect.stats:
|
||||
icon_present = true
|
||||
new_effect.expired.connect(remove_effect)
|
||||
effects.append(new_effect)
|
||||
if !icon_present:
|
||||
var icon = icon_scene.instantiate()
|
||||
icon.texture = new_effect.stats.icon
|
||||
icons.append(icon)
|
||||
hbox.add_child(icon)
|
||||
|
||||
|
||||
func remove_effect(expiring_effect : StatusEffect):
|
||||
effects.erase(expiring_effect)
|
||||
var has_remaining_stack = false
|
||||
for effect in effects:
|
||||
if effect.stats == expiring_effect.stats:
|
||||
has_remaining_stack = true
|
||||
if !has_remaining_stack:
|
||||
for icon in icons:
|
||||
if icon.texture == expiring_effect.stats.icon:
|
||||
icons.erase(icon)
|
||||
icon.queue_free()
|
||||
break
|
6
Scripts/StatusEffects/StatusOnFire.gd
Normal file
6
Scripts/StatusEffects/StatusOnFire.gd
Normal file
@ -0,0 +1,6 @@
|
||||
extends StatusEffect
|
||||
class_name StatusOnFire
|
||||
|
||||
|
||||
func proc():
|
||||
affected.damage(stats.potency)
|
44
Scripts/StatusEffects/status_effect.gd
Normal file
44
Scripts/StatusEffects/status_effect.gd
Normal file
@ -0,0 +1,44 @@
|
||||
extends Node
|
||||
class_name StatusEffect
|
||||
|
||||
signal expired(effect : StatusEffect)
|
||||
|
||||
var stats : StatusStats
|
||||
|
||||
var affected :
|
||||
set(value):
|
||||
affected = value
|
||||
on_attached()
|
||||
var cooldown := 0.0
|
||||
var other_cooldown := 0.0
|
||||
var time_existed := 0.0
|
||||
|
||||
|
||||
func on_attached():
|
||||
pass
|
||||
|
||||
|
||||
func on_removed():
|
||||
expired.emit(self)
|
||||
|
||||
|
||||
func proc():
|
||||
pass
|
||||
|
||||
|
||||
func _ready():
|
||||
other_cooldown = 1.0 / stats.proc_frequency
|
||||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
time_existed += delta
|
||||
if time_existed >= stats.duration:
|
||||
on_removed()
|
||||
queue_free()
|
||||
return
|
||||
if stats.proc_frequency > 0.0:
|
||||
cooldown += delta
|
||||
if cooldown >= other_cooldown:
|
||||
cooldown -= other_cooldown
|
||||
proc()
|
||||
|
18
Scripts/StatusEffects/status_sticky.gd
Normal file
18
Scripts/StatusEffects/status_sticky.gd
Normal file
@ -0,0 +1,18 @@
|
||||
extends StatusEffect
|
||||
class_name StatusSticky
|
||||
|
||||
|
||||
func on_attached():
|
||||
super.on_attached()
|
||||
affected.movement_speed = affected.stats.movement_speed * (1.0 - stats.potency)
|
||||
|
||||
|
||||
func on_removed():
|
||||
super.on_removed()
|
||||
var siblings = get_parent().get_children()
|
||||
var stickies = 0
|
||||
for node in siblings:
|
||||
if node is StatusSticky:
|
||||
stickies += 1
|
||||
if stickies == 1:
|
||||
affected.movement_speed = affected.stats.movement_speed
|
13
Scripts/alert_popup.gd
Normal file
13
Scripts/alert_popup.gd
Normal file
@ -0,0 +1,13 @@
|
||||
extends PanelContainer
|
||||
class_name AlertPopup
|
||||
|
||||
signal completed
|
||||
|
||||
func set_popup(prompt_text, dismiss_text):
|
||||
$VBoxContainer/Label.text = prompt_text
|
||||
$VBoxContainer/Button.text = dismiss_text
|
||||
|
||||
|
||||
func _on_button_pressed() -> void:
|
||||
completed.emit()
|
||||
queue_free()
|
30
Scripts/card_hand.gd
Normal file
30
Scripts/card_hand.gd
Normal file
@ -0,0 +1,30 @@
|
||||
extends Node2D
|
||||
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
|
||||
|
||||
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 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)
|
||||
|
||||
|
||||
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)
|
59
Scripts/card_printer.gd
Normal file
59
Scripts/card_printer.gd
Normal file
@ -0,0 +1,59 @@
|
||||
extends StaticBody3D
|
||||
class_name CardPrinter
|
||||
|
||||
@export var cards : Array[CardInHand]
|
||||
@export var item_card_scene : PackedScene
|
||||
var card_available = false
|
||||
@export var button_collider : CollisionShape3D
|
||||
@export var button_box : Node3D
|
||||
@export var choice_colliders : Array[CollisionShape3D]
|
||||
|
||||
|
||||
func randomize_cards():
|
||||
var weight_total = 0
|
||||
for rarity in Data.Rarity:
|
||||
weight_total += Data.rarity_weights[rarity]
|
||||
|
||||
var generated_rarity = randi_range(0, weight_total)
|
||||
var decided_rarity := 0
|
||||
|
||||
for rarity in Data.Rarity:
|
||||
weight_total -= Data.rarity_weights[rarity]
|
||||
if generated_rarity >= weight_total:
|
||||
decided_rarity = Data.Rarity[rarity]
|
||||
break
|
||||
|
||||
var card_array = []
|
||||
for x in Data.cards:
|
||||
if x.rarity == decided_rarity:
|
||||
card_array.append(x)
|
||||
var card
|
||||
for x in cards:
|
||||
if card_array.size() > 0:
|
||||
card = card_array.pick_random()
|
||||
card_array.erase(card)
|
||||
x.set_card(card)
|
||||
$Node3D.set_visible(true)
|
||||
for x in choice_colliders:
|
||||
x.disabled = false
|
||||
card_available = true
|
||||
|
||||
|
||||
func retrieve_card(i):
|
||||
$Node3D.set_visible(false)
|
||||
for x in choice_colliders:
|
||||
x.disabled = true
|
||||
if card_available:
|
||||
var card = cards[i].stats
|
||||
var item = item_card_scene.instantiate() as ItemCard
|
||||
item.card = card
|
||||
add_child(item)
|
||||
item.position += -transform.basis.z * 2
|
||||
button_collider.disabled = false
|
||||
button_box.position = Vector3(0,0,0)
|
||||
|
||||
|
||||
func _on_static_body_3d_button_interacted(_value) -> void:
|
||||
button_collider.disabled = true
|
||||
button_box.position = Vector3(0,0,-0.2)
|
||||
randomize_cards()
|
36
Scripts/chatbox.gd
Normal file
36
Scripts/chatbox.gd
Normal file
@ -0,0 +1,36 @@
|
||||
extends Control
|
||||
class_name Chatbox
|
||||
|
||||
signal opened
|
||||
signal closed
|
||||
|
||||
var text_selected := false
|
||||
var username := "default"
|
||||
|
||||
func _input(event: InputEvent) -> void:
|
||||
if event.is_action_pressed("Open Text Chat"):
|
||||
if text_selected:
|
||||
closed.emit()
|
||||
$VBoxContainer/LineEdit.deselect()
|
||||
$VBoxContainer/LineEdit.visible = false
|
||||
text_selected = false
|
||||
if $VBoxContainer/LineEdit.text.length() != 0:
|
||||
if $VBoxContainer/LineEdit.text.begins_with("/"):
|
||||
Game.parse_command($VBoxContainer/LineEdit.text, multiplayer.get_unique_id())
|
||||
else:
|
||||
rpc("append_message", username, $VBoxContainer/LineEdit.text)
|
||||
$VBoxContainer/LineEdit.clear()
|
||||
else:
|
||||
opened.emit()
|
||||
$VBoxContainer/LineEdit.visible = true
|
||||
$VBoxContainer/LineEdit.grab_focus()
|
||||
text_selected = true
|
||||
|
||||
|
||||
func change_username(old_name, new_name):
|
||||
append_message("server", old_name + " has changed their display name to " + new_name)
|
||||
|
||||
|
||||
@rpc("reliable","call_local","any_peer")
|
||||
func append_message(user, content):
|
||||
$VBoxContainer/RichTextLabel.append_text("[" + user + "] " + content + "\n")
|
19
Scripts/confirmation_popup.gd
Normal file
19
Scripts/confirmation_popup.gd
Normal file
@ -0,0 +1,19 @@
|
||||
extends PanelContainer
|
||||
class_name ConfirmationPopup
|
||||
|
||||
signal completed(outcome)
|
||||
|
||||
func set_popup(prompt_text, confirm_text, cancel_text):
|
||||
$VBoxContainer/Label.text = prompt_text
|
||||
$VBoxContainer/HBoxContainer/Confirm.text = confirm_text
|
||||
$VBoxContainer/HBoxContainer/Cancel.text = cancel_text
|
||||
|
||||
|
||||
func _on_confirm_pressed() -> void:
|
||||
completed.emit(true)
|
||||
queue_free()
|
||||
|
||||
|
||||
func _on_cancel_pressed() -> void:
|
||||
completed.emit(false)
|
||||
queue_free()
|
23
Scripts/damage_particle.gd
Normal file
23
Scripts/damage_particle.gd
Normal file
@ -0,0 +1,23 @@
|
||||
extends Sprite3D
|
||||
|
||||
@onready var label: Label = $SubViewport/Label
|
||||
var time_alive := 0.0
|
||||
var movement_speed := 1.0
|
||||
var movement_vector : Vector3
|
||||
|
||||
func _ready():
|
||||
var theta = deg_to_rad(40)
|
||||
var z = randf_range(cos(theta), 1)
|
||||
var phi = randf_range(0, 2 * PI)
|
||||
var vector = Vector3(sqrt(1 - pow(z, 2)) * cos(phi), z, sqrt(1 - pow(z, 2)) * sin(phi))
|
||||
movement_vector = vector.normalized()
|
||||
|
||||
func set_number(num):
|
||||
label.text = str(num)
|
||||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
time_alive += delta
|
||||
position += movement_vector * movement_speed * delta
|
||||
if time_alive >= 1.0:
|
||||
queue_free()
|
53
Scripts/data.gd
Normal file
53
Scripts/data.gd
Normal file
@ -0,0 +1,53 @@
|
||||
extends Node
|
||||
|
||||
var characters : Array[HeroClass]
|
||||
var cards : Array[Card]
|
||||
var enemies : Array[Enemy]
|
||||
var keymaps : Array[PlayerKeymap]
|
||||
var preferences : PlayerPreferences
|
||||
var player_profile : PlayerProfile
|
||||
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}
|
||||
var rarity_weights = {
|
||||
"COMMON" = 100,
|
||||
"UNCOMMON" = 60,
|
||||
"RARE" = 20,
|
||||
"EPIC" = 8,
|
||||
"LEGENDARY" = 1
|
||||
}
|
||||
|
||||
func _ready() -> void:
|
||||
player_profile = PlayerProfile.load_profile_from_disk()
|
||||
preferences = PlayerPreferences.load_profile_from_disk()
|
||||
player_keymap = PlayerKeymap.load_profile_from_disk()
|
||||
preferences.apply_graphical_settings(get_viewport())
|
||||
player_keymap.apply()
|
||||
|
||||
characters.append(preload("res://PCs/Red/red.tres"))
|
||||
characters.append(preload("res://PCs/Green/green.tres"))
|
||||
characters.append(preload("res://PCs/Blue/blue.tres"))
|
||||
|
||||
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/Flamethrower/card_flamethrower.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"))
|
||||
|
||||
enemies.append(preload("res://Worlds/GreenPlanet/Enemies/dog.tres"))
|
||||
enemies.append(preload("res://Worlds/GreenPlanet/Enemies/dog_fast.tres"))
|
||||
enemies.append(preload("res://Worlds/GreenPlanet/Enemies/dog_heavy.tres"))
|
||||
enemies.append(preload("res://Worlds/GreenPlanet/Enemies/dog_boss.tres"))
|
||||
enemies.append(preload("res://Worlds/GreenPlanet/Enemies/airenemy.tres"))
|
||||
enemies.append(preload("res://Worlds/GreenPlanet/Enemies/airenemy2.tres"))
|
||||
|
||||
keymaps.append(preload("res://Resources/Keymaps/qwerty.tres"))
|
||||
keymaps.append(preload("res://Resources/Keymaps/azerty.tres"))
|
||||
keymaps.append(preload("res://Resources/Keymaps/dvorak.tres"))
|
||||
keymaps.append(preload("res://Resources/Keymaps/colemak.tres"))
|
||||
keymaps.append(preload("res://Resources/Keymaps/workman.tres"))
|
103
Scripts/edit_tool.gd
Normal file
103
Scripts/edit_tool.gd
Normal file
@ -0,0 +1,103 @@
|
||||
extends Node3D
|
||||
class_name EditTool
|
||||
|
||||
@export var hero : Hero
|
||||
@export var inventory : Inventory
|
||||
@export var ray : RayCast3D
|
||||
@export var wall_preview : TowerBase
|
||||
@export var build_preview_material : StandardMaterial3D
|
||||
@export var progress_bar : TextureProgressBar
|
||||
|
||||
var enabled := true
|
||||
var point_id := -1
|
||||
var obstacle_last_point : int
|
||||
var valid_point := false
|
||||
var is_looking_at_tower_base := false
|
||||
var ray_collider
|
||||
var ray_point
|
||||
|
||||
var interact_key_held := false
|
||||
var interacted_once := false
|
||||
var interact_held_time := 0.0
|
||||
var interact_hold_time := 0.5
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
wall_preview.set_material(build_preview_material)
|
||||
wall_preview.toggle_collision()
|
||||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
if !enabled:
|
||||
ray_collider = null
|
||||
ray_point = null
|
||||
wall_preview.set_visible(false)
|
||||
return
|
||||
|
||||
if interact_key_held and !interacted_once and valid_point and hero.currency >= Data.wall_cost and ray.is_colliding() and Game.level.a_star_graph_3d.point_is_build_location(point_id):
|
||||
interact_held_time += delta
|
||||
set_progress_percent(interact_held_time / interact_hold_time)
|
||||
if interact_held_time >= interact_hold_time:
|
||||
set_progress_percent(0)
|
||||
interacted_once = true
|
||||
build_wall()
|
||||
if !interact_key_held:
|
||||
interact_held_time = 0.0
|
||||
interacted_once = false
|
||||
set_progress_percent(0)
|
||||
|
||||
point_id = -1
|
||||
if ray.is_colliding():
|
||||
if !interact_key_held:
|
||||
wall_preview.set_visible(true)
|
||||
ray_collider = ray.get_collider()
|
||||
ray_point = ray.get_collision_point()
|
||||
|
||||
is_looking_at_tower_base = ray_collider is TowerBase
|
||||
if Game.level:
|
||||
point_id = Game.level.a_star_graph_3d.astar.get_closest_point(ray_point)
|
||||
if !Game.level.a_star_graph_3d.point_is_build_location(point_id) or hero.currency < Data.wall_cost:
|
||||
wall_preview.set_visible(false)
|
||||
else:
|
||||
var point_position = Game.level.a_star_graph_3d.astar.get_point_position(point_id)
|
||||
wall_preview.global_position = point_position
|
||||
wall_preview.global_rotation = Vector3.ZERO
|
||||
if obstacle_last_point != point_id:
|
||||
obstacle_last_point = point_id
|
||||
if Game.level.a_star_graph_3d.test_path_if_point_toggled(point_id):
|
||||
build_preview_material.albedo_color = Color.GREEN
|
||||
build_preview_material.albedo_color.a = 0.8
|
||||
valid_point = true
|
||||
else:
|
||||
build_preview_material.albedo_color = Color.RED
|
||||
build_preview_material.albedo_color.a = 0.8
|
||||
valid_point = false
|
||||
else:
|
||||
ray_collider = null
|
||||
ray_point = null
|
||||
is_looking_at_tower_base = false
|
||||
wall_preview.set_visible(false)
|
||||
|
||||
|
||||
func interact():
|
||||
if ray_collider is TowerBase:
|
||||
var tower_base = ray_collider as TowerBase
|
||||
put_card_in_tower_base(tower_base)
|
||||
|
||||
|
||||
func build_wall():
|
||||
if point_id >= 0 and valid_point and hero.currency >= Data.wall_cost:
|
||||
hero.currency -= Data.wall_cost
|
||||
Game.level.a_star_graph_3d.toggle_point(point_id)
|
||||
wall_preview.set_visible(false)
|
||||
|
||||
|
||||
func put_card_in_tower_base(tower_base: TowerBase):
|
||||
if tower_base.has_card:
|
||||
inventory.add(tower_base.remove_card())
|
||||
else:
|
||||
tower_base.add_card(inventory.remove())
|
||||
|
||||
|
||||
func set_progress_percent(value: float):
|
||||
progress_bar.value = progress_bar.max_value * value
|
36
Scripts/eight_direction_sprite.gd
Normal file
36
Scripts/eight_direction_sprite.gd
Normal file
@ -0,0 +1,36 @@
|
||||
extends Sprite3D
|
||||
class_name EightDirectionSprite3D
|
||||
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
var cam = get_viewport().get_camera_3d()
|
||||
if !cam:
|
||||
return
|
||||
var tile_size = texture.region.size.x
|
||||
|
||||
#stupid algorithm for dummy game developers
|
||||
var camera_look_dir_3D = cam.global_position.direction_to(global_position).normalized()
|
||||
var a = Vector2(global_transform.basis.z.x, global_transform.basis.z.z).normalized()
|
||||
var b = Vector2(camera_look_dir_3D.x, camera_look_dir_3D.z).normalized()
|
||||
var dot = a.x * b.x + a.y * b.y
|
||||
var det = a.x * b.y - a.y * b.x
|
||||
var final = rad_to_deg(atan2(det, dot)) + 180
|
||||
|
||||
var t = texture.region
|
||||
if final > 337.5 or final < 22.5:
|
||||
t = Rect2(tile_size * 4, t.position.y, tile_size, tile_size)
|
||||
elif final > 22.5 and final < 67.5:
|
||||
t = Rect2(tile_size * 5, t.position.y, tile_size, tile_size)
|
||||
elif final > 67.5 and final < 112.5:
|
||||
t = Rect2(tile_size * 6, t.position.y, tile_size, tile_size)
|
||||
elif final > 112.5 and final < 157.5:
|
||||
t = Rect2(tile_size * 7, t.position.y, tile_size, tile_size)
|
||||
elif final > 157.5 and final < 202.5:
|
||||
t = Rect2(0, t.position.y, tile_size, tile_size)
|
||||
elif final > 202.5 and final < 247.5:
|
||||
t = Rect2(tile_size * 1, t.position.y, tile_size, tile_size)
|
||||
elif final > 247.5 and final < 292.5:
|
||||
t = Rect2(tile_size * 2, t.position.y, tile_size, tile_size)
|
||||
elif final > 292.5 and final < 337.5:
|
||||
t = Rect2(tile_size * 3, t.position.y, tile_size, tile_size)
|
||||
texture.region = t
|
66
Scripts/enemy_spawner.gd
Normal file
66
Scripts/enemy_spawner.gd
Normal file
@ -0,0 +1,66 @@
|
||||
extends Node3D
|
||||
class_name EnemySpawner
|
||||
|
||||
@export var path : VisualizedPath
|
||||
@export var type : Data.EnemyType
|
||||
@export var dest : Node3D
|
||||
|
||||
var signal_for_after_enemy_died
|
||||
var signal_for_after_enemy_reached_goal
|
||||
signal signal_for_when_enemy_spawns
|
||||
|
||||
var current_wave
|
||||
var enemy_spawn_timers = {}
|
||||
var enemies_spawned = {}
|
||||
var enemies_to_spawn := 0
|
||||
var done_spawning = true
|
||||
|
||||
@export var land_enemy_scene : PackedScene
|
||||
@export var air_enemy_scene : PackedScene
|
||||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
if enemies_to_spawn == 0:
|
||||
done_spawning = true
|
||||
return
|
||||
|
||||
for x in enemy_spawn_timers:
|
||||
if enemies_spawned[x] == current_wave[x]:
|
||||
continue
|
||||
var enemy_stats = x
|
||||
enemy_spawn_timers[x] += delta
|
||||
if enemy_spawn_timers[x] >= enemy_stats.spawn_cooldown:
|
||||
if type == Data.EnemyType.LAND:
|
||||
var enemy = land_enemy_scene.instantiate() as EnemyController
|
||||
enemy.stats = enemy_stats
|
||||
enemy.died.connect(signal_for_after_enemy_died)
|
||||
enemy.reached_goal.connect(signal_for_after_enemy_reached_goal)
|
||||
path.add_child(enemy)
|
||||
enemy_spawn_timers[x] -= enemy_stats.spawn_cooldown
|
||||
signal_for_when_enemy_spawns.emit()
|
||||
if type == Data.EnemyType.AIR:
|
||||
var enemy = air_enemy_scene.instantiate() as AirEnemyController
|
||||
enemy.stats = enemy_stats
|
||||
enemy.destination = dest
|
||||
enemy.died.connect(signal_for_after_enemy_died)
|
||||
enemy.reached_goal.connect(signal_for_after_enemy_reached_goal)
|
||||
add_child(enemy)
|
||||
enemy_spawn_timers[x] -= enemy_stats.spawn_cooldown
|
||||
signal_for_when_enemy_spawns.emit()
|
||||
enemies_spawned[x] += 1
|
||||
enemies_to_spawn -= 1
|
||||
|
||||
|
||||
func spawn_wave(value):
|
||||
var relevant_enemies = {}
|
||||
for x in value:
|
||||
if x.target_type == type:
|
||||
relevant_enemies[x] = value[x]
|
||||
current_wave = relevant_enemies
|
||||
enemies_to_spawn = 0
|
||||
enemy_spawn_timers = {}
|
||||
for x in current_wave:
|
||||
enemies_to_spawn += current_wave[x]
|
||||
enemy_spawn_timers[x] = 0.0
|
||||
enemies_spawned[x] = 0
|
||||
done_spawning = false
|
35
Scripts/float_and_spin.gd
Normal file
35
Scripts/float_and_spin.gd
Normal file
@ -0,0 +1,35 @@
|
||||
extends RayCast3D
|
||||
|
||||
@export_range(0.0, 3.0) var float_height := 1.5
|
||||
@export_range(0.0, 2.0) var bounce_dist := 0.5
|
||||
@export_range(0.0, 2.0) var bounce_speed := 0.4
|
||||
@export_range(0.0, 4.0) var spin_speed := 0.5
|
||||
@export var curve: Curve
|
||||
|
||||
var start_height = 0.0
|
||||
var dest_height = 0.0
|
||||
var t = 0.0
|
||||
|
||||
func _ready():
|
||||
start_height = position.y
|
||||
|
||||
#raycast downwards and position the item at a set height above the ground that the raycast
|
||||
#presumably hits
|
||||
|
||||
#Now I know what you're thinking: "if the item is placed on the ground in the editor anyway, we can put
|
||||
#the item at the correct height by simply adding the height to the existing y value that the item will have
|
||||
#anyway when the game starts" but what you're not considering is that for ease of placement the model will probably
|
||||
#always be given an offset from the root nodes position so as youre placing it in the editor its not clipping through
|
||||
#the ground, and some items might be given more or less of this vertical offset, if we do it at runtime by actually
|
||||
#checking where the ground is, you dont need to fuck around with this offset at the scene level, you just adjust
|
||||
#the script variable and the item can figure itself out, yeah its fancy but its also a nice creature comfort.
|
||||
if is_colliding():
|
||||
start_height = get_collision_point().y + (1 * float_height) - (bounce_dist / 2.0)
|
||||
dest_height = start_height + (bounce_dist / 2.0)
|
||||
|
||||
func _process(delta):
|
||||
t += bounce_speed * delta
|
||||
position.y = start_height + (dest_height - start_height) * curve.sample(t)
|
||||
if t >= 1.0:
|
||||
t = 0.0
|
||||
rotation.y += spin_speed * delta
|
211
Scripts/game.gd
Normal file
211
Scripts/game.gd
Normal file
@ -0,0 +1,211 @@
|
||||
extends Node
|
||||
|
||||
signal wave_started(wave_number)
|
||||
signal wave_finished(wave_number)
|
||||
signal base_took_damage(remaining_health)
|
||||
signal game_started
|
||||
signal game_restarted
|
||||
signal lost_game
|
||||
signal won_game
|
||||
signal enemy_number_changed(number_of_enemies)
|
||||
|
||||
var level_scene = load("res://Worlds/GreenPlanet/Levels/first_level.tscn")
|
||||
var player_scene = load("res://PCs/hero.tscn")
|
||||
var main_menu_scene_path = "res://Scenes/Menus/main_menu.tscn"
|
||||
var multiplayer_lobby_scene_path = "res://Scenes/Menus/multiplayer_lobby.tscn"
|
||||
var singleplayer_lobby_scene_path = "res://Scenes/Menus/singleplayer_lobby.tscn"
|
||||
var won_game_scene = load("res://Scenes/Menus/won_game_screen.tscn")
|
||||
var lose_game_scene = load("res://Scenes/Menus/lost_game_screen.tscn")
|
||||
var connected_players_nodes = {}
|
||||
var game_active := false
|
||||
var level : Level
|
||||
var enemies := 0
|
||||
var objective_health := 120
|
||||
var wave := 0
|
||||
var upcoming_wave
|
||||
var pot : int
|
||||
|
||||
|
||||
func parse_command(text : String, peer_id : int):
|
||||
if text.substr(1, 4) == "give":
|
||||
var gift_name = text.substr(6) as String
|
||||
var gift = Data.cards[0]
|
||||
for x in Data.cards:
|
||||
if x.title == gift_name:
|
||||
gift = x
|
||||
connected_players_nodes[peer_id].inventory.add(gift)
|
||||
|
||||
|
||||
func spawn_level():
|
||||
level = level_scene.instantiate() as Level
|
||||
for x in level.enemy_spawns:
|
||||
#x.path = level.a_star_graph_3d.visualized_path
|
||||
x.signal_for_after_enemy_died = enemy_died
|
||||
x.signal_for_after_enemy_reached_goal = damage_goal
|
||||
x.signal_for_when_enemy_spawns.connect(increase_enemy_count)
|
||||
add_child(level)
|
||||
|
||||
|
||||
func spawn_players(player_array, player_profiles, chatbox_open_signal, chatbox_closed_signal):
|
||||
var p_i = 0
|
||||
player_array.sort()
|
||||
for peer_id in player_array:
|
||||
var player = player_scene.instantiate() as Hero
|
||||
player.name = str(peer_id)
|
||||
player.position = level.player_spawns[p_i].global_position
|
||||
player.profile = player_profiles[peer_id]
|
||||
player.hero_class = Data.characters[player_profiles[peer_id].preferred_class]
|
||||
player.ready_state_changed.connect(ready_player)
|
||||
if peer_id == multiplayer.get_unique_id():
|
||||
chatbox_open_signal.connect(player.pause)
|
||||
chatbox_closed_signal.connect(player.unpause)
|
||||
player.set_multiplayer_authority(peer_id)
|
||||
connected_players_nodes[peer_id] = player
|
||||
wave_started.connect(player.exit_editing_mode)
|
||||
wave_finished.connect(player.enter_editing_mode)
|
||||
base_took_damage.connect(player.hud.set_lives_count)
|
||||
enemy_number_changed.connect(player.hud.set_enemy_count)
|
||||
add_child(player)
|
||||
p_i += 1
|
||||
start_game()
|
||||
|
||||
|
||||
func ready_player(_value):
|
||||
for key in connected_players_nodes:
|
||||
if connected_players_nodes[key].ready_state == false:
|
||||
return
|
||||
for key in connected_players_nodes:
|
||||
connected_players_nodes[key].ready_state = false
|
||||
spawn_enemy_wave()
|
||||
|
||||
|
||||
func spawn_enemy_wave():
|
||||
wave += 1
|
||||
level.a_star_graph_3d.find_path()
|
||||
level.a_star_graph_3d.visualized_path.disable_visualization()
|
||||
for spawn in level.enemy_spawns:
|
||||
spawn.spawn_wave(upcoming_wave)
|
||||
wave_started.emit(wave)
|
||||
|
||||
|
||||
func set_upcoming_wave():
|
||||
var spawn_power = WaveManager.calculate_spawn_power(wave + 1, connected_players_nodes.size())
|
||||
upcoming_wave = WaveManager.generate_wave(spawn_power, level.enemy_pool)
|
||||
pot = 6 + (spawn_power / 100)
|
||||
|
||||
|
||||
func increase_enemy_count():
|
||||
enemies += 1
|
||||
enemy_number_changed.emit(enemies)
|
||||
|
||||
|
||||
func enemy_died():
|
||||
enemies -= 1
|
||||
enemy_number_changed.emit(enemies)
|
||||
for x in level.enemy_spawns:
|
||||
if !x.done_spawning:
|
||||
return
|
||||
if enemies == 0:
|
||||
end_wave()
|
||||
if wave >= 20:
|
||||
win_game()
|
||||
|
||||
|
||||
func damage_goal(penalty):
|
||||
enemies -= 1
|
||||
enemy_number_changed.emit(enemies)
|
||||
objective_health -= penalty
|
||||
base_took_damage.emit(objective_health)
|
||||
if objective_health <= 0:
|
||||
lose_game()
|
||||
elif enemies == 0:
|
||||
end_wave()
|
||||
if wave >= 20:
|
||||
win_game()
|
||||
|
||||
|
||||
func end_wave():
|
||||
for peer_id in connected_players_nodes:
|
||||
connected_players_nodes[peer_id].currency += pot / connected_players_nodes.size()
|
||||
level.a_star_graph_3d.visualized_path.enable_visualization()
|
||||
wave_finished.emit(wave)
|
||||
set_upcoming_wave()
|
||||
if wave < 20:
|
||||
for key in connected_players_nodes:
|
||||
connected_players_nodes[key].hud.set_upcoming_wave(upcoming_wave)
|
||||
|
||||
|
||||
func remove_player(peer_id):
|
||||
connected_players_nodes[peer_id].queue_free()
|
||||
connected_players_nodes.erase(peer_id)
|
||||
|
||||
|
||||
func start_game():
|
||||
game_active = true
|
||||
level.a_star_graph_3d.make_grid()
|
||||
level.a_star_graph_3d.find_path()
|
||||
set_upcoming_wave()
|
||||
for peer_id in connected_players_nodes:
|
||||
connected_players_nodes[peer_id].currency = 20
|
||||
connected_players_nodes[peer_id].hud.set_upcoming_wave(upcoming_wave)
|
||||
game_started.emit()
|
||||
|
||||
|
||||
func restart_game():
|
||||
#implement game reloading system
|
||||
for peer_id in connected_players_nodes:
|
||||
connected_players_nodes[peer_id].queue_free()
|
||||
connected_players_nodes.clear()
|
||||
level.queue_free()
|
||||
enemies = 0
|
||||
objective_health = 100
|
||||
wave = 0
|
||||
spawn_level()
|
||||
game_restarted.emit()
|
||||
pass
|
||||
|
||||
|
||||
func lose_game():
|
||||
if game_active == false:
|
||||
return
|
||||
game_active = false
|
||||
var menu = lose_game_scene.instantiate()
|
||||
add_child(menu)
|
||||
lost_game.emit()
|
||||
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
|
||||
for peer_id in connected_players_nodes:
|
||||
connected_players_nodes[peer_id].pause()
|
||||
|
||||
|
||||
func win_game():
|
||||
if game_active == false:
|
||||
return
|
||||
game_active = false
|
||||
var menu = won_game_scene.instantiate()
|
||||
add_child(menu)
|
||||
won_game.emit()
|
||||
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
|
||||
for peer_id in connected_players_nodes:
|
||||
connected_players_nodes[peer_id].pause()
|
||||
|
||||
|
||||
func quit_to_desktop():
|
||||
multiplayer.multiplayer_peer.close()
|
||||
multiplayer.multiplayer_peer = null
|
||||
get_tree().quit()
|
||||
|
||||
|
||||
func scene_switch_main_menu():
|
||||
multiplayer.multiplayer_peer.close()
|
||||
multiplayer.multiplayer_peer = null
|
||||
for node in get_children():
|
||||
node.queue_free()
|
||||
get_tree().change_scene_to_file(main_menu_scene_path)
|
||||
|
||||
|
||||
func scene_switch_to_multiplayer_lobby():
|
||||
get_tree().change_scene_to_file(multiplayer_lobby_scene_path)
|
||||
|
||||
|
||||
func scene_switch_to_singleplayer_lobby():
|
||||
get_tree().change_scene_to_file(singleplayer_lobby_scene_path)
|
2
Scripts/ground_enemy_controller.gd
Normal file
2
Scripts/ground_enemy_controller.gd
Normal file
@ -0,0 +1,2 @@
|
||||
extends Node3D
|
||||
class_name GroundEnemyController
|
27
Scripts/health.gd
Normal file
27
Scripts/health.gd
Normal file
@ -0,0 +1,27 @@
|
||||
extends Node
|
||||
class_name Health
|
||||
|
||||
signal health_depleted
|
||||
signal health_changed(health)
|
||||
|
||||
@export var damage_particle_scene : PackedScene
|
||||
|
||||
@export var max_health := 10
|
||||
var current_health
|
||||
|
||||
func take_damage(damage):
|
||||
var marker = damage_particle_scene.instantiate()
|
||||
get_tree().root.add_child(marker)
|
||||
marker.set_number(damage)
|
||||
marker.position = get_parent().global_position + Vector3.UP
|
||||
|
||||
current_health -= damage
|
||||
health_changed.emit(current_health)
|
||||
if current_health <= 0:
|
||||
health_depleted.emit()
|
||||
|
||||
|
||||
func heal_damage(healing):
|
||||
current_health += healing
|
||||
if current_health > max_health:
|
||||
current_health = max_health
|
13
Scripts/hitbox.gd
Normal file
13
Scripts/hitbox.gd
Normal file
@ -0,0 +1,13 @@
|
||||
extends CollisionShape3D
|
||||
class_name Hitbox
|
||||
|
||||
signal took_damage(amount)
|
||||
|
||||
|
||||
func damage(amount):
|
||||
networked_damage.rpc(amount)
|
||||
|
||||
|
||||
@rpc("any_peer","call_local")
|
||||
func networked_damage(amount):
|
||||
took_damage.emit(amount)
|
10
Scripts/interact_button.gd
Normal file
10
Scripts/interact_button.gd
Normal file
@ -0,0 +1,10 @@
|
||||
extends StaticBody3D
|
||||
class_name InteractButton
|
||||
|
||||
signal button_interacted(value)
|
||||
|
||||
@export var button_press_value := 0
|
||||
@export var press_cost := 0
|
||||
|
||||
func press():
|
||||
button_interacted.emit(button_press_value)
|
66
Scripts/inventory.gd
Normal file
66
Scripts/inventory.gd
Normal file
@ -0,0 +1,66 @@
|
||||
extends Node
|
||||
class_name Inventory
|
||||
|
||||
signal item_added(item)
|
||||
signal item_removed(item)
|
||||
|
||||
@export var max_size := 0
|
||||
var contents : Array[Card] = []
|
||||
var selected_index := 0
|
||||
var selected_item : Card :
|
||||
get:
|
||||
return contents[selected_index]
|
||||
set(_value):
|
||||
return
|
||||
|
||||
|
||||
func add(card : Card) -> bool:
|
||||
if card != null and contents.size() < max_size or max_size == 0:
|
||||
contents.append(card)
|
||||
item_added.emit(card)
|
||||
networked_add.rpc(Data.cards.find(card))
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
func remove_at(index : int) -> Card:
|
||||
if contents.size() <= 0:
|
||||
return null
|
||||
var card = contents[index]
|
||||
contents.remove_at(index)
|
||||
if selected_index >= contents.size() and selected_index > 0:
|
||||
selected_index -= 1
|
||||
item_removed.emit(card)
|
||||
networked_remove_at.rpc(index)
|
||||
return card
|
||||
|
||||
|
||||
func remove() -> Card:
|
||||
return remove_at(selected_index)
|
||||
|
||||
|
||||
func increment_selected():
|
||||
if contents.size() > 0:
|
||||
selected_index += 1
|
||||
if selected_index >= contents.size():
|
||||
selected_index = 0
|
||||
|
||||
|
||||
func decrement_selected():
|
||||
if contents.size() > 0:
|
||||
selected_index -= 1
|
||||
if selected_index < 0:
|
||||
selected_index = contents.size() - 1
|
||||
|
||||
|
||||
@rpc("reliable")
|
||||
func networked_add(value):
|
||||
contents.append(Data.cards[value])
|
||||
item_added.emit(Data.cards[value])
|
||||
|
||||
|
||||
@rpc("reliable")
|
||||
func networked_remove_at(value):
|
||||
var item = contents[value]
|
||||
contents.remove_at(value)
|
||||
item_removed.emit(item)
|
14
Scripts/item_card.gd
Normal file
14
Scripts/item_card.gd
Normal file
@ -0,0 +1,14 @@
|
||||
extends StaticBody3D
|
||||
class_name ItemCard
|
||||
|
||||
@export var card : Card
|
||||
|
||||
|
||||
func pick_up() -> Card:
|
||||
queue_free()
|
||||
networked_pick_up.rpc()
|
||||
return card
|
||||
|
||||
|
||||
@rpc func networked_pick_up():
|
||||
queue_free()
|
8
Scripts/level.gd
Normal file
8
Scripts/level.gd
Normal file
@ -0,0 +1,8 @@
|
||||
extends GridMap
|
||||
class_name Level
|
||||
|
||||
@export var enemy_pool : Array[Enemy]
|
||||
@export var player_spawns : Array[Node3D] = []
|
||||
@export var enemy_spawns : Array[Node3D] = []
|
||||
@export var enemy_goals : Array[Node3D] = []
|
||||
@export var a_star_graph_3d : AStarGraph3D
|
20
Scripts/lifebar_segment.gd
Normal file
20
Scripts/lifebar_segment.gd
Normal file
@ -0,0 +1,20 @@
|
||||
extends Control
|
||||
class_name LivesBarSegment
|
||||
|
||||
var lives_left := 6
|
||||
|
||||
func take_life(value : int):
|
||||
for x in value:
|
||||
lives_left -= 1
|
||||
if lives_left == 5:
|
||||
$AnimationPlayer.play("lose1")
|
||||
if lives_left == 4:
|
||||
$AnimationPlayer2.play("lose2")
|
||||
if lives_left == 3:
|
||||
$AnimationPlayer3.play("lose3")
|
||||
if lives_left == 2:
|
||||
$AnimationPlayer4.play("lose4")
|
||||
if lives_left == 1:
|
||||
$AnimationPlayer5.play("lose5")
|
||||
if lives_left == 0:
|
||||
$AnimationPlayer6.play("lose6")
|
10
Scripts/lives_bar.gd
Normal file
10
Scripts/lives_bar.gd
Normal file
@ -0,0 +1,10 @@
|
||||
extends TextureRect
|
||||
|
||||
@export var segments : Array[LivesBarSegment]
|
||||
var lives := 120.0
|
||||
|
||||
|
||||
func take_life():
|
||||
var segment_to_animate = ceil(lives / 6.0) - 1
|
||||
lives -= 1
|
||||
segments[segment_to_animate].take_life(1)
|
15
Scripts/loadout_editor.gd
Normal file
15
Scripts/loadout_editor.gd
Normal file
@ -0,0 +1,15 @@
|
||||
extends Panel
|
||||
class_name LoadoutEditor
|
||||
|
||||
signal character_selected(character)
|
||||
|
||||
func _ready() -> void:
|
||||
for i in Data.characters.size():
|
||||
var button = Button.new()
|
||||
button.text = Data.characters[i].hero_name
|
||||
button.pressed.connect(set_character.bind(i))
|
||||
$HBoxContainer.add_child(button)
|
||||
|
||||
|
||||
func set_character(i: int):
|
||||
character_selected.emit(i)
|
10
Scripts/lost_game_screen.gd
Normal file
10
Scripts/lost_game_screen.gd
Normal file
@ -0,0 +1,10 @@
|
||||
extends Control
|
||||
|
||||
|
||||
func _on_quit_button_pressed() -> void:
|
||||
Game.quit_to_desktop()
|
||||
|
||||
|
||||
func _on_restart_button_pressed() -> void:
|
||||
Game.restart_game()
|
||||
queue_free()
|
46
Scripts/main_menu.gd
Normal file
46
Scripts/main_menu.gd
Normal file
@ -0,0 +1,46 @@
|
||||
extends Control
|
||||
|
||||
var confirmation_popup_scene = preload("res://Scenes/Menus/confirmation_popup.tscn")
|
||||
var text_input_popup_scene = preload("res://Scenes/Menus/text_input_popup.tscn")
|
||||
var multiplayer_lobby_scene_path = "res://Scenes/multiplayer_lobby.tscn"
|
||||
var options_menu_scene = preload("res://Scenes/Menus/options_menu.tscn")
|
||||
|
||||
func _ready() -> void:
|
||||
$ProfileEditor/VBoxContainer/HBoxContainer/DisplayName.text = Data.player_profile.display_name
|
||||
|
||||
|
||||
func _on_display_name_edit_pressed() -> void:
|
||||
var popup = text_input_popup_scene.instantiate() as TextInputPopup
|
||||
popup.set_popup(Data.player_profile.display_name, "Display Name", "Confirm")
|
||||
popup.completed.connect(change_profile_display_name)
|
||||
add_child(popup)
|
||||
|
||||
|
||||
func change_profile_display_name(display_name):
|
||||
$ProfileEditor/VBoxContainer/HBoxContainer/DisplayName.text = display_name
|
||||
Data.player_profile.set_display_name(display_name)
|
||||
|
||||
|
||||
func _on_quit_button_pressed() -> void:
|
||||
var popup = confirmation_popup_scene.instantiate() as ConfirmationPopup
|
||||
popup.set_popup("Are you sure you want to quit?", "Yes", "No")
|
||||
popup.completed.connect(quit_game)
|
||||
add_child(popup)
|
||||
|
||||
|
||||
func quit_game(confirmation):
|
||||
if confirmation:
|
||||
get_tree().quit()
|
||||
|
||||
|
||||
func _on_play_button_pressed() -> void:
|
||||
Game.scene_switch_to_singleplayer_lobby()
|
||||
|
||||
|
||||
func _on_options_button_pressed() -> void:
|
||||
var menu = options_menu_scene.instantiate()
|
||||
add_child(menu)
|
||||
|
||||
|
||||
func _on_multiplayer_button_pressed() -> void:
|
||||
Game.scene_switch_to_multiplayer_lobby()
|
10
Scripts/minimap_cam.gd
Normal file
10
Scripts/minimap_cam.gd
Normal file
@ -0,0 +1,10 @@
|
||||
extends Camera3D
|
||||
class_name MinimapCamera3D
|
||||
|
||||
@export var anchor : Node3D
|
||||
@export var face_north : bool
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
global_position = anchor.global_position + (Vector3.UP * 100)
|
||||
if face_north:
|
||||
rotation.y = anchor.rotation.y
|
124
Scripts/multiplayer_lobby.gd
Normal file
124
Scripts/multiplayer_lobby.gd
Normal file
@ -0,0 +1,124 @@
|
||||
extends Control
|
||||
class_name MultiplayerLobby
|
||||
|
||||
signal player_connected(peer_id, player_profile)
|
||||
signal player_disconnected(peer_id)
|
||||
signal disconnected_from_server
|
||||
|
||||
const SERVER_PORT := 58008
|
||||
const MAX_PLAYERS := 4
|
||||
|
||||
var enet_peer = ENetMultiplayerPeer.new()
|
||||
|
||||
@export var server_form : ServerForm
|
||||
@export var scoreboard : Scoreboard
|
||||
@export var loadout_editor : LoadoutEditor
|
||||
@export var chatbox : Chatbox
|
||||
var alert_popup_scene = preload("res://Scenes/Menus/alert_popup.tscn")
|
||||
var connected_players_profiles = {}
|
||||
|
||||
|
||||
func _ready():
|
||||
multiplayer.peer_connected.connect(_on_player_connected)
|
||||
multiplayer.peer_disconnected.connect(_on_player_disconnected)
|
||||
multiplayer.connected_to_server.connect(_on_connection_succeeded)
|
||||
multiplayer.connection_failed.connect(_on_connection_failed)
|
||||
multiplayer.server_disconnected.connect(_on_server_disconnected)
|
||||
|
||||
|
||||
func _on_player_connected(peer_id):
|
||||
add_player.rpc_id(peer_id, Data.player_profile.to_dict())
|
||||
if multiplayer.get_unique_id() == 1:
|
||||
print("Player connected with id: " + str(peer_id))
|
||||
|
||||
|
||||
func _on_player_disconnected(peer_id):
|
||||
connected_players_profiles.erase(peer_id)
|
||||
player_disconnected.emit(peer_id)
|
||||
|
||||
|
||||
func _on_connection_succeeded():
|
||||
setup_game(multiplayer.get_unique_id())
|
||||
|
||||
|
||||
func _on_connection_failed():
|
||||
multiplayer.multiplayer_peer = null
|
||||
var popup = alert_popup_scene.instantiate() as AlertPopup
|
||||
popup.set_popup("Unable to connect to server", "OK")
|
||||
add_child(popup)
|
||||
|
||||
|
||||
func _on_server_disconnected():
|
||||
multiplayer.multiplayer_peer = null
|
||||
disconnected_from_server.emit()
|
||||
|
||||
|
||||
func create_server() -> void:
|
||||
enet_peer.create_server(SERVER_PORT, MAX_PLAYERS)
|
||||
multiplayer.multiplayer_peer = enet_peer
|
||||
setup_game(1)
|
||||
|
||||
|
||||
func setup_game(peer_id):
|
||||
player_disconnected.connect(Game.remove_player)
|
||||
Game.spawn_level()
|
||||
scoreboard.all_players_ready.connect(start_game)
|
||||
Game.game_restarted.connect(setup_the_ui)
|
||||
setup_the_ui()
|
||||
chatbox.username = Data.player_profile.display_name
|
||||
Data.player_profile.display_name_changed.connect(chatbox.change_username)
|
||||
loadout_editor.character_selected.connect(Data.player_profile.set_preferred_class)
|
||||
loadout_editor.character_selected.connect(edit_player_profile)
|
||||
connected_players_profiles[peer_id] = Data.player_profile
|
||||
player_connected.emit(peer_id, Data.player_profile)
|
||||
|
||||
|
||||
func setup_the_ui():
|
||||
scoreboard.unready_all_players()
|
||||
scoreboard.set_visible(true)
|
||||
loadout_editor.set_visible(true)
|
||||
$ReadyButton.set_visible(true)
|
||||
chatbox.set_visible(true)
|
||||
|
||||
|
||||
func connect_to_server() -> void:
|
||||
var ip = server_form.get_server_ip() if server_form.get_server_ip() else "localhost"
|
||||
var port = server_form.get_server_port() if server_form.get_server_port() else str(SERVER_PORT)
|
||||
enet_peer.create_client(ip, int(port))
|
||||
multiplayer.multiplayer_peer = enet_peer
|
||||
|
||||
|
||||
func ready_player():
|
||||
var peer_id = multiplayer.get_unique_id()
|
||||
networked_ready_player.rpc(peer_id)
|
||||
|
||||
|
||||
func start_game():
|
||||
enet_peer.refuse_new_connections = true
|
||||
Game.spawn_players(connected_players_profiles.keys(), connected_players_profiles, chatbox.opened, chatbox.closed)
|
||||
scoreboard.set_visible(false)
|
||||
loadout_editor.set_visible(false)
|
||||
|
||||
|
||||
func edit_player_profile(_argument):
|
||||
var profile_dict = Data.player_profile.to_dict()
|
||||
networked_edit_player_profile.rpc(multiplayer.get_unique_id(), profile_dict)
|
||||
|
||||
|
||||
@rpc("any_peer", "reliable", "call_local")
|
||||
func networked_edit_player_profile(peer_id, new_profile_dict):
|
||||
connected_players_profiles[peer_id].set_display_name(new_profile_dict["display_name"])
|
||||
connected_players_profiles[peer_id].set_preferred_class(new_profile_dict["preferred_class"])
|
||||
|
||||
|
||||
@rpc("any_peer","reliable")
|
||||
func add_player(new_player_profile_dict):
|
||||
var new_player_peer_id = multiplayer.get_remote_sender_id()
|
||||
var new_player_profile = PlayerProfile.from_dict(new_player_profile_dict)
|
||||
connected_players_profiles[new_player_peer_id] = new_player_profile
|
||||
player_connected.emit(new_player_peer_id, new_player_profile)
|
||||
|
||||
|
||||
@rpc("any_peer", "reliable", "call_local")
|
||||
func networked_ready_player(peer_id):
|
||||
scoreboard.set_player_ready_state(peer_id, true)
|
7
Scripts/on_top_camera.gd
Normal file
7
Scripts/on_top_camera.gd
Normal file
@ -0,0 +1,7 @@
|
||||
extends Camera3D
|
||||
|
||||
@export var clone_camera : Node3D
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
global_position = clone_camera.global_position
|
||||
global_rotation = clone_camera.global_rotation
|
123
Scripts/options_menu.gd
Normal file
123
Scripts/options_menu.gd
Normal file
@ -0,0 +1,123 @@
|
||||
extends Control
|
||||
class_name OptionsMenu
|
||||
|
||||
@export var look_sens_slider : HSlider
|
||||
@export var look_sens_input : SpinBox
|
||||
@export var toggle_sprint_checkbox : CheckButton
|
||||
@export var vsync_dropdown : OptionButton
|
||||
@export var aa_dropdown : OptionButton
|
||||
@export var window_dropdown : OptionButton
|
||||
@export var invert_lookY : CheckButton
|
||||
@export var invert_lookX : CheckButton
|
||||
@export var fov_input : SpinBox
|
||||
@export var fov_slider : HSlider
|
||||
var keybind_boxes = []
|
||||
var keybind_buttons = {}
|
||||
var key_event
|
||||
var selected_button
|
||||
var selected_button_button
|
||||
var listening_for_key := false
|
||||
|
||||
func _ready():
|
||||
look_sens_slider.value = Data.preferences.mouse_sens
|
||||
look_sens_input.value = Data.preferences.mouse_sens
|
||||
toggle_sprint_checkbox.button_pressed = Data.preferences.toggle_sprint
|
||||
vsync_dropdown.selected = Data.preferences.vsync_mode
|
||||
aa_dropdown.selected = Data.preferences.aa_mode
|
||||
invert_lookY.button_pressed = Data.preferences.invert_lookY
|
||||
invert_lookX.button_pressed = Data.preferences.invert_lookX
|
||||
fov_input.value = Data.preferences.hfov
|
||||
fov_slider.value = Data.preferences.hfov
|
||||
|
||||
for index in Data.keymaps.size():
|
||||
var map = Data.keymaps[index]
|
||||
var button = Button.new()
|
||||
button.text = map.title
|
||||
button.pressed.connect(set_keymap.bind(index))
|
||||
$VBoxContainer/TabContainer/Keybinds/HBoxContainer.add_child(button)
|
||||
load_keybind_labels()
|
||||
|
||||
|
||||
func set_keymap(keymap_index):
|
||||
Data.player_keymap = Data.keymaps[keymap_index]
|
||||
Data.player_keymap.apply()
|
||||
load_keybind_labels()
|
||||
|
||||
|
||||
func load_keybind_labels():
|
||||
for box in keybind_boxes:
|
||||
box.queue_free()
|
||||
keybind_boxes.clear()
|
||||
for action in InputMap.get_actions():
|
||||
if !action.begins_with("ui_"):
|
||||
var box = HBoxContainer.new()
|
||||
var alabel = Label.new()
|
||||
var elabel = Button.new()
|
||||
alabel.text = action
|
||||
if InputMap.action_get_events(action).size() > 0:
|
||||
elabel.text = InputMap.action_get_events(action)[0].as_text()
|
||||
elabel.size_flags_horizontal += Control.SIZE_EXPAND
|
||||
alabel.size_flags_horizontal += Control.SIZE_EXPAND
|
||||
alabel.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
alabel.size_flags_stretch_ratio = 2.0
|
||||
#elabel.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
|
||||
box.add_child(alabel)
|
||||
box.add_child(elabel)
|
||||
elabel.pressed.connect(_on_keybind_button_pressed.bind(elabel))
|
||||
keybind_buttons[elabel] = action
|
||||
$VBoxContainer/TabContainer/Keybinds/ScrollContainer/VBoxContainer.add_child(box)
|
||||
keybind_boxes.append(box)
|
||||
|
||||
|
||||
func _on_cancel_pressed() -> void:
|
||||
queue_free()
|
||||
|
||||
|
||||
func _on_confirm_pressed() -> void:
|
||||
Data.preferences.mouse_sens = look_sens_slider.value
|
||||
Data.preferences.toggle_sprint = toggle_sprint_checkbox.button_pressed
|
||||
Data.preferences.vsync_mode = vsync_dropdown.selected
|
||||
Data.preferences.aa_mode = aa_dropdown.selected
|
||||
Data.preferences.windowed_mode = window_dropdown.selected
|
||||
Data.preferences.invert_lookY = invert_lookY.button_pressed
|
||||
Data.preferences.invert_lookX = invert_lookX.button_pressed
|
||||
Data.preferences.apply_graphical_settings(get_viewport())
|
||||
Data.preferences.save_profile_to_disk()
|
||||
Data.player_keymap.save_profile_to_disk()
|
||||
queue_free()
|
||||
|
||||
|
||||
func _on_mouse_sens_spin_box_value_changed(value: float) -> void:
|
||||
look_sens_slider.value = value
|
||||
|
||||
|
||||
func _on_mouse_sens_h_slider_value_changed(value: float) -> void:
|
||||
look_sens_input.value = value
|
||||
|
||||
|
||||
func _on_fov_spin_box_value_changed(value: float) -> void:
|
||||
if value < 40.0:
|
||||
value = 40.0
|
||||
if value > 160.0:
|
||||
value = 160.0
|
||||
fov_slider.value = value
|
||||
Data.preferences.hfov = value
|
||||
|
||||
|
||||
func _on_fov_h_slider_value_changed(value: float) -> void:
|
||||
fov_input.value = value
|
||||
Data.preferences.hfov = value
|
||||
|
||||
|
||||
func _on_keybind_button_pressed(value: Button) -> void:
|
||||
selected_button = keybind_buttons[value]
|
||||
selected_button_button = value
|
||||
listening_for_key = true
|
||||
|
||||
|
||||
func _input(event: InputEvent) -> void:
|
||||
if listening_for_key and (event is InputEventKey or event is InputEventMouseButton or event is InputEventJoypadButton):
|
||||
key_event = event
|
||||
listening_for_key = false
|
||||
Data.player_keymap.replace_action_event(selected_button, key_event)
|
||||
selected_button_button.text = key_event.as_text()
|
16
Scripts/path_visual_thing.gd
Normal file
16
Scripts/path_visual_thing.gd
Normal file
@ -0,0 +1,16 @@
|
||||
extends PathFollow3D
|
||||
|
||||
@export var speed = 0.5
|
||||
@export var world_model : Node3D
|
||||
@export var minimap_model : Node3D
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
progress += speed * delta
|
||||
|
||||
|
||||
func set_world_visible(value: bool):
|
||||
world_model.set_visible(value)
|
||||
|
||||
|
||||
func set_minimap_visible(value: bool):
|
||||
minimap_model.set_visible(value)
|
46
Scripts/pause_menu.gd
Normal file
46
Scripts/pause_menu.gd
Normal file
@ -0,0 +1,46 @@
|
||||
extends Control
|
||||
class_name PauseMenu
|
||||
|
||||
signal closed
|
||||
|
||||
var options_menu_scene = preload("res://Scenes/Menus/options_menu.tscn")
|
||||
var confirmation_popup_scene = preload("res://Scenes/Menus/confirmation_popup.tscn")
|
||||
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
if event.is_action_pressed("Pause"):
|
||||
accept_event()
|
||||
_on_resume_pressed()
|
||||
|
||||
|
||||
func _on_resume_pressed() -> void:
|
||||
closed.emit()
|
||||
queue_free()
|
||||
|
||||
|
||||
func _on_options_pressed() -> void:
|
||||
var menu = options_menu_scene.instantiate()
|
||||
add_child(menu)
|
||||
|
||||
|
||||
func _on_quit_to_main_menu_pressed() -> void:
|
||||
var popup = confirmation_popup_scene.instantiate() as ConfirmationPopup
|
||||
popup.set_popup("Are you sure you want to quit and return to main menu?", "Yes", "No")
|
||||
popup.completed.connect(return_to_menu)
|
||||
add_child(popup)
|
||||
|
||||
|
||||
func return_to_menu(confirmation):
|
||||
if confirmation:
|
||||
Game.scene_switch_main_menu()
|
||||
|
||||
|
||||
func _on_quit_to_desktop_pressed() -> void:
|
||||
var popup = confirmation_popup_scene.instantiate() as ConfirmationPopup
|
||||
popup.set_popup("Are you sure you want to quit?", "Yes", "No")
|
||||
popup.completed.connect(quit_game)
|
||||
add_child(popup)
|
||||
|
||||
|
||||
func quit_game(confirmation):
|
||||
if confirmation:
|
||||
Game.quit_to_desktop()
|
46
Scripts/scoreboard.gd
Normal file
46
Scripts/scoreboard.gd
Normal file
@ -0,0 +1,46 @@
|
||||
extends PanelContainer
|
||||
class_name Scoreboard
|
||||
|
||||
signal all_players_ready
|
||||
|
||||
var entry_scene = preload("res://Scenes/UI/scoreboard_entry.tscn")
|
||||
var entries = {}
|
||||
|
||||
func _ready() -> void:
|
||||
$VBoxContainer/DummyEntry1.queue_free()
|
||||
$VBoxContainer/DummyEntry2.queue_free()
|
||||
$VBoxContainer/DummyEntry3.queue_free()
|
||||
|
||||
|
||||
func get_player_entry(peer_id) -> ScoreboardEntry:
|
||||
return entries[peer_id]
|
||||
|
||||
|
||||
func set_player_ready_state(peer_id: int, state: bool):
|
||||
entries[peer_id].set_ready_state(state)
|
||||
for id in entries:
|
||||
if !entries[id].get_ready_state():
|
||||
return
|
||||
all_players_ready.emit()
|
||||
unready_all_players()
|
||||
|
||||
|
||||
func unready_all_players():
|
||||
for peer_id in entries:
|
||||
entries[peer_id].set_ready_state(false)
|
||||
|
||||
|
||||
func add_player(peer_id: int, player_profile: PlayerProfile):
|
||||
var entry = entry_scene.instantiate() as ScoreboardEntry
|
||||
entry.name = str(peer_id)
|
||||
entry.set_display_name("", player_profile.get_display_name())
|
||||
entry.set_character(0, player_profile.get_preferred_class())
|
||||
player_profile.display_name_changed.connect(entry.set_display_name)
|
||||
player_profile.preferred_class_changed.connect(entry.set_character)
|
||||
entries[peer_id] = entry
|
||||
$VBoxContainer.add_child(entry)
|
||||
|
||||
|
||||
func remove_player(peer_id: int):
|
||||
entries[peer_id].queue_free()
|
||||
entries.erase(peer_id)
|
28
Scripts/scoreboard_entry.gd
Normal file
28
Scripts/scoreboard_entry.gd
Normal file
@ -0,0 +1,28 @@
|
||||
extends HBoxContainer
|
||||
class_name ScoreboardEntry
|
||||
|
||||
var display_name: String
|
||||
var character: int
|
||||
var ready_state: bool
|
||||
|
||||
|
||||
func set_display_name(_old_name: String, new_name: String):
|
||||
display_name = new_name
|
||||
$DisplayName.text = new_name
|
||||
func get_display_name() -> String:
|
||||
return display_name
|
||||
|
||||
func set_character(_old_class: int, new_class: int):
|
||||
character = new_class
|
||||
$CharacterName.text = Data.characters[new_class].hero_name
|
||||
func get_character() -> int:
|
||||
return character
|
||||
|
||||
func set_ready_state(state: bool):
|
||||
ready_state = state
|
||||
if state:
|
||||
$TextureRect.texture.region = Rect2(32, 0, 32, 32)
|
||||
else:
|
||||
$TextureRect.texture.region = Rect2(0, 0, 32, 32)
|
||||
func get_ready_state() -> bool:
|
||||
return ready_state
|
18
Scripts/server_form.gd
Normal file
18
Scripts/server_form.gd
Normal file
@ -0,0 +1,18 @@
|
||||
extends PanelContainer
|
||||
class_name ServerForm
|
||||
|
||||
signal connect_button_pressed
|
||||
signal host_button_pressed
|
||||
|
||||
|
||||
func _on_host_pressed() -> void:
|
||||
host_button_pressed.emit()
|
||||
queue_free()
|
||||
func _on_connect_pressed() -> void:
|
||||
connect_button_pressed.emit()
|
||||
queue_free()
|
||||
|
||||
func get_server_ip() -> String:
|
||||
return $VBoxContainer/HBoxContainer/ServerIP.text
|
||||
func get_server_port() -> String:
|
||||
return $VBoxContainer/HBoxContainer2/ServerPort.text
|
41
Scripts/singleplayer_lobby.gd
Normal file
41
Scripts/singleplayer_lobby.gd
Normal file
@ -0,0 +1,41 @@
|
||||
extends Control
|
||||
|
||||
@export var scoreboard : Scoreboard
|
||||
@export var loadout_editor : LoadoutEditor
|
||||
@export var chatbox : Chatbox
|
||||
var connected_players_profiles = {}
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
setup_game()
|
||||
|
||||
|
||||
func setup_game():
|
||||
Game.spawn_level()
|
||||
scoreboard.add_player(1, Data.player_profile)
|
||||
scoreboard.all_players_ready.connect(start_game)
|
||||
Game.game_restarted.connect(setup_the_ui)
|
||||
setup_the_ui()
|
||||
chatbox.username = Data.player_profile.display_name
|
||||
Data.player_profile.display_name_changed.connect(chatbox.change_username)
|
||||
loadout_editor.character_selected.connect(Data.player_profile.set_preferred_class)
|
||||
loadout_editor.character_selected.connect(edit_player_profile)
|
||||
connected_players_profiles[1] = Data.player_profile
|
||||
|
||||
|
||||
func edit_player_profile(_argument):
|
||||
var profile_dict = Data.player_profile.to_dict()
|
||||
|
||||
|
||||
func start_game():
|
||||
Game.spawn_players(connected_players_profiles.keys(), connected_players_profiles, chatbox.opened, chatbox.closed)
|
||||
scoreboard.set_visible(false)
|
||||
loadout_editor.set_visible(false)
|
||||
|
||||
|
||||
func setup_the_ui():
|
||||
scoreboard.unready_all_players()
|
||||
scoreboard.set_visible(true)
|
||||
loadout_editor.set_visible(true)
|
||||
$ReadyButton.set_visible(true)
|
||||
chatbox.set_visible(true)
|
14
Scripts/target_dummy.gd
Normal file
14
Scripts/target_dummy.gd
Normal file
@ -0,0 +1,14 @@
|
||||
extends EnemyController
|
||||
class_name Dummy
|
||||
|
||||
func _on_health_health_depleted() -> void:
|
||||
$Dog/Health.max_health = stats.health
|
||||
$Dog/Health.current_health = stats.health
|
||||
$Dog/SubViewport/ProgressBar.max_value = stats.health
|
||||
$Dog/SubViewport/ProgressBar.value = stats.health
|
||||
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
progress += movement_speed * delta
|
||||
if progress_ratio >= 1:
|
||||
progress_ratio = 0
|
14
Scripts/text_input_popup.gd
Normal file
14
Scripts/text_input_popup.gd
Normal file
@ -0,0 +1,14 @@
|
||||
extends PanelContainer
|
||||
class_name TextInputPopup
|
||||
|
||||
signal completed(outcome)
|
||||
|
||||
func set_popup(prompt_text, placeholder_text, confirm_text):
|
||||
$VBoxContainer/LineEdit.text = prompt_text
|
||||
$VBoxContainer/LineEdit.placeholder_text = placeholder_text
|
||||
$VBoxContainer/Button.text = confirm_text
|
||||
|
||||
|
||||
func _on_button_pressed() -> void:
|
||||
completed.emit($VBoxContainer/LineEdit.text)
|
||||
queue_free()
|
39
Scripts/tower_base.gd
Normal file
39
Scripts/tower_base.gd
Normal file
@ -0,0 +1,39 @@
|
||||
extends StaticBody3D
|
||||
class_name TowerBase
|
||||
|
||||
@export var inventory : Inventory
|
||||
@export var block : CSGBox3D
|
||||
@export var collider : CollisionShape3D
|
||||
@export var minimap_icon : Sprite3D
|
||||
|
||||
var tower = null
|
||||
var has_card : bool :
|
||||
set(_value):
|
||||
return
|
||||
get:
|
||||
return inventory.contents.size() != 0
|
||||
|
||||
|
||||
func add_card(card: Card) -> bool:
|
||||
var result = inventory.add(card)
|
||||
if result:
|
||||
tower = card.turret.instantiate() as Tower
|
||||
tower.stats = card.tower_stats
|
||||
minimap_icon.modulate = Color.RED
|
||||
add_child(tower)
|
||||
return result
|
||||
|
||||
|
||||
func remove_card() -> Card:
|
||||
tower.queue_free()
|
||||
tower = null
|
||||
minimap_icon.modulate = Color.GREEN
|
||||
return inventory.remove()
|
||||
|
||||
|
||||
func set_material(value: StandardMaterial3D):
|
||||
block.material = value
|
||||
|
||||
|
||||
func toggle_collision():
|
||||
collider.disabled = !collider.disabled
|
26
Scripts/visualized_path.gd
Normal file
26
Scripts/visualized_path.gd
Normal file
@ -0,0 +1,26 @@
|
||||
extends Path3D
|
||||
class_name VisualizedPath
|
||||
|
||||
var visual_scene = preload("res://Scenes/path_visual_thing.tscn")
|
||||
var length := 0.0
|
||||
var visualizer_points = []
|
||||
|
||||
func spawn_visualizer_points() -> void:
|
||||
var new_length = curve.get_baked_length()
|
||||
for x in new_length - length:
|
||||
var point = visual_scene.instantiate()
|
||||
visualizer_points.append(point)
|
||||
add_child(point)
|
||||
length = new_length
|
||||
for x in visualizer_points.size():
|
||||
visualizer_points[x].progress_ratio = float(x) / visualizer_points.size()
|
||||
|
||||
|
||||
func disable_visualization():
|
||||
for x in visualizer_points:
|
||||
x.set_world_visible(false)
|
||||
|
||||
|
||||
func enable_visualization():
|
||||
for x in visualizer_points:
|
||||
x.set_world_visible(true)
|
21
Scripts/wave_manager.gd
Normal file
21
Scripts/wave_manager.gd
Normal file
@ -0,0 +1,21 @@
|
||||
extends Node
|
||||
|
||||
|
||||
func calculate_spawn_power(wave_number : int, number_of_players : int) -> int:
|
||||
return 20 + (50 * number_of_players) + (30 * wave_number)
|
||||
|
||||
|
||||
func generate_wave(spawn_power : int, spawn_pool : Array[Enemy]) -> Dictionary:
|
||||
var wave = {}
|
||||
#var sp_used = 0
|
||||
var enemy_types = randi_range(1, 5)
|
||||
var enemy_choices = spawn_pool.duplicate()
|
||||
var sp_allotment = 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[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))
|
||||
return wave
|
10
Scripts/won_game_screen.gd
Normal file
10
Scripts/won_game_screen.gd
Normal file
@ -0,0 +1,10 @@
|
||||
extends Control
|
||||
|
||||
|
||||
func _on_quit_button_pressed() -> void:
|
||||
Game.quit_to_desktop()
|
||||
|
||||
|
||||
func _on_play_button_pressed() -> void:
|
||||
Game.restart_game()
|
||||
queue_free()
|
Reference in New Issue
Block a user