Move bull ability FX into the authorable systems/fx/ library
The bull's kick/dash/slam/roll and movement particles were ~112 lines of hand-built CPUParticles3D in player.gd, bypassing the Fx system entirely (Fx.spawn had zero callers) — so Siim couldn't author them in the inspector. - Add six inspector-editable effect scenes (bull_slam/kick/dash one-shots + bull_hoof_dust/charge_trail/roll_sparks continuous); wire player.gd to Fx.spawn() / new Fx.attach(); register them + the gallery + smoke test. - Extract the coded, non-particle slam rings + kick flash into a new bull_impact_fx.gd component (bull_legs.gd pattern). - Drop 12 now-obsolete DP dust look-params (the .tscn owns the look now). - Trim god-file comment noise: rewrite an AI-placeholder barrel block to typed code, drop dead commented-out bounce/topple code, fix stale post-reorg file paths (Assets/… → art/…, PascalCase scene names → snake_case). - Document the Siim workflow in systems/fx/README.md; update CLAUDE.md. player.gd 1307 → 944 lines. Full suite green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CCyaJPTHCH9EDthpf4A5Sj
This commit is contained in:
+32
-3
@@ -6,8 +6,9 @@ change to how a hit *feels* is a `.tscn` Siim edits in the inspector — no code
|
||||
|
||||
```gdscript
|
||||
# anywhere in gameplay code
|
||||
Fx.spawn(&"blood_burst", global_transform) # at a node
|
||||
Fx.spawn(&"blood_burst", global_transform) # one-shot: fire and forget (self-frees)
|
||||
Fx.spawn(&"slam_dust", Transform3D(Basis(), point)) # at a world point
|
||||
var trail := Fx.attach(&"bull_charge_trail", node) # continuous: caller keeps it + toggles .emitting
|
||||
```
|
||||
|
||||
## Siim's workflow
|
||||
@@ -25,6 +26,33 @@ Fx.spawn(&"slam_dust", Transform3D(Basis(), point)) # at a world point
|
||||
New effect? Add a `fx/<name>.tscn` (root = `CPUParticles3D` with `one_shot_effect.gd` attached — copy
|
||||
an existing one), then register its name → path in `fx/fx.gd`.
|
||||
|
||||
## Bull ability effects
|
||||
|
||||
The bull's moves are wired to effect scenes here, so **you can retune every one in the inspector** —
|
||||
open the `.tscn`, drag its colour ramp / curves / emission, save. No code.
|
||||
|
||||
| Ability | Effect scene(s) | Kind |
|
||||
|---|---|---|
|
||||
| Ground slam | `bull_slam.tscn` (dust) | one-shot burst |
|
||||
| Back-kick | `bull_kick.tscn` — dust **+** its `stones` and `sparks` child emitters | one-shot burst |
|
||||
| Dash / roll launch | `bull_dash.tscn` | one-shot burst |
|
||||
| Hoof dust (walking / sprinting) | `bull_hoof_dust.tscn` | continuous |
|
||||
| Charge trail (the blue streak) | `bull_charge_trail.tscn` | continuous |
|
||||
| Roll sparks | `bull_roll_sparks.tscn` | continuous |
|
||||
|
||||
**Two kinds, edited the same way.** *One-shot* effects fire once and clean themselves up — they show
|
||||
up in the gallery, so preview and tune them there. *Continuous* effects follow the bull and switch on
|
||||
and off with what it's doing (dust while moving, the trail during a roll); the gallery can't show
|
||||
those, so preview one by opening its `.tscn` and ticking **Emitting** on in the inspector's viewport.
|
||||
|
||||
Two things stay in code and aren't yours to tune here: the slam's expanding shockwave **rings** and the
|
||||
kick's white **flash-pop** — those are stretched flat meshes, not particles (`actors/player/bull_impact_fx.gd`).
|
||||
The dust's *walk-vs-sprint size* is also code (a fixed multiplier); its colour and shape are the `.tscn`.
|
||||
|
||||
**Composite effect** (like the kick): one scene, several `CPUParticles3D` — a root plus child emitters.
|
||||
`one_shot_effect.gd` fires every emitter in the scene together, so you can build a layered hit
|
||||
(dust + debris + sparks) as one file and tune each layer separately.
|
||||
|
||||
## House style (keep effects reading as one game)
|
||||
|
||||
**Palette** — pull from these, don't invent per-effect colours:
|
||||
@@ -52,8 +80,9 @@ the inspector and safe on-device. Keep this constraint when adding effects.
|
||||
|
||||
| File | What it is |
|
||||
|---|---|
|
||||
| `fx.gd` | The `Fx` autoload — `spawn(name, xform, parent=null)`; name→path registry |
|
||||
| `one_shot_effect.gd` | Root helper: fires the emitter(s) once, frees the scene when done |
|
||||
| `fx.gd` | The `Fx` autoload — `spawn()` (one-shot) / `attach()` (continuous); name→path registry |
|
||||
| `one_shot_effect.gd` | Root helper: fires every emitter in the scene once, frees it when done |
|
||||
| `blood_burst.tscn` etc. | The effects themselves — **edit these** |
|
||||
| `bull_*.tscn` | The bull's ability effects (see the table above) — **edit these** |
|
||||
| `effect_gallery.tscn` | Preview stage (run it to review) |
|
||||
| `_seed_placeholders.gd` | One-off generator that wrote the first-draft defaults; not used at runtime |
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
[gd_scene format=3]
|
||||
|
||||
; Bull charge trail — the blue energy streak that streams off the bull during a roll/dash.
|
||||
; Continuous: the player instances it once under the bull, toggles `emitting`, and steers
|
||||
; `direction` opposite the travel direction each frame. Tune the look here; see README.md.
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="mat"]
|
||||
transparency = 1
|
||||
cull_mode = 2
|
||||
shading_mode = 0
|
||||
vertex_color_use_as_albedo = true
|
||||
|
||||
[sub_resource type="SphereMesh" id="mesh"]
|
||||
material = SubResource("mat")
|
||||
radius = 0.065
|
||||
height = 0.13
|
||||
radial_segments = 4
|
||||
rings = 2
|
||||
|
||||
[sub_resource type="Curve" id="curve"]
|
||||
_data = [Vector2(0, 1), 0.0, 0.0, 0, 0, Vector2(0.6, 0.8), 0.0, 0.0, 0, 0, Vector2(1, 0), 0.0, 0.0, 0, 0]
|
||||
point_count = 3
|
||||
|
||||
[sub_resource type="Gradient" id="grad"]
|
||||
colors = PackedColorArray(0.3, 0.65, 1, 1, 0.3, 0.65, 1, 0)
|
||||
|
||||
[node name="bull_charge_trail" type="CPUParticles3D"]
|
||||
emitting = false
|
||||
amount = 18
|
||||
lifetime = 0.3
|
||||
local_coords = false
|
||||
randomness = 0.4
|
||||
mesh = SubResource("mesh")
|
||||
direction = Vector3(0, 0.2, 1)
|
||||
spread = 25.0
|
||||
gravity = Vector3(0, 2, 0)
|
||||
initial_velocity_min = 4.0
|
||||
initial_velocity_max = 9.0
|
||||
scale_amount_min = 0.4
|
||||
scale_amount_max = 1.2
|
||||
scale_amount_curve = SubResource("curve")
|
||||
color_ramp = SubResource("grad")
|
||||
@@ -0,0 +1,44 @@
|
||||
[gd_scene format=3]
|
||||
|
||||
; Bull dash burst — the one-shot streak of speed shards left when a dash/roll launches. Fired
|
||||
; via Fx.spawn(&"bull_dash", xform); the spawn transform aims local -Z along the dash direction,
|
||||
; so the shards streak the right way. Tune the look here; see systems/fx/README.md.
|
||||
|
||||
[ext_resource type="Script" path="res://systems/fx/one_shot_effect.gd" id="oneshot"]
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="mat"]
|
||||
transparency = 1
|
||||
cull_mode = 2
|
||||
shading_mode = 0
|
||||
vertex_color_use_as_albedo = true
|
||||
|
||||
[sub_resource type="BoxMesh" id="mesh"]
|
||||
material = SubResource("mat")
|
||||
size = Vector3(0.045, 0.045, 0.3)
|
||||
|
||||
[sub_resource type="Curve" id="curve"]
|
||||
_data = [Vector2(0, 1), 0.0, 0.0, 0, 0, Vector2(1, 0), 0.0, 0.0, 0, 0]
|
||||
point_count = 2
|
||||
|
||||
[sub_resource type="Gradient" id="grad"]
|
||||
colors = PackedColorArray(0.5, 0.82, 1, 1, 0.5, 0.82, 1, 0)
|
||||
|
||||
[node name="bull_dash" type="CPUParticles3D"]
|
||||
emitting = false
|
||||
amount = 18
|
||||
lifetime = 0.22
|
||||
one_shot = true
|
||||
local_coords = false
|
||||
explosiveness = 0.92
|
||||
randomness = 0.15
|
||||
mesh = SubResource("mesh")
|
||||
direction = Vector3(0, 0, -1)
|
||||
spread = 15.0
|
||||
gravity = Vector3(0, 0, 0)
|
||||
initial_velocity_min = 18.0
|
||||
initial_velocity_max = 35.0
|
||||
scale_amount_min = 0.8
|
||||
scale_amount_max = 2.8
|
||||
scale_amount_curve = SubResource("curve")
|
||||
color_ramp = SubResource("grad")
|
||||
script = ExtResource("oneshot")
|
||||
@@ -0,0 +1,43 @@
|
||||
[gd_scene format=3]
|
||||
|
||||
; Bull hoof dust — the little kick-up under each hoof while moving. Continuous (not one-shot):
|
||||
; the player instances one per hoof, positions it, and toggles `emitting`. Charge sprint bumps
|
||||
; scale/velocity in code. Tune the look here; see systems/fx/README.md.
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="mat"]
|
||||
transparency = 1
|
||||
cull_mode = 2
|
||||
shading_mode = 0
|
||||
vertex_color_use_as_albedo = true
|
||||
|
||||
[sub_resource type="SphereMesh" id="mesh"]
|
||||
material = SubResource("mat")
|
||||
radius = 0.07
|
||||
height = 0.14
|
||||
radial_segments = 4
|
||||
rings = 2
|
||||
|
||||
[sub_resource type="Curve" id="curve"]
|
||||
_data = [Vector2(0, 0.2), 0.0, 0.0, 0, 0, Vector2(0.4, 1), 0.0, 0.0, 0, 0, Vector2(1, 0), 0.0, 0.0, 0, 0]
|
||||
point_count = 3
|
||||
|
||||
[sub_resource type="Gradient" id="grad"]
|
||||
colors = PackedColorArray(0.94, 0.9, 0.83, 1, 0.94, 0.9, 0.83, 0)
|
||||
|
||||
[node name="bull_hoof_dust" type="CPUParticles3D"]
|
||||
emitting = false
|
||||
amount = 12
|
||||
lifetime = 0.9
|
||||
local_coords = false
|
||||
explosiveness = 0.35
|
||||
randomness = 0.6
|
||||
mesh = SubResource("mesh")
|
||||
direction = Vector3(0, 1, 0)
|
||||
spread = 45.0
|
||||
gravity = Vector3(0, -1.5, 0)
|
||||
initial_velocity_min = 0.8
|
||||
initial_velocity_max = 1.8
|
||||
scale_amount_min = 0.2
|
||||
scale_amount_max = 0.4
|
||||
scale_amount_curve = SubResource("curve")
|
||||
color_ramp = SubResource("grad")
|
||||
@@ -0,0 +1,115 @@
|
||||
[gd_scene format=3]
|
||||
|
||||
; Bull back-kick impact — the one-shot combined dust + stones + sparks thrown behind the kick.
|
||||
; A composite effect: the root emitter is the dust; the "stones" and "sparks" child emitters fire
|
||||
; with it (one_shot_effect.gd arms every CPUParticles3D in the scene). Fired via
|
||||
; Fx.spawn(&"bull_kick", xform); the spawn transform aims local -Z along the kick-back direction.
|
||||
; The bright flash-pop is a separate coded effect (bull_impact_fx.gd). Tune all three looks here.
|
||||
|
||||
[ext_resource type="Script" path="res://systems/fx/one_shot_effect.gd" id="oneshot"]
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="mat"]
|
||||
transparency = 1
|
||||
cull_mode = 2
|
||||
shading_mode = 0
|
||||
vertex_color_use_as_albedo = true
|
||||
|
||||
[sub_resource type="SphereMesh" id="dust_mesh"]
|
||||
material = SubResource("mat")
|
||||
radius = 0.12
|
||||
height = 0.24
|
||||
radial_segments = 4
|
||||
rings = 2
|
||||
|
||||
[sub_resource type="Curve" id="dust_curve"]
|
||||
_data = [Vector2(0, 0.3), 0.0, 0.0, 0, 0, Vector2(0.3, 1.5), 0.0, 0.0, 0, 0, Vector2(1, 0), 0.0, 0.0, 0, 0]
|
||||
point_count = 3
|
||||
|
||||
[sub_resource type="Gradient" id="dust_grad"]
|
||||
colors = PackedColorArray(1, 0.78, 0.18, 0.95, 1, 0.45, 0.05, 0)
|
||||
|
||||
[sub_resource type="SphereMesh" id="stone_mesh"]
|
||||
material = SubResource("mat")
|
||||
radius = 0.055
|
||||
height = 0.11
|
||||
radial_segments = 3
|
||||
rings = 1
|
||||
|
||||
[sub_resource type="Curve" id="stone_curve"]
|
||||
_data = [Vector2(0, 1), 0.0, 0.0, 0, 0, Vector2(0.6, 1), 0.0, 0.0, 0, 0, Vector2(1, 0), 0.0, 0.0, 0, 0]
|
||||
point_count = 3
|
||||
|
||||
[sub_resource type="Gradient" id="stone_grad"]
|
||||
colors = PackedColorArray(0.62, 0.58, 0.52, 1, 0.62, 0.58, 0.52, 0)
|
||||
|
||||
[sub_resource type="SphereMesh" id="spark_mesh"]
|
||||
material = SubResource("mat")
|
||||
radius = 0.04
|
||||
height = 0.08
|
||||
radial_segments = 3
|
||||
rings = 1
|
||||
|
||||
[sub_resource type="Curve" id="spark_curve"]
|
||||
_data = [Vector2(0, 1), 0.0, 0.0, 0, 0, Vector2(0.5, 0.6), 0.0, 0.0, 0, 0, Vector2(1, 0), 0.0, 0.0, 0, 0]
|
||||
point_count = 3
|
||||
|
||||
[sub_resource type="Gradient" id="spark_grad"]
|
||||
colors = PackedColorArray(1, 0.98, 0.6, 1, 1, 1, 1, 0)
|
||||
|
||||
[node name="bull_kick" type="CPUParticles3D"]
|
||||
emitting = false
|
||||
amount = 40
|
||||
lifetime = 1.2
|
||||
one_shot = true
|
||||
local_coords = false
|
||||
explosiveness = 0.8
|
||||
randomness = 0.5
|
||||
mesh = SubResource("dust_mesh")
|
||||
direction = Vector3(0, 0.7, -1)
|
||||
spread = 65.0
|
||||
gravity = Vector3(0, -6, 0)
|
||||
initial_velocity_min = 3.0
|
||||
initial_velocity_max = 9.0
|
||||
scale_amount_min = 0.5
|
||||
scale_amount_max = 2.2
|
||||
scale_amount_curve = SubResource("dust_curve")
|
||||
color_ramp = SubResource("dust_grad")
|
||||
script = ExtResource("oneshot")
|
||||
|
||||
[node name="stones" type="CPUParticles3D" parent="."]
|
||||
emitting = false
|
||||
amount = 14
|
||||
lifetime = 0.65
|
||||
one_shot = true
|
||||
local_coords = false
|
||||
explosiveness = 0.92
|
||||
randomness = 0.6
|
||||
mesh = SubResource("stone_mesh")
|
||||
direction = Vector3(0, 0.35, -1)
|
||||
spread = 45.0
|
||||
gravity = Vector3(0, -9.8, 0)
|
||||
initial_velocity_min = 5.0
|
||||
initial_velocity_max = 14.0
|
||||
scale_amount_min = 0.5
|
||||
scale_amount_max = 1.8
|
||||
scale_amount_curve = SubResource("stone_curve")
|
||||
color_ramp = SubResource("stone_grad")
|
||||
|
||||
[node name="sparks" type="CPUParticles3D" parent="."]
|
||||
emitting = false
|
||||
amount = 22
|
||||
lifetime = 0.28
|
||||
one_shot = true
|
||||
local_coords = false
|
||||
explosiveness = 0.95
|
||||
randomness = 0.4
|
||||
mesh = SubResource("spark_mesh")
|
||||
direction = Vector3(0, 0.5, -1)
|
||||
spread = 30.0
|
||||
gravity = Vector3(0, -14, 0)
|
||||
initial_velocity_min = 8.0
|
||||
initial_velocity_max = 18.0
|
||||
scale_amount_min = 0.6
|
||||
scale_amount_max = 2.0
|
||||
scale_amount_curve = SubResource("spark_curve")
|
||||
color_ramp = SubResource("spark_grad")
|
||||
@@ -0,0 +1,42 @@
|
||||
[gd_scene format=3]
|
||||
|
||||
; Bull roll sparks — the dirt/ember spray flung outward from the spinning roll ("Powerball").
|
||||
; Continuous: the player instances it once under the bull and toggles `emitting` during a roll.
|
||||
; World-space so the spray peels off into a trail. Tune the look here; see README.md.
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="mat"]
|
||||
transparency = 1
|
||||
cull_mode = 2
|
||||
shading_mode = 0
|
||||
vertex_color_use_as_albedo = true
|
||||
|
||||
[sub_resource type="SphereMesh" id="mesh"]
|
||||
material = SubResource("mat")
|
||||
radius = 0.05
|
||||
height = 0.1
|
||||
radial_segments = 4
|
||||
rings = 2
|
||||
|
||||
[sub_resource type="Curve" id="curve"]
|
||||
_data = [Vector2(0, 0.3), 0.0, 0.0, 0, 0, Vector2(0.3, 1), 0.0, 0.0, 0, 0, Vector2(1, 0), 0.0, 0.0, 0, 0]
|
||||
point_count = 3
|
||||
|
||||
[sub_resource type="Gradient" id="grad"]
|
||||
colors = PackedColorArray(1, 0.78, 0.32, 1, 0.7, 0.42, 0.14, 0)
|
||||
|
||||
[node name="bull_roll_sparks" type="CPUParticles3D"]
|
||||
emitting = false
|
||||
amount = 46
|
||||
lifetime = 0.45
|
||||
local_coords = false
|
||||
randomness = 0.7
|
||||
mesh = SubResource("mesh")
|
||||
direction = Vector3(0, 1, 0)
|
||||
spread = 180.0
|
||||
gravity = Vector3(0, -6, 0)
|
||||
initial_velocity_min = 3.0
|
||||
initial_velocity_max = 8.0
|
||||
scale_amount_min = 0.5
|
||||
scale_amount_max = 1.3
|
||||
scale_amount_curve = SubResource("curve")
|
||||
color_ramp = SubResource("grad")
|
||||
@@ -0,0 +1,47 @@
|
||||
[gd_scene format=3]
|
||||
|
||||
; Bull ground-slam dust — the one-shot dirt burst thrown up when the slam lands. Fired via
|
||||
; Fx.spawn(&"bull_slam", xform) at the impact point. The expanding shockwave RINGS are a
|
||||
; separate coded effect (actors/player/bull_impact_fx.gd), not this. Tune the look here.
|
||||
|
||||
[ext_resource type="Script" path="res://systems/fx/one_shot_effect.gd" id="oneshot"]
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="mat"]
|
||||
transparency = 1
|
||||
cull_mode = 2
|
||||
shading_mode = 0
|
||||
vertex_color_use_as_albedo = true
|
||||
|
||||
[sub_resource type="SphereMesh" id="mesh"]
|
||||
material = SubResource("mat")
|
||||
radius = 0.09
|
||||
height = 0.18
|
||||
radial_segments = 4
|
||||
rings = 2
|
||||
|
||||
[sub_resource type="Curve" id="curve"]
|
||||
_data = [Vector2(0, 0.25), 0.0, 0.0, 0, 0, Vector2(0.35, 1.6), 0.0, 0.0, 0, 0, Vector2(1, 0), 0.0, 0.0, 0, 0]
|
||||
point_count = 3
|
||||
|
||||
[sub_resource type="Gradient" id="grad"]
|
||||
colors = PackedColorArray(0.88, 0.74, 0.46, 1, 0.88, 0.74, 0.46, 0)
|
||||
|
||||
[node name="bull_slam" type="CPUParticles3D"]
|
||||
emitting = false
|
||||
amount = 48
|
||||
lifetime = 1.1
|
||||
one_shot = true
|
||||
local_coords = false
|
||||
explosiveness = 0.85
|
||||
randomness = 0.5
|
||||
mesh = SubResource("mesh")
|
||||
direction = Vector3(0, 1, 0)
|
||||
spread = 85.0
|
||||
gravity = Vector3(0, -7, 0)
|
||||
initial_velocity_min = 3.5
|
||||
initial_velocity_max = 10.0
|
||||
scale_amount_min = 0.5
|
||||
scale_amount_max = 2.0
|
||||
scale_amount_curve = SubResource("curve")
|
||||
color_ramp = SubResource("grad")
|
||||
script = ExtResource("oneshot")
|
||||
@@ -4,7 +4,10 @@ extends Node3D
|
||||
## press Play, then edit any effect .tscn in the inspector and re-run to compare. See fx/README.md.
|
||||
|
||||
const _CYCLE_SEC := 1.6
|
||||
const _EFFECTS: Array[StringName] = [&"blood_burst", &"slam_dust", &"spark", &"hit_flash"]
|
||||
const _EFFECTS: Array[StringName] = [
|
||||
&"blood_burst", &"slam_dust", &"spark", &"hit_flash",
|
||||
&"bull_slam", &"bull_kick", &"bull_dash",
|
||||
]
|
||||
|
||||
@onready var _label: Label = $UI/Label
|
||||
# Reached by path rather than the Fx global so the placeholder-seeder can compile and attach this
|
||||
|
||||
@@ -7,10 +7,18 @@ extends Node
|
||||
## placeholder .tscn are seeded — see tools/seed_fx_placeholders.gd.
|
||||
|
||||
const _PATHS: Dictionary = {
|
||||
# Generic hit effects.
|
||||
&"blood_burst": "res://systems/fx/blood_burst.tscn",
|
||||
&"slam_dust": "res://systems/fx/slam_dust.tscn",
|
||||
&"spark": "res://systems/fx/spark.tscn",
|
||||
&"hit_flash": "res://systems/fx/hit_flash.tscn",
|
||||
# Bull ability effects — one-shots fired by spawn(), continuous ones held via attach().
|
||||
&"bull_slam": "res://systems/fx/bull_slam.tscn",
|
||||
&"bull_kick": "res://systems/fx/bull_kick.tscn",
|
||||
&"bull_dash": "res://systems/fx/bull_dash.tscn",
|
||||
&"bull_hoof_dust": "res://systems/fx/bull_hoof_dust.tscn",
|
||||
&"bull_charge_trail": "res://systems/fx/bull_charge_trail.tscn",
|
||||
&"bull_roll_sparks": "res://systems/fx/bull_roll_sparks.tscn",
|
||||
}
|
||||
|
||||
var _cache: Dictionary = {}
|
||||
@@ -32,6 +40,18 @@ func spawn(effect: StringName, xform: Transform3D, parent: Node = null) -> Node:
|
||||
return inst
|
||||
|
||||
|
||||
# Instance a CONTINUOUS effect as a child of `parent` and hand it back. Unlike spawn(), it neither
|
||||
# positions nor auto-frees the effect — the caller keeps the reference to place it and toggle
|
||||
# `emitting` (hoof dust that follows the bull, the charge trail). Returns null if unknown/missing.
|
||||
func attach(effect: StringName, parent: Node) -> Node:
|
||||
var scene := _scene(effect)
|
||||
if scene == null or parent == null:
|
||||
return null
|
||||
var inst := scene.instantiate()
|
||||
parent.add_child(inst)
|
||||
return inst
|
||||
|
||||
|
||||
func _scene(effect: StringName) -> PackedScene:
|
||||
if _cache.has(effect):
|
||||
return _cache[effect]
|
||||
|
||||
Reference in New Issue
Block a user