Spiderweb obstacles added. Latest libstormgames module update. Level 4 created, needs adjustment, currently way too hard.
This commit is contained in:
280
levels/Wicked Quest/4.json
Normal file
280
levels/Wicked Quest/4.json
Normal file
@@ -0,0 +1,280 @@
|
|||||||
|
{
|
||||||
|
"level_id": 4,
|
||||||
|
"name": "Spider's Domain",
|
||||||
|
"description": "The spiders have claimed this part of the graveyard. Be careful to not disturb them. You know how spiders think, if you can't eat it, kill it!",
|
||||||
|
"player_start": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"objects": [
|
||||||
|
{
|
||||||
|
"x_range": [5, 8],
|
||||||
|
"y": 3,
|
||||||
|
"sound": "coin",
|
||||||
|
"collectible": true,
|
||||||
|
"static": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "spider_web",
|
||||||
|
"x": 10,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 15,
|
||||||
|
"y": 3,
|
||||||
|
"sound": "coffin",
|
||||||
|
"type": "coffin"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x_range": [20, 23],
|
||||||
|
"y": 3,
|
||||||
|
"sound": "coin",
|
||||||
|
"collectible": true,
|
||||||
|
"static": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x_range": [25, 35],
|
||||||
|
"y": 0,
|
||||||
|
"enemy_type": "goblin",
|
||||||
|
"health": 4,
|
||||||
|
"damage": 2,
|
||||||
|
"attack_range": 1,
|
||||||
|
"attack_pattern": {
|
||||||
|
"type": "patrol"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x_range": [30, 45],
|
||||||
|
"y": 12,
|
||||||
|
"type": "skull_storm",
|
||||||
|
"damage": 3,
|
||||||
|
"maximum_skulls": 2,
|
||||||
|
"frequency": {
|
||||||
|
"min": 2,
|
||||||
|
"max": 4
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "spider_web",
|
||||||
|
"x": 40,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 45,
|
||||||
|
"y": 0,
|
||||||
|
"type": "grave",
|
||||||
|
"sound": "grave",
|
||||||
|
"static": true,
|
||||||
|
"zombie_spawn_chance": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x_range": [50, 54],
|
||||||
|
"y": 3,
|
||||||
|
"sound": "coin",
|
||||||
|
"collectible": true,
|
||||||
|
"static": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 60,
|
||||||
|
"y": 3,
|
||||||
|
"sound": "coffin",
|
||||||
|
"type": "coffin"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x_range": [65, 85],
|
||||||
|
"y": 0,
|
||||||
|
"enemy_type": "witch",
|
||||||
|
"health": 8,
|
||||||
|
"damage": 2,
|
||||||
|
"attack_range": 1,
|
||||||
|
"attack_pattern": {
|
||||||
|
"type": "patrol"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "spider_web",
|
||||||
|
"x": 75,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x_range": [80, 95],
|
||||||
|
"y": 15,
|
||||||
|
"type": "skull_storm",
|
||||||
|
"damage": 4,
|
||||||
|
"maximum_skulls": 3,
|
||||||
|
"frequency": {
|
||||||
|
"min": 1,
|
||||||
|
"max": 3
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x_range": [90, 93],
|
||||||
|
"y": 3,
|
||||||
|
"sound": "coin",
|
||||||
|
"collectible": true,
|
||||||
|
"static": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 100,
|
||||||
|
"y": 0,
|
||||||
|
"type": "catapult",
|
||||||
|
"fire_interval": 4000,
|
||||||
|
"range": 20
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 110,
|
||||||
|
"y": 0,
|
||||||
|
"type": "grave",
|
||||||
|
"sound": "grave",
|
||||||
|
"static": true,
|
||||||
|
"zombie_spawn_chance": 50
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x_range": [115, 118],
|
||||||
|
"y": 3,
|
||||||
|
"sound": "coin",
|
||||||
|
"collectible": true,
|
||||||
|
"static": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "spider_web",
|
||||||
|
"x": 120,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x_range": [125, 145],
|
||||||
|
"y": 0,
|
||||||
|
"enemy_type": "ghoul",
|
||||||
|
"health": 12,
|
||||||
|
"damage": 3,
|
||||||
|
"attack_range": 2,
|
||||||
|
"attack_pattern": {
|
||||||
|
"type": "hunter",
|
||||||
|
"turn_threshold": 4
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 135,
|
||||||
|
"y": 3,
|
||||||
|
"sound": "coffin",
|
||||||
|
"type": "coffin"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x_range": [140, 160],
|
||||||
|
"y": 15,
|
||||||
|
"type": "skull_storm",
|
||||||
|
"damage": 4,
|
||||||
|
"maximum_skulls": 3,
|
||||||
|
"frequency": {
|
||||||
|
"min": 1,
|
||||||
|
"max": 3
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x_range": [150, 153],
|
||||||
|
"y": 3,
|
||||||
|
"sound": "coin",
|
||||||
|
"collectible": true,
|
||||||
|
"static": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 165,
|
||||||
|
"y": 0,
|
||||||
|
"type": "grave",
|
||||||
|
"sound": "grave",
|
||||||
|
"static": true,
|
||||||
|
"item": "guts",
|
||||||
|
"zombie_spawn_chance": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "spider_web",
|
||||||
|
"x": 175,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x_range": [180, 200],
|
||||||
|
"y": 0,
|
||||||
|
"enemy_type": "witch",
|
||||||
|
"health": 8,
|
||||||
|
"damage": 2,
|
||||||
|
"attack_range": 1,
|
||||||
|
"attack_pattern": {
|
||||||
|
"type": "patrol"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x_range": [185, 188],
|
||||||
|
"y": 3,
|
||||||
|
"sound": "coin",
|
||||||
|
"collectible": true,
|
||||||
|
"static": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 195,
|
||||||
|
"y": 3,
|
||||||
|
"item": "hand_of_glory",
|
||||||
|
"sound": "coffin",
|
||||||
|
"type": "coffin"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x_range": [205, 225],
|
||||||
|
"y": 12,
|
||||||
|
"type": "skull_storm",
|
||||||
|
"damage": 4,
|
||||||
|
"maximum_skulls": 3,
|
||||||
|
"frequency": {
|
||||||
|
"min": 1,
|
||||||
|
"max": 3
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x_range": [210, 213],
|
||||||
|
"y": 3,
|
||||||
|
"sound": "coin",
|
||||||
|
"collectible": true,
|
||||||
|
"static": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "spider_web",
|
||||||
|
"x": 220,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 225,
|
||||||
|
"y": 0,
|
||||||
|
"type": "catapult",
|
||||||
|
"fire_interval": 4000,
|
||||||
|
"range": 20
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x_range": [230, 245],
|
||||||
|
"y": 0,
|
||||||
|
"enemy_type": "ghoul",
|
||||||
|
"health": 12,
|
||||||
|
"damage": 3,
|
||||||
|
"attack_range": 2,
|
||||||
|
"attack_pattern": {
|
||||||
|
"type": "hunter",
|
||||||
|
"turn_threshold": 4
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x_range": [235, 238],
|
||||||
|
"y": 3,
|
||||||
|
"sound": "coin",
|
||||||
|
"collectible": true,
|
||||||
|
"static": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 245,
|
||||||
|
"y": 3,
|
||||||
|
"sound": "coffin",
|
||||||
|
"type": "coffin"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"boundaries": {
|
||||||
|
"left": 0,
|
||||||
|
"right": 250
|
||||||
|
},
|
||||||
|
"footstep_sound": "footstep_tall_grass"
|
||||||
|
}
|
||||||
Submodule libstormgames updated: b479811a98...7cbbc64d27
BIN
sounds/get_cauldron.ogg
(Stored with Git LFS)
BIN
sounds/get_cauldron.ogg
(Stored with Git LFS)
Binary file not shown.
BIN
sounds/get_guts.ogg
(Stored with Git LFS)
BIN
sounds/get_guts.ogg
(Stored with Git LFS)
Binary file not shown.
BIN
sounds/hit_spiderweb.ogg
(Stored with Git LFS)
Normal file
BIN
sounds/hit_spiderweb.ogg
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
sounds/spider.ogg
(Stored with Git LFS)
Normal file
BIN
sounds/spider.ogg
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
sounds/spider_dies.ogg
(Stored with Git LFS)
Normal file
BIN
sounds/spider_dies.ogg
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
sounds/spiderweb.ogg
(Stored with Git LFS)
Normal file
BIN
sounds/spiderweb.ogg
(Stored with Git LFS)
Normal file
Binary file not shown.
@@ -41,6 +41,11 @@ class Enemy(Object):
|
|||||||
self.damage = level.player.get_max_health() # Instant death
|
self.damage = level.player.get_max_health() # Instant death
|
||||||
self.health = 1 # Easy to kill
|
self.health = 1 # Easy to kill
|
||||||
self.attackCooldown = 1500 # Slower attack rate
|
self.attackCooldown = 1500 # Slower attack rate
|
||||||
|
elif enemyType == "spider":
|
||||||
|
speedMultiplier = kwargs.get('speed_multiplier', 2.0)
|
||||||
|
self.movementSpeed *= speedMultiplier # Spiders are faster
|
||||||
|
self.attackPattern = {'type': 'hunter'} # Spiders actively hunt the player
|
||||||
|
self.turnThreshold = 3 # Spiders turn around quickly to chase player
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def xPos(self):
|
def xPos(self):
|
||||||
|
|||||||
161
src/level.py
161
src/level.py
@@ -95,6 +95,27 @@ class Level:
|
|||||||
item=obj.get("item", "random") # Get item type or default to random
|
item=obj.get("item", "random") # Get item type or default to random
|
||||||
)
|
)
|
||||||
self.objects.append(coffin)
|
self.objects.append(coffin)
|
||||||
|
# Check if this is a spider web
|
||||||
|
elif obj.get("type") == "spider_web":
|
||||||
|
# Check distance from graves
|
||||||
|
isValidPosition = True
|
||||||
|
for existingObj in self.objects:
|
||||||
|
if (existingObj.soundName == "grave" and
|
||||||
|
not hasattr(existingObj, 'graveItem')):
|
||||||
|
distance = abs(obj["x"] - existingObj.xPos)
|
||||||
|
if distance < 3:
|
||||||
|
isValidPosition = False
|
||||||
|
break
|
||||||
|
|
||||||
|
if isValidPosition:
|
||||||
|
web = Object(
|
||||||
|
obj["x"], # Just pass the single x value
|
||||||
|
obj["y"],
|
||||||
|
"spiderweb",
|
||||||
|
isStatic=True,
|
||||||
|
isCollectible=False,
|
||||||
|
)
|
||||||
|
self.objects.append(web)
|
||||||
# Check if this is an enemy
|
# Check if this is an enemy
|
||||||
elif "enemy_type" in obj:
|
elif "enemy_type" in obj:
|
||||||
enemy = Enemy(
|
enemy = Enemy(
|
||||||
@@ -229,7 +250,21 @@ class Level:
|
|||||||
|
|
||||||
if obj.hit(self.player.xPos):
|
if obj.hit(self.player.xPos):
|
||||||
self.bouncing_items.append(obj.dropped_item)
|
self.bouncing_items.append(obj.dropped_item)
|
||||||
#speak(f"{obj.dropped_item.soundName} falls out!")
|
|
||||||
|
def spawn_spider(self, xPos, yPos):
|
||||||
|
"""Spawn a spider at the given position"""
|
||||||
|
spider = Enemy(
|
||||||
|
[xPos - 5, xPos + 5], # Give spider a patrol range
|
||||||
|
yPos,
|
||||||
|
"spider",
|
||||||
|
self.sounds,
|
||||||
|
self,
|
||||||
|
health=8,
|
||||||
|
damage=8,
|
||||||
|
attack_range=1,
|
||||||
|
speed_multiplier=2.0
|
||||||
|
)
|
||||||
|
self.enemies.append(spider)
|
||||||
|
|
||||||
def handle_collisions(self):
|
def handle_collisions(self):
|
||||||
"""Handle all collision checks and return True if level is complete."""
|
"""Handle all collision checks and return True if level is complete."""
|
||||||
@@ -243,7 +278,7 @@ class Level:
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
# Handle grave edge warnings
|
# Handle grave edge warnings
|
||||||
if obj.isHazard:
|
if obj.isHazard and obj.soundName != "spiderweb": # Explicitly exclude spiderwebs
|
||||||
distance = abs(self.player.xPos - obj.xPos)
|
distance = abs(self.player.xPos - obj.xPos)
|
||||||
currentTime = pygame.time.get_ticks()
|
currentTime = pygame.time.get_ticks()
|
||||||
if (distance <= 2 and not self.player.isJumping and not self.player.isInvincible
|
if (distance <= 2 and not self.player.isJumping and not self.player.isInvincible
|
||||||
@@ -254,56 +289,80 @@ class Level:
|
|||||||
play_sound(self.sounds['edge'])
|
play_sound(self.sounds['edge'])
|
||||||
self.lastWarningTime = currentTime
|
self.lastWarningTime = currentTime
|
||||||
|
|
||||||
if obj.is_in_range(self.player.xPos):
|
if not obj.is_in_range(self.player.xPos):
|
||||||
if obj.isCollectible and self.player.isJumping:
|
continue
|
||||||
currentPos = round(self.player.xPos)
|
|
||||||
if currentPos not in obj.collectedPositions:
|
# Handle collectibles
|
||||||
play_sound(self.sounds[f'get_{obj.soundName}'])
|
if obj.isCollectible and self.player.isJumping:
|
||||||
obj.collect_at_position(currentPos)
|
currentPos = round(self.player.xPos)
|
||||||
self.player.collectedItems.append(obj.soundName)
|
if currentPos not in obj.collectedPositions:
|
||||||
|
play_sound(self.sounds[f'get_{obj.soundName}'])
|
||||||
|
obj.collect_at_position(currentPos)
|
||||||
|
self.player.collectedItems.append(obj.soundName)
|
||||||
|
self.player.stats.update_stat('Items collected', 1)
|
||||||
|
if obj.soundName == "coin":
|
||||||
|
self.player._coins += 1
|
||||||
|
self.player.stats.update_stat('Bone dust', 1)
|
||||||
|
if self.player._coins % 5 == 0:
|
||||||
|
# Only heal if below max health
|
||||||
|
if self.player.get_health() < self.player.get_max_health():
|
||||||
|
self.player.set_health(min(
|
||||||
|
self.player.get_health() + 1,
|
||||||
|
self.player.get_max_health()
|
||||||
|
))
|
||||||
|
|
||||||
|
if self.player._coins % 100 == 0:
|
||||||
|
# Extra life
|
||||||
|
self.player._coins = 0
|
||||||
|
self.player._lives += 1
|
||||||
|
play_sound(self.sounds['get_extra_life'])
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Handle spiderweb - this should trigger for both walking and jumping if not ducking
|
||||||
|
if obj.soundName == "spiderweb" and not self.player.isDucking:
|
||||||
|
# Create and apply web effect
|
||||||
|
webEffect = PowerUp(
|
||||||
|
obj.xPos,
|
||||||
|
obj.yPos,
|
||||||
|
'spiderweb',
|
||||||
|
self.sounds,
|
||||||
|
0 # No direction needed since it's just for effect
|
||||||
|
)
|
||||||
|
webEffect.level = self # Pass level reference for spider spawning
|
||||||
|
play_sound(self.sounds['hit_spiderweb'])
|
||||||
|
webEffect.apply_effect(self.player)
|
||||||
|
|
||||||
|
# Deactivate web
|
||||||
|
obj.isActive = False
|
||||||
|
obj.channel = obj_stop(obj.channel)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Handle graves and other hazards
|
||||||
|
if obj.isHazard and not self.player.isJumping:
|
||||||
|
if isinstance(obj, GraveObject):
|
||||||
|
can_collect = obj.collect_grave_item(self.player)
|
||||||
|
|
||||||
|
if can_collect:
|
||||||
|
# Successfully collected item while ducking
|
||||||
|
play_sound(self.sounds[f'get_{obj.graveItem}'])
|
||||||
self.player.stats.update_stat('Items collected', 1)
|
self.player.stats.update_stat('Items collected', 1)
|
||||||
if obj.soundName == "coin":
|
# Create PowerUp to handle the item effect
|
||||||
self.player._coins += 1
|
item = PowerUp(obj.xPos, obj.yPos, obj.graveItem, self.sounds, 1)
|
||||||
self.player.stats.update_stat('Bone dust', 1)
|
item.apply_effect(self.player)
|
||||||
if self.player._coins % 5 == 0:
|
# Stop grave's current audio channel
|
||||||
# Only heal if below max health
|
if obj.channel:
|
||||||
if self.player.get_health() < self.player.get_max_health():
|
obj_stop(obj.channel)
|
||||||
self.player.set_health(min(
|
# Remove the grave
|
||||||
self.player.get_health() + 1,
|
obj.graveItem = None
|
||||||
self.player.get_max_health()
|
obj.channel = None
|
||||||
))
|
obj.isActive = False # Mark the grave as inactive after collection
|
||||||
|
continue
|
||||||
if self.player._coins % 100 == 0:
|
elif not self.player.isInvincible:
|
||||||
# Extra life
|
# Kill player for normal graves or non-ducking collision
|
||||||
self.player._coins = 0
|
play_sound(self.sounds[obj.soundName])
|
||||||
self.player._lives += 1
|
speak("You fell in an open grave! Now, it's yours!")
|
||||||
play_sound(self.sounds['get_extra_life'])
|
self.player.set_health(0)
|
||||||
|
return False
|
||||||
elif obj.isHazard and not self.player.isJumping:
|
|
||||||
if isinstance(obj, GraveObject):
|
|
||||||
can_collect = obj.collect_grave_item(self.player)
|
|
||||||
|
|
||||||
if can_collect:
|
|
||||||
# Successfully collected item while ducking
|
|
||||||
play_sound(self.sounds[f'get_{obj.graveItem}'])
|
|
||||||
self.player.stats.update_stat('Items collected', 1)
|
|
||||||
# Create PowerUp to handle the item effect
|
|
||||||
item = PowerUp(obj.xPos, obj.yPos, obj.graveItem, self.sounds, 1)
|
|
||||||
item.apply_effect(self.player)
|
|
||||||
# Stop grave's current audio channel
|
|
||||||
if obj.channel:
|
|
||||||
obj_stop(obj.channel)
|
|
||||||
# Remove the grave
|
|
||||||
obj.graveItem = None
|
|
||||||
obj.channel = None
|
|
||||||
obj.isActive = False # Mark the grave as inactive after collection
|
|
||||||
continue
|
|
||||||
elif not self.player.isInvincible:
|
|
||||||
# Kill player for normal graves or non-ducking collision
|
|
||||||
play_sound(self.sounds[obj.soundName])
|
|
||||||
speak("You fell in an open grave! Now, it's yours!")
|
|
||||||
self.player.set_health(0)
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Handle boundaries
|
# Handle boundaries
|
||||||
if self.player.xPos < self.leftBoundary:
|
if self.player.xPos < self.leftBoundary:
|
||||||
|
|||||||
@@ -86,6 +86,13 @@ class Player:
|
|||||||
|
|
||||||
def update(self, currentTime):
|
def update(self, currentTime):
|
||||||
"""Update player state"""
|
"""Update player state"""
|
||||||
|
if hasattr(self, 'webPenaltyEndTime'):
|
||||||
|
if currentTime >= self.webPenaltyEndTime:
|
||||||
|
self.moveSpeed *= 2 # Restore speed
|
||||||
|
if self.currentWeapon:
|
||||||
|
self.currentWeapon.attackDuration *= 0.5 # Restore attack speed
|
||||||
|
del self.webPenaltyEndTime
|
||||||
|
|
||||||
# Check if invincibility has expired
|
# Check if invincibility has expired
|
||||||
if self.isInvincible and currentTime - self.invincibilityStartTime >= self.invincibilityDuration:
|
if self.isInvincible and currentTime - self.invincibilityStartTime >= self.invincibilityDuration:
|
||||||
self.isInvincible = False
|
self.isInvincible = False
|
||||||
|
|||||||
@@ -57,6 +57,22 @@ class PowerUp(Object):
|
|||||||
broomWeapon = Weapon.create_witch_broom()
|
broomWeapon = Weapon.create_witch_broom()
|
||||||
player.add_weapon(broomWeapon)
|
player.add_weapon(broomWeapon)
|
||||||
player.equip_weapon(broomWeapon)
|
player.equip_weapon(broomWeapon)
|
||||||
|
elif self.item_type == 'spiderweb':
|
||||||
|
# Bounce player back (happens even if invincible)
|
||||||
|
player.xPos -= 3 if player.xPos > self.xPos else -3
|
||||||
|
|
||||||
|
# Only apply debuffs if not invincible
|
||||||
|
if not player.isInvincible:
|
||||||
|
# Half speed and double attack time for 15 seconds
|
||||||
|
player.moveSpeed *= 0.5
|
||||||
|
if player.currentWeapon:
|
||||||
|
player.currentWeapon.attackDuration *= 2
|
||||||
|
# Set timer for penalty removal
|
||||||
|
player.webPenaltyEndTime = pygame.time.get_ticks() + 15000
|
||||||
|
|
||||||
|
# Tell level to spawn a spider
|
||||||
|
if hasattr(self, 'level'):
|
||||||
|
self.level.spawn_spider(self.xPos, self.yPos)
|
||||||
|
|
||||||
# Stop movement sound when collected
|
# Stop movement sound when collected
|
||||||
if self.channel:
|
if self.channel:
|
||||||
|
|||||||
Reference in New Issue
Block a user