added the game

This commit is contained in:
JHDev2006
2025-09-13 16:30:32 +01:00
parent 5ef689109b
commit 3773bdaf64
3616 changed files with 263702 additions and 0 deletions

View File

@@ -0,0 +1,10 @@
class_name AchievementProgressCalculator
extends Node
@export var target_number := 8
func _ready() -> void:
get_progress()
func get_progress() -> int:
return 0

View File

@@ -0,0 +1 @@
uid://dwrso5q5r5bak

View File

@@ -0,0 +1,17 @@
class_name AnimationPauser
extends Node
@export var animation_player: AnimationPlayer = null
@export var paused := false
signal just_paused
signal resumed
func _process(_delta: float) -> void:
animation_player.speed_scale = int(not paused)
func on_switch_hit() -> void:
paused = not paused
if paused: just_paused.emit()
else: resumed.emit()

View File

@@ -0,0 +1 @@
uid://cb0spbe0l8mof

View File

@@ -0,0 +1,75 @@
class_name BasicEnemyMovement
extends Node
@export var ledge_detection_cast: RayCast2D = null
var can_move := true
@export var auto_call := true
@export var move_speed := 32
@export var second_quest_speed := 36
@onready var current_speed := move_speed
@export var bounce_on_land := false
@export var bounce_height := -200
@export var visuals: Node2D
@export var follow_player := false
var can_hit := true
var can_bounce := true
var active := true
func _ready() -> void:
if owner is CharacterBody2D:
owner.floor_constant_speed = true
owner.floor_max_angle = 0.80
func _physics_process(delta: float) -> void:
if auto_call:
handle_movement(delta)
if visuals != null:
visuals.scale.x = owner.direction
func handle_movement(delta: float) -> void:
if active == false: return
if Global.second_quest and owner is Enemy:
move_speed = second_quest_speed
apply_gravity(delta)
if owner.is_on_wall():
wall_hit()
elif ledge_detection_cast != null and owner.is_on_floor():
ledge_detection_cast.floor_normal = owner.get_floor_normal()
ledge_detection_cast.position.x = abs(ledge_detection_cast.position.x) * owner.direction
if ledge_detection_cast.is_colliding() == false:
wall_hit()
if follow_player and owner.is_on_floor():
player_direction_check()
current_speed = abs(owner.velocity.x)
if current_speed < move_speed:
current_speed = move_speed
if owner.is_on_floor():
current_speed = move_speed
if bounce_on_land:
owner.velocity.y = bounce_height
owner.velocity.x = (current_speed if can_move else 0) * owner.direction
owner.move_and_slide()
func apply_gravity(delta: float) -> void:
owner.velocity.y += (Global.entity_gravity / delta) * delta
owner.velocity.y = clamp(owner.velocity.y, -INF, Global.entity_max_fall_speed)
func player_direction_check() -> void:
var target_player = get_tree().get_first_node_in_group("Players")
owner.direction = sign(target_player.global_position.x - owner.global_position.x)
func wall_hit() -> void:
if can_hit == false:
return
can_hit = false
owner.direction *= -1
await get_tree().create_timer(0.1, false).timeout
can_hit = true

View File

@@ -0,0 +1 @@
uid://dlq6o2rg1x7in

View File

@@ -0,0 +1,20 @@
class_name BasicStaticMovement
extends Node
@export var auto_call := true
@export var visuals: Node2D = null
func _physics_process(delta: float) -> void:
if auto_call:
handle_movement(delta)
func handle_movement(delta: float) -> void:
apply_gravity(delta)
if owner.is_on_floor():
owner.velocity.x = lerpf(owner.velocity.x, 0, delta * 20)
owner.move_and_slide()
func apply_gravity(delta: float) -> void:
owner.velocity.y += (Global.entity_gravity / delta) * delta
owner.velocity.y = clamp(owner.velocity.y, -INF, Global.entity_max_fall_speed)

View File

@@ -0,0 +1 @@
uid://bx6r8sjar6cwr

View File

@@ -0,0 +1,4 @@
extends Node
func _ready() -> void:
pass

View File

@@ -0,0 +1 @@
uid://cfcyhay33ctae

View File

@@ -0,0 +1,33 @@
class_name BlockBouncingDetection
extends Node
@export_enum("Collision", "Hitbox") var detection_type := 0
@export var hitbox: Area2D = null
@export var can_change_direction := false
signal block_bounced(block: Block)
func _physics_process(_delta: float) -> void:
if detection_type == 0:
collision_detect()
else:
hitbox_detect()
func collision_detect() -> void:
var collision: KinematicCollision2D = owner.move_and_collide(Vector2.DOWN, true)
if is_instance_valid(collision):
if collision.get_collider() is Block:
if collision.get_collider().bouncing:
block_bounced.emit(collision.get_collider())
return
func hitbox_detect() -> void:
if is_instance_valid(hitbox) == false: return
for i in hitbox.get_overlapping_bodies():
if i is Block:
if i.bouncing:
block_bounced.emit(i)
if can_change_direction:
owner.direction = sign(owner.global_position.x - i.global_position.x)
return

View File

@@ -0,0 +1 @@
uid://cmg61722ktg2m

