master
chendian 1 year ago
parent f9f594c071
commit 1a77a19782

@ -2,7 +2,7 @@
importer="texture"
type="CompressedTexture2D"
uid="uid://bqqa04cq6brul"
uid="uid://4lejpbicrnqv"
path="res://.godot/imported/flare6.png-425b6dd7d4da8681440e5835ffe39b92.ctex"
metadata={
"vram_texture": false

Binary file not shown.

Binary file not shown.

@ -0,0 +1,18 @@
[remap]
importer="MagicaVoxel.With.Extensions.To.Mesh"
type="Mesh"
uid="uid://bwr0d6m8io62t"
path="res://.godot/imported/cp_normal.vox-917047b737e691151f103928045fbd7d.mesh"
[deps]
source_file="res://resource/mesh_level/cp_normal.vox"
dest_files=["res://.godot/imported/cp_normal.vox-917047b737e691151f103928045fbd7d.mesh"]
[params]
Scale=0.02
GreedyMeshGenerator=true
SnapToGround=false
FirstKeyframeOnly=true

@ -1,4 +1,4 @@
[gd_resource type="MeshLibrary" load_steps=7 format=3 uid="uid://cnmlppi3r8sl0"]
[gd_resource type="MeshLibrary" load_steps=8 format=3 uid="uid://cnmlppi3r8sl0"]
[ext_resource type="ArrayMesh" uid="uid://3mgx1q8pqlsu" path="res://resource/mesh_level/c_monster01.vox" id="1_fi3ob"]
[ext_resource type="ArrayMesh" path="res://.godot/imported/a_grass_short.vox-a142a969f3f4d81df7c9b8a622160369.mesh" id="2_ksnfs"]
@ -6,6 +6,7 @@
[ext_resource type="ArrayMesh" path="res://.godot/imported/a_grass_tiny.vox-5649b282be35301cf0799eff3a3270f9.mesh" id="4_07cnl"]
[ext_resource type="ArrayMesh" path="res://.godot/imported/a_light01.vox-fbcf673ae8a560d03094ae6e13722ce5.mesh" id="5_kvpgw"]
[ext_resource type="ArrayMesh" path="res://.godot/imported/a_door01.vox-05441f19fa29fc6c4605f6947f653d77.mesh" id="6_custc"]
[ext_resource type="ArrayMesh" path="res://.godot/imported/cp_normal.vox-917047b737e691151f103928045fbd7d.mesh" id="7_t7ifr"]
[resource]
item/0/name = "c_monster01"
@ -44,3 +45,9 @@ item/5/mesh_transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0)
item/5/shapes = []
item/5/navigation_mesh_transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0)
item/5/navigation_layers = 1
item/6/name = "cp_normal"
item/6/mesh = ExtResource("7_t7ifr")
item/6/mesh_transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0)
item/6/shapes = []
item/6/navigation_mesh_transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0)
item/6/navigation_layers = 1

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -2,7 +2,7 @@
[ext_resource type="Script" path="res://script/effect/particle.gd" id="1_oydbn"]
[ext_resource type="Shader" path="res://render/shader/particle.gdshader" id="2_j4418"]
[ext_resource type="Texture2D" uid="uid://bqqa04cq6brul" path="res://render/texture/particle/shape/flare6.png" id="3_46a2r"]
[ext_resource type="Texture2D" uid="uid://4lejpbicrnqv" path="res://render/texture/particle/shape/flare6.png" id="3_46a2r"]
[ext_resource type="Texture2D" uid="uid://ddrm1reu30eux" path="res://render/texture/common/common_emission.tres" id="4_p0ocr"]
[ext_resource type="Texture2D" uid="uid://bslhqlcg8cw2l" path="res://render/texture/particle/shape/flare3.png" id="4_xpm5f"]
[ext_resource type="Texture2D" uid="uid://colhtjt8jon3v" path="res://render/texture/particle/shape/smoke.png" id="5_rp3hs"]

File diff suppressed because one or more lines are too long

