custom resources, player damage, bad guy damage, animations, ui

This commit is contained in:
OddlyTimbot 2025-05-26 20:57:03 -04:00
parent 7f787903b7
commit a93515f621
20 changed files with 418 additions and 36 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://bgcr86uy5kqei"
path="res://.godot/imported/Player Death 64x64.png-0c6ff54e7d9aad74b66dce47376541f8.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://assets/graphics/player/death/Player Death 64x64.png"
dest_files=["res://.godot/imported/Player Death 64x64.png-0c6ff54e7d9aad74b66dce47376541f8.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://bvp2ecy6hu04v"
path="res://.godot/imported/Player Hurt 48x48.png-a720e51cb19103e76b22ab6c1b81302d.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://assets/graphics/player/hurt/Player Hurt 48x48.png"
dest_files=["res://.godot/imported/Player Hurt 48x48.png-a720e51cb19103e76b22ab6c1b81302d.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

View File

@ -20,4 +20,5 @@ position = Vector2(4, 3)
scale = Vector2(0.1, 0.1)
texture = ExtResource("2_y25gk")
[connection signal="area_entered" from="." to="." method="_on_area_entered"]
[connection signal="body_entered" from="." to="." method="_on_body_entered"]

View File

@ -1,4 +1,4 @@
[gd_scene load_steps=20 format=4 uid="uid://cwuxdg5c8ylmp"]
[gd_scene load_steps=21 format=4 uid="uid://cwuxdg5c8ylmp"]
[ext_resource type="Script" uid="uid://bus3b1g717jlm" path="res://scripts/scene_manager.gd" id="2_lbhrr"]
[ext_resource type="PackedScene" uid="uid://cgk1d1f5ffbbd" path="res://scenes/player.tscn" id="2_lnu2h"]
@ -13,6 +13,7 @@
[ext_resource type="Texture2D" uid="uid://bu2davrqnpe" path="res://assets/graphics/background/plane.png" id="6_kvuet"]
[ext_resource type="PackedScene" uid="uid://c0b4iaixh56in" path="res://scenes/coin.tscn" id="12_dinhu"]
[ext_resource type="PackedScene" uid="uid://cro4avvf0dbbr" path="res://scenes/slime.tscn" id="13_kvuet"]
[ext_resource type="PackedScene" uid="uid://c5onww62xhfy" path="res://scenes/ui.tscn" id="14_trtic"]
[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_gee14"]
texture = ExtResource("3_u5sy4")
@ -487,7 +488,7 @@ shape = SubResource("WorldBoundaryShape2D_16wxc")
[node name="crates" type="Node2D" parent="."]
[node name="RigidBody2D3" parent="crates" instance=ExtResource("4_iywne")]
position = Vector2(367, 271)
position = Vector2(242, 215)
[node name="triggers" type="Node2D" parent="."]
@ -514,10 +515,14 @@ position = Vector2(557, 314)
[node name="enemies" type="Node2D" parent="."]
[node name="Area2D" parent="enemies" instance=ExtResource("13_kvuet")]
position = Vector2(396, 275)
position = Vector2(396, 276)
[node name="Area2D2" parent="enemies" instance=ExtResource("13_kvuet")]
position = Vector2(594, 309)
[node name="CanvasLayer" type="CanvasLayer" parent="."]
[node name="ui" parent="CanvasLayer" instance=ExtResource("14_trtic")]
[connection signal="triggerFired" from="triggers/Trigger" to="." method="_on_trigger_fired"]
[connection signal="triggerFired" from="triggers/Trigger2" to="." method="_on_trigger_fired"]

View File

@ -1,17 +1,75 @@
[gd_scene load_steps=35 format=3 uid="uid://cgk1d1f5ffbbd"]
[gd_scene load_steps=51 format=3 uid="uid://cgk1d1f5ffbbd"]
[ext_resource type="Script" uid="uid://dmchcjip7pcfj" path="res://scripts/player.gd" id="1_3vyb7"]
[ext_resource type="Texture2D" uid="uid://x7vc805d7m4t" path="res://assets/graphics/player/jump/player jump 48x48.png" id="2_dqkch"]
[ext_resource type="Texture2D" uid="uid://bgcr86uy5kqei" path="res://assets/graphics/player/death/Player Death 64x64.png" id="2_fjrip"]
[ext_resource type="Texture2D" uid="uid://dbcs1873oc7m7" path="res://assets/graphics/player/idle/Player Idle 48x48.png" id="2_g2els"]
[ext_resource type="Texture2D" uid="uid://chiih26xngprp" path="res://assets/graphics/player/run/player run 48x48.png" id="3_qhqgy"]
[ext_resource type="Texture2D" uid="uid://dqwcn03jnrupq" path="res://assets/graphics/player/melee/Player Punch 64x64.png" id="4_qlg0r"]
[ext_resource type="Texture2D" uid="uid://bvp2ecy6hu04v" path="res://assets/graphics/player/hurt/Player Hurt 48x48.png" id="4_smehm"]
[sub_resource type="CircleShape2D" id="CircleShape2D_ryu2m"]
[sub_resource type="AtlasTexture" id="AtlasTexture_pf23h"]
atlas = ExtResource("2_fjrip")
region = Rect2(0, 0, 48, 48)
[sub_resource type="AtlasTexture" id="AtlasTexture_dt7fs"]
atlas = ExtResource("2_fjrip")
region = Rect2(48, 0, 48, 48)
[sub_resource type="AtlasTexture" id="AtlasTexture_wqfne"]
atlas = ExtResource("2_fjrip")
region = Rect2(96, 0, 48, 48)
[sub_resource type="AtlasTexture" id="AtlasTexture_wnwbv"]
atlas = ExtResource("2_fjrip")
region = Rect2(144, 0, 48, 48)
[sub_resource type="AtlasTexture" id="AtlasTexture_gl8cc"]
atlas = ExtResource("2_fjrip")
region = Rect2(192, 0, 48, 48)
[sub_resource type="AtlasTexture" id="AtlasTexture_487ah"]
atlas = ExtResource("2_fjrip")
region = Rect2(240, 0, 48, 48)
[sub_resource type="AtlasTexture" id="AtlasTexture_md1ol"]
atlas = ExtResource("2_fjrip")
region = Rect2(288, 0, 48, 48)
[sub_resource type="AtlasTexture" id="AtlasTexture_bj30b"]
atlas = ExtResource("2_fjrip")
region = Rect2(336, 0, 48, 48)
[sub_resource type="AtlasTexture" id="AtlasTexture_jc3p3"]
atlas = ExtResource("2_fjrip")
region = Rect2(384, 0, 48, 48)
[sub_resource type="AtlasTexture" id="AtlasTexture_hax0n"]
atlas = ExtResource("2_fjrip")
region = Rect2(432, 0, 48, 48)
[sub_resource type="AtlasTexture" id="AtlasTexture_i4ail"]
atlas = ExtResource("2_dqkch")
region = Rect2(96, 0, 48, 48)
[sub_resource type="AtlasTexture" id="AtlasTexture_t4otl"]
atlas = ExtResource("4_smehm")
region = Rect2(0, 0, 48, 48)
[sub_resource type="AtlasTexture" id="AtlasTexture_j2b1d"]
atlas = ExtResource("4_smehm")
region = Rect2(48, 0, 48, 48)
[sub_resource type="AtlasTexture" id="AtlasTexture_cs1tg"]
atlas = ExtResource("4_smehm")
region = Rect2(96, 0, 48, 48)
[sub_resource type="AtlasTexture" id="AtlasTexture_2dvfe"]
atlas = ExtResource("4_smehm")
region = Rect2(144, 0, 48, 48)
[sub_resource type="AtlasTexture" id="AtlasTexture_qhqgy"]
atlas = ExtResource("2_g2els")
region = Rect2(0, 0, 48, 48)
@ -112,6 +170,41 @@ region = Rect2(336, 0, 48, 48)
animations = [{
"frames": [{
"duration": 1.0,
"texture": SubResource("AtlasTexture_pf23h")
}, {
"duration": 1.0,
"texture": SubResource("AtlasTexture_dt7fs")
}, {
"duration": 1.0,
"texture": SubResource("AtlasTexture_wqfne")
}, {
"duration": 1.0,
"texture": SubResource("AtlasTexture_wnwbv")
}, {
"duration": 1.0,
"texture": SubResource("AtlasTexture_gl8cc")
}, {
"duration": 1.0,
"texture": SubResource("AtlasTexture_487ah")
}, {
"duration": 1.0,
"texture": SubResource("AtlasTexture_md1ol")
}, {
"duration": 1.0,
"texture": SubResource("AtlasTexture_bj30b")
}, {
"duration": 1.0,
"texture": SubResource("AtlasTexture_jc3p3")
}, {
"duration": 1.0,
"texture": SubResource("AtlasTexture_hax0n")
}],
"loop": false,
"name": &"death",
"speed": 12.0
}, {
"frames": [{
"duration": 1.0,
"texture": SubResource("AtlasTexture_i4ail")
}],
"loop": false,
@ -120,6 +213,23 @@ animations = [{
}, {
"frames": [{
"duration": 1.0,
"texture": SubResource("AtlasTexture_t4otl")
}, {
"duration": 1.0,
"texture": SubResource("AtlasTexture_j2b1d")
}, {
"duration": 1.0,
"texture": SubResource("AtlasTexture_cs1tg")
}, {
"duration": 1.0,
"texture": SubResource("AtlasTexture_2dvfe")
}],
"loop": false,
"name": &"hurt",
"speed": 12.0
}, {
"frames": [{
"duration": 1.0,
"texture": SubResource("AtlasTexture_qhqgy")
}, {
"duration": 1.0,
@ -248,7 +358,7 @@ one_shot = true
texture_filter = 1
position = Vector2(0, -7)
sprite_frames = SubResource("SpriteFrames_jej6c")
animation = &"idle"
animation = &"death"
autoplay = "idle"
[node name="Camera2D" type="Camera2D" parent="."]

View File

@ -1,8 +1,24 @@
[gd_scene load_steps=9 format=3 uid="uid://cro4avvf0dbbr"]
[gd_scene load_steps=13 format=3 uid="uid://cro4avvf0dbbr"]
[ext_resource type="Script" uid="uid://bk0hrjj1738iy" path="res://scripts/slime.gd" id="1_p2gj0"]
[ext_resource type="Texture2D" uid="uid://bc5qrj3rg2rey" path="res://assets/graphics/enemies/slime_green.png" id="2_n6pvg"]
[sub_resource type="AtlasTexture" id="AtlasTexture_p2gj0"]
atlas = ExtResource("2_n6pvg")
region = Rect2(0, 48, 24, 24)
[sub_resource type="AtlasTexture" id="AtlasTexture_n6pvg"]
atlas = ExtResource("2_n6pvg")
region = Rect2(24, 48, 24, 24)
[sub_resource type="AtlasTexture" id="AtlasTexture_v5wyi"]
atlas = ExtResource("2_n6pvg")
region = Rect2(48, 48, 24, 24)
[sub_resource type="AtlasTexture" id="AtlasTexture_0l8pv"]
atlas = ExtResource("2_n6pvg")
region = Rect2(72, 48, 24, 24)
[sub_resource type="AtlasTexture" id="AtlasTexture_pjw23"]
atlas = ExtResource("2_n6pvg")
region = Rect2(0, 24, 24, 24)
@ -23,6 +39,23 @@ region = Rect2(72, 24, 24, 24)
animations = [{
"frames": [{
"duration": 1.0,
"texture": SubResource("AtlasTexture_p2gj0")
}, {
"duration": 1.0,
"texture": SubResource("AtlasTexture_n6pvg")
}, {
"duration": 1.0,
"texture": SubResource("AtlasTexture_v5wyi")
}, {
"duration": 1.0,
"texture": SubResource("AtlasTexture_0l8pv")
}],
"loop": false,
"name": &"hurt",
"speed": 12.0
}, {
"frames": [{
"duration": 1.0,
"texture": SubResource("AtlasTexture_pjw23")
}, {
"duration": 1.0,
@ -48,9 +81,8 @@ script = ExtResource("1_p2gj0")
[node name="AnimatedSprite2D" type="AnimatedSprite2D" parent="."]
texture_filter = 1
sprite_frames = SubResource("SpriteFrames_v5wyi")
animation = &"run"
animation = &"hurt"
autoplay = "run"
frame_progress = 0.677109
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
position = Vector2(0, 4.5)

46
scenes/ui.tscn Normal file
View File

@ -0,0 +1,46 @@
[gd_scene load_steps=2 format=3 uid="uid://c5onww62xhfy"]
[ext_resource type="Script" uid="uid://daslx4en4rji1" path="res://scripts/ui.gd" id="1_nt7q6"]
[node name="Control" type="Control"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_nt7q6")
[node name="VBoxContainer" type="VBoxContainer" parent="."]
layout_mode = 1
anchors_preset = 10
anchor_right = 1.0
grow_horizontal = 2
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"]
layout_mode = 2
[node name="MarginContainer" type="MarginContainer" parent="VBoxContainer/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
[node name="health" type="Label" parent="VBoxContainer/HBoxContainer/MarginContainer"]
layout_mode = 2
text = "Health"
[node name="MarginContainer2" type="MarginContainer" parent="VBoxContainer/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
[node name="timer" type="Label" parent="VBoxContainer/HBoxContainer/MarginContainer2"]
layout_mode = 2
horizontal_alignment = 1
[node name="MarginContainer3" type="MarginContainer" parent="VBoxContainer/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
[node name="coins" type="Label" parent="VBoxContainer/HBoxContainer/MarginContainer3"]
layout_mode = 2
text = "Coins"
horizontal_alignment = 2

View File

@ -17,5 +17,10 @@ func _physics_process(delta: float) -> void:
func _on_body_entered(body: Node2D) -> void:
if not body is Player:
print("Bullet hit")
bulletHit.emit(body, self)
func _on_area_entered(area: Area2D) -> void:
if area is Slime:
print("bullet hitting enemy")
bulletHit.emit(area, self)

View File

@ -6,18 +6,29 @@ var timer = Timer.new()
var coinsCollectedTotal := 0
var levels = ["res://scenes/game.tscn","res://scenes/level2.tscn","res://scenes/level3.tscn"]
var timers = [10, 15, 25]
var timers = [50, 15, 25]
var currentLevel = 0
var playerHealth = 100
var player:CharacterStats
var slime:CharacterStats
var enemiesDict = {}
signal levelCompleteSignal(level)
signal destroySignal(body)
signal playerDamage
signal playerDamage(health, maxhealth)
signal playerDeath
signal countDown(timeRemaining)
signal coinUpdate(totalCoins)
signal enemyHurt(enemy)
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
#load custom resource for player and slime
player = load("res://scripts/res/playerstats.tres")
slime = load("res://scripts/res/slimestats.tres")
add_child(timer)
timer.wait_time = 1
timer.one_shot = false
@ -26,6 +37,8 @@ func _ready() -> void:
func secondCounter():
timeLimit -=1
countDown.emit(timeLimit)
if timeLimit <= 0:
#loser!!!!
levelCompleteSignal.emit(levels[currentLevel])
@ -41,39 +54,60 @@ func _process(delta: float) -> void:
func _on_trigger_fired(effect: Variant, body: Variant) -> void:
print("Game controller knows :: "+effect)
if effect=="destroy":
if body is Crate:
print("Crates Remaining " + str(totalCrates) )
destroySignal.emit(body)
func _on_coin_collected(body, coin):
coinsCollectedTotal += 1
coinUpdate.emit(coinsCollectedTotal)
destroySignal.emit(coin)
print("GC knows coin collected")
func _on_player_damage(body, enemy):
if enemy is Slime:
print("slime attack!!!")
playerHealth -= 10
if playerHealth >0:
print("Player damaged")
playerDamage.emit()
else:
print("Player dead")
playerDeath.emit()
playerHealth -= enemiesDict[enemy]["damage"]
if playerHealth >0:
playerDamage.emit(playerHealth, player.max_health)
else:
playerDeath.emit()
func addEnemyToLevel(enemy):
var randHealth:int = randi() % 20
var randDamage:int = randi() % 10
print("randoms "+str(randHealth)+" "+str(randDamage))
var enemyStat = {
"health": slime.health + randHealth,
"damage": slime.meleeDamage + randDamage
}
enemiesDict[enemy] = enemyStat
func numberOfCrates(value):
totalCrates = value
print("Total crates at GC: "+str(totalCrates) )
func bulletDamage(body, bullet):
print("Game controller knows about bullet hit")
if body is Slime:
enemiesDict[body]["health"] -= player.rangeDamage
#send custom signal
enemyHurt.emit(body)
if enemiesDict[body]["health"] <= 0:
destroySignal.emit(body)
enemiesDict.erase(body)
func reset():
timeLimit = timers[currentLevel]
playerHealth = 100
playerHealth = player.starting_health
func death():
print("player death animation complete")
levelCompleteSignal.emit(levels[currentLevel])

View File

@ -18,7 +18,7 @@ const JUMP_VELOCITY = -400.0
@export var acceleration = 15
@export var hard_gravity = 5
enum State{IDLE, RUN, JUMP, FALLING, MELEE}
enum State{IDLE, RUN, JUMP, FALLING, MELEE, HURT, DEATH}
var current_state = State.IDLE
enum FaceDirection{LEFT, RIGHT}
@ -31,15 +31,18 @@ var upJump:bool = false
signal deathComplete
func _player_damage():
func _player_damage(_currentHealth, _maxHealth):
print("Player taking damage!")
current_state = State.HURT
func _player_death():
print("Player dead!!")
current_state = State.DEATH
func _physics_process(delta: float) -> void:
handle_input()
if current_state != State.HURT and current_state != State.DEATH:
handle_input()
update_movement(delta)
update_states()
update_animation()
@ -136,6 +139,10 @@ func update_animation()->void:
player_graphic.play("fall")
State.MELEE:
player_graphic.play("melee")
State.HURT:
player_graphic.play("hurt")
State.DEATH:
player_graphic.play("death")
func handle_collisions()->void:
for i in get_slide_collision_count():
@ -166,3 +173,7 @@ func _on_animation_finished() -> void:
upJump = false
State.MELEE:
current_state = State.IDLE
State.HURT:
current_state = State.IDLE
State.DEATH:
deathComplete.emit()

View File

@ -0,0 +1,8 @@
class_name CharacterStats extends Resource
@export var max_health:int = 100
@export var starting_health:int = 100
@export var health:int = 100
@export var meleeDamage:int = 10
@export var rangeDamage:int = 0

View File

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

View File

@ -0,0 +1,12 @@
[gd_resource type="Resource" script_class="CharacterStats" load_steps=2 format=3 uid="uid://d0evd4qs7p8vx"]
[ext_resource type="Script" uid="uid://da5ofgqbqvfv4" path="res://scripts/res/characterstats.gd" id="1_4nldt"]
[resource]
script = ExtResource("1_4nldt")
max_health = 100
starting_health = 100
health = 100
meleeDamage = 10
rangeDamage = 20
metadata/_custom_type_script = "uid://da5ofgqbqvfv4"

View File

@ -0,0 +1,12 @@
[gd_resource type="Resource" script_class="CharacterStats" load_steps=2 format=3 uid="uid://cofkrecvkxrlq"]
[ext_resource type="Script" uid="uid://da5ofgqbqvfv4" path="res://scripts/res/characterstats.gd" id="1_fftob"]
[resource]
script = ExtResource("1_fftob")
max_health = 50
starting_health = 50
health = 50
meleeDamage = 30
rangeDamage = 0
metadata/_custom_type_script = "uid://da5ofgqbqvfv4"

View File

@ -6,6 +6,7 @@ extends Node
@onready var coins: Node2D = $"../coins"
@onready var enemies: Node2D = $"../enemies"
@onready var player: Player = $"../CharacterBody2D"
@onready var ui: Control = $"../CanvasLayer/ui"
var bullet = preload("res://scenes/bullet.tscn")
var bulletArray=[]
@ -32,12 +33,17 @@ func buildLevel()->void:
for obj in enemies.get_children():
if obj is Slime:
obj.playerDamageSignal.connect(Gamecontroller._on_player_damage)
Gamecontroller.enemyHurt.connect(obj.damage)
Gamecontroller.addEnemyToLevel(obj)
#Wire up signals
Gamecontroller.levelCompleteSignal.connect(loadLevel)
Gamecontroller.destroySignal.connect(destroy)
Gamecontroller.playerDamage.connect(player._player_damage)
Gamecontroller.playerDamage.connect(ui.healthUpdate)
Gamecontroller.coinUpdate.connect(ui.coinsUpdate)
Gamecontroller.playerDeath.connect(player._player_death)
Gamecontroller.countDown.connect(ui.timerUpdate)
#player connection
player.deathComplete.connect(Gamecontroller.death)
@ -66,7 +72,7 @@ func bulletFactory():
return mybullet
func makeBullet(position, speed):
print("Scene manager makes a bullet")
var mybullet = bulletFactory()
mybullet.setSpeed(speed)
mybullet.transform = position

View File

@ -10,15 +10,28 @@ var speed = 100
var direction = 1
func _process(delta: float) -> void:
if not right_down_cast.is_colliding() or right_side_cast.is_colliding():
if not right_down_cast.is_colliding():
direction = -1
sprite.flip_h = true
if not left_down_cast.is_colliding() or left_side_cast.is_colliding():
if right_side_cast.is_colliding():
if not right_side_cast.get_collider() is Player:
direction = -1
sprite.flip_h = true
if not left_down_cast.is_colliding():
direction = 1
sprite.flip_h = false
if left_side_cast.is_colliding():
if not left_side_cast.get_collider() is Player:
direction = 1
sprite.flip_h = false
position.x += direction * speed * delta
func _on_body_entered(body: Node2D) -> void:
print("Slime Contact")
playerDamageSignal.emit(body, self)
if body is Player:
playerDamageSignal.emit(body, self)
func damage(body)->void:
if body == self:
sprite.play("hurt")

17
scripts/ui.gd Normal file
View File

@ -0,0 +1,17 @@
extends Control
@onready var health: Label = $VBoxContainer/HBoxContainer/MarginContainer/health
@onready var timer: Label = $VBoxContainer/HBoxContainer/MarginContainer2/timer
@onready var coins: Label = $VBoxContainer/HBoxContainer/MarginContainer3/coins
func _ready() -> void:
health.text = "Health 100"
func healthUpdate(currentHealth, maxHealth):
health.text = "Health"+str(currentHealth)
func coinsUpdate(amt) -> void:
coins.text = "Coins "+str(amt)
func timerUpdate(timeRemaining)-> void:
timer.text = str(timeRemaining)

1
scripts/ui.gd.uid Normal file
View File

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