View File

@@ -0,0 +1,23 @@
class_name BlockHitter
extends Node
@export var hitbox: Area2D = null
@export var can_break_bricks := false
@export var enabled := true:
set(value):
enabled = value
set_physics_process(value)
signal block_hit(block: Block)
func _ready() -> void:
hitbox.set_collision_mask_value(3, true)
func _physics_process(_delta: float) -> void:
for i in hitbox.get_overlapping_bodies():
if i is Block and i.global_position.y < owner.global_position.y:
i.shell_block_hit.emit(self)
block_hit.emit(i)
if i is BrickBlock:
if i.item == null:
i.destroy()

View File

@@ -0,0 +1 @@
uid://dkjpfs3sm3go3

View File

@@ -0,0 +1,99 @@
class_name PropertyExposer
extends Node
@export var properties: Array[String] = []
@export var filters: Dictionary[String, String] = {}
@export var properties_force_selector: Dictionary[String, PackedScene] = {}
const base64_charset := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
static var entity_map := {}
signal modifier_applied
func _ready() -> void:
name = "EditorPropertyExposer"
if entity_map.is_empty():
entity_map = JSON.parse_string(FileAccess.open(EntityIDMapper.MAP_PATH, FileAccess.READ).get_as_text())
func get_string() -> String:
var string = ""
for i in properties:
string += ","
if owner is Track:
if owner.get(i) is Array:
for x in owner.get(i):
string += base64_charset[(Track.DIRECTIONS.find(x))]
if owner.get(i) is String:
string += owner.get(i).replace(",", "&")
elif owner.get(i) is PackedScene:
var key = EntityIDMapper.get_map_id(owner.get(i).resource_path)
if key == null or key == "":
key = "!!"
string += key
elif owner.get(i) is int:
if owner.get(i) >= 64:
string += encode_to_base64_2char(owner.get(i))
else:
string += base64_charset[owner.get(i)]
elif owner.get(i) is bool:
string += base64_charset[int(owner.get(i))]
elif owner.get(i) == null:
string += "!!"
return string
func apply_string(entity_string := "") -> void:
var idx := 2
var slice = entity_string.split(",")
for i in properties:
if slice.size() <= idx:
return
var value = slice[idx]
if owner is Track:
if owner.get(i) is Array:
for x in value:
owner.get(i).append(Track.DIRECTIONS[base64_charset.find(x)])
owner._ready()
if owner.get(i) is String:
owner.set(i, value.replace("&", ","))
if owner.get(i) is PackedScene or (owner.get(i) == null and i == "item"):
var scene = entity_map.get(value)
if scene != null:
owner.set(i, load(entity_map.get(value)[0]))
elif owner.get(i) is int:
var num = value
if value.length() > 1:
num = decode_from_base64_2char(value)
else:
num = base64_charset.find(value)
owner.set(i, num)
elif owner.get(i) is bool:
owner.set(i, bool(base64_charset.find(value)))
idx += 1
modifier_applied.emit()
func encode_to_base64_2char(value: int) -> String:
if value < 0 or value >= 4096:
push_error("Value out of range for 2-char base64 encoding.")
return ""
var char1 = base64_charset[(value >> 6) & 0b111111] # Top 6 bits
var char2 = base64_charset[value & 0b111111] # Bottom 6 bits
return char1 + char2
func decode_from_base64_2char(encoded: String) -> int:
if encoded.length() != 2:
push_error("Encoded string must be exactly 2 characters.")
return -1
var char1_val = base64_charset.find(encoded[0])
var char2_val = base64_charset.find(encoded[1])
if char1_val == -1 or char2_val == -1:
push_error("Invalid character in base64 string.")
return -1
return (char1_val << 6) | char2_val

View File

@@ -0,0 +1 @@
uid://ctfbuoxtnnl0q

View File

@@ -0,0 +1,25 @@
class_name EnemyPlayerDetection
extends Node
@export var hitbox: Area2D = null
@export var height := 4
signal player_hit(player: Player)
signal player_stomped_on(player: Player)
signal invincible_player_hit(player: Player)
func _ready() -> void:
hitbox.area_entered.connect(area_entered)
func area_entered(area: Area2D) -> void:
if area.owner is Player:
player_entered(area.owner)
func player_entered(player: Player) -> void:
if player.is_invincible or player.has_hammer:
invincible_player_hit.emit(player)
elif (player.velocity.y >= 15 or (player.global_position.y + height < owner.global_position.y)) and player.in_water == false:
player_stomped_on.emit(player)
else:
player_hit.emit(player)

View File

@@ -0,0 +1 @@
uid://chj8hu207lrh

View File

@@ -0,0 +1,13 @@
class_name ExplosionDetection
extends Node
@export var hitbox: Area2D = null
signal explosion_entered(explosion: Node2D)
func _ready() -> void:
if hitbox != null:
hitbox.area_entered.connect(area_entered)
func area_entered(area: Area2D) -> void:
if area.owner is Explosion:
explosion_entered.emit(area.owner)

View File

@@ -0,0 +1 @@
uid://ba18grqjixded

View File

