完善 event 的 memo 机制,从 event_stage.dialogue 配置 stages 名,使用自定义 inspector 显示 stages 并关联 tooltip
This commit is contained in:
parent
6e093d3bfc
commit
510550b4a4
19
README.md
19
README.md
@ -122,6 +122,24 @@ func _parse_node(ground_node, node:Node, existing_vars:Dictionary, code_lines:Pa
|
|||||||
|
|
||||||
## 线索笔记说明
|
## 线索笔记说明
|
||||||
|
|
||||||
|
1. Aseprite 中编辑 slice 区域,配置区域名: c02_slices.ase
|
||||||
|
2. aseprite_slice_to_atlas.tscn 自动生成切图
|
||||||
|
3. 在 ux_note.tscn 中使用切图
|
||||||
|
4. 多语言本地化使用 c02_slices.ase 的不同图层导出,如 c02_slices.png 与 c02_slices_en.png
|
||||||
|
|
||||||
|
|
||||||
|
## Event 系统说明
|
||||||
|
|
||||||
|
1. 事件阶段管理:event_stage.dialogue
|
||||||
|
2. 事件绑定:EventBinder
|
||||||
|
- 通过事件阶段,控制父节点的显示/启用状态
|
||||||
|
- 订阅父节点交互 signal ,更新事件阶段
|
||||||
|
3. 复杂事件集合:Event2D
|
||||||
|
- 自动生成并绑定脚本,用脚本更灵活地控制子节点状态
|
||||||
|
- 配合 ProAnimatedSprite 节点,可在编辑器直接预览小动画
|
||||||
|
4. memo 备忘系统:
|
||||||
|
- 自动读取 event_stage.dialogue 的信息,写入到 EventBinder/Event2D 的 memo 中
|
||||||
|
- EventBinder/Event2D 的阶段 Checkbox 的 tooltip 提示文本可以看到序号所对应的阶段名
|
||||||
|
|
||||||
|
|
||||||
## 存档结构
|
## 存档结构
|
||||||
@ -164,3 +182,4 @@ current_scene 是通过 GroundLoader 加载的,在 ground loader 加载 ground
|
|||||||
- 转场 process 机制优化:暂停 & AnimationPlayer 保持运行
|
- 转场 process 机制优化:暂停 & AnimationPlayer 保持运行
|
||||||
- EventManager 控制事件,使用 Event2D 控制绑定关系
|
- EventManager 控制事件,使用 Event2D 控制绑定关系
|
||||||
- Vibe Control 控制氛围情绪音乐
|
- Vibe Control 控制氛围情绪音乐
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
@tool
|
||||||
extends Node
|
extends Node
|
||||||
|
|
||||||
const DialogueResource = preload("./dialogue_resource.gd")
|
const DialogueResource = preload("./dialogue_resource.gd")
|
||||||
|
@ -30,7 +30,7 @@ const DialogueLine = preload("./dialogue_line.gd")
|
|||||||
## be a title string or a stringified line number). Runs any mutations along the way and then returns
|
## be a title string or a stringified line number). Runs any mutations along the way and then returns
|
||||||
## the first dialogue line encountered.
|
## the first dialogue line encountered.
|
||||||
func get_next_dialogue_line(title: String = "", extra_game_states: Array = [], mutation_behaviour: DMConstants.MutationBehaviour = DMConstants.MutationBehaviour.Wait) -> DialogueLine:
|
func get_next_dialogue_line(title: String = "", extra_game_states: Array = [], mutation_behaviour: DMConstants.MutationBehaviour = DMConstants.MutationBehaviour.Wait) -> DialogueLine:
|
||||||
return await Engine.get_singleton("DialogueManager").get_next_dialogue_line(self, title, extra_game_states, mutation_behaviour)
|
return await DialogueManager.get_next_dialogue_line(self, title, extra_game_states, mutation_behaviour)
|
||||||
|
|
||||||
|
|
||||||
## Get the list of any titles found in the file.
|
## Get the list of any titles found in the file.
|
||||||
|
BIN
addons/property-inspector/event_2d/checked.png
Executable file
BIN
addons/property-inspector/event_2d/checked.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 177 B |
34
addons/property-inspector/event_2d/checked.png.import
Normal file
34
addons/property-inspector/event_2d/checked.png.import
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://dhan4svvxtqwd"
|
||||||
|
path="res://.godot/imported/checked.png-cb94f802c96994b2840cade9448d51ae.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://addons/property-inspector/event_2d/checked.png"
|
||||||
|
dest_files=["res://.godot/imported/checked.png-cb94f802c96994b2840cade9448d51ae.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
|
34
addons/property-inspector/event_2d/radio_checked.png.import
Normal file
34
addons/property-inspector/event_2d/radio_checked.png.import
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://rq4dsxooay2"
|
||||||
|
path="res://.godot/imported/radio_checked.png-e28720cbc9fafadcb0954b33d0f47b82.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://addons/property-inspector/event_2d/radio_checked.png"
|
||||||
|
dest_files=["res://.godot/imported/radio_checked.png-e28720cbc9fafadcb0954b33d0f47b82.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
|
85
addons/property-inspector/event_2d/stages_editor.gd
Normal file
85
addons/property-inspector/event_2d/stages_editor.gd
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
extends EditorProperty
|
||||||
|
|
||||||
|
var h_box: HBoxContainer = HBoxContainer.new()
|
||||||
|
var stage_nodes := []
|
||||||
|
|
||||||
|
var updating = false
|
||||||
|
var property_name: String
|
||||||
|
|
||||||
|
|
||||||
|
func _init(p_name) -> void:
|
||||||
|
property_name = p_name
|
||||||
|
add_child(h_box)
|
||||||
|
|
||||||
|
|
||||||
|
func _get_property() -> Array[Dictionary]:
|
||||||
|
return get_edited_object()[property_name]
|
||||||
|
|
||||||
|
|
||||||
|
func _update_property() -> void:
|
||||||
|
# Clear the control.
|
||||||
|
for s in stage_nodes:
|
||||||
|
s.queue_free()
|
||||||
|
stage_nodes.clear()
|
||||||
|
_reload_stages()
|
||||||
|
|
||||||
|
|
||||||
|
var box_checked_texture = preload("res://addons/property-inspector/event_2d/checked.png")
|
||||||
|
var box_unchecked_texture = preload("res://addons/property-inspector/event_2d/unchecked.png")
|
||||||
|
|
||||||
|
|
||||||
|
func _make_unique_and_sort(arr: Array[int]) -> void:
|
||||||
|
var unique_arr: Array[int] = []
|
||||||
|
for element in arr:
|
||||||
|
if not element in unique_arr:
|
||||||
|
unique_arr.append(element)
|
||||||
|
unique_arr.sort()
|
||||||
|
arr.assign(unique_arr)
|
||||||
|
|
||||||
|
|
||||||
|
func _reload_stages():
|
||||||
|
var obj = get_edited_object()
|
||||||
|
if obj == null:
|
||||||
|
printerr("obj is null")
|
||||||
|
return
|
||||||
|
var stages = obj[property_name] as Array[int]
|
||||||
|
var event_name
|
||||||
|
if property_name == "pre_event_stages":
|
||||||
|
event_name = obj["pre_event_name"]
|
||||||
|
elif property_name == "event_stages":
|
||||||
|
event_name = obj["event_name"]
|
||||||
|
elif property_name == "updater_stages":
|
||||||
|
event_name = obj["updater_event"]
|
||||||
|
var stages_name: PackedStringArray = await EventManager.get_event_stage_map_array(event_name)
|
||||||
|
# print("stages:", stages)
|
||||||
|
h_box.add_theme_constant_override("separation", 1)
|
||||||
|
for i in 7:
|
||||||
|
# make the text inside the box
|
||||||
|
var container := AspectRatioContainer.new()
|
||||||
|
container.set_anchors_preset(PRESET_FULL_RECT)
|
||||||
|
var box: CheckBox = CheckBox.new() as CheckBox
|
||||||
|
box.button_pressed = i in stages
|
||||||
|
box.toggled.connect(_on_toggle_checkbox.bind(i))
|
||||||
|
box.add_theme_icon_override("checked", box_checked_texture)
|
||||||
|
box.add_theme_icon_override("unchecked", box_unchecked_texture)
|
||||||
|
container.add_child(box)
|
||||||
|
var label := Label.new()
|
||||||
|
label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||||
|
container.add_child(label)
|
||||||
|
label.text = str(i)
|
||||||
|
h_box.add_child(container)
|
||||||
|
stage_nodes.append(container)
|
||||||
|
if stages_name.size() > i:
|
||||||
|
box.tooltip_text = stages_name[i]
|
||||||
|
|
||||||
|
|
||||||
|
func _on_toggle_checkbox(on: bool, id: int):
|
||||||
|
# 防止 read-only 所以 duplicate
|
||||||
|
var arr = get_edited_object()[property_name].duplicate()
|
||||||
|
if on:
|
||||||
|
arr.append(id)
|
||||||
|
else:
|
||||||
|
arr.erase(id)
|
||||||
|
_make_unique_and_sort(arr)
|
||||||
|
emit_changed(property_name, arr)
|
||||||
|
get_edited_object()._auto_memo()
|
1
addons/property-inspector/event_2d/stages_editor.gd.uid
Normal file
1
addons/property-inspector/event_2d/stages_editor.gd.uid
Normal file
@ -0,0 +1 @@
|
|||||||
|
uid://bjsrudsa3cpyx
|
BIN
addons/property-inspector/event_2d/unchecked.png
Executable file
BIN
addons/property-inspector/event_2d/unchecked.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 177 B |
34
addons/property-inspector/event_2d/unchecked.png.import
Normal file
34
addons/property-inspector/event_2d/unchecked.png.import
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://c8un06r0tg2ur"
|
||||||
|
path="res://.godot/imported/unchecked.png-ec639bca865e24a176cd170b2f702f9a.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://addons/property-inspector/event_2d/unchecked.png"
|
||||||
|
dest_files=["res://.godot/imported/unchecked.png-ec639bca865e24a176cd170b2f702f9a.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
|
@ -2,10 +2,11 @@ extends EditorInspectorPlugin
|
|||||||
|
|
||||||
var move_editor = preload("pro_animation_sprite2d/pro_animation_move_editor.gd")
|
var move_editor = preload("pro_animation_sprite2d/pro_animation_move_editor.gd")
|
||||||
var action_editor = preload("pro_animation_sprite2d/pro_animation_action_editor.gd")
|
var action_editor = preload("pro_animation_sprite2d/pro_animation_action_editor.gd")
|
||||||
|
var stages_editor = preload("event_2d/stages_editor.gd")
|
||||||
|
|
||||||
|
|
||||||
func _can_handle(object):
|
func _can_handle(object):
|
||||||
return object is ProAnimatedSprite2D
|
return object is ProAnimatedSprite2D or object is Event2D or object is EventBinder
|
||||||
|
|
||||||
|
|
||||||
func _parse_property(object, type, name, hint_type, hint_string, usage_flags, wide):
|
func _parse_property(object, type, name, hint_type, hint_string, usage_flags, wide):
|
||||||
@ -17,5 +18,7 @@ func _parse_property(object, type, name, hint_type, hint_string, usage_flags, wi
|
|||||||
elif name == "action_configs":
|
elif name == "action_configs":
|
||||||
add_property_editor(name, action_editor.new())
|
add_property_editor(name, action_editor.new())
|
||||||
return true
|
return true
|
||||||
else:
|
elif name == "pre_event_stages" or name == "event_stages" or name == "updater_stages":
|
||||||
|
add_property_editor(name, stages_editor.new(name))
|
||||||
|
return true
|
||||||
return false
|
return false
|
||||||
|
@ -1,19 +1,23 @@
|
|||||||
~ EventStage
|
~ EventStage_c00
|
||||||
# 0demo 1release
|
release_stage: 0:demo 1:release
|
||||||
release_stage
|
|
||||||
|
|
||||||
# 0:关闭 1:打开 2:放入小蝉人偶 3:全部放置正确_可摇手柄 4:已播放完成
|
|
||||||
c02_musicbox_stage
|
|
||||||
# 1:已交互疯子 2:小鞋已掉落
|
|
||||||
c02_madman_interacted_stage
|
|
||||||
# 0:默认 1:寻找弹珠_老虎钳可以换弹珠 2:给出弹珠 3:游戏结束_小猫纸片 4:游戏结束_小猫离开
|
|
||||||
c02_ball_game_stage
|
|
||||||
|
|
||||||
# c03 第二章
|
|
||||||
# 0未开始 1邀请 2完成
|
|
||||||
c03_invite_xchan_supper
|
|
||||||
|
|
||||||
=> END
|
=> END
|
||||||
|
|
||||||
~ ArchiveStage
|
~ EventStage_c02
|
||||||
|
c02_musicbox_stage: 0:初始化_关闭 1:打开 2:放入小蝉人偶 3:全部放置正确_可摇手柄 4:已播放完成
|
||||||
|
c02_madman_interacted_stage: 0:初始化 1:已交互疯子 2:小鞋已掉落
|
||||||
|
c02_ball_game_stage: 0:初始化 1:寻找弹珠_老虎钳可以换弹珠 2:给出弹珠 3:游戏结束_小猫纸片 4:游戏结束_小猫离开
|
||||||
|
=> END
|
||||||
|
|
||||||
|
~ EventStage_c03
|
||||||
|
c03_s01_meat_put: 0:初始化 1:已放肉
|
||||||
|
c03_invite_xchan_supper: 0:初始化 1:已偷听_需邀请 2:已完成邀请
|
||||||
|
=> END
|
||||||
|
|
||||||
|
~ EventStage_c04
|
||||||
|
=> END
|
||||||
|
|
||||||
|
~ EventStage_c05
|
||||||
|
=> END
|
||||||
|
|
||||||
|
~ EventStage_c06
|
||||||
=> END
|
=> END
|
@ -45,6 +45,7 @@ func _ready() -> void:
|
|||||||
# if event_name != &"":
|
# if event_name != &"":
|
||||||
# name = "Event_" + event_name
|
# name = "Event_" + event_name
|
||||||
if Engine.is_editor_hint():
|
if Engine.is_editor_hint():
|
||||||
|
_auto_memo()
|
||||||
return
|
return
|
||||||
if event_name != "":
|
if event_name != "":
|
||||||
stage = EventManager.get_stage(event_name)
|
stage = EventManager.get_stage(event_name)
|
||||||
@ -150,17 +151,20 @@ func _copy_getter():
|
|||||||
|
|
||||||
func _auto_memo() -> void:
|
func _auto_memo() -> void:
|
||||||
var memo_str := ""
|
var memo_str := ""
|
||||||
|
# if EventManager
|
||||||
if pre_event_name != &"" and pre_event_mode != "none":
|
if pre_event_name != &"" and pre_event_mode != "none":
|
||||||
memo_str += "当前置事件["
|
memo_str += "当前置事件["
|
||||||
memo_str += pre_event_name
|
memo_str += pre_event_name
|
||||||
memo_str += "=" if pre_event_mode == "show" else "!="
|
memo_str += "=" if pre_event_mode == "show" else "!="
|
||||||
memo_str += "|".join(pre_event_stages) + "]"
|
var stages = await EventManager.map_event_stages(pre_event_name, pre_event_stages)
|
||||||
|
memo_str += "|".join(stages) + "]"
|
||||||
if event_name != &"" and event_mode != "none":
|
if event_name != &"" and event_mode != "none":
|
||||||
memo_str += "" if memo_str.is_empty() else "\n并且"
|
memo_str += "" if memo_str.is_empty() else "\n并且"
|
||||||
memo_str += "当事件["
|
memo_str += "当事件["
|
||||||
memo_str += event_name
|
memo_str += event_name
|
||||||
memo_str += "=" if event_mode == "show" else "!="
|
memo_str += "=" if event_mode == "show" else "!="
|
||||||
memo_str += "|".join(event_stages) + "]时"
|
var stages = await EventManager.map_event_stages(event_name, event_stages)
|
||||||
|
memo_str += "|".join(stages) + "]时"
|
||||||
memo_str += "" if memo_str.is_empty() else "显示该节点"
|
memo_str += "" if memo_str.is_empty() else "显示该节点"
|
||||||
var existing_lines = event_memo.split("\n")
|
var existing_lines = event_memo.split("\n")
|
||||||
# 保留 # 开头的 line
|
# 保留 # 开头的 line
|
||||||
|
@ -169,12 +169,14 @@ func _auto_memo() -> void:
|
|||||||
if updater_event != "":
|
if updater_event != "":
|
||||||
memo_str = updater_event
|
memo_str = updater_event
|
||||||
memo_str += "=" if updater_stage_mode == "include" else "!="
|
memo_str += "=" if updater_stage_mode == "include" else "!="
|
||||||
memo_str += "|".join(updater_stages) + " 时,"
|
var stages = await EventManager.map_event_stages(updater_event, updater_stages)
|
||||||
|
memo_str += "[" + "|".join(stages) + "] 时,"
|
||||||
memo_str += "显示" if updater_mode == "shower" else "启用"
|
memo_str += "显示" if updater_mode == "shower" else "启用"
|
||||||
memo_str += ": [" + parent_name + "]\n"
|
memo_str += ": [" + parent_name + "]\n"
|
||||||
if trigger_event != "" and trigger_mode != "none":
|
if trigger_event != "" and trigger_mode != "none":
|
||||||
memo_str += "[" + parent_name + "] " + trigger_mode + " 时: "
|
memo_str += "[" + parent_name + "] " + trigger_mode + " 时: "
|
||||||
memo_str += trigger_event + "=" + str(trigger_stage)
|
var stages = await EventManager.map_event_stages(trigger_event, [trigger_stage])
|
||||||
|
memo_str += trigger_event + "=" + str(stages[0])
|
||||||
memo_str += "(greater)" if trigger_set_stage_if_greater else "(any)"
|
memo_str += "(greater)" if trigger_set_stage_if_greater else "(any)"
|
||||||
var existing_lines = memo.split("\n")
|
var existing_lines = memo.split("\n")
|
||||||
# 保留 # 开头的 line
|
# 保留 # 开头的 line
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
|
@tool
|
||||||
extends Node
|
extends Node
|
||||||
|
|
||||||
signal stage_updated(event_name: StringName, stage: int)
|
signal stage_updated(event_name: StringName, stage: int)
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
|
if Engine.is_editor_hint():
|
||||||
|
return
|
||||||
# 0demo 1release
|
# 0demo 1release
|
||||||
set_stage("release_stage", GlobalConfig.RELEASE_STAGE)
|
set_stage("release_stage", GlobalConfig.RELEASE_STAGE)
|
||||||
|
|
||||||
@ -67,3 +70,67 @@ func next_staged(event_name: StringName, stage_max := 99999) -> void:
|
|||||||
|
|
||||||
func prop_interacted(e_name, prop_key, interacted_times) -> void:
|
func prop_interacted(e_name, prop_key, interacted_times) -> void:
|
||||||
print("Event: %s interacted with %s. total times: %s" % [e_name, prop_key, interacted_times])
|
print("Event: %s interacted with %s. total times: %s" % [e_name, prop_key, interacted_times])
|
||||||
|
|
||||||
|
|
||||||
|
##### TOOL 方法
|
||||||
|
|
||||||
|
# event_name -> {stages:PackedStringArray, update_time:int}
|
||||||
|
var _debug_event_stage_dict := {}
|
||||||
|
|
||||||
|
|
||||||
|
# return: stages
|
||||||
|
func get_event_stage_map_array(event_name: StringName) -> PackedStringArray:
|
||||||
|
if not Engine.is_editor_hint():
|
||||||
|
return PackedStringArray()
|
||||||
|
var current_dict = _debug_event_stage_dict.get(event_name)
|
||||||
|
var time_msec = Time.get_ticks_msec()
|
||||||
|
# 3秒更新
|
||||||
|
if current_dict and time_msec - current_dict["update_time"] < 3000:
|
||||||
|
return current_dict["stages"]
|
||||||
|
# 文件: res://asset/dialogue/event_stage.dialogue
|
||||||
|
var event_stage_resource = load("uid://dohpsb4jttuv1") as DialogueResource
|
||||||
|
for title in event_stage_resource.get_titles():
|
||||||
|
var lines = await Util.get_lines(event_stage_resource, title)
|
||||||
|
for line in lines:
|
||||||
|
var line_text = line.text.strip_edges()
|
||||||
|
var e_name = line.character
|
||||||
|
if line_text.begins_with("#"):
|
||||||
|
continue
|
||||||
|
#c03_invite_xchan_supper: 0:未开始 1:已偷听,需邀请 2:已完成邀请
|
||||||
|
var parts = line_text.split(" ")
|
||||||
|
var stages = PackedStringArray()
|
||||||
|
for id in range(0, len(parts)):
|
||||||
|
var tuple = parts[id].split(":")
|
||||||
|
if len(tuple) == 2:
|
||||||
|
var event_stage = int(tuple[0])
|
||||||
|
if stages.size() < event_stage + 1:
|
||||||
|
stages.resize(event_stage + 1)
|
||||||
|
stages[event_stage] = tuple[1]
|
||||||
|
for id in len(stages):
|
||||||
|
if stages[id] == "":
|
||||||
|
stages[id] = str(id)
|
||||||
|
_debug_event_stage_dict[e_name] = {
|
||||||
|
"stages": stages,
|
||||||
|
"update_time": time_msec,
|
||||||
|
}
|
||||||
|
if _debug_event_stage_dict.has(event_name):
|
||||||
|
prints("reload", event_name, "stages:", _debug_event_stage_dict[event_name]["stages"])
|
||||||
|
return _debug_event_stage_dict[event_name]["stages"]
|
||||||
|
return PackedStringArray()
|
||||||
|
|
||||||
|
|
||||||
|
# map stage_id->name, 必定保持相同长度的数组
|
||||||
|
func map_event_stages(
|
||||||
|
event_name: StringName, stages: Array, including_id := true
|
||||||
|
) -> PackedStringArray:
|
||||||
|
var result = PackedStringArray()
|
||||||
|
result.resize(stages.size())
|
||||||
|
var dict = await get_event_stage_map_array(event_name)
|
||||||
|
for id in range(stages.size()):
|
||||||
|
var stage = stages[id]
|
||||||
|
if dict.size() > stage:
|
||||||
|
if including_id:
|
||||||
|
result[id] = str(stage) + ":" + dict[stage]
|
||||||
|
else:
|
||||||
|
result[id] = str(stage)
|
||||||
|
return result
|
||||||
|
@ -206,9 +206,7 @@ func enable_prop_item(prop_key: String) -> void:
|
|||||||
func enable_important_item(prop_key: String, display_inspector = true) -> void:
|
func enable_important_item(prop_key: String, display_inspector = true) -> void:
|
||||||
var prop_hud = get_prop_hud()
|
var prop_hud = get_prop_hud()
|
||||||
if prop_hud:
|
if prop_hud:
|
||||||
prop_hud.inventory.enable_important_item(prop_key)
|
prop_hud.enable_important_item(prop_key, display_inspector)
|
||||||
# if display_inspector:
|
|
||||||
# prop_hud.display_inspector(prop_key)
|
|
||||||
else:
|
else:
|
||||||
printerr("enable_important_item PropHud node not found")
|
printerr("enable_important_item PropHud node not found")
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
[gd_scene load_steps=66 format=3 uid="uid://cootarwb44vvh"]
|
[gd_scene load_steps=67 format=3 uid="uid://cootarwb44vvh"]
|
||||||
|
|
||||||
[ext_resource type="PackedScene" uid="uid://dayyx4jerj7io" path="res://scene/ground/ground.tscn" id="1_qkymk"]
|
[ext_resource type="PackedScene" uid="uid://dayyx4jerj7io" path="res://scene/ground/ground.tscn" id="1_qkymk"]
|
||||||
[ext_resource type="Script" uid="uid://cbt0ubygchxvv" path="res://scene/ground/scene/c02/s06_二楼.gd" id="2_4dg6u"]
|
[ext_resource type="Script" uid="uid://cbt0ubygchxvv" path="res://scene/ground/scene/c02/s06_二楼.gd" id="2_4dg6u"]
|
||||||
@ -46,6 +46,7 @@
|
|||||||
[ext_resource type="Texture2D" uid="uid://b5kolhax7pf4u" path="res://asset/art/scene/c02/s06_二楼楼道/小猫纸条.png" id="37_rjlld"]
|
[ext_resource type="Texture2D" uid="uid://b5kolhax7pf4u" path="res://asset/art/scene/c02/s06_二楼楼道/小猫纸条.png" id="37_rjlld"]
|
||||||
[ext_resource type="Script" uid="uid://bnm8wuspfx303" path="res://scene/ground/script/c02/event_2d_xchan_run_away.gd" id="39_t5e0j"]
|
[ext_resource type="Script" uid="uid://bnm8wuspfx303" path="res://scene/ground/script/c02/event_2d_xchan_run_away.gd" id="39_t5e0j"]
|
||||||
[ext_resource type="SpriteFrames" uid="uid://di43shn22n5ph" path="res://asset/art/gif/c00_通用动作/c00_通用动作_frames.tres" id="40_7i4w0"]
|
[ext_resource type="SpriteFrames" uid="uid://di43shn22n5ph" path="res://asset/art/gif/c00_通用动作/c00_通用动作_frames.tres" id="40_7i4w0"]
|
||||||
|
[ext_resource type="Script" uid="uid://n56a07gyjq1u" path="res://scene/ground/script/c03/s02_event_2d偷听陆仁小蝶对话后.gd" id="46_sqio2"]
|
||||||
|
|
||||||
[sub_resource type="Animation" id="Animation_k01ve"]
|
[sub_resource type="Animation" id="Animation_k01ve"]
|
||||||
length = 0.001
|
length = 0.001
|
||||||
@ -971,6 +972,8 @@ prop_key = "prop_不存在的钥匙"
|
|||||||
|
|
||||||
[node name="EventBinder开门" type="Node" parent="Ground/DeployLayer/portal_5"]
|
[node name="EventBinder开门" type="Node" parent="Ground/DeployLayer/portal_5"]
|
||||||
script = ExtResource("10_0k27j")
|
script = ExtResource("10_0k27j")
|
||||||
|
memo = "current_chapter_stage!=1|2 时,启用: [_5]
|
||||||
|
"
|
||||||
updater_event = &"current_chapter_stage"
|
updater_event = &"current_chapter_stage"
|
||||||
updater_stage_mode = "exclude"
|
updater_stage_mode = "exclude"
|
||||||
updater_stages = Array[int]([1, 2])
|
updater_stages = Array[int]([1, 2])
|
||||||
@ -1307,6 +1310,7 @@ position = Vector2(164, 42)
|
|||||||
script = ExtResource("35_h3h1a")
|
script = ExtResource("35_h3h1a")
|
||||||
pre_event_name = &"c02_ball_game_stage"
|
pre_event_name = &"c02_ball_game_stage"
|
||||||
pre_event_stages = Array[int]([3])
|
pre_event_stages = Array[int]([3])
|
||||||
|
event_memo = "当前置事件[c02_ball_game_stage=0]显示该节点"
|
||||||
metadata/_custom_type_script = "uid://bkkiyk5jkdw4d"
|
metadata/_custom_type_script = "uid://bkkiyk5jkdw4d"
|
||||||
|
|
||||||
[node name="AnimatedSprite2D" type="AnimatedSprite2D" parent="Ground/DeployLayer/Event_小猫纸条"]
|
[node name="AnimatedSprite2D" type="AnimatedSprite2D" parent="Ground/DeployLayer/Event_小猫纸条"]
|
||||||
@ -1331,6 +1335,7 @@ position = Vector2(9, -4)
|
|||||||
script = ExtResource("39_t5e0j")
|
script = ExtResource("39_t5e0j")
|
||||||
event_name = &"c02_2f_xchan_run_away"
|
event_name = &"c02_2f_xchan_run_away"
|
||||||
event_stages = Array[int]([1])
|
event_stages = Array[int]([1])
|
||||||
|
event_memo = "当事件[c02_2f_xchan_run_away=0]时显示该节点"
|
||||||
metadata/_custom_type_script = "uid://bkkiyk5jkdw4d"
|
metadata/_custom_type_script = "uid://bkkiyk5jkdw4d"
|
||||||
|
|
||||||
[node name="Ambush走到尽头后小蝉跑" parent="Ground/DeployLayer/Event2D_xchan_run_away" instance=ExtResource("14_k01ve")]
|
[node name="Ambush走到尽头后小蝉跑" parent="Ground/DeployLayer/Event2D_xchan_run_away" instance=ExtResource("14_k01ve")]
|
||||||
@ -1352,6 +1357,13 @@ move_configs = Array[Dictionary]([{
|
|||||||
}])
|
}])
|
||||||
debug_mov_animation = "c00_头套小婵_run"
|
debug_mov_animation = "c00_头套小婵_run"
|
||||||
|
|
||||||
|
[node name="Event2D偷听陆仁对话后" type="Node2D" parent="Ground/DeployLayer" index="24"]
|
||||||
|
script = ExtResource("46_sqio2")
|
||||||
|
event_name = &"c03_invite_xchan_supper"
|
||||||
|
event_stages = Array[int]([2])
|
||||||
|
event_memo = "当事件[c03_invite_xchan_supper=2:已完成邀请]时显示该节点"
|
||||||
|
metadata/_custom_type_script = "uid://bkkiyk5jkdw4d"
|
||||||
|
|
||||||
[node name="MainPlayer" parent="Ground" index="5"]
|
[node name="MainPlayer" parent="Ground" index="5"]
|
||||||
position = Vector2(63, 95)
|
position = Vector2(63, 95)
|
||||||
catty_light_energy = 0.5
|
catty_light_energy = 0.5
|
||||||
|
@ -216,7 +216,7 @@ prop_key = "prop_奇怪的肉"
|
|||||||
|
|
||||||
[node name="EventBinder" type="Node" parent="Ground/DeployLayer/Interactable放肉处"]
|
[node name="EventBinder" type="Node" parent="Ground/DeployLayer/Interactable放肉处"]
|
||||||
script = ExtResource("6_yaa68")
|
script = ExtResource("6_yaa68")
|
||||||
memo = "[放肉处] interacted时c03_s01_meat_put=1(greater)"
|
memo = "[放肉处] interacted 时: c03_s01_meat_put=1:已放肉(greater)"
|
||||||
trigger_event = &"c03_s01_meat_put"
|
trigger_event = &"c03_s01_meat_put"
|
||||||
trigger_mode = "interacted"
|
trigger_mode = "interacted"
|
||||||
metadata/_custom_type_script = "uid://0wjaho6qkg6s"
|
metadata/_custom_type_script = "uid://0wjaho6qkg6s"
|
||||||
@ -225,7 +225,7 @@ metadata/_custom_type_script = "uid://0wjaho6qkg6s"
|
|||||||
script = ExtResource("7_obrgj")
|
script = ExtResource("7_obrgj")
|
||||||
event_name = &"c03_s01_meat_put"
|
event_name = &"c03_s01_meat_put"
|
||||||
event_stages = Array[int]([1])
|
event_stages = Array[int]([1])
|
||||||
event_memo = "c03_s01_meat_put=0|2"
|
event_memo = "当事件[c03_s01_meat_put=1:已放肉]时显示该节点"
|
||||||
metadata/_custom_type_script = "uid://bkkiyk5jkdw4d"
|
metadata/_custom_type_script = "uid://bkkiyk5jkdw4d"
|
||||||
|
|
||||||
[node name="窗口闪动" type="AnimatedSprite2D" parent="Ground/DeployLayer/Event2D放肉后"]
|
[node name="窗口闪动" type="AnimatedSprite2D" parent="Ground/DeployLayer/Event2D放肉后"]
|
||||||
@ -247,7 +247,7 @@ packed_scene = ExtResource("7_d27sg")
|
|||||||
|
|
||||||
[node name="EventBinder" type="Node" parent="Ground/DeployLayer/Event2D放肉后/CloseupDemo公告"]
|
[node name="EventBinder" type="Node" parent="Ground/DeployLayer/Event2D放肉后/CloseupDemo公告"]
|
||||||
script = ExtResource("6_yaa68")
|
script = ExtResource("6_yaa68")
|
||||||
memo = "release_stage=0 时,显示: [公告]
|
memo = "release_stage=[0:demo] 时,显示: [公告]
|
||||||
"
|
"
|
||||||
updater_event = &"release_stage"
|
updater_event = &"release_stage"
|
||||||
updater_mode = "shower"
|
updater_mode = "shower"
|
||||||
@ -358,10 +358,10 @@ hook_method = "eavesdrop_luren"
|
|||||||
|
|
||||||
[node name="EventBinder" type="Node" parent="Ground/DeployLayer/Ambush偷听陆仁"]
|
[node name="EventBinder" type="Node" parent="Ground/DeployLayer/Ambush偷听陆仁"]
|
||||||
script = ExtResource("6_yaa68")
|
script = ExtResource("6_yaa68")
|
||||||
memo = "c03_s01_meat_put=1 时,启用: [偷听陆仁]
|
memo = "c03_s01_meat_put=[1:已放肉] 时,启用: [偷听陆仁]
|
||||||
[偷听陆仁] triggered 时: c03_偷听并邀请小蝶=1(greater)"
|
[偷听陆仁] triggered 时: c03_invite_xchan_supper=1:已偷听_需邀请(greater)"
|
||||||
updater_event = &"c03_s01_meat_put"
|
updater_event = &"c03_s01_meat_put"
|
||||||
trigger_event = &"c03_偷听并邀请小蝶"
|
trigger_event = &"c03_invite_xchan_supper"
|
||||||
trigger_mode = "triggered"
|
trigger_mode = "triggered"
|
||||||
metadata/_custom_type_script = "uid://0wjaho6qkg6s"
|
metadata/_custom_type_script = "uid://0wjaho6qkg6s"
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
[gd_scene load_steps=17 format=3 uid="uid://ctwy1ubhm68la"]
|
[gd_scene load_steps=17 format=3 uid="uid://ctwy1ubhm68la"]
|
||||||
|
|
||||||
[ext_resource type="PackedScene" uid="uid://dayyx4jerj7io" path="res://scene/ground/ground.tscn" id="1_jad6f"]
|
[ext_resource type="PackedScene" uid="uid://dayyx4jerj7io" path="res://scene/ground/ground.tscn" id="1_jad6f"]
|
||||||
[ext_resource type="Script" path="res://scene/ground/scene/c03/s02_瞎子新卧室.gd" id="2_y504i"]
|
[ext_resource type="Script" uid="uid://tk4wg0i4payx" path="res://scene/ground/scene/c03/s02_瞎子新卧室.gd" id="2_y504i"]
|
||||||
[ext_resource type="Script" uid="uid://cpejxlfni6n52" path="res://manager/audio_manager/vibe_sfx.gd" id="3_kchgf"]
|
[ext_resource type="Script" uid="uid://cpejxlfni6n52" path="res://manager/audio_manager/vibe_sfx.gd" id="3_kchgf"]
|
||||||
[ext_resource type="Texture2D" uid="uid://vc2hn6t5bedg" path="res://asset/art/scene/c03/s02_瞎子新卧室/bg_瞎子新卧室.png" id="4_hehhg"]
|
[ext_resource type="Texture2D" uid="uid://vc2hn6t5bedg" path="res://asset/art/scene/c03/s02_瞎子新卧室/bg_瞎子新卧室.png" id="4_hehhg"]
|
||||||
[ext_resource type="Texture2D" uid="uid://7ay1ttob8qwm" path="res://asset/art/scene/c02/s08_瞎子卧室/e_床板.png" id="5_xifhb"]
|
[ext_resource type="Texture2D" uid="uid://7ay1ttob8qwm" path="res://asset/art/scene/c02/s08_瞎子卧室/e_床板.png" id="5_xifhb"]
|
||||||
@ -179,19 +179,25 @@ texture = ExtResource("7_u55tr")
|
|||||||
|
|
||||||
[node name="Event2D偷听陆仁对话后" type="Node2D" parent="Ground/DeployLayer" index="4"]
|
[node name="Event2D偷听陆仁对话后" type="Node2D" parent="Ground/DeployLayer" index="4"]
|
||||||
script = ExtResource("8_pixqd")
|
script = ExtResource("8_pixqd")
|
||||||
event_name = &"c03_偷听并邀请小蝶"
|
event_name = &"c03_invite_xchan_supper"
|
||||||
event_stages = Array[int]([1])
|
event_stages = Array[int]([1])
|
||||||
event_memo = "当事件[c03_偷听并邀请小蝶=1]时显示该节点"
|
event_memo = "当事件[c03_invite_xchan_supper=1:已偷听_需邀请]时显示该节点"
|
||||||
metadata/_custom_type_script = "uid://bkkiyk5jkdw4d"
|
metadata/_custom_type_script = "uid://bkkiyk5jkdw4d"
|
||||||
|
|
||||||
[node name="瞎子抽烟" parent="Ground/DeployLayer/Event2D偷听陆仁对话后" instance=ExtResource("10_jad6f")]
|
[node name="瞎子抽烟" parent="Ground/DeployLayer/Event2D偷听陆仁对话后" instance=ExtResource("10_jad6f")]
|
||||||
position = Vector2(229, 39)
|
position = Vector2(229, 39)
|
||||||
sprite_frames = ExtResource("10_2hv3f")
|
sprite_frames = ExtResource("10_2hv3f")
|
||||||
animation = &"瞎子_咳嗽吐血"
|
animation = &"瞎子_咳嗽吐血"
|
||||||
frame = 28
|
|
||||||
action_configs = Array[Dictionary]([{
|
action_configs = Array[Dictionary]([{
|
||||||
&"animation_intro": "瞎子_抽烟",
|
&"animation_intro": "瞎子_抽烟",
|
||||||
&"animation_next": "瞎子_坐拿烟_左呼吸"
|
&"animation_next": "瞎子_坐拿烟_左呼吸",
|
||||||
|
"animation_wait_time": 0.0,
|
||||||
|
"intro_loop": 1
|
||||||
|
}, {
|
||||||
|
"animation_intro": "瞎子_咳嗽吐血",
|
||||||
|
&"animation_next": "瞎子_坐拿烟_左呼吸",
|
||||||
|
"animation_wait_time": 0.0,
|
||||||
|
"intro_loop": 1
|
||||||
}])
|
}])
|
||||||
|
|
||||||
[node name="MainPlayer" parent="Ground" index="5"]
|
[node name="MainPlayer" parent="Ground" index="5"]
|
||||||
|
@ -16,9 +16,15 @@ func _on_global_stage_updated(e: StringName, s: int):
|
|||||||
|
|
||||||
|
|
||||||
func _on_ground_ready(ground: Ground2D):
|
func _on_ground_ready(ground: Ground2D):
|
||||||
if stage == 0:
|
|
||||||
# 邀请小蝉晚饭
|
# 邀请小蝉晚饭
|
||||||
EventManager.set_stage(event_name, 1)
|
if stage == 1:
|
||||||
|
var the_blind = $"瞎子抽烟" as AnimatedSprite2D
|
||||||
|
the_blind.play("瞎子_抽烟")
|
||||||
|
await the_blind.animation_finished
|
||||||
|
the_blind.play("瞎子_咳嗽吐血")
|
||||||
|
await the_blind.animation_finished
|
||||||
|
# 邀请成功
|
||||||
|
EventManager.set_stage(event_name, 2)
|
||||||
|
|
||||||
|
|
||||||
func _on_pre_stage_updated():
|
func _on_pre_stage_updated():
|
||||||
|
@ -460,6 +460,18 @@ func _toggle_btn_ability(v: bool) -> void:
|
|||||||
right_btn.disabled = !v
|
right_btn.disabled = !v
|
||||||
|
|
||||||
|
|
||||||
|
func enable_important_item(prop_key: String, display_inspector: bool) -> void:
|
||||||
|
if not inventory or not prop_key:
|
||||||
|
return
|
||||||
|
if not items_dict.has(prop_key):
|
||||||
|
push_error("ImportantPropItem not found! key=" + prop_key)
|
||||||
|
return
|
||||||
|
inventory.enable_important_item(prop_key)
|
||||||
|
SceneManager.pop_notification("ui_notify_important_item_update")
|
||||||
|
if display_inspector:
|
||||||
|
inspect_item(prop_key)
|
||||||
|
|
||||||
|
|
||||||
func enable_prop_item(prop_key: String, inspect := true) -> void:
|
func enable_prop_item(prop_key: String, inspect := true) -> void:
|
||||||
if not inventory or not prop_key:
|
if not inventory or not prop_key:
|
||||||
return
|
return
|
||||||
@ -480,13 +492,17 @@ func enable_prop_item(prop_key: String, inspect := true) -> void:
|
|||||||
func inspect_item(prop_key: String, display_words_only := false):
|
func inspect_item(prop_key: String, display_words_only := false):
|
||||||
var inspector = SceneManager.get_inspector()
|
var inspector = SceneManager.get_inspector()
|
||||||
if inspector:
|
if inspector:
|
||||||
|
var texture_path = items_dict[prop_key].texture_path
|
||||||
var inspect_path = items_dict[prop_key].inspect_path
|
var inspect_path = items_dict[prop_key].inspect_path
|
||||||
# 是否有独立的 inspect 图片
|
# 是否有独立的 inspect 图片
|
||||||
if inspect_path:
|
if inspect_path:
|
||||||
var texture = load(inspect_path) as Texture2D
|
var texture = load(inspect_path) as Texture2D
|
||||||
inspector.pop_prop_inspection(prop_key, texture, false, display_words_only)
|
inspector.pop_prop_inspection(prop_key, texture, false, display_words_only)
|
||||||
else:
|
else:
|
||||||
var texture = cached_inventory_textures[prop_key]
|
var texture = cached_inventory_textures.get(prop_key)
|
||||||
|
if not texture:
|
||||||
|
texture = load(texture_path) as Texture2D
|
||||||
|
cached_inventory_textures[prop_key] = texture
|
||||||
inspector.pop_prop_inspection(prop_key, texture, true, display_words_only)
|
inspector.pop_prop_inspection(prop_key, texture, true, display_words_only)
|
||||||
|
|
||||||
|
|
||||||
|
@ -20,7 +20,6 @@ signal quit_and_hidden
|
|||||||
@onready var content_label = %ContentLabel as Label
|
@onready var content_label = %ContentLabel as Label
|
||||||
@onready var tip_label = %TipLabel as Label
|
@onready var tip_label = %TipLabel as Label
|
||||||
|
|
||||||
|
|
||||||
var tip_cover = "Q: " + tr("ui_退出") + " " + "E: " + tr("ui_阅读")
|
var tip_cover = "Q: " + tr("ui_退出") + " " + "E: " + tr("ui_阅读")
|
||||||
var tip_notes = "Q: " + tr("ui_退出") + " " + "E: " + tr("ui_收起")
|
var tip_notes = "Q: " + tr("ui_退出") + " " + "E: " + tr("ui_收起")
|
||||||
var texture_cover: Texture2D
|
var texture_cover: Texture2D
|
||||||
@ -162,8 +161,10 @@ func pop_prop_inspection(
|
|||||||
if not display_words_only:
|
if not display_words_only:
|
||||||
text += obtain_str + ": " + prop_title + "[#item]\n"
|
text += obtain_str + ": " + prop_title + "[#item]\n"
|
||||||
# 道具的一句话说明
|
# 道具的一句话说明
|
||||||
text += tr(prop_key + "_说明").replace("{br}", "\n").strip_edges() + "\n"
|
var original_word_lines = tr(prop_key + "_说明").replace("{br}", "\n").split("\n")
|
||||||
text += "=> END"
|
# 缩略只要第一行
|
||||||
|
text += original_word_lines[0] + ("..." if len(original_word_lines) > 1 else "")
|
||||||
|
text += "\n=> END"
|
||||||
var current_prop_res = DialogueManager.create_resource_from_text(text)
|
var current_prop_res = DialogueManager.create_resource_from_text(text)
|
||||||
# 手动跳过的同时显示下一句
|
# 手动跳过的同时显示下一句
|
||||||
if is_instance_valid(balloon):
|
if is_instance_valid(balloon):
|
||||||
|
@ -41,7 +41,6 @@ func enable_important_item(prop_key: String) -> void:
|
|||||||
if not important_items.has(prop_key):
|
if not important_items.has(prop_key):
|
||||||
important_items.append(prop_key)
|
important_items.append(prop_key)
|
||||||
unviewed_important_items.append(prop_key)
|
unviewed_important_items.append(prop_key)
|
||||||
SceneManager.pop_notification("ui_notify_important_item_update")
|
|
||||||
|
|
||||||
|
|
||||||
func has_prop(prop_key: String) -> bool:
|
func has_prop(prop_key: String) -> bool:
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
|
@tool
|
||||||
extends Node
|
extends Node
|
||||||
|
|
||||||
|
|
||||||
#### Timer
|
#### Timer
|
||||||
func wait(duration: float) -> void:
|
func wait(duration: float) -> void:
|
||||||
if duration <= 0:
|
if duration <= 0:
|
||||||
|
Loading…
Reference in New Issue
Block a user