@ -1,9 +1,138 @@
[gd_scene load_steps=2 format=3 uid="uid://c342lbsv1d6gi"]
[gd_scene load_steps=8 format=3 uid="uid://c342lbsv1d6gi"]
[ext_resource type="Script" path="res://script/level/level_door.gd" id="1_dsgdf"]
[ext_resource type="ArrayMesh" uid="uid://b5asibailmqi8" path="res://resource/mesh_level/m_stone_door1.vox" id="1_rxqyg"]
[sub_resource type="BoxShape3D" id="BoxShape3D_unlmt"]
size = Vector3(0.64, 10, 0.64)
[sub_resource type="Animation" id="Animation_24f85"]
length = 0.001
tracks/0/type = "position_3d"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("Mesh")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = PackedFloat32Array(0, 1, 0, -1.28, 0)
tracks/1/type = "value"
tracks/1/imported = false
tracks/1/enabled = true
tracks/1/path = NodePath("Mesh:visible")
tracks/1/interp = 1
tracks/1/loop_wrap = true
tracks/1/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 1,
"values": [false]
}
tracks/2/type = "value"
tracks/2/imported = false
tracks/2/enabled = true
tracks/2/path = NodePath("Block/CollisionShape3D:disabled")
tracks/2/interp = 1
tracks/2/loop_wrap = true
tracks/2/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 1,
"values": [true]
}
[sub_resource type="Animation" id="Animation_ijrpm"]
length = 2.0
step = 1.0
tracks/0/type = "position_3d"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("Mesh")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = PackedFloat32Array(0, 1, 0, -1.28, 0, 2, 1, 0, 0, 0)
tracks/1/type = "value"
tracks/1/imported = false
tracks/1/enabled = true
tracks/1/path = NodePath("Mesh:visible")
tracks/1/interp = 1
tracks/1/loop_wrap = true
tracks/1/keys = {
"times": PackedFloat32Array(0, 2),
"transitions": PackedFloat32Array(1, 1),
"update": 1,
"values": [true, true]
}
tracks/2/type = "value"
tracks/2/imported = false
tracks/2/enabled = true
tracks/2/path = NodePath("Block/CollisionShape3D:disabled")
tracks/2/interp = 1
tracks/2/loop_wrap = true
tracks/2/keys = {
"times": PackedFloat32Array(0, 2),
"transitions": PackedFloat32Array(1, 1),
"update": 1,
"values": [false, false]
}
[sub_resource type="Animation" id="Animation_nrn6r"]
resource_name = "new_animation"
length = 2.0
step = 1.0
tracks/0/type = "position_3d"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("Mesh")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = PackedFloat32Array(0, 1, 0, 0, 0, 2, 1, 0, -1.28, 0)
tracks/1/type = "value"
tracks/1/imported = false
tracks/1/enabled = true
tracks/1/path = NodePath("Mesh:visible")
tracks/1/interp = 1
tracks/1/loop_wrap = true
tracks/1/keys = {
"times": PackedFloat32Array(0, 2),
"transitions": PackedFloat32Array(1, 1),
"update": 1,
"values": [true, false]
}
tracks/2/type = "value"
tracks/2/imported = false
tracks/2/enabled = true
tracks/2/path = NodePath("Block/CollisionShape3D:disabled")
tracks/2/interp = 1
tracks/2/loop_wrap = true
tracks/2/keys = {
"times": PackedFloat32Array(0, 2),
"transitions": PackedFloat32Array(1, 1),
"update": 1,
"values": [false, true]
}
[sub_resource type="AnimationLibrary" id="AnimationLibrary_8ir0x"]
_data = {
"RESET": SubResource("Animation_24f85"),
"close": SubResource("Animation_ijrpm"),
"open": SubResource("Animation_nrn6r")
}
[node name="Door" type="Node3D"]
script = ExtResource("1_dsgdf")
[node name="Mesh" type="MeshInstance3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -1.28, 0)
visible = false
mesh = ExtResource("1_rxqyg")
skeleton = NodePath("../..")
[node name="Block" type="StaticBody3D" parent="."]
[node name="CollisionShape3D" type="CollisionShape3D" parent="Block"]
shape = SubResource("BoxShape3D_unlmt")
disabled = true
[node name="AnimationPlayer" type="AnimationPlayer" parent="."]
libraries = {
"": SubResource("AnimationLibrary_8ir0x")
}

@ -126,7 +126,7 @@ func refresh_mesh_library(path_list: Array, from_editor_tool: bool = false):
var is_ground = mesh_name.begins_with("g_")
if is_ground:
mesh_name = mesh_name.trim_prefix("g_")
var is_character: bool = mesh_name.begins_with("c_") or mesh_name.begins_with("a_")
var is_character: bool = mesh_name.begins_with("c_") or mesh_name.begins_with("cp_") or mesh_name.begins_with("a_")
var mesh: Mesh = load(file_name_full) as Mesh
var mesh_library: MeshLibrary = mesh_library_ground if is_ground else (mesh_library_character if is_character else mesh_library_level)
var mesh_library_id_list: Array[int] = mesh_library_ground_id_list if is_ground else mesh_library_level_id_list
@ -153,7 +153,7 @@ func refresh_mesh_library(path_list: Array, from_editor_tool: bool = false):
"h": item_shapes = default_shape_normal_half if not is_ground else default_shape_large_half
"s1": item_shapes = default_shape_normal_stair1 if not is_ground else default_shape_large_stair1
"s2": item_shapes = default_shape_normal_stair2 if not is_ground else default_shape_large_stair2
"c", "a", "n": pass
"c", "cp", "a", "n": pass
_: item_shapes = [mesh.create_convex_shape(), Transform3D.IDENTITY]
if item_shapes:
mesh_library.set_item_shapes(mesh_id, item_shapes)

@ -101,6 +101,7 @@ func settle(from: int, to: int, dir: Vector2, attack: AttackCfg) -> HitResult:
var cfg_from: CharacterCfg = character_from.cfg()
var cfg_to: CharacterCfg = character_to.cfg()
var is_dead = character_to.get_status("is_dead")
var is_stun = character_to.get_status("is_stun")
var hp = character_to.get_status("hp")
var shield = character_to.get_status("shield")
@ -190,6 +191,10 @@ func settle(from: int, to: int, dir: Vector2, attack: AttackCfg) -> HitResult:
# character_from.add_mp_sub(damage * cfg_from.mp.add_rate_attack, true)
character_to.add_mp_sub(damage * cfg_to.mp.add_rate_hit, true)
#被动锁定目标转移
if not status.is_lock and not is_dead and not is_bullet:
character_from.set_status("target", character_to.id())
#受击结束警戒
character_to.set_status("ai_is_alert", true)

@ -17,6 +17,9 @@ func _process(delta) -> void:
lock_cd_dict[key] -= delta
else:
lock_cd_dict.erase(key)
#当前锁定目标死亡
if status.target and not is_target_lockable(status.target):
character.set_target(0)
#没有锁定目标 尝试被动锁定
if not status.target and not is_lock:
var enemy_list: Array[Character] = Global.character_mgr.get_enemy_list(character.id())
@ -35,6 +38,7 @@ func _process(delta) -> void:
if is_lock:
var enemy_list: Array[Character] = Global.character_mgr.get_enemy_list(character.id())
var pos_player: Vector2 = character.pos2D()
enemy_list.filter( func(a: Character): return is_target_lockable(a.id()))
enemy_list.sort_custom(
func(a: Character, b: Character):
var a_lock_cd: bool = a.id() in lock_cd_dict
@ -79,6 +83,18 @@ func check_action_pressed(key: String) -> bool:
return true
func is_target_lockable(target_id: int) -> bool:
var target: Character = Global.character_mgr.get_character(target_id)
if not target:
return false
if target.cfg().type != Enum.ECharacterType.Monster:
return false
if target.team() == character.team():
return false
if target.get_status("is_dead"):
return false
return true
func lock(is_pressed: bool):
is_lock = is_pressed

@ -79,8 +79,11 @@ func cast_skill_mp_cost(cfg: SkillCfg) -> bool:
func cast_skill_by_name(name: String, cast_dir: Vector2):
var cfg: SkillCfg = skill_dict.get(name)
if cfg:
cast_skill(cfg, cast_dir)
if not cfg:
return
if not cast_skill_mp_cost(cfg):
return
cast_skill(cfg, cast_dir)
func cast_skill(cfg: SkillCfg, cast_dir: Vector2, action_key: String = ""):

@ -0,0 +1,8 @@
extends Node3D
class_name LevelDoor
func set_open(is_open: bool) -> void:
if is_open:
$AnimationPlayer.play("open")
else:
$AnimationPlayer.play("close")

@ -0,0 +1,5 @@
extends Resource
class_name LevelEnemy
@export var cfg: CharacterCfg
@export var num: int

@ -23,20 +23,29 @@ class_name LevelInstance
set_focus_from(false)
set_focus_to(false)
@export var enemy_nums: Array[LevelEnemy]
@onready var grid_block_material: Material = load("res://render/material/grid_block.tres") as Material
var level_area_cfg: LevelAreaCfg #运行时设置
var character_spots: Array[ChacacterSpot] #运行时设置
var character_born_spots: Array[ChacacterBornSpot] #运行时设置
var level_active_spots: Array[LevelActiveSpot] #运行时设置
var enemy_cfgs: Array[CharacterCfg] #运行时设置
class ChacacterSpot:
var pos: Vector3
var cfg: CharacterCfg
class ChacacterBornSpot:
var pos: Vector3
var type: int
class LevelActiveSpot:
var pos: Vector3
var scene: PackedScene
func _on_size_change() -> void:
var level_range: CSGBox3D = $LevelRange as CSGBox3D
var size_basic: Vector3 = Setting.size_basic
@ -57,44 +66,51 @@ func _set_focus(is_focus: bool, key: String) -> void:
return
if is_focus:
grid_block_material.set_shader_parameter("is_focus_"+key, true)
grid_block_material.set_shader_parameter("focus_min_"+key, pos_min())
grid_block_material.set_shader_parameter("focus_max_"+key, pos_max())
grid_block_material.set_shader_parameter("focus_min_"+key, pos_min() - Vector3.ONE * 0.32)
grid_block_material.set_shader_parameter("focus_max_"+key, pos_max() + Vector3.ONE * 0.32)
else:
grid_block_material.set_shader_parameter("is_focus_"+key, false)
func level_size() -> Vector2:
func get_level_size() -> Vector2:
var level_range: CSGBox3D = $LevelRange as CSGBox3D
var level_size: Vector3 = level_range.size
return Vector2(level_size.x, level_size.z)
func level_pos() -> Vector2:
func get_level_pos() -> Vector2:
var pos: Vector3 = get_global_position()
return Vector2(pos.x, pos.z)
func set_level_range_visible(visible: bool) -> void:
func set_level_range_visible(value: bool) -> void:
var level_range: CSGBox3D = $LevelRange as CSGBox3D
level_range.visible = visible
level_range.visible = value
func init_character_spots(level_character: GridMap) -> void:
character_spots = []
character_born_spots = []
level_active_spots = []
enemy_cfgs = []
var id_min: Vector3i = Util.get_level_grid_pos(pos_min())
var id_max: Vector3i = Util.get_level_grid_pos(pos_max())
var mesh_library: MeshLibrary = level_character.get_mesh_library()
for x in range(id_min.x, id_max.x+1):
for y in range(id_min.y, id_max.y+1):
for z in range(id_min.z, id_max.z+1):
for x in range(id_min.x, id_max.x):
for y in range(id_min.y, id_max.y):
for z in range(id_min.z, id_max.z):
var pos: Vector3i = Vector3i(x, y, z)
var float_pos: Vector3 = Util.get_level_float_pos(pos) + Vector3(0.32, 0, 0.32)
var item_id: int = level_character.get_cell_item(pos)
if item_id == GridMap.INVALID_CELL_ITEM:
continue
var name: String = mesh_library.get_item_name(item_id)
if name.begins_with("c_"):
var character_name: String = name.trim_prefix("c_")
var item_name: String = mesh_library.get_item_name(item_id)
if item_name.begins_with("cp_"):
var character_born_spot = ChacacterBornSpot.new()
character_born_spot.pos = float_pos
character_born_spots.append(character_born_spot)
elif item_name.begins_with("c_"):
var character_name: String = item_name.trim_prefix("c_")
var character_cfg: CharacterCfg = Util.get_character_cfg_by_name(character_name)
if not character_cfg:
continue
@ -102,8 +118,8 @@ func init_character_spots(level_character: GridMap) -> void:
character_spot.cfg = character_cfg
character_spot.pos = float_pos
character_spots.append(character_spot)
if name.begins_with("a_"):
var scene_name: String = name.trim_prefix("a_")
elif item_name.begins_with("a_"):
var scene_name: String = item_name.trim_prefix("a_")
var scene : PackedScene = Util.get_level_active_scene_by_name(scene_name)
if not scene:
continue
@ -112,21 +128,36 @@ func init_character_spots(level_character: GridMap) -> void:
level_active_spot.pos = float_pos
level_active_spots.append(level_active_spot)
for enemy_num: LevelEnemy in enemy_nums:
for i in enemy_num.num:
enemy_cfgs.append(enemy_num.cfg)
func get_character_spots() -> Array[ChacacterSpot]:
return character_spots
func get_character_born_spots() -> Array[ChacacterBornSpot]:
return character_born_spots
func get_enemy_cfgs() -> Array[CharacterCfg]:
return enemy_cfgs
func get_level_active_spots() -> Array[LevelActiveSpot]:
return level_active_spots
func pos_min() -> Vector3:
return get_global_position() - Vector3(0.64, 0.64, 0.64)
return get_global_position()
func pos_max() -> Vector3:
var level_range: CSGBox3D = $LevelRange as CSGBox3D
var level_size: Vector3 = level_range.size
return get_global_position() + level_size + Vector3(0.64, 0.64, 0.64)
return get_global_position() + level_size
func is_in_active_area(pos: Vector3) -> bool:
var active_pos_min: Vector3 = pos_min() + Vector3(0.96, 0, 0.96)
var active_pos_max: Vector3 = pos_max() - Vector3(0.96, 0, 0.96)
return pos.x >= active_pos_min.x and pos.x <= active_pos_max.x and \
pos.z >= active_pos_min.z and pos.z <= active_pos_max.z and \
pos.y >= active_pos_min.y and pos.y <= active_pos_max.y

@ -7,12 +7,18 @@ var cur_level_instance: LevelInstance
var level_instance_dict: Dictionary = {}
var level_loading_rate: float
var level_active_spots: Array[Node]
var level_character_born_spots: Array[LevelInstance.ChacacterBornSpot]
var level_enemy_cfgs: Array[CharacterCfg]
var level_doors: Array[LevelDoor]
var is_level_active: bool
var enemy_count: int #由关卡生成的怪物数量
var current_enmey: Array[int] #当前存活怪物id
func _ready():
Global.level_mgr = self
SignalManager.character_create.connect(on_character_create)
SignalManager.character_pos_changed.connect(on_character_pos_changed)
SignalManager.character_destroy.connect(on_character_destroy)
SignalManager.character_die.connect(on_character_die)
func init():
@ -47,6 +53,8 @@ func on_character_create(id: int, type: int, pos: Vector3):
if type == Enum.ECharacterType.Player:
target = id
on_character_pos_changed(id, pos)
elif type == Enum.ECharacterType.Monster:
current_enmey.append(id)
func on_character_pos_changed(id: int, pos: Vector3):
@ -54,7 +62,18 @@ func on_character_pos_changed(id: int, pos: Vector3):
set_player_position(pos)
func on_character_destroy(id: int): target = 0
func on_character_die(id: int):
if id in current_enmey:
current_enmey.remove_at(current_enmey.find(id))
#强制战斗关卡通关
if not cur_level_instance.is_force_battle:
return
if enemy_born(1):
return
if current_enmey.size() > 0:
return
for level_door: LevelDoor in level_doors:
level_door.set_open(true)
func set_player_position(pos: Vector3) -> void:
@ -65,32 +84,60 @@ func set_player_position(pos: Vector3) -> void:
return #todo 不存在的关卡 主角直接死亡
if cur_level_instance:
if new_level_instance == cur_level_instance:
check_level_active(pos)
return
cur_level_instance.set_focus_from(true)
new_level_instance.set_focus_to(true)
cur_level_instance = new_level_instance
enter_level()
func enter_level() -> void:
SignalManager.level_loading_start.emit()
#创建关卡内角色
for character_spot: LevelInstance.ChacacterSpot in new_level_instance.get_character_spots():
Global.character_mgr.create_character(character_spot.cfg, Enum.ETeam.Monster, character_spot.pos)
is_level_active = false
level_active_spots = []
level_doors = []
#创建关卡内活动部件
for level_active_spot: Node in level_active_spots:
level_active_spot.queue_free()
level_active_spots = []
for level_active_spot: LevelInstance.LevelActiveSpot in new_level_instance.get_level_active_spots():
for level_active_spot: LevelInstance.LevelActiveSpot in cur_level_instance.get_level_active_spots():
var new_level_active: Node = level_active_spot.scene.instantiate()
add_child(new_level_active)
new_level_active.global_position = level_active_spot.pos
level_active_spots.append(new_level_active)
if new_level_active is LevelDoor:
level_doors.append(new_level_active)
#更新刷怪数据
level_character_born_spots = cur_level_instance.get_character_born_spots()
level_enemy_cfgs = cur_level_instance.get_enemy_cfgs()
enemy_count = 0
new_level_instance.set_focus_to(true)
cur_level_instance = new_level_instance
level_loading_rate = 1-level_loading_rate
level.set_level_loading_rate(level_loading_rate)
SignalManager.level_size_change.emit(cur_level_instance.level_size())
SignalManager.level_pos_change.emit(cur_level_instance.level_pos())
SignalManager.level_size_change.emit(cur_level_instance.get_level_size())
SignalManager.level_pos_change.emit(cur_level_instance.get_level_pos())
SignalManager.level_loading_end.emit()
func check_level_active(pos: Vector3) -> void:
if is_level_active:
return
var is_active: bool = cur_level_instance.is_in_active_area(pos)
if not is_active:
return
is_level_active = true
for level_door: LevelDoor in level_doors:
level_door.set_open(true)
#创建关卡内角色
for character_spot: LevelInstance.ChacacterSpot in cur_level_instance.get_character_spots():
Global.character_mgr.create_character(character_spot.cfg, Enum.ETeam.Monster, character_spot.pos)
#强制战斗关门刷怪
if cur_level_instance.is_force_battle:
if enemy_born(level_character_born_spots.size()):
for level_door: LevelDoor in level_doors:
level_door.set_open(false)
func get_level_id_list(pos: Vector3, size: Vector3i) -> Array[Vector3i]:
var size_basic: Vector3 = Setting.size_basic
var ret: Array[Vector3i] = []
@ -100,3 +147,21 @@ func get_level_id_list(pos: Vector3, size: Vector3i) -> Array[Vector3i]:
var pos_sub: Vector3 = pos +Vector3(x*size_basic.x, y*size_basic.y, z*size_basic.z)
ret.append(Util.get_level_id(pos_sub))
return ret
func enemy_born(count : int) -> bool:
if level_character_born_spots.size() == 0:
return false
var count_max: int = level_enemy_cfgs.size()
count = min(count, count_max - enemy_count)
if count == 0:
return false
var count_start = enemy_count
for i in count:
var cfg_index: int = count_start + i
var cfg: CharacterCfg = level_enemy_cfgs[cfg_index]
var pos_index: int = enemy_count % level_character_born_spots.size()
var character_spot: LevelInstance.ChacacterBornSpot = level_character_born_spots[pos_index]
Global.character_mgr.create_character(cfg, Enum.ETeam.Monster, character_spot.pos)
enemy_count += 1
return true

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Loading…
Cancel
Save