@@ -0,0 +1,15 @@
class_name FireballDetection
extends Node
@export var hitbox: Area2D = null
@export var play_sfx_on_hit := false
signal fireball_hit(fireball: FireBall)
func _ready() -> void:
if hitbox != null:
hitbox.area_entered.connect(area_entered)
func area_entered(area: Area2D) -> void:
if area.owner is FireBall:
fireball_hit.emit(area.owner)
area.owner.hit(play_sfx_on_hit)

View File

@@ -0,0 +1 @@
uid://dri2d5jtu0fbq

View File

@@ -0,0 +1,47 @@
class_name GibSpawner
extends Node
@export var visuals: Node = null
@export_enum("Spin", "Drop", "Poof") var gib_type := 0
@export var play_death_sfx := true
const ENTITY_GIB = preload("res://Scenes/Prefabs/Entities/EntityGib.tscn")
signal gib_about_to_spawn
func summon_gib(direction := 1, play_sfx := play_death_sfx, override_gib_type := gib_type) -> void:
gib_about_to_spawn.emit()
if play_sfx:
play_die_sfx()
if override_gib_type == 2:
summon_poof()
return
var node = ENTITY_GIB.instantiate()
visuals.show()
if visuals.has_node("ResourceSetterNew"):
visuals.get_node("ResourceSetterNew").update_on_spawn = false
node.visuals = visuals.duplicate()
node.visuals.set_process(false)
node.global_position = visuals.global_position
node.visuals.position = Vector2.ZERO
node.visuals.offset = Vector2.ZERO
node.gib_type = override_gib_type
node.direction = direction
owner.add_sibling(node)
func play_die_sfx() -> void:
AudioManager.play_sfx("kick", owner.global_position)
const SMOKE_PARTICLE = preload("uid://d08nv4qtfouv1")
func summon_poof() -> void:
var particle = SMOKE_PARTICLE.instantiate()
particle.global_position = visuals.global_position + Vector2(0, 8)
owner.add_sibling(particle)
func stomp_die(player: Player, add_combo := true) -> void:
DiscoLevel.combo_amount += 1
AudioManager.play_sfx("enemy_stomp", owner.global_position)
player.enemy_bounce_off(add_combo)
summon_gib(1, false, 1)
owner.queue_free()

View File

@@ -0,0 +1 @@
uid://c3gg32ivrlq8n

View File

@@ -0,0 +1,15 @@
class_name IcicleDetection
extends Node
@export var hitbox: Area2D = null
signal icicle_detected(icicle: Icicle)
func _ready() -> void:
if hitbox != null:
hitbox.area_entered.connect(area_entered)
func area_entered(area: Area2D) -> void:
if area.owner is Icicle:
if area.owner.falling:
icicle_detected.emit(area.owner)

View File

@@ -0,0 +1 @@
uid://dq860i312isk

View File

@@ -0,0 +1,23 @@
class_name LedgeDetectionCast
extends RayCast2D
@export var floor_normal := Vector2.UP
@export var ray_length := 24
var floor_direction := 1
var direction := 1
## Hypotenuse = floor_angle
## Opposite = ???
## Adjacent = position.x
func _physics_process(_delta: float) -> void:
target_position.y = ray_length
if floor_normal.x > 0:
floor_direction = 1
elif floor_normal.x < 0:
floor_direction = -1
else:
position.y = -(ray_length / 2.0)
return
position.y = ((-floor_normal.y * (position.x)) * (floor_direction)) - (ray_length / 2.0)

View File

@@ -0,0 +1 @@
uid://blfnd65xcx78c

View File

@@ -0,0 +1,29 @@
class_name LevelPersistance
extends Node
static var active_nodes := [[], []]
var active := false
@onready var path := get_path_string()
signal enabled
signal enabled_2
static func reset_states() -> void:
active_nodes = [[], []]
Checkpoint.old_state = [[], []]
func _ready() -> void:
return
func set_as_active() -> void:
if owner.has_meta("no_persist"): return
active_nodes[0].append(path)
func set_as_active_2() -> void:
if owner.has_meta("no_persist"): return
active_nodes[1].append(path)
func get_path_string() -> String:
return Global.current_level.scene_file_path + str(Vector2i(owner.global_position / 8))

View File

@@ -0,0 +1 @@
uid://maqpreddu5kg

View File

@@ -0,0 +1,18 @@
class_name OffScreenDespawner
extends Node
var can_despawn := false
func _ready() -> void:
can_despawn = false
await get_tree().create_timer(0.5, false).timeout
can_despawn = true
func on_screen_exited() -> void:
if Global.level_editor != null:
if Global.level_editor.current_state == LevelEditor.EditorState.PLAYTESTING or Global.current_game_mode == Global.GameMode.CUSTOM_LEVEL:
await get_tree().physics_frame
if can_despawn:
owner.queue_free()
else:
owner.queue_free()

View File

@@ -0,0 +1 @@
uid://33no4mylhh1r

View File

@@ -0,0 +1,28 @@
class_name PSwitcher
extends Node
var enabled := true
@export_file("*.tscn") var new_scene := ""
@export var new_offset := Vector2.ZERO
@export var properties := []
var is_switched := false
func _ready() -> void:
Global.p_switch_toggle.connect(switch_to_other)
if Global.p_switch_active and not is_switched:
switch_to_other()
func switch_to_other() -> void:
if enabled == false: return
if new_scene != "":
var new = load(new_scene).instantiate()
new.global_position = owner.global_position + new_offset
if new.has_node("PSwitcher"):
new.get_node("PSwitcher").new_scene = owner.scene_file_path
new.get_node("PSwitcher").is_switched = true
for i in properties:
new.set(i, owner.get(i))
owner.call_deferred("add_sibling", new)
owner.queue_free()

View File

@@ -0,0 +1 @@
uid://bul1nbd2in1gn

View File

@@ -0,0 +1,11 @@
class_name PackStreamPlayer
extends AudioStreamPlayer
@onready var resource_getter = ResourceGetter.new()
func _ready() -> void:
update()
Global.level_theme_changed.connect(update)
func update() -> void:
stream = resource_getter.get_resource(stream)

View File

@@ -0,0 +1 @@
uid://dmtw1jesw1vl7

View File

@@ -0,0 +1,70 @@
class_name ResourceGetter
extends Node
var original_resource: Resource = null
static var cache := {}
func get_resource(resource: Resource) -> Resource:
if resource == null:
return null
if original_resource == null:
original_resource = resource
if cache.has(original_resource.resource_path) and resource is not AtlasTexture:
return cache.get(original_resource.resource_path)
var path := ""
if original_resource is AtlasTexture:
path = get_resource_path(original_resource.atlas.resource_path)
else:
path = get_resource_path(original_resource.resource_path)
if path == original_resource.resource_path:
return original_resource
if original_resource is Texture:
var new_resource = null
if path.contains("user://"):
new_resource = ImageTexture.create_from_image(Image.load_from_file(path))
else:
new_resource = load(path)
send_to_cache(original_resource.resource_path, new_resource)
if original_resource is AtlasTexture:
var atlas = AtlasTexture.new()
atlas.atlas = new_resource
atlas.region = original_resource.region
return atlas
return new_resource
elif original_resource is AudioStream:
if path.get_file().contains(".wav"):
var new_resource = AudioStreamWAV.load_from_file(path)
send_to_cache(original_resource.resource_path, new_resource)
return new_resource
elif path.get_file().contains(".mp3"):
var new_resource = AudioStreamMP3.load_from_file(path)
send_to_cache(original_resource.resource_path, new_resource)
return new_resource
elif original_resource is Font:
var new_font = FontFile.new()
new_font.load_bitmap_font(path)
send_to_cache(original_resource.resource_path, new_font)
return new_font
send_to_cache(original_resource.resource_path, original_resource)
return original_resource
func send_to_cache(resource_path := "", resource_to_cache: Resource = null) -> void:
if cache.has(resource_path) == false:
cache.set(resource_path, resource_to_cache)
func get_resource_path(resource_path := "") -> String:
for i in Settings.file.visuals.resource_packs:
var test = resource_path.replace("res://Assets/", "user://resource_packs/" + i + "/")
if FileAccess.file_exists(test):
return test
return resource_path

View File

@@ -0,0 +1 @@
uid://bsp584niccobr

View File

@@ -0,0 +1,146 @@
class_name ResourceSetter
extends Node
@export var node_to_affect: Node = null
@export var property_name := ""
@export var themed_resource: ThemedResource = null
@export var use_classic_theming := false
@export var use_cache := true
signal sprites_updated
static var cache := {}
func _enter_tree() -> void:
Global.level_theme_changed.connect(update_sprites)
Global.level_time_changed.connect(update_sprites)
func _ready() -> void:
update_sprites()
func update_sprites() -> void:
cache.clear()
if themed_resource == null:
node_to_affect.set(property_name, null)
return
var resource = get_resource(themed_resource, node_to_affect, true, use_cache)
node_to_affect.set(property_name, resource)
if node_to_affect is AnimatedSprite2D:
node_to_affect.play()
sprites_updated.emit()
static func get_resource(resource: Resource, node: Node = null, assign := false, cache_enabled := true) -> RefCounted:
if resource == null:
return resource
var og_path = resource.resource_path
if resource is AtlasTexture:
og_path = resource.atlas.resource_path
if resource is ThemedResource:
if resource.get(Global.level_theme) != null:
resource = get_resource(resource.get(Global.level_theme))
else:
resource = get_resource(resource.Overworld)
if resource is CampaignResource:
if resource.get(Global.current_campaign) != null:
resource = get_resource(resource.get(Global.current_campaign))
else:
resource = get_resource(resource.SMB1)
if assign:
if resource is AtlasTexture:
resource.filter_clip = true
if resource is SpriteFrames:
if node is not AnimatedSprite2D:
resource = resource.get_frame_texture(resource.get_animation_names()[0], 0)
if Settings.file.visuals.resource_packs.is_empty() == false:
for i in Settings.file.visuals.resource_packs:
resource = get_override_resource(resource, i)
if cache.has(og_path) == false:
cache[og_path] = resource.duplicate()
if resource == null:
pass
return resource
static func get_override_resource(resource: Resource = null, resource_pack := "") -> Object:
if resource == null:
return
if resource_pack == "":
return
var original_resource_path = resource.resource_path
var resource_path = get_override_resource_path(resource.resource_path, resource_pack)
if FileAccess.file_exists(resource_path):
if resource is Texture:
resource = create_image_from_path(resource_path)
elif resource is SpriteFrames:
resource = create_new_sprite_frames(resource, resource_pack)
if resource is AudioStream:
if resource_path.contains(".mp3"):
var resource_loops = resource.has_loop()
resource = AudioStreamMP3.load_from_file(resource_path)
resource.set_loop(resource_loops)
elif resource_path.contains(".wav"):
resource = AudioStreamWAV.load_from_file(resource_path)
if resource is FontVariation:
resource_path = get_override_resource_path(resource.base_font.resource_path, resource_pack)
if FileAccess.file_exists(resource_path):
var new_font = FontFile.new()
var variation = resource.duplicate()
new_font.load_bitmap_font(resource_path.replace(".png", ".fnt"))
variation.base_font = new_font
resource = variation
else:
if resource is SpriteFrames:
resource = create_new_sprite_frames(resource, resource_pack)
if resource is AtlasTexture:
resource_path = get_override_resource_path(resource.atlas.resource_path, resource_pack)
if FileAccess.file_exists(resource_path):
var new_resource = AtlasTexture.new()
new_resource.atlas = create_image_from_path(get_override_resource_path(resource.atlas.resource_path, resource_pack))
new_resource.region = resource.region
return new_resource
if resource is AudioStreamInteractive:
resource = get_override_resource(resource.get_clip_stream(0), resource_pack)
if resource is FontVariation:
resource_path = get_override_resource_path(resource.base_font.resource_path, resource_pack)
if FileAccess.file_exists(resource_path):
var new_font = FontFile.new()
var variation = resource.duplicate()
new_font.load_bitmap_font(resource_path.replace(".png", ".fnt"))
variation.base_font = new_font
resource = variation
return resource
static func create_image_from_path(file_path := "") -> ImageTexture:
var image = Image.new()
image.load(file_path)
return ImageTexture.create_from_image(image)
static func create_new_sprite_frames(old_sprite_frames: SpriteFrames, resource_pack := "") -> SpriteFrames:
var new_frames = SpriteFrames.new()
new_frames.remove_animation("default")
for i in old_sprite_frames.get_animation_names():
new_frames.add_animation(i)
for x in old_sprite_frames.get_frame_count(i):
var frame = AtlasTexture.new()
var old_frame = old_sprite_frames.get_frame_texture(i, x)
frame.atlas = get_override_resource(old_frame.atlas, resource_pack)
frame.region = old_frame.region
new_frames.add_frame(i, frame, old_sprite_frames.get_frame_duration(i, x))
new_frames.set_animation_loop(i, old_sprite_frames.get_animation_loop(i))
new_frames.set_animation_speed(i, old_sprite_frames.get_animation_speed(i))
return new_frames
static func get_pure_resource_path(resource_path := "") -> String:
if Settings.file.visuals.resource_packs.is_empty() == false:
for i in Settings.file.visuals.resource_packs:
var new_path = get_override_resource_path(resource_path, i)
new_path = new_path.replace("user://custom_characters/", "user://resource_packs/" + new_path + "/Sprites/Players/CustomCharacters/")
if FileAccess.file_exists(new_path):
return new_path
return resource_path
static func get_override_resource_path(resource_path := "", resource_pack := "") -> String:
if resource_pack != "":
return resource_path.replace("res://Assets", "user://resource_packs/" + resource_pack)
else:
return resource_path

View File

@@ -0,0 +1 @@
uid://cq6f682453q6o

View File

@@ -0,0 +1,313 @@
class_name ResourceSetterNew
extends Node
@export var node_to_affect: Node = null
@export var property_node: Node = null
@export var property_name := ""
@export var mode: ResourceMode = ResourceMode.SPRITE_FRAMES
@export var resource_json: JSON = null:
set(value):
resource_json = value
update_resource()
enum ResourceMode {SPRITE_FRAMES, TEXTURE, AUDIO, RAW}
@export var use_cache := true
static var cache := {}
static var property_cache := {}
var current_json_path := ""
static var state := [0, 0]
static var pack_configs := {}
var config_to_use := {}
var is_random := false
signal updated
@export var force_properties := {}
var update_on_spawn := true
func _init() -> void:
set_process_mode(Node.PROCESS_MODE_ALWAYS)
func _ready() -> void:
safety_check()
if update_on_spawn:
update_resource()
Global.level_time_changed.connect(update_resource)
Global.level_theme_changed.connect(update_resource)
func safety_check() -> void:
if Settings.file.visuals.resource_packs.has("BaseAssets") == false:
Settings.file.visuals.resource_packs.append("BaseAssets")
func update_resource() -> void:
randomize()
if is_inside_tree() == false or is_queued_for_deletion() or resource_json == null or node_to_affect == null:
return
if state != [Global.level_theme, Global.theme_time]:
cache.clear()
property_cache.clear()
if node_to_affect != null:
var resource = get_resource(resource_json)
node_to_affect.set(property_name, resource)
if node_to_affect is AnimatedSprite2D:
node_to_affect.play()
state = [Global.level_theme, Global.theme_time]
updated.emit()
func get_resource(json_file: JSON) -> Resource:
if cache.has(json_file.resource_path) and use_cache and force_properties.is_empty():
if property_cache.has(json_file.resource_path):
apply_properties(property_cache[json_file.resource_path])
return cache[json_file.resource_path]
var resource: Resource = null
var resource_path = json_file.resource_path
config_to_use = {}
for i in Settings.file.visuals.resource_packs:
resource_path = get_resource_pack_path(resource_path, i)
var source_json = JSON.parse_string(FileAccess.open(resource_path, FileAccess.READ).get_as_text())
if source_json == null:
Global.log_error("Error parsing " + resource_path + "!")
return
var json = source_json.duplicate()
var source_resource_path = ""
if json.has("variations"):
json = get_variation_json(json.variations)
if json.has("source"):
if json.get("source") is String:
source_resource_path = json_file.resource_path.replace(json_file.resource_path.get_file(), json.source)
else:
Global.log_error("Error getting variations! " + resource_path)
return
for i in Settings.file.visuals.resource_packs:
source_resource_path = get_resource_pack_path(source_resource_path, i)
if json.has("rect"):
resource = load_image_from_path(source_resource_path)
var atlas = AtlasTexture.new()
atlas.atlas = resource
atlas.region = Rect2(json.rect[0], json.rect[1], json.rect[2], json.rect[3])
resource = atlas
if json.has("properties"):
apply_properties(json.get("properties"))
if use_cache:
property_cache[json_file.resource_path] = json.properties.duplicate()
elif source_json.has("properties"):
apply_properties(source_json.get("properties"))
if use_cache:
property_cache[json_file.resource_path] = source_json.properties.duplicate()
match mode:
ResourceMode.SPRITE_FRAMES:
var animation_json = {}
if json.has("animations"):
animation_json = json.get("animations")
elif source_json.has("animations"):
animation_json = source_json.get("animations")
if animation_json != {}:
resource = load_image_from_path(source_resource_path)
if json.has("rect"):
var atlas = AtlasTexture.new()
atlas.atlas = resource
atlas.region = Rect2(json.rect[0], json.rect[1], json.rect[2], json.rect[3])
resource = atlas
resource = create_sprite_frames_from_image(resource, animation_json)
else:
resource = load_image_from_path(source_resource_path)
if json.has("rect"):
var atlas = AtlasTexture.new()
atlas.atlas = resource
atlas.region = Rect2(json.rect[0], json.rect[1], json.rect[2], json.rect[3])
resource = atlas
var sprite_frames = SpriteFrames.new()
sprite_frames.add_frame("default", resource)
resource = sprite_frames
ResourceMode.TEXTURE:
if json.get("source") is Array:
resource = AnimatedTexture.new()
resource.frames = json.get("source").size()
var idx := 0
for i in json.get("source"):
var frame_path = ResourceSetter.get_pure_resource_path(json_file.resource_path.replace(json_file.resource_path.get_file(), i))
print(frame_path)
resource.set_frame_texture(idx, load_image_from_path(frame_path))
idx += 1
else:
resource = load_image_from_path(source_resource_path)
if json.has("rect"):
var rect = json.rect
var atlas = AtlasTexture.new()
atlas.atlas = resource
atlas.region = Rect2(rect[0], rect[1], rect[2], rect[3])
resource = atlas
ResourceMode.AUDIO:
resource = load_audio_from_path(source_resource_path)
ResourceMode.RAW:
pass
if cache.has(json_file.resource_path) == false and use_cache and not is_random:
cache[json_file.resource_path] = resource
return resource
func apply_properties(properties := {}) -> void:
if property_node == null:
return
for i in properties.keys():
property_node.set(i, properties[i])
func get_variation_json(json := {}) -> Dictionary:
var level_theme = Global.level_theme
if force_properties.has("Theme"):
level_theme = force_properties.Theme
for i in json.keys().filter(func(key): return key.contains("config:")):
if config_to_use != {}:
var option_name = i.get_slice(":", 1)
if config_to_use.options.has(option_name):
json = get_variation_json(json[i][config_to_use.options[option_name]])
break
if json.has(level_theme) == false:
level_theme = "default"
if json.has(level_theme):
if json.get(level_theme).has("link"):
json = get_variation_json(json[json.get(level_theme).get("link")])
else:
json = get_variation_json(json[level_theme])
var level_time = Global.theme_time
if force_properties.has("Time"):
level_time = force_properties.Time
if json.has(level_time):
json = get_variation_json(json[level_time])
var campaign = Global.current_campaign
if force_properties.has("Campaign"):
is_random = true
campaign = force_properties.Campaign
if json.has(campaign) == false:
campaign = "SMB1"
if json.has(campaign):
if json.get(campaign).has("link"):
json = get_variation_json(json[json.get(campaign).get("link")])
else:
json = get_variation_json(json[campaign])
if json.has("choices"):
is_random = true
json = get_variation_json(json.choices.pick_random())
var world = "World" + str(Global.world_num)
if force_properties.has("World"):
is_random = true
world = "World" + str(force_properties.World)
print(world)
if json.has(world) == false:
world = "World1"
if json.has(world):
if json.get(world).has("link"):
json = get_variation_json(json[json.get(world).get("link")])
else:
json = get_variation_json(json[world])
var level_string = "Level" + str(Global.level_num)
if json.has(level_string) == false:
level_string = "Level1"
if json.has(level_string):
if json.get(level_string).has("link"):
json = get_variation_json(json[json.get(level_string).get("link")])
else:
json = get_variation_json(json[level_string])
var game_mode = "GameMode:" + Global.game_mode_strings[Global.current_game_mode]
if json.has(game_mode) == false:
game_mode = "GameMode:" + Global.game_mode_strings[0]
if json.has(game_mode):
if json.get(game_mode).has("link"):
json = get_variation_json(json[json.get(game_mode).get("link")])
else:
json = get_variation_json(json[game_mode])
var chara = "Character:" + Player.CHARACTERS[int(Global.player_characters[0])]
if json.has(chara) == false:
chara = "Character:Mario"
if json.has(chara):
if json.get(chara).has("link"):
json = get_variation_json(json[json.get(chara).get("link")])
else:
json = get_variation_json(json[chara])
var boo = "RaceBoo:" + str(BooRaceHandler.boo_colour)
if json.has(boo) == false:
boo = "RaceBoo:0"
if force_properties.has("RaceBoo"):
boo = "RaceBoo:" + str(force_properties["RaceBoo"])
if json.has(boo):
if json.get(boo).has("link"):
json = get_variation_json(json[json.get(boo).get("link")])
else:
json = get_variation_json(json[boo])
return json
func get_resource_pack_path(res_path := "", resource_pack := "") -> String:
var user_path := res_path.replace("res://Assets", "user://resource_packs/" + resource_pack)
user_path = user_path.replace("user://custom_characters/", "user://resource_packs/" + resource_pack + "/Sprites/Players/CustomCharacters/")
if FileAccess.file_exists(user_path):
if FileAccess.file_exists("user://resource_packs/" + resource_pack + "/config.json"):
config_to_use = JSON.parse_string(FileAccess.open("user://resource_packs/" + resource_pack + "/config.json", FileAccess.READ).get_as_text())
if config_to_use == null:
Global.log_error("Error parsing Config File! (" + resource_pack + ")")
config_to_use = {}
return user_path
else:
return res_path
func create_sprite_frames_from_image(image: Resource, animation_json := {}) -> SpriteFrames:
var sprite_frames = SpriteFrames.new()
sprite_frames.remove_animation("default")
for anim_name in animation_json.keys():
sprite_frames.add_animation(anim_name)
for frame in animation_json[anim_name].frames:
var frame_texture = AtlasTexture.new()
frame_texture.atlas = image
frame_texture.region = Rect2(frame[0], frame[1], frame[2], frame[3])
frame_texture.filter_clip = true
sprite_frames.add_frame(anim_name, frame_texture)
sprite_frames.set_animation_loop(anim_name, animation_json[anim_name].loop)
sprite_frames.set_animation_speed(anim_name, animation_json[anim_name].speed)
return sprite_frames
func clear_cache() -> void:
for i in cache.keys():
if cache[i] == null:
cache.erase(i)
cache.clear()
property_cache.clear()
func load_image_from_path(path := "") -> ImageTexture:
if path.contains("res://"):
if path.contains("NULL"):
return null
return load(path)
var image = Image.new()
if path == "":
print([path, owner.name])
image.load(path)
return ImageTexture.create_from_image(image)
func load_audio_from_path(path := "") -> AudioStream:
var stream = null
if path.contains("res://"):
return load(path)
if path.contains(".wav"):
stream = AudioStreamWAV.load_from_file(path)
elif path.contains(".mp3"):
stream = AudioStreamMP3.load_from_file(path)
return stream

View File

@@ -0,0 +1 @@
uid://cbal8ms2oe1ik

View File

@@ -0,0 +1,12 @@
@tool
extends CollisionPolygon2D
@export var offset := Vector2.ZERO
@export var height := 0.0
func _physics_process(_delta: float) -> void:
update()
func update() -> void:
var height_to_use = height
position.y = -height_to_use / 2 * scale.y - offset.y

View File

@@ -0,0 +1 @@
uid://cwti0ks5sfov3

View File

@@ -0,0 +1,17 @@
@tool
extends CollisionShape2D
@export var offset := Vector2.ZERO
@export var link: Node2D
func _ready() -> void:
set_process(Engine.is_editor_hint())
func _process(_delta: float) -> void:
update()
func update() -> void:
var height_to_use = shape.size.y
if link != null:
height_to_use *= link.scale.y * link.scale.y
position.y = -height_to_use / 2 * scale.y - offset.y

View File

@@ -0,0 +1 @@
uid://cp1bh6fi6tpa5

View File

@@ -0,0 +1,27 @@
class_name ScoreNoteSpawner
extends Node
const ONE_UP_NOTE = preload("res://Scenes/Parts/OneUpNote.tscn")
const SCORE_NOTE = preload("res://Scenes/Parts/ScoreNote.tscn")
@export var note_offset := Vector2(0, -8)
@export var add_score := false
@export var play_sfx := false
func spawn_note(amount = 100, amount_2 := 0) -> void:
if amount is not int or amount_2 != 0:
amount = amount_2
var note = SCORE_NOTE.instantiate()
note.global_position = owner.global_position + note_offset
if add_score:
Global.score += amount
note.get_node("Container/Label").text = str(amount)
if play_sfx:
play_death_sfx()
Global.current_level.add_child(note)
func play_death_sfx() -> void:
AudioManager.play_sfx("kick", owner.global_position)
func spawn_one_up_note() -> void:
var note = ONE_UP_NOTE.instantiate()
note.global_position = owner.global_position + note_offset
owner.add_sibling(note)

View File

@@ -0,0 +1 @@
uid://5octqlf4ohel

View File

@@ -0,0 +1,21 @@
class_name SecondQuestReplacer
extends Node
@export_file("*.tscn") var new_scene := ""
@export var properties: Array[String] = []
func _ready() -> void:
if Global.second_quest and new_scene != "" and new_scene != owner.scene_file_path:
if owner.owner != null:
await owner.owner.ready
var node = load(new_scene).instantiate()
node.global_position = owner.global_position
node.global_rotation = owner.global_rotation
for i in properties:
node.set(i, owner.get(i))
owner.add_sibling(node)
if owner is RopeElevatorPlatform:
owner.linked_platform.linked_platform = node
owner.queue_free()
else:
queue_free()

View File

@@ -0,0 +1 @@
uid://d0mqkvopasu8k

View File

@@ -0,0 +1,18 @@
class_name ShellDetection
extends Node
@export var hitbox: Area2D = null
signal moving_shell_entered(shell: Node2D)
func _ready() -> void:
hitbox.area_entered.connect(area_entered)
func area_entered(area: Area2D) -> void:
if area.owner is Shell and area.owner != owner:
if abs(area.owner.velocity.x) > 0:
moving_shell_entered.emit(area.owner)
area.owner.add_combo()
func destroy_shell(shell: Shell) -> void:
shell.die_from_object(owner)

View File

@@ -0,0 +1 @@
uid://bbww34oiexbx2

View File

@@ -0,0 +1,10 @@
class_name TileGrabber
extends Node
@export var value_name := "item"
@export var saved_node: Node = null
@export var delete_grabbed := false
func tile_grabbed(tile: Node) -> void:
saved_node = tile
owner.set(value_name, saved_node)

View File

@@ -0,0 +1 @@
uid://cxucnfgd5yivr

View File

@@ -0,0 +1,21 @@
class_name TilesetTextureSetter
extends Node
@export var tile_map: TileMapLayer
@export var texture: Texture = null:
set(value):
texture = value
texture_changed.emit()
signal texture_changed
@export var atlas_id := 0
func _ready() -> void:
update()
texture_changed.connect(update)
func update() -> void:
var source = tile_map.tile_set.get_source(atlas_id)
if source != null:
source.texture = texture

View File

@@ -0,0 +1 @@
uid://73oviwf6bbys

View File

@@ -0,0 +1,33 @@
class_name TimerSprite
extends Sprite2D
@export var max_value := 1.0
@export var value_name := ""
@export_enum("Global", "Player", "Timer") var object := 0
@export var timer: Timer = null
@export var warn_sfx: AudioStreamPlayer = null
@export var warn_threshold := 0.7
var can_warn := false
func _ready() -> void:
texture = ResourceSetter.get_resource(texture, self)
func _process(_delta: float) -> void:
var node = owner if object == 1 else Global
if object == 2:
node = timer
var value = node.get(value_name)
var percent = inverse_lerp(max_value, 0, value)
percent = clamp(percent, 0, 1)
get_parent().visible = percent < 1 and Settings.file.visuals.visible_timers
frame = lerp(0, 6, percent)
if percent >= warn_threshold and Settings.file.audio.extra_sfx == 1:
if can_warn:
can_warn = false
AudioManager.play_global_sfx("timer_warning")
else:
can_warn = true

View File

@@ -0,0 +1 @@
uid://dn5efttgugwvb

View File

@@ -0,0 +1,12 @@
class_name TimerStarter
extends Node
func _ready() -> void:
if Global.level_editor != null:
Global.level_editor.level_start.connect(start_timers)
start_timers()
func start_timers() -> void:
for i in get_children():
if i is Timer:
i.start()

View File

@@ -0,0 +1 @@
uid://cucfssmfmttbk

View File

@@ -0,0 +1,19 @@
class_name TrackJoint
extends Node
signal attached
@export var offset := Vector2(0, 8)
@export var movement_node: Node = null
@export var disable_physics := true
var rider: TrackRider = null
var is_attached := false
func detach() -> void:
if rider == null: return
owner.physics_interpolation_mode = Node.PHYSICS_INTERPOLATION_MODE_INHERIT
rider.attached_entity = null
rider.queue_free()
get_parent().reparent(rider.get_parent())
owner.reset_physics_interpolation()

View File

@@ -0,0 +1 @@
uid://d4a7yp6e55u8t