Merge branch 'master' into opengl
This commit is contained in:
commit
8b026f5b84
5
.gitignore
vendored
5
.gitignore
vendored
@ -1,8 +1,9 @@
|
|||||||
# ignore debug
|
# 忽略构建内容
|
||||||
out/
|
out/
|
||||||
build/
|
build/
|
||||||
|
# 忽略调试内容
|
||||||
.vscode/
|
.vscode/
|
||||||
__pycache__/
|
__pycache__/
|
||||||
*/__pycache__/
|
*/__pycache__/
|
||||||
# ignore test
|
# 忽略测试文件
|
||||||
test.py
|
test.py
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
**本项目为个人python语言学习的练习项目,仅供个人学习和研究使用,不得用于其他用途。如果这个游戏侵犯了版权,请联系我删除**
|
**本项目为个人python语言学习的练习项目,仅供个人学习和研究使用,不得用于其他用途。如果这个游戏侵犯了版权,请联系我删除**
|
||||||
|
|
||||||
* 已有的植物:向日葵,豌豆射手,坚果墙,寒冰射手,樱桃炸弹,双发射手,三线射手,大嘴花,小喷菇,土豆雷,地刺,胆小菇,倭瓜,火爆辣椒,阳光菇,寒冰菇,魅惑菇,火炬树桩,睡莲,杨桃,咖啡豆,海蘑菇,高坚果,缠绕水草,毁灭菇,墓碑吞噬者,大喷菇
|
* 已有的植物:向日葵,豌豆射手,坚果墙,寒冰射手,樱桃炸弹,双发射手,三线射手,大嘴花,小喷菇,土豆雷,地刺,胆小菇,倭瓜,火爆辣椒,阳光菇,寒冰菇,魅惑菇,火炬树桩,睡莲,杨桃,咖啡豆,海蘑菇,高坚果,缠绕水草,毁灭菇,墓碑吞噬者,大喷菇,大蒜
|
||||||
* 已有的僵尸:普通僵尸,旗帜僵尸,路障僵尸,铁桶僵尸,读报僵尸,橄榄球僵尸,鸭子救生圈僵尸,铁门僵尸,撑杆跳僵尸,冰车僵尸,潜水僵尸
|
* 已有的僵尸:普通僵尸,旗帜僵尸,路障僵尸,铁桶僵尸,读报僵尸,橄榄球僵尸,鸭子救生圈僵尸,铁门僵尸,撑杆跳僵尸,冰车僵尸,潜水僵尸
|
||||||
* 使用 JSON 文件记录关卡信息数据
|
* 使用 JSON 文件记录关卡信息数据
|
||||||
* 支持选择植物卡片
|
* 支持选择植物卡片
|
||||||
@ -21,7 +21,7 @@
|
|||||||
|
|
||||||
## 环境要求
|
## 环境要求
|
||||||
|
|
||||||
* `Python` >= 3.7,最好使用最新版
|
* `Python` >= 3.10,最好使用最新版
|
||||||
* `Python-Pygame` >= 1.9,最好使用最新版
|
* `Python-Pygame` >= 1.9,最好使用最新版
|
||||||
|
|
||||||
## 开始游戏
|
## 开始游戏
|
||||||
@ -44,7 +44,6 @@ python main.py
|
|||||||
- 程序包含名称、版本等信息
|
- 程序包含名称、版本等信息
|
||||||
- 得到的验证最多(相对)
|
- 得到的验证最多(相对)
|
||||||
- 并非每次提交都会更新,更新可能不及时
|
- 并非每次提交都会更新,更新可能不及时
|
||||||
- `0.7.25.0`之后的部分版本被标注成了`pre-release`,实际上仍然为普通版本,按需下载即可
|
|
||||||
- 也可以直接下载GitHub Workflow[自动利用Nuitka构建的版本(点击跳转)](https://github.com/wszqkzqk/pypvz/releases/tag/Latest)(推荐):
|
- 也可以直接下载GitHub Workflow[自动利用Nuitka构建的版本(点击跳转)](https://github.com/wszqkzqk/pypvz/releases/tag/Latest)(推荐):
|
||||||
- 使用MSVC编译
|
- 使用MSVC编译
|
||||||
- 每次提交均会更新,保证更新及时
|
- 每次提交均会更新,保证更新及时
|
||||||
@ -111,6 +110,7 @@ nuitka --mingw64 --standalone `
|
|||||||
--include-data-file=C:\Users\17265\AppData\Local\Programs\Python\Python310\Lib\site-packages\pygame\libopusfile-0.dll=libopusfile-0.dll `
|
--include-data-file=C:\Users\17265\AppData\Local\Programs\Python\Python310\Lib\site-packages\pygame\libopusfile-0.dll=libopusfile-0.dll `
|
||||||
--include-data-file=C:\Users\17265\AppData\Local\Programs\Python\Python310\Lib\site-packages\pygame\libvorbisfile-3.dll=libvorbisfile-3.dll `
|
--include-data-file=C:\Users\17265\AppData\Local\Programs\Python\Python310\Lib\site-packages\pygame\libvorbisfile-3.dll=libvorbisfile-3.dll `
|
||||||
--include-data-file=C:\Users\17265\AppData\Local\Programs\Python\Python310\Lib\site-packages\pygame\libvorbis-0.dll=libvorbis-0.dll `
|
--include-data-file=C:\Users\17265\AppData\Local\Programs\Python\Python310\Lib\site-packages\pygame\libvorbis-0.dll=libvorbis-0.dll `
|
||||||
|
--lto=yes `
|
||||||
--windows-disable-console `
|
--windows-disable-console `
|
||||||
--windows-product-name=pypvz `
|
--windows-product-name=pypvz `
|
||||||
--windows-company-name=null `
|
--windows-company-name=null `
|
||||||
@ -122,6 +122,7 @@ nuitka --mingw64 --standalone `
|
|||||||
* 其中`C:\Users\17265\AppData\Local\Programs\Python\Python310\Lib\site-packages\pygame\xxx.dll`应当替换为`xxx.dll`实际所在路径
|
* 其中`C:\Users\17265\AppData\Local\Programs\Python\Python310\Lib\site-packages\pygame\xxx.dll`应当替换为`xxx.dll`实际所在路径
|
||||||
* 由于仅复制了`opus`与`vorbis`的解码器,故要求所有背景音乐都要以opus或vorbis编码
|
* 由于仅复制了`opus`与`vorbis`的解码器,故要求所有背景音乐都要以opus或vorbis编码
|
||||||
* `--windows-product-version=`表示版本号信息,所跟内容格式必须为`x.x.x.x`
|
* `--windows-product-version=`表示版本号信息,所跟内容格式必须为`x.x.x.x`
|
||||||
|
* 建议开启`--lto=yes`选项优化链接,如果编译失败可以关闭此选项
|
||||||
|
|
||||||
可执行文件生成路径为`./out/main.exe`
|
可执行文件生成路径为`./out/main.exe`
|
||||||
|
|
||||||
|
|||||||
@ -2,38 +2,36 @@ import random
|
|||||||
import pygame as pg
|
import pygame as pg
|
||||||
from .. import tool
|
from .. import tool
|
||||||
from .. import constants as c
|
from .. import constants as c
|
||||||
from copy import deepcopy
|
|
||||||
|
|
||||||
class Map():
|
class Map():
|
||||||
def __init__(self, background_type):
|
def __init__(self, background_type):
|
||||||
self.background_type = background_type
|
self.background_type = background_type
|
||||||
# 注意:从0开始编号
|
# 注意:从0开始编号
|
||||||
# 集合内容需要deepcopy
|
|
||||||
if self.background_type in c.POOL_EQUIPPED_BACKGROUNDS:
|
if self.background_type in c.POOL_EQUIPPED_BACKGROUNDS:
|
||||||
self.width = c.GRID_POOL_X_LEN
|
self.width = c.GRID_POOL_X_LEN
|
||||||
self.height = c.GRID_POOL_Y_LEN
|
self.height = c.GRID_POOL_Y_LEN
|
||||||
self.gridHeightSize = c.GRID_POOL_Y_SIZE
|
self.gridHeightSize = c.GRID_POOL_Y_SIZE
|
||||||
self.map = [[(deepcopy(c.MAP_STATE_EMPTY), deepcopy(c.MAP_STATE_WATER))[y in {2, 3}] for x in range(self.width)] for y in range(self.height)]
|
self.map = [[(c.INIT_MAP_GRID(c.MAP_GRASS), c.INIT_MAP_GRID(c.MAP_WATER))[y in {2, 3}] for x in range(self.width)] for y in range(self.height)]
|
||||||
elif self.background_type in c.ON_ROOF_BACKGROUNDS:
|
elif self.background_type in c.ON_ROOF_BACKGROUNDS:
|
||||||
self.width = c.GRID_ROOF_X_LEN
|
self.width = c.GRID_ROOF_X_LEN
|
||||||
self.height = c.GRID_ROOF_Y_LEN
|
self.height = c.GRID_ROOF_Y_LEN
|
||||||
self.gridHeightSize = c.GRID_ROOF_Y_SIZE
|
self.gridHeightSize = c.GRID_ROOF_Y_SIZE
|
||||||
self.map = [[deepcopy(c.MAP_STATE_TILE) for x in range(self.width)] for y in range(self.height)]
|
self.map = [[c.INIT_MAP_GRID(c.MAP_TILE) for x in range(self.width)] for y in range(self.height)]
|
||||||
elif self.background_type == c.BACKGROUND_SINGLE:
|
elif self.background_type == c.BACKGROUND_SINGLE:
|
||||||
self.width = c.GRID_X_LEN
|
self.width = c.GRID_X_LEN
|
||||||
self.height = c.GRID_Y_LEN
|
self.height = c.GRID_Y_LEN
|
||||||
self.gridHeightSize = c.GRID_Y_SIZE
|
self.gridHeightSize = c.GRID_Y_SIZE
|
||||||
self.map = [[(deepcopy(c.MAP_STATE_UNAVAILABLE), deepcopy(c.MAP_STATE_EMPTY))[y == 2] for x in range(self.width)] for y in range(self.height)]
|
self.map = [[(c.INIT_MAP_GRID(c.MAP_UNAVAILABLE), c.INIT_MAP_GRID(c.MAP_GRASS))[y == 2] for x in range(self.width)] for y in range(self.height)]
|
||||||
elif self.background_type == c.BACKGROUND_TRIPLE:
|
elif self.background_type == c.BACKGROUND_TRIPLE:
|
||||||
self.width = c.GRID_X_LEN
|
self.width = c.GRID_X_LEN
|
||||||
self.height = c.GRID_Y_LEN
|
self.height = c.GRID_Y_LEN
|
||||||
self.gridHeightSize = c.GRID_Y_SIZE
|
self.gridHeightSize = c.GRID_Y_SIZE
|
||||||
self.map = [[(deepcopy(c.MAP_STATE_UNAVAILABLE), deepcopy(c.MAP_STATE_EMPTY))[y in {1, 2, 3}] for x in range(self.width)] for y in range(self.height)]
|
self.map = [[(c.INIT_MAP_GRID(c.MAP_UNAVAILABLE), c.INIT_MAP_GRID(c.MAP_GRASS))[y in {1, 2, 3}] for x in range(self.width)] for y in range(self.height)]
|
||||||
else:
|
else:
|
||||||
self.width = c.GRID_X_LEN
|
self.width = c.GRID_X_LEN
|
||||||
self.height = c.GRID_Y_LEN
|
self.height = c.GRID_Y_LEN
|
||||||
self.gridHeightSize = c.GRID_Y_SIZE
|
self.gridHeightSize = c.GRID_Y_SIZE
|
||||||
self.map = [[deepcopy(c.MAP_STATE_EMPTY) for x in range(self.width)] for y in range(self.height)]
|
self.map = [[c.INIT_MAP_GRID(c.MAP_GRASS) for x in range(self.width)] for y in range(self.height)]
|
||||||
|
|
||||||
def isValid(self, map_x, map_y):
|
def isValid(self, map_x, map_y):
|
||||||
if (map_x < 0 or map_x >= self.width or
|
if (map_x < 0 or map_x >= self.width or
|
||||||
@ -139,7 +137,7 @@ class Map():
|
|||||||
map_y * c.GRID_Y_SIZE + c.GRID_Y_SIZE//5 * 3 + c.MAP_OFFSET_Y)
|
map_y * c.GRID_Y_SIZE + c.GRID_Y_SIZE//5 * 3 + c.MAP_OFFSET_Y)
|
||||||
|
|
||||||
def setMapGridType(self, map_x, map_y, plot_type):
|
def setMapGridType(self, map_x, map_y, plot_type):
|
||||||
self.map[map_y][map_x] = plot_type
|
self.map[map_y][map_x][c.MAP_PLOT_TYPE] = plot_type
|
||||||
|
|
||||||
def addMapPlant(self, map_x, map_y, plantName, sleep=False):
|
def addMapPlant(self, map_x, map_y, plantName, sleep=False):
|
||||||
self.map[map_y][map_x][c.MAP_PLANT].add(plantName)
|
self.map[map_y][map_x][c.MAP_PLANT].add(plantName)
|
||||||
|
|||||||
@ -38,7 +38,7 @@ class Car(pg.sprite.Sprite):
|
|||||||
|
|
||||||
# 豌豆及孢子类普通子弹
|
# 豌豆及孢子类普通子弹
|
||||||
class Bullet(pg.sprite.Sprite):
|
class Bullet(pg.sprite.Sprite):
|
||||||
def __init__(self, x, start_y, dest_y, name, damage, effect=None, passedTorchWood=None):
|
def __init__(self, x, start_y, dest_y, name, damage, effect=None, passedTorchWood=None, damageType=c.ZOMBIE_DEAFULT_DAMAGE):
|
||||||
pg.sprite.Sprite.__init__(self)
|
pg.sprite.Sprite.__init__(self)
|
||||||
|
|
||||||
self.name = name
|
self.name = name
|
||||||
@ -55,6 +55,7 @@ class Bullet(pg.sprite.Sprite):
|
|||||||
self.y_vel = 15 if (dest_y > start_y) else -15
|
self.y_vel = 15 if (dest_y > start_y) else -15
|
||||||
self.x_vel = 10
|
self.x_vel = 10
|
||||||
self.damage = damage
|
self.damage = damage
|
||||||
|
self.damageType = damageType
|
||||||
self.effect = effect
|
self.effect = effect
|
||||||
self.state = c.FLY
|
self.state = c.FLY
|
||||||
self.current_time = 0
|
self.current_time = 0
|
||||||
@ -182,11 +183,11 @@ class Fume(pg.sprite.Sprite):
|
|||||||
|
|
||||||
# 杨桃的子弹
|
# 杨桃的子弹
|
||||||
class StarBullet(Bullet):
|
class StarBullet(Bullet):
|
||||||
def __init__(self, x, start_y, damage, direction, level): # direction指星星飞行方向
|
def __init__(self, x, start_y, damage, direction, level, damageType = c.ZOMBIE_DEAFULT_DAMAGE): # direction指星星飞行方向
|
||||||
Bullet.__init__(self, x, start_y, start_y, c.BULLET_STAR, damage)
|
Bullet.__init__(self, x, start_y, start_y, c.BULLET_STAR, damage, damageType = damageType)
|
||||||
|
|
||||||
self.level = level
|
self.level = level
|
||||||
_, self.map_y = self.level.map.getMapIndex(self.rect.x, self.rect.centery)
|
self.map_y = self.level.map.getMapIndex(self.rect.x, self.rect.centery)[1]
|
||||||
self.direction = direction
|
self.direction = direction
|
||||||
|
|
||||||
def update(self, game_info):
|
def update(self, game_info):
|
||||||
@ -206,20 +207,19 @@ class StarBullet(Bullet):
|
|||||||
else:
|
else:
|
||||||
self.rect.x -= 10
|
self.rect.x -= 10
|
||||||
self.handleMapYPosition()
|
self.handleMapYPosition()
|
||||||
if ((self.rect.x > c.SCREEN_WIDTH + 60) or (self.rect.x < -40)
|
if ((self.rect.x > c.SCREEN_WIDTH + 60) or (self.rect.x < -60)
|
||||||
or (self.rect.y > c.SCREEN_HEIGHT) or (self.rect.y < 0)):
|
or (self.rect.y > c.SCREEN_HEIGHT) or (self.rect.y < 0)):
|
||||||
self.kill()
|
self.kill()
|
||||||
elif self.state == c.EXPLODE:
|
elif self.state == c.EXPLODE:
|
||||||
if (self.current_time - self.explode_timer) > 250:
|
if (self.current_time - self.explode_timer) >= 250:
|
||||||
self.kill()
|
self.kill()
|
||||||
|
|
||||||
# 这里用的是坚果保龄球的代码改一下,实现子弹换行
|
# 这里用的是坚果保龄球的代码改一下,实现子弹换行
|
||||||
def handleMapYPosition(self):
|
def handleMapYPosition(self):
|
||||||
if self.direction == c.STAR_UPWARD:
|
if self.direction == c.STAR_UPWARD:
|
||||||
_, map_y1 = self.level.map.getMapIndex(self.rect.x, self.rect.centery + 40)
|
map_y1 = self.level.map.getMapIndex(self.rect.x, self.rect.centery + 40)[1]
|
||||||
else:
|
else:
|
||||||
_, map_y1 = self.level.map.getMapIndex(self.rect.x, self.rect.centery + 20)
|
map_y1 = self.level.map.getMapIndex(self.rect.x, self.rect.centery + 20)[1]
|
||||||
# _, map_y2 = self.level.map.getMapIndex(self.rect.x, self.rect.bottom +20)
|
|
||||||
if (self.map_y != map_y1) and (0 <= map_y1 <= self.level.map_y_len-1): # 换行
|
if (self.map_y != map_y1) and (0 <= map_y1 <= self.level.map_y_len-1): # 换行
|
||||||
self.level.bullet_groups[self.map_y].remove(self)
|
self.level.bullet_groups[self.map_y].remove(self)
|
||||||
self.level.bullet_groups[map_y1].add(self)
|
self.level.bullet_groups[map_y1].add(self)
|
||||||
@ -1160,7 +1160,7 @@ class WallNutBowling(Plant):
|
|||||||
self.handleMapYPosition()
|
self.handleMapYPosition()
|
||||||
if self.shouldChangeDirection():
|
if self.shouldChangeDirection():
|
||||||
self.changeDirection(-1)
|
self.changeDirection(-1)
|
||||||
if self.init_rect.x > c.SCREEN_WIDTH:
|
if self.init_rect.x > c.SCREEN_WIDTH + 60:
|
||||||
self.health = 0
|
self.health = 0
|
||||||
self.move_timer += self.move_interval
|
self.move_timer += self.move_interval
|
||||||
|
|
||||||
@ -1170,8 +1170,8 @@ class WallNutBowling(Plant):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def handleMapYPosition(self):
|
def handleMapYPosition(self):
|
||||||
_, map_y1 = self.level.map.getMapIndex(self.init_rect.x, self.init_rect.centery)
|
map_y1 = self.level.map.getMapIndex(self.init_rect.x, self.init_rect.centery)[1]
|
||||||
_, map_y2 = self.level.map.getMapIndex(self.init_rect.x, self.init_rect.bottom)
|
map_y2 = self.level.map.getMapIndex(self.init_rect.x, self.init_rect.bottom)[1]
|
||||||
if self.map_y != map_y1 and map_y1 == map_y2:
|
if self.map_y != map_y1 and map_y1 == map_y2:
|
||||||
# wallnut bowls to another row, should modify which plant group it belongs to
|
# wallnut bowls to another row, should modify which plant group it belongs to
|
||||||
self.level.plant_groups[self.map_y].remove(self)
|
self.level.plant_groups[self.map_y].remove(self)
|
||||||
@ -1252,7 +1252,7 @@ class RedWallNutBowling(Plant):
|
|||||||
elif (self.current_time - self.move_timer) >= self.move_interval:
|
elif (self.current_time - self.move_timer) >= self.move_interval:
|
||||||
self.rotate_degree = (self.rotate_degree - 30) % 360
|
self.rotate_degree = (self.rotate_degree - 30) % 360
|
||||||
self.init_rect.x += self.vel_x
|
self.init_rect.x += self.vel_x
|
||||||
if self.init_rect.x > c.SCREEN_WIDTH:
|
if self.init_rect.x > c.SCREEN_WIDTH + 60:
|
||||||
self.health = 0
|
self.health = 0
|
||||||
self.move_timer += self.move_interval
|
self.move_timer += self.move_interval
|
||||||
|
|
||||||
@ -1319,7 +1319,7 @@ class StarFruit(Plant):
|
|||||||
if (zombie.name == c.SNORKELZOMBIE) and (zombie.frames == zombie.swim_frames):
|
if (zombie.name == c.SNORKELZOMBIE) and (zombie.frames == zombie.swim_frames):
|
||||||
return False
|
return False
|
||||||
if zombie.state != c.DIE:
|
if zombie.state != c.DIE:
|
||||||
_, zombieMapY = self.level.map.getMapIndex(zombie.rect.centerx, zombie.rect.bottom)
|
zombieMapY = self.level.map.getMapIndex(zombie.rect.centerx, zombie.rect.bottom)[1]
|
||||||
if (self.rect.x >= zombie.rect.x) and (self.map_y == zombieMapY): # 对于同行且在杨桃后的僵尸
|
if (self.rect.x >= zombie.rect.x) and (self.map_y == zombieMapY): # 对于同行且在杨桃后的僵尸
|
||||||
return True
|
return True
|
||||||
# 斜向上,理想直线方程为:f(zombie.rect.x) = -0.75*(zombie.rect.x - (self.rect.right - 5)) + self.rect.y - 10
|
# 斜向上,理想直线方程为:f(zombie.rect.x) = -0.75*(zombie.rect.x - (self.rect.right - 5)) + self.rect.y - 10
|
||||||
@ -1338,7 +1338,9 @@ class StarFruit(Plant):
|
|||||||
if self.shoot_timer == 0:
|
if self.shoot_timer == 0:
|
||||||
self.shoot_timer = self.current_time - 700
|
self.shoot_timer = self.current_time - 700
|
||||||
elif (self.current_time - self.shoot_timer) >= 1400:
|
elif (self.current_time - self.shoot_timer) >= 1400:
|
||||||
self.bullet_group.add(StarBullet(self.rect.left - 10, self.rect.y + 15, c.BULLET_DAMAGE_NORMAL, c.STAR_BACKWARD, self.level))
|
# 向后打的杨桃子弹无视铁门与报纸防具
|
||||||
|
self.bullet_group.add(StarBullet(self.rect.left - 10, self.rect.y + 15, c.BULLET_DAMAGE_NORMAL, c.STAR_BACKWARD, self.level, damageType = c.ZOMBIE_COMMON_DAMAGE))
|
||||||
|
# 其他方向的杨桃子弹伤害效果与豌豆等同
|
||||||
self.bullet_group.add(StarBullet(self.rect.centerx - 20, self.rect.bottom - self.rect.h - 15, c.BULLET_DAMAGE_NORMAL, c.STAR_UPWARD, self.level))
|
self.bullet_group.add(StarBullet(self.rect.centerx - 20, self.rect.bottom - self.rect.h - 15, c.BULLET_DAMAGE_NORMAL, c.STAR_UPWARD, self.level))
|
||||||
self.bullet_group.add(StarBullet(self.rect.centerx - 20, self.rect.bottom - 5, c.BULLET_DAMAGE_NORMAL, c.STAR_DOWNWARD, self.level))
|
self.bullet_group.add(StarBullet(self.rect.centerx - 20, self.rect.bottom - 5, c.BULLET_DAMAGE_NORMAL, c.STAR_DOWNWARD, self.level))
|
||||||
self.bullet_group.add(StarBullet(self.rect.right - 5, self.rect.bottom - 20, c.BULLET_DAMAGE_NORMAL, c.STAR_FORWARD_DOWN, self.level))
|
self.bullet_group.add(StarBullet(self.rect.right - 5, self.rect.bottom - 20, c.BULLET_DAMAGE_NORMAL, c.STAR_FORWARD_DOWN, self.level))
|
||||||
@ -1589,6 +1591,7 @@ class DoomShroom(Plant):
|
|||||||
else:
|
else:
|
||||||
self.image.set_alpha(255)
|
self.image.set_alpha(255)
|
||||||
|
|
||||||
|
|
||||||
# 用于描述毁灭菇的坑
|
# 用于描述毁灭菇的坑
|
||||||
class Hole(Plant):
|
class Hole(Plant):
|
||||||
def __init__(self, x, y, plotType):
|
def __init__(self, x, y, plotType):
|
||||||
@ -1770,7 +1773,7 @@ class FumeShroom(Plant):
|
|||||||
|
|
||||||
class IceFrozenPlot(Plant):
|
class IceFrozenPlot(Plant):
|
||||||
def __init__(self, x, y):
|
def __init__(self, x, y):
|
||||||
Plant.__init__(self, x, y, c.ICE_FROZEN_PLOT, c.INF, None)
|
Plant.__init__(self, x, y, c.ICEFROZENPLOT, c.INF, None)
|
||||||
self.timer = 0
|
self.timer = 0
|
||||||
|
|
||||||
def idling(self):
|
def idling(self):
|
||||||
|
|||||||
@ -196,8 +196,8 @@ class Zombie(pg.sprite.Sprite):
|
|||||||
self.changeFrames(self.walk_frames)
|
self.changeFrames(self.walk_frames)
|
||||||
self.helmetType2 = False
|
self.helmetType2 = False
|
||||||
|
|
||||||
if ((self.current_time - self.walk_timer) > (c.ZOMBIE_WALK_INTERVAL * self.getTimeRatio())
|
if (self.current_time - self.walk_timer) > (c.ZOMBIE_WALK_INTERVAL * self.getTimeRatio()):
|
||||||
and self.handleGarlicYChange()):
|
self.handleGarlicYChange()
|
||||||
self.walk_timer = self.current_time
|
self.walk_timer = self.current_time
|
||||||
if self.is_hypno:
|
if self.is_hypno:
|
||||||
self.rect.x += 1
|
self.rect.x += 1
|
||||||
@ -206,7 +206,6 @@ class Zombie(pg.sprite.Sprite):
|
|||||||
|
|
||||||
def handleGarlicYChange(self):
|
def handleGarlicYChange(self):
|
||||||
if self.targetYChange < 0:
|
if self.targetYChange < 0:
|
||||||
self.setWalk()
|
|
||||||
if self.rect.bottom > self.originalY + self.targetYChange: # 注意这里加的是负数
|
if self.rect.bottom > self.originalY + self.targetYChange: # 注意这里加的是负数
|
||||||
self.rect.bottom -= 3
|
self.rect.bottom -= 3
|
||||||
# 过半时换行
|
# 过半时换行
|
||||||
@ -214,12 +213,12 @@ class Zombie(pg.sprite.Sprite):
|
|||||||
(self.rect.bottom >= self.originalY + 0.5*self.targetYChange)):
|
(self.rect.bottom >= self.originalY + 0.5*self.targetYChange)):
|
||||||
self.level.zombie_groups[self.mapY].remove(self)
|
self.level.zombie_groups[self.mapY].remove(self)
|
||||||
self.level.zombie_groups[self.targetMapY].add(self)
|
self.level.zombie_groups[self.targetMapY].add(self)
|
||||||
|
self.toChangeGroup = False
|
||||||
else:
|
else:
|
||||||
self.rect.bottom = self.originalY + self.targetYChange
|
self.rect.bottom = self.originalY + self.targetYChange
|
||||||
|
self.originalY = self.rect.bottom
|
||||||
self.targetYChange = 0
|
self.targetYChange = 0
|
||||||
return None
|
|
||||||
elif self.targetYChange > 0:
|
elif self.targetYChange > 0:
|
||||||
self.setWalk()
|
|
||||||
if self.rect.bottom < self.originalY + self.targetYChange: # 注意这里加的是负数
|
if self.rect.bottom < self.originalY + self.targetYChange: # 注意这里加的是负数
|
||||||
self.rect.bottom += 3
|
self.rect.bottom += 3
|
||||||
# 过半时换行
|
# 过半时换行
|
||||||
@ -227,12 +226,11 @@ class Zombie(pg.sprite.Sprite):
|
|||||||
(self.rect.bottom <= self.originalY + 0.5*self.targetYChange)):
|
(self.rect.bottom <= self.originalY + 0.5*self.targetYChange)):
|
||||||
self.level.zombie_groups[self.mapY].remove(self)
|
self.level.zombie_groups[self.mapY].remove(self)
|
||||||
self.level.zombie_groups[self.targetMapY].add(self)
|
self.level.zombie_groups[self.targetMapY].add(self)
|
||||||
|
self.toChangeGroup = False
|
||||||
else:
|
else:
|
||||||
self.rect.bottom = self.originalY + self.targetYChange
|
self.rect.bottom = self.originalY + self.targetYChange
|
||||||
|
self.originalY = self.rect.bottom
|
||||||
self.targetYChange = 0
|
self.targetYChange = 0
|
||||||
return None
|
|
||||||
else:
|
|
||||||
return True
|
|
||||||
|
|
||||||
def attacking(self):
|
def attacking(self):
|
||||||
if self.checkToDie(self.losthead_attack_frames):
|
if self.checkToDie(self.losthead_attack_frames):
|
||||||
@ -435,7 +433,7 @@ class Zombie(pg.sprite.Sprite):
|
|||||||
self.health -= damage
|
self.health -= damage
|
||||||
else:
|
else:
|
||||||
print('警告:植物攻击类型错误,现在默认进行类豌豆射手型攻击')
|
print('警告:植物攻击类型错误,现在默认进行类豌豆射手型攻击')
|
||||||
setDamage(damage, effect=effect, damageType=c.ZOMBIE_DEAFULT_DAMAGE)
|
self.setDamage(damage, effect=effect, damageType=c.ZOMBIE_DEAFULT_DAMAGE)
|
||||||
|
|
||||||
# 记录攻击时间
|
# 记录攻击时间
|
||||||
self.hit_timer = self.current_time
|
self.hit_timer = self.current_time
|
||||||
@ -715,8 +713,8 @@ class NewspaperZombie(Zombie):
|
|||||||
self.helmetType2 = False
|
self.helmetType2 = False
|
||||||
# 触发报纸撕裂音效
|
# 触发报纸撕裂音效
|
||||||
pg.mixer.Sound(os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))) ,"resources", "sound", "newspaperRip.ogg")).play()
|
pg.mixer.Sound(os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))) ,"resources", "sound", "newspaperRip.ogg")).play()
|
||||||
if ((self.current_time - self.walk_timer) > (c.ZOMBIE_WALK_INTERVAL * self.getTimeRatio())
|
if ((self.current_time - self.walk_timer) > (c.ZOMBIE_WALK_INTERVAL * self.getTimeRatio())):
|
||||||
and self.handleGarlicYChange()):
|
self.handleGarlicYChange()
|
||||||
self.walk_timer = self.current_time
|
self.walk_timer = self.current_time
|
||||||
if self.frames == self.lostnewspaper_frames:
|
if self.frames == self.lostnewspaper_frames:
|
||||||
pass
|
pass
|
||||||
@ -1116,10 +1114,10 @@ class Zomboni(Zombie):
|
|||||||
# 造冰
|
# 造冰
|
||||||
mapX, mapY = self.map.getMapIndex(self.rect.right - 40, self.rect.bottom)
|
mapX, mapY = self.map.getMapIndex(self.rect.right - 40, self.rect.bottom)
|
||||||
if 0 <= mapX < c.GRID_X_LEN:
|
if 0 <= mapX < c.GRID_X_LEN:
|
||||||
if c.ICE_FROZEN_PLOT not in self.map.map[mapY][mapX]:
|
if c.ICEFROZENPLOT not in self.map.map[mapY][mapX]:
|
||||||
x, y = self.map.getMapGridPos(mapX, mapY)
|
x, y = self.map.getMapGridPos(mapX, mapY)
|
||||||
self.plant_group.add(self.IceFrozenPlot(x, y))
|
self.plant_group.add(self.IceFrozenPlot(x, y))
|
||||||
self.map.map[mapY][mapX][c.MAP_PLANT].add(c.ICE_FROZEN_PLOT)
|
self.map.map[mapY][mapX][c.MAP_PLANT].add(c.ICEFROZENPLOT)
|
||||||
|
|
||||||
self.speed = max(0.6, 1.5 - (c.GRID_X_LEN + 1 - mapX)*0.225)
|
self.speed = max(0.6, 1.5 - (c.GRID_X_LEN + 1 - mapX)*0.225)
|
||||||
|
|
||||||
@ -1178,6 +1176,9 @@ class SnorkelZombie(Zombie):
|
|||||||
self.frames = self.walk_frames
|
self.frames = self.walk_frames
|
||||||
|
|
||||||
def walking(self):
|
def walking(self):
|
||||||
|
if self.checkToDie(self.losthead_walk_frames):
|
||||||
|
return
|
||||||
|
|
||||||
# 在水池范围内
|
# 在水池范围内
|
||||||
# 在右侧岸左
|
# 在右侧岸左
|
||||||
if self.rect.centerx <= c.MAP_POOL_FRONT_X - 25:
|
if self.rect.centerx <= c.MAP_POOL_FRONT_X - 25:
|
||||||
@ -1199,8 +1200,8 @@ class SnorkelZombie(Zombie):
|
|||||||
if self.swimming:
|
if self.swimming:
|
||||||
self.changeFrames(self.walk_frames)
|
self.changeFrames(self.walk_frames)
|
||||||
self.swimming = False
|
self.swimming = False
|
||||||
if ((self.current_time - self.walk_timer) > (c.ZOMBIE_WALK_INTERVAL * self.getTimeRatio())
|
if (self.current_time - self.walk_timer) > (c.ZOMBIE_WALK_INTERVAL * self.getTimeRatio()):
|
||||||
and self.handleGarlicYChange()):
|
self.handleGarlicYChange()
|
||||||
self.walk_timer = self.current_time
|
self.walk_timer = self.current_time
|
||||||
# 正在上浮或者下潜不用移动
|
# 正在上浮或者下潜不用移动
|
||||||
if (self.frames == self.float_frames) or (self.frames == self.sink_frames):
|
if (self.frames == self.float_frames) or (self.frames == self.sink_frames):
|
||||||
|
|||||||
@ -136,7 +136,7 @@ BACKGROUND_DAY_LIKE_BACKGROUNDS = {
|
|||||||
|
|
||||||
# 夜晚地图的墓碑数量等级
|
# 夜晚地图的墓碑数量等级
|
||||||
GRADE_GRAVES = 'grade_graves'
|
GRADE_GRAVES = 'grade_graves'
|
||||||
# 不同墓碑等级对应的信息
|
# 不同墓碑等级对应的信息,列表位置对应的是墓碑等级
|
||||||
GRAVES_GRADE_INFO = (0, 4, 7, 11)
|
GRAVES_GRADE_INFO = (0, 4, 7, 11)
|
||||||
|
|
||||||
# 僵尸生成方式
|
# 僵尸生成方式
|
||||||
@ -158,10 +158,11 @@ MAP_WATER = 'water'
|
|||||||
MAP_TILE = 'tile' # 指屋顶上的瓦片
|
MAP_TILE = 'tile' # 指屋顶上的瓦片
|
||||||
MAP_UNAVAILABLE = 'unavailable' # 指完全不能种植物的地方,包括无草皮的荒地和坚果保龄球等红线右侧
|
MAP_UNAVAILABLE = 'unavailable' # 指完全不能种植物的地方,包括无草皮的荒地和坚果保龄球等红线右侧
|
||||||
# 地图单元格状态
|
# 地图单元格状态
|
||||||
MAP_STATE_EMPTY = {MAP_PLANT:set(), MAP_SLEEP:False, MAP_PLOT_TYPE:MAP_GRASS} # 由于同一格显然不可能种两个相同的植物,所以用集合
|
# 注意是可变对象,不能直接引用
|
||||||
MAP_STATE_WATER = {MAP_PLANT:set(), MAP_SLEEP:False, MAP_PLOT_TYPE:MAP_WATER}
|
# 不喜欢用深拷贝,直接改用函数表示,需要时直接调用该函数生成即可
|
||||||
MAP_STATE_TILE = {MAP_PLANT:set(), MAP_SLEEP:False, MAP_PLOT_TYPE:MAP_TILE}
|
# 由于同一格显然不可能种两个相同的植物,所以用集合
|
||||||
MAP_STATE_UNAVAILABLE = {MAP_PLANT:set(), MAP_SLEEP:False, MAP_PLOT_TYPE:MAP_UNAVAILABLE}
|
def INIT_MAP_GRID(PLOT_TYPE):
|
||||||
|
return {MAP_PLANT:set(), MAP_SLEEP:False, MAP_PLOT_TYPE:PLOT_TYPE}
|
||||||
|
|
||||||
# 地图相关像素数据
|
# 地图相关像素数据
|
||||||
BACKGROUND_OFFSET_X = 220
|
BACKGROUND_OFFSET_X = 220
|
||||||
@ -193,7 +194,7 @@ PANEL_Y_INTERNAL = 69
|
|||||||
PANEL_X_INTERNAL = 53
|
PANEL_X_INTERNAL = 53
|
||||||
BAR_CARD_X_INTERNAL = 51
|
BAR_CARD_X_INTERNAL = 51
|
||||||
|
|
||||||
# 所选植物信息索引
|
# 植物卡片信息索引
|
||||||
PLANT_NAME_INDEX = 0
|
PLANT_NAME_INDEX = 0
|
||||||
CARD_INDEX = 1
|
CARD_INDEX = 1
|
||||||
SUN_INDEX = 2
|
SUN_INDEX = 2
|
||||||
@ -237,7 +238,7 @@ SEASHROOM = 'SeaShroom'
|
|||||||
TALLNUT = 'TallNut'
|
TALLNUT = 'TallNut'
|
||||||
TANGLEKLEP = 'TangleKlep'
|
TANGLEKLEP = 'TangleKlep'
|
||||||
DOOMSHROOM = 'DoomShroom'
|
DOOMSHROOM = 'DoomShroom'
|
||||||
ICE_FROZEN_PLOT = 'IceFrozenPlot'
|
ICEFROZENPLOT = 'IceFrozenPlot'
|
||||||
HOLE = 'Hole'
|
HOLE = 'Hole'
|
||||||
GRAVE = 'Grave'
|
GRAVE = 'Grave'
|
||||||
GRAVEBUSTER = 'GraveBuster'
|
GRAVEBUSTER = 'GraveBuster'
|
||||||
@ -246,9 +247,9 @@ GARLIC = 'Garlic'
|
|||||||
|
|
||||||
|
|
||||||
# 植物集体属性集合
|
# 植物集体属性集合
|
||||||
# 在生效时不用与僵尸进行碰撞检测的对象
|
# 在生效时不用与僵尸进行碰撞检测的对象(即生效时不可发生被僵尸啃食的事件)
|
||||||
SKIP_ZOMBIE_COLLISION_CHECK_WHEN_WORKING = {
|
SKIP_ZOMBIE_COLLISION_CHECK_WHEN_WORKING = {
|
||||||
# 注意爆炸坚果的触发也是啃食类碰撞,因此这里不能省略
|
# 注意爆炸坚果的触发也是啃食类碰撞,因此只能算作爆炸后不检测
|
||||||
SQUASH, ICESHROOM,
|
SQUASH, ICESHROOM,
|
||||||
REDWALLNUTBOWLING, CHERRYBOMB,
|
REDWALLNUTBOWLING, CHERRYBOMB,
|
||||||
JALAPENO, DOOMSHROOM,
|
JALAPENO, DOOMSHROOM,
|
||||||
@ -257,12 +258,15 @@ SKIP_ZOMBIE_COLLISION_CHECK_WHEN_WORKING = {
|
|||||||
|
|
||||||
# 非植物对象
|
# 非植物对象
|
||||||
NON_PLANT_OBJECTS = {
|
NON_PLANT_OBJECTS = {
|
||||||
HOLE, ICE_FROZEN_PLOT,
|
HOLE, ICEFROZENPLOT,
|
||||||
GRAVE,
|
GRAVE,
|
||||||
}
|
}
|
||||||
|
|
||||||
# 所有可能不用与僵尸进行碰撞检测的对象
|
# 所有可能不用与僵尸进行碰撞检测的对象
|
||||||
CAN_SKIP_ZOMBIE_COLLISION_CHECK = ( # 这里运用了集合运算
|
CAN_SKIP_ZOMBIE_COLLISION_CHECK = ( # 这里运用了集合运算
|
||||||
|
# 注意这个外围的小括号是用来换行的
|
||||||
|
# 各个部分末!尾!千!万!不!能!加!逗!号!!!
|
||||||
|
|
||||||
# 生效时不检测的植物
|
# 生效时不检测的植物
|
||||||
SKIP_ZOMBIE_COLLISION_CHECK_WHEN_WORKING |
|
SKIP_ZOMBIE_COLLISION_CHECK_WHEN_WORKING |
|
||||||
# 非植物对象
|
# 非植物对象
|
||||||
@ -274,7 +278,7 @@ CAN_SKIP_ZOMBIE_COLLISION_CHECK = ( # 这里运用了集合运算
|
|||||||
# 死亡时不触发音效的对象
|
# 死亡时不触发音效的对象
|
||||||
PLANT_DIE_SOUND_EXCEPTIONS = {
|
PLANT_DIE_SOUND_EXCEPTIONS = {
|
||||||
WALLNUTBOWLING, TANGLEKLEP,
|
WALLNUTBOWLING, TANGLEKLEP,
|
||||||
ICE_FROZEN_PLOT, HOLE,
|
ICEFROZENPLOT, HOLE,
|
||||||
GRAVE, JALAPENO,
|
GRAVE, JALAPENO,
|
||||||
REDWALLNUTBOWLING, CHERRYBOMB,
|
REDWALLNUTBOWLING, CHERRYBOMB,
|
||||||
}
|
}
|
||||||
@ -635,4 +639,4 @@ CHOOSE = 'choose'
|
|||||||
PLAY = 'play'
|
PLAY = 'play'
|
||||||
|
|
||||||
# 无穷大常量
|
# 无穷大常量
|
||||||
INF = float('inf')
|
INF = float("inf") # python传递字符串性能较低,故在这里对inf声明一次,以后仅需调用即可
|
||||||
|
|||||||
@ -93,15 +93,11 @@ class Level(tool.State):
|
|||||||
self.sun_group = pg.sprite.Group()
|
self.sun_group = pg.sprite.Group()
|
||||||
self.head_group = pg.sprite.Group()
|
self.head_group = pg.sprite.Group()
|
||||||
|
|
||||||
self.plant_groups = []
|
# 改用列表生成器直接生成内容,不再在这里使用for循环
|
||||||
self.zombie_groups = []
|
self.plant_groups = [pg.sprite.Group() for i in range(self.map_y_len)]
|
||||||
self.hypno_zombie_groups = [] #zombies who are hypno after eating hypnoshroom
|
self.zombie_groups = [pg.sprite.Group() for i in range(self.map_y_len)]
|
||||||
self.bullet_groups = []
|
self.hypno_zombie_groups = [pg.sprite.Group() for i in range(self.map_y_len)] #zombies who are hypno after eating hypnoshroom
|
||||||
for i in range(self.map_y_len):
|
self.bullet_groups = [pg.sprite.Group() for i in range(self.map_y_len)]
|
||||||
self.plant_groups.append(pg.sprite.Group())
|
|
||||||
self.zombie_groups.append(pg.sprite.Group())
|
|
||||||
self.hypno_zombie_groups.append(pg.sprite.Group())
|
|
||||||
self.bullet_groups.append(pg.sprite.Group())
|
|
||||||
|
|
||||||
|
|
||||||
# 按照规则生成每一波僵尸
|
# 按照规则生成每一波僵尸
|
||||||
@ -189,7 +185,7 @@ class Level(tool.State):
|
|||||||
unoccupied = []
|
unoccupied = []
|
||||||
occupied = []
|
occupied = []
|
||||||
# 毁灭菇坑与冰道应当特殊化
|
# 毁灭菇坑与冰道应当特殊化
|
||||||
exceptionObjects = {c.HOLE, c.ICE_FROZEN_PLOT}
|
exceptionObjects = {c.HOLE, c.ICEFROZENPLOT}
|
||||||
# 遍历能生成墓碑的区域
|
# 遍历能生成墓碑的区域
|
||||||
for mapY in range(0, 4):
|
for mapY in range(0, 4):
|
||||||
for mapX in range(4, 8):
|
for mapX in range(4, 8):
|
||||||
@ -320,7 +316,7 @@ class Level(tool.State):
|
|||||||
def setupCars(self):
|
def setupCars(self):
|
||||||
self.cars = []
|
self.cars = []
|
||||||
for i in range(self.map_y_len):
|
for i in range(self.map_y_len):
|
||||||
_, y = self.map.getMapGridPos(0, i)
|
y = self.map.getMapGridPos(0, i)[1]
|
||||||
self.cars.append(plant.Car(-40, y+20, i))
|
self.cars.append(plant.Car(-40, y+20, i))
|
||||||
|
|
||||||
# 更新函数每帧被调用,将鼠标事件传入给状态处理函数
|
# 更新函数每帧被调用,将鼠标事件传入给状态处理函数
|
||||||
@ -344,7 +340,7 @@ class Level(tool.State):
|
|||||||
def initBowlingMap(self):
|
def initBowlingMap(self):
|
||||||
for x in range(3, self.map.width):
|
for x in range(3, self.map.width):
|
||||||
for y in range(self.map.height):
|
for y in range(self.map.height):
|
||||||
self.map.setMapGridType(x, y, c.MAP_STATE_UNAVAILABLE) # 将坚果保龄球红线右侧设置为不可种植任何植物
|
self.map.setMapGridType(x, y, c.MAP_UNAVAILABLE) # 将坚果保龄球红线右侧设置为不可种植任何植物
|
||||||
|
|
||||||
def initState(self):
|
def initState(self):
|
||||||
if c.CHOOSEBAR_TYPE in self.map_data:
|
if c.CHOOSEBAR_TYPE in self.map_data:
|
||||||
@ -804,34 +800,34 @@ class Level(tool.State):
|
|||||||
x, y = self.map.getMapGridPos(0, map_y)
|
x, y = self.map.getMapGridPos(0, map_y)
|
||||||
|
|
||||||
# 新增的僵尸也需要在这里声明
|
# 新增的僵尸也需要在这里声明
|
||||||
if name == c.NORMAL_ZOMBIE:
|
match name:
|
||||||
|
case c.NORMAL_ZOMBIE:
|
||||||
self.zombie_groups[map_y].add(zombie.NormalZombie(c.ZOMBIE_START_X + random.randint(-20, 20) + hugeWaveMove, y, self.head_group))
|
self.zombie_groups[map_y].add(zombie.NormalZombie(c.ZOMBIE_START_X + random.randint(-20, 20) + hugeWaveMove, y, self.head_group))
|
||||||
elif name == c.CONEHEAD_ZOMBIE:
|
case c.CONEHEAD_ZOMBIE:
|
||||||
self.zombie_groups[map_y].add(zombie.ConeHeadZombie(c.ZOMBIE_START_X + random.randint(-20, 20) + hugeWaveMove, y, self.head_group))
|
self.zombie_groups[map_y].add(zombie.ConeHeadZombie(c.ZOMBIE_START_X + random.randint(-20, 20) + hugeWaveMove, y, self.head_group))
|
||||||
elif name == c.BUCKETHEAD_ZOMBIE:
|
case c.BUCKETHEAD_ZOMBIE:
|
||||||
self.zombie_groups[map_y].add(zombie.BucketHeadZombie(c.ZOMBIE_START_X + random.randint(-20, 20) + hugeWaveMove, y, self.head_group))
|
self.zombie_groups[map_y].add(zombie.BucketHeadZombie(c.ZOMBIE_START_X + random.randint(-20, 20) + hugeWaveMove, y, self.head_group))
|
||||||
elif name == c.FLAG_ZOMBIE:
|
case c.FLAG_ZOMBIE:
|
||||||
self.zombie_groups[map_y].add(zombie.FlagZombie(c.ZOMBIE_START_X, y, self.head_group))
|
self.zombie_groups[map_y].add(zombie.FlagZombie(c.ZOMBIE_START_X, y, self.head_group))
|
||||||
elif name == c.NEWSPAPER_ZOMBIE:
|
case c.NEWSPAPER_ZOMBIE:
|
||||||
self.zombie_groups[map_y].add(zombie.NewspaperZombie(c.ZOMBIE_START_X + random.randint(-20, 20) + hugeWaveMove, y, self.head_group))
|
self.zombie_groups[map_y].add(zombie.NewspaperZombie(c.ZOMBIE_START_X + random.randint(-20, 20) + hugeWaveMove, y, self.head_group))
|
||||||
elif name == c.FOOTBALL_ZOMBIE:
|
case c.FOOTBALL_ZOMBIE:
|
||||||
self.zombie_groups[map_y].add(zombie.FootballZombie(c.ZOMBIE_START_X + random.randint(-20, 20) + hugeWaveMove, y, self.head_group))
|
self.zombie_groups[map_y].add(zombie.FootballZombie(c.ZOMBIE_START_X + random.randint(-20, 20) + hugeWaveMove, y, self.head_group))
|
||||||
elif name == c.DUCKY_TUBE_ZOMBIE:
|
case c.DUCKY_TUBE_ZOMBIE:
|
||||||
self.zombie_groups[map_y].add(zombie.DuckyTubeZombie(c.ZOMBIE_START_X + random.randint(-20, 20) + hugeWaveMove, y, self.head_group))
|
self.zombie_groups[map_y].add(zombie.DuckyTubeZombie(c.ZOMBIE_START_X + random.randint(-20, 20) + hugeWaveMove, y, self.head_group))
|
||||||
elif name == c.CONEHEAD_DUCKY_TUBE_ZOMBIE:
|
case c.CONEHEAD_DUCKY_TUBE_ZOMBIE:
|
||||||
self.zombie_groups[map_y].add(zombie.ConeHeadDuckyTubeZombie(c.ZOMBIE_START_X + random.randint(-20, 20) + hugeWaveMove, y, self.head_group))
|
self.zombie_groups[map_y].add(zombie.ConeHeadDuckyTubeZombie(c.ZOMBIE_START_X + random.randint(-20, 20) + hugeWaveMove, y, self.head_group))
|
||||||
elif name == c.BUCKETHEAD_DUCKY_TUBE_ZOMBIE:
|
case c.BUCKETHEAD_DUCKY_TUBE_ZOMBIE:
|
||||||
self.zombie_groups[map_y].add(zombie.BucketHeadDuckyTubeZombie(c.ZOMBIE_START_X + random.randint(-20, 20) + hugeWaveMove, y, self.head_group))
|
self.zombie_groups[map_y].add(zombie.BucketHeadDuckyTubeZombie(c.ZOMBIE_START_X + random.randint(-20, 20) + hugeWaveMove, y, self.head_group))
|
||||||
elif name == c.SCREEN_DOOR_ZOMBIE:
|
case c.SCREEN_DOOR_ZOMBIE:
|
||||||
self.zombie_groups[map_y].add(zombie.ScreenDoorZombie(c.ZOMBIE_START_X + random.randint(-20, 20) + hugeWaveMove, y, self.head_group))
|
self.zombie_groups[map_y].add(zombie.ScreenDoorZombie(c.ZOMBIE_START_X + random.randint(-20, 20) + hugeWaveMove, y, self.head_group))
|
||||||
elif name == c.POLE_VAULTING_ZOMBIE:
|
case c.POLE_VAULTING_ZOMBIE:
|
||||||
# 本来撑杆跳生成位置不同,对齐左端可认为修正了一部分(看作移动了70),只需要相对修改即可
|
# 本来撑杆跳生成位置不同,对齐左端可认为修正了一部分(看作移动了70),只需要相对修改即可
|
||||||
self.zombie_groups[map_y].add(zombie.PoleVaultingZombie(c.ZOMBIE_START_X + random.randint(0, 10) + hugeWaveMove, y, self.head_group))
|
self.zombie_groups[map_y].add(zombie.PoleVaultingZombie(c.ZOMBIE_START_X + random.randint(0, 10) + hugeWaveMove, y, self.head_group))
|
||||||
elif name == c.ZOMBONI:
|
case c.ZOMBONI:
|
||||||
# 冰车僵尸生成位置不同
|
# 冰车僵尸生成位置不同
|
||||||
self.zombie_groups[map_y].add(zombie.Zomboni(c.ZOMBIE_START_X + random.randint(0, 10) + hugeWaveMove, y, self.plant_groups[map_y], self.map, plant.IceFrozenPlot))
|
self.zombie_groups[map_y].add(zombie.Zomboni(c.ZOMBIE_START_X + random.randint(0, 10) + hugeWaveMove, y, self.plant_groups[map_y], self.map, plant.IceFrozenPlot))
|
||||||
elif name == c.SNORKELZOMBIE:
|
case c.SNORKELZOMBIE:
|
||||||
# 潜水僵尸生成位置不同
|
|
||||||
self.zombie_groups[map_y].add(zombie.SnorkelZombie(c.ZOMBIE_START_X + random.randint(0, 10) + hugeWaveMove, y, self.head_group))
|
self.zombie_groups[map_y].add(zombie.SnorkelZombie(c.ZOMBIE_START_X + random.randint(0, 10) + hugeWaveMove, y, self.head_group))
|
||||||
|
|
||||||
# 能否种植物的判断:
|
# 能否种植物的判断:
|
||||||
@ -856,69 +852,69 @@ class Level(tool.State):
|
|||||||
map_x, map_y = self.map.getMapIndex(x, y)
|
map_x, map_y = self.map.getMapIndex(x, y)
|
||||||
|
|
||||||
# 新植物也需要在这里声明
|
# 新植物也需要在这里声明
|
||||||
if self.plant_name == c.SUNFLOWER:
|
match self.plant_name:
|
||||||
|
case c.SUNFLOWER:
|
||||||
new_plant = plant.SunFlower(x, y, self.sun_group)
|
new_plant = plant.SunFlower(x, y, self.sun_group)
|
||||||
elif self.plant_name == c.PEASHOOTER:
|
case c.PEASHOOTER:
|
||||||
new_plant = plant.PeaShooter(x, y, self.bullet_groups[map_y])
|
new_plant = plant.PeaShooter(x, y, self.bullet_groups[map_y])
|
||||||
elif self.plant_name == c.SNOWPEASHOOTER:
|
case c.SNOWPEASHOOTER:
|
||||||
new_plant = plant.SnowPeaShooter(x, y, self.bullet_groups[map_y])
|
new_plant = plant.SnowPeaShooter(x, y, self.bullet_groups[map_y])
|
||||||
elif self.plant_name == c.WALLNUT:
|
case c.WALLNUT:
|
||||||
new_plant = plant.WallNut(x, y)
|
new_plant = plant.WallNut(x, y)
|
||||||
elif self.plant_name == c.CHERRYBOMB:
|
case c.CHERRYBOMB:
|
||||||
new_plant = plant.CherryBomb(x, y)
|
new_plant = plant.CherryBomb(x, y)
|
||||||
elif self.plant_name == c.THREEPEASHOOTER:
|
case c.THREEPEASHOOTER:
|
||||||
new_plant = plant.ThreePeaShooter(x, y, self.bullet_groups, map_y, self.map.background_type)
|
new_plant = plant.ThreePeaShooter(x, y, self.bullet_groups, map_y, self.map.background_type)
|
||||||
elif self.plant_name == c.REPEATERPEA:
|
case c.REPEATERPEA:
|
||||||
new_plant = plant.RepeaterPea(x, y, self.bullet_groups[map_y])
|
new_plant = plant.RepeaterPea(x, y, self.bullet_groups[map_y])
|
||||||
elif self.plant_name == c.CHOMPER:
|
case c.CHOMPER:
|
||||||
new_plant = plant.Chomper(x, y)
|
new_plant = plant.Chomper(x, y)
|
||||||
elif self.plant_name == c.PUFFSHROOM:
|
case c.PUFFSHROOM:
|
||||||
new_plant = plant.PuffShroom(x, y, self.bullet_groups[map_y])
|
new_plant = plant.PuffShroom(x, y, self.bullet_groups[map_y])
|
||||||
elif self.plant_name == c.POTATOMINE:
|
case c.POTATOMINE:
|
||||||
new_plant = plant.PotatoMine(x, y)
|
new_plant = plant.PotatoMine(x, y)
|
||||||
elif self.plant_name == c.SQUASH:
|
case c.SQUASH:
|
||||||
new_plant = plant.Squash(x, y, self.map.map[map_y][map_x][c.MAP_PLANT])
|
new_plant = plant.Squash(x, y, self.map.map[map_y][map_x][c.MAP_PLANT])
|
||||||
elif self.plant_name == c.SPIKEWEED:
|
case c.SPIKEWEED:
|
||||||
new_plant = plant.Spikeweed(x, y)
|
new_plant = plant.Spikeweed(x, y)
|
||||||
elif self.plant_name == c.JALAPENO:
|
case c.JALAPENO:
|
||||||
new_plant = plant.Jalapeno(x, y)
|
new_plant = plant.Jalapeno(x, y)
|
||||||
elif self.plant_name == c.SCAREDYSHROOM:
|
case c.SCAREDYSHROOM:
|
||||||
new_plant = plant.ScaredyShroom(x, y, self.bullet_groups[map_y])
|
new_plant = plant.ScaredyShroom(x, y, self.bullet_groups[map_y])
|
||||||
elif self.plant_name == c.SUNSHROOM:
|
case c.SUNSHROOM:
|
||||||
new_plant = plant.SunShroom(x, y, self.sun_group)
|
new_plant = plant.SunShroom(x, y, self.sun_group)
|
||||||
elif self.plant_name == c.ICESHROOM:
|
case c.ICESHROOM:
|
||||||
new_plant = plant.IceShroom(x, y)
|
new_plant = plant.IceShroom(x, y)
|
||||||
elif self.plant_name == c.HYPNOSHROOM:
|
case c.HYPNOSHROOM:
|
||||||
new_plant = plant.HypnoShroom(x, y)
|
new_plant = plant.HypnoShroom(x, y)
|
||||||
elif self.plant_name == c.WALLNUTBOWLING:
|
case c.WALLNUTBOWLING:
|
||||||
new_plant = plant.WallNutBowling(x, y, map_y, self)
|
new_plant = plant.WallNutBowling(x, y, map_y, self)
|
||||||
elif self.plant_name == c.REDWALLNUTBOWLING:
|
case c.REDWALLNUTBOWLING:
|
||||||
new_plant = plant.RedWallNutBowling(x, y)
|
new_plant = plant.RedWallNutBowling(x, y)
|
||||||
elif self.plant_name == c.LILYPAD:
|
case c.LILYPAD:
|
||||||
new_plant = plant.LilyPad(x, y)
|
new_plant = plant.LilyPad(x, y)
|
||||||
elif self.plant_name == c.TORCHWOOD:
|
case c.TORCHWOOD:
|
||||||
new_plant = plant.TorchWood(x, y, self.bullet_groups[map_y])
|
new_plant = plant.TorchWood(x, y, self.bullet_groups[map_y])
|
||||||
elif self.plant_name == c.STARFRUIT:
|
case c.STARFRUIT:
|
||||||
new_plant = plant.StarFruit(x, y, self.bullet_groups[map_y], self)
|
new_plant = plant.StarFruit(x, y, self.bullet_groups[map_y], self)
|
||||||
elif self.plant_name == c.COFFEEBEAN:
|
case c.COFFEEBEAN:
|
||||||
new_plant = plant.CoffeeBean(x, y, self.plant_groups[map_y], self.map.map[map_y][map_x], self.map, map_x)
|
new_plant = plant.CoffeeBean(x, y, self.plant_groups[map_y], self.map.map[map_y][map_x], self.map, map_x)
|
||||||
elif self.plant_name == c.SEASHROOM:
|
case c.SEASHROOM:
|
||||||
new_plant = plant.SeaShroom(x, y, self.bullet_groups[map_y])
|
new_plant = plant.SeaShroom(x, y, self.bullet_groups[map_y])
|
||||||
elif self.plant_name == c.TALLNUT:
|
case c.TALLNUT:
|
||||||
new_plant = plant.TallNut(x, y)
|
new_plant = plant.TallNut(x, y)
|
||||||
elif self.plant_name == c.TANGLEKLEP:
|
case c.TANGLEKLEP:
|
||||||
new_plant = plant.TangleKlep(x, y)
|
new_plant = plant.TangleKlep(x, y)
|
||||||
elif self.plant_name == c.DOOMSHROOM:
|
case c.DOOMSHROOM:
|
||||||
if ((self.map_data[c.BACKGROUND_TYPE] in c.ON_ROOF_BACKGROUNDS) or
|
if self.map.gridHeightSize == c.GRID_Y_SIZE:
|
||||||
(self.map_data[c.BACKGROUND_TYPE] in c.POOL_EQUIPPED_BACKGROUNDS)):
|
|
||||||
new_plant = plant.DoomShroom(x, y, self.map.map[map_y][map_x][c.MAP_PLANT], explode_y_range=3)
|
new_plant = plant.DoomShroom(x, y, self.map.map[map_y][map_x][c.MAP_PLANT], explode_y_range=3)
|
||||||
else:
|
else:
|
||||||
new_plant = plant.DoomShroom(x, y, self.map.map[map_y][map_x][c.MAP_PLANT], explode_y_range=2)
|
new_plant = plant.DoomShroom(x, y, self.map.map[map_y][map_x][c.MAP_PLANT], explode_y_range=2)
|
||||||
elif self.plant_name == c.GRAVEBUSTER:
|
case c.GRAVEBUSTER:
|
||||||
new_plant = plant.GraveBuster(x, y, self.plant_groups[map_y], self.map, map_x)
|
new_plant = plant.GraveBuster(x, y, self.plant_groups[map_y], self.map, map_x)
|
||||||
elif self.plant_name == c.FUMESHROOM:
|
case c.FUMESHROOM:
|
||||||
new_plant = plant.FumeShroom(x, y, self.bullet_groups[map_y], self.zombie_groups[map_y])
|
new_plant = plant.FumeShroom(x, y, self.bullet_groups[map_y], self.zombie_groups[map_y])
|
||||||
elif self.plant_name == c.GARLIC:
|
case c.GARLIC:
|
||||||
new_plant = plant.Garlic(x, y)
|
new_plant = plant.Garlic(x, y)
|
||||||
|
|
||||||
|
|
||||||
@ -1015,7 +1011,7 @@ class Level(tool.State):
|
|||||||
continue
|
continue
|
||||||
if collided_func(zombie, bullet):
|
if collided_func(zombie, bullet):
|
||||||
if zombie.state != c.DIE:
|
if zombie.state != c.DIE:
|
||||||
zombie.setDamage(bullet.damage, effect=bullet.effect, damageType=c.ZOMBIE_DEAFULT_DAMAGE)
|
zombie.setDamage(bullet.damage, effect=bullet.effect, damageType=bullet.damageType)
|
||||||
bullet.setExplode()
|
bullet.setExplode()
|
||||||
# 火球有溅射伤害
|
# 火球有溅射伤害
|
||||||
if bullet.name == c.BULLET_FIREBALL:
|
if bullet.name == c.BULLET_FIREBALL:
|
||||||
@ -1045,7 +1041,7 @@ class Level(tool.State):
|
|||||||
# 被攻击对象是植物时才可能刷新
|
# 被攻击对象是植物时才可能刷新
|
||||||
if zombie.prey_is_plant:
|
if zombie.prey_is_plant:
|
||||||
# 新植物种在被攻击植物同一格时才可能刷新
|
# 新植物种在被攻击植物同一格时才可能刷新
|
||||||
if (mapX, mapY) == self.newPlantAndPositon[1]:
|
if (zombie.preyMapX, zombie.preyMapY) == self.newPlantAndPositon[1]:
|
||||||
# 如果被攻击植物是睡莲和花盆,同一格种了植物必然刷新
|
# 如果被攻击植物是睡莲和花盆,同一格种了植物必然刷新
|
||||||
# 如果被攻击植物不是睡莲和花盆,同一格种了南瓜头才刷新
|
# 如果被攻击植物不是睡莲和花盆,同一格种了南瓜头才刷新
|
||||||
if ((zombie.prey.name not in {c.LILYPAD, "花盆(未实现)"})
|
if ((zombie.prey.name not in {c.LILYPAD, "花盆(未实现)"})
|
||||||
@ -1102,11 +1098,11 @@ class Level(tool.State):
|
|||||||
targetPlant = None
|
targetPlant = None
|
||||||
|
|
||||||
if targetPlant:
|
if targetPlant:
|
||||||
|
zombie.preyMapX, zombie.preyMapY = self.map.getMapIndex(targetPlant.rect.centerx, targetPlant.rect.centery)
|
||||||
# 撑杆跳的特殊情况
|
# 撑杆跳的特殊情况
|
||||||
if zombie.name in {c.POLE_VAULTING_ZOMBIE} and (not zombie.jumped):
|
if zombie.name in {c.POLE_VAULTING_ZOMBIE} and (not zombie.jumped):
|
||||||
if not zombie.jumping:
|
if not zombie.jumping:
|
||||||
zombie.jumpMap_x, zombie.jumpMap_y = self.map.getMapIndex(targetPlant.rect.centerx, targetPlant.rect.centery)
|
zombie.jumpMap_x, zombie.jumpMap_y = min(c.GRID_X_LEN - 1, zombie.preyMapX), min(self.map_y_len - 1, zombie.preyMapY)
|
||||||
zombie.jumpMap_x, zombie.jumpMap_y = min(c.GRID_X_LEN - 1, zombie.jumpMap_x), min(self.map_y_len - 1, zombie.jumpMap_y)
|
|
||||||
jumpX = targetPlant.rect.x - c.GRID_X_SIZE * 0.6
|
jumpX = targetPlant.rect.x - c.GRID_X_SIZE * 0.6
|
||||||
if c.TALLNUT in self.map.map[zombie.jumpMap_y][zombie.jumpMap_x][c.MAP_PLANT]:
|
if c.TALLNUT in self.map.map[zombie.jumpMap_y][zombie.jumpMap_x][c.MAP_PLANT]:
|
||||||
zombie.setJump(False, jumpX)
|
zombie.setJump(False, jumpX)
|
||||||
@ -1132,6 +1128,9 @@ class Level(tool.State):
|
|||||||
elif targetPlant.name == c.REDWALLNUTBOWLING:
|
elif targetPlant.name == c.REDWALLNUTBOWLING:
|
||||||
if targetPlant.state == c.IDLE:
|
if targetPlant.state == c.IDLE:
|
||||||
targetPlant.setAttack()
|
targetPlant.setAttack()
|
||||||
|
elif zombie.targetYChange:
|
||||||
|
# 大蒜作用正在生效的僵尸不进行传递
|
||||||
|
continue
|
||||||
elif targetPlant.name == c.GARLIC:
|
elif targetPlant.name == c.GARLIC:
|
||||||
zombie.setAttack(targetPlant)
|
zombie.setAttack(targetPlant)
|
||||||
# 向吃过大蒜的僵尸传入level
|
# 向吃过大蒜的僵尸传入level
|
||||||
@ -1145,7 +1144,7 @@ class Level(tool.State):
|
|||||||
else:
|
else:
|
||||||
_move = random.randint(0, 1)*2 - 1
|
_move = random.randint(0, 1)*2 - 1
|
||||||
if self.map.map[i][0][c.MAP_PLOT_TYPE] != self.map.map[i + _move][0][c.MAP_PLOT_TYPE]:
|
if self.map.map[i][0][c.MAP_PLOT_TYPE] != self.map.map[i + _move][0][c.MAP_PLOT_TYPE]:
|
||||||
_move = -_move
|
_move = -(_move)
|
||||||
zombie.targetMapY = i + _move
|
zombie.targetMapY = i + _move
|
||||||
zombie.targetYChange = _move * self.map.gridHeightSize
|
zombie.targetYChange = _move * self.map.gridHeightSize
|
||||||
else:
|
else:
|
||||||
@ -1351,7 +1350,7 @@ class Level(tool.State):
|
|||||||
targetPlant.explode_x_range, effect=c.BULLET_EFFECT_UNICE)
|
targetPlant.explode_x_range, effect=c.BULLET_EFFECT_UNICE)
|
||||||
# 消除冰道
|
# 消除冰道
|
||||||
for item in self.plant_groups[i]:
|
for item in self.plant_groups[i]:
|
||||||
if item.name == c.ICE_FROZEN_PLOT:
|
if item.name == c.ICEFROZENPLOT:
|
||||||
item.health = 0
|
item.health = 0
|
||||||
elif targetPlant.name == c.ICESHROOM:
|
elif targetPlant.name == c.ICESHROOM:
|
||||||
self.freezeZombies(targetPlant)
|
self.freezeZombies(targetPlant)
|
||||||
@ -1394,7 +1393,8 @@ class Level(tool.State):
|
|||||||
def checkLose(self):
|
def checkLose(self):
|
||||||
for i in range(self.map_y_len):
|
for i in range(self.map_y_len):
|
||||||
for zombie in self.zombie_groups[i]:
|
for zombie in self.zombie_groups[i]:
|
||||||
if zombie.rect.right < -20 and (not zombie.lostHead) and zombie.state != c.DIE:
|
if zombie.rect.right < -20 and (not zombie.lostHead) and (zombie.state != c.DIE):
|
||||||
|
print(zombie.rect.right, zombie.lostHead, zombie.state,zombie.name)
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|||||||
@ -97,8 +97,10 @@ class Menu(tool.State):
|
|||||||
# 高亮冒险模式按钮
|
# 高亮冒险模式按钮
|
||||||
if self.inAreaAdventure(x, y):
|
if self.inAreaAdventure(x, y):
|
||||||
self.adventure_highlight_time = self.current_time
|
self.adventure_highlight_time = self.current_time
|
||||||
|
# 高亮退出按钮
|
||||||
elif self.inAreaExit(x, y):
|
elif self.inAreaExit(x, y):
|
||||||
self.exit_highlight_time = self.current_time
|
self.exit_highlight_time = self.current_time
|
||||||
|
# 高亮小游戏按钮
|
||||||
elif self.inAreaLittleGame(x, y):
|
elif self.inAreaLittleGame(x, y):
|
||||||
self.littleGame_highlight_time = self.current_time
|
self.littleGame_highlight_time = self.current_time
|
||||||
|
|
||||||
@ -126,8 +128,6 @@ class Menu(tool.State):
|
|||||||
x, y = mouse_pos
|
x, y = mouse_pos
|
||||||
if self.inAreaLittleGame(x, y):
|
if self.inAreaLittleGame(x, y):
|
||||||
self.done = True
|
self.done = True
|
||||||
# 确实小游戏还是用的level
|
|
||||||
# 因为目前暂时没有生存模式和解谜模式,所以暂时设置为这样
|
|
||||||
self.persist[c.GAME_MODE] = c.MODE_LITTLEGAME
|
self.persist[c.GAME_MODE] = c.MODE_LITTLEGAME
|
||||||
# 播放点击音效
|
# 播放点击音效
|
||||||
pg.mixer.Sound(os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))) ,"resources", "sound", "buttonclick.ogg")).play()
|
pg.mixer.Sound(os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))) ,"resources", "sound", "buttonclick.ogg")).play()
|
||||||
|
|||||||
@ -90,7 +90,8 @@ class Control():
|
|||||||
self.mouse_pos = pg.mouse.get_pos()
|
self.mouse_pos = pg.mouse.get_pos()
|
||||||
self.mouse_click[0], _, self.mouse_click[1] = pg.mouse.get_pressed()
|
self.mouse_click[0], _, self.mouse_click[1] = pg.mouse.get_pressed()
|
||||||
# self.mouse_click[0]表示左键,self.mouse_click[1]表示右键
|
# self.mouse_click[0]表示左键,self.mouse_click[1]表示右键
|
||||||
print('点击位置:', self.mouse_pos, '左右键点击情况:', self.mouse_click)
|
print( f"点击位置: ({self.mouse_pos[0]:3}, {self.mouse_pos[1]:3})",
|
||||||
|
f"左右键点击情况: {self.mouse_click}")
|
||||||
|
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
@ -188,16 +189,14 @@ def load_all_gfx(directory, colorkey=c.WHITE, accept=('.png', '.jpg', '.bmp', '.
|
|||||||
# 用于消除文件边框影响
|
# 用于消除文件边框影响
|
||||||
def loadZombieImageRect():
|
def loadZombieImageRect():
|
||||||
file_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'resources', 'data', 'entity', 'zombie.json')
|
file_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'resources', 'data', 'entity', 'zombie.json')
|
||||||
f = open(file_path)
|
with open(file_path) as f:
|
||||||
data = json.load(f)
|
data = json.load(f)
|
||||||
f.close()
|
|
||||||
return data[c.ZOMBIE_IMAGE_RECT]
|
return data[c.ZOMBIE_IMAGE_RECT]
|
||||||
|
|
||||||
def loadPlantImageRect():
|
def loadPlantImageRect():
|
||||||
file_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'resources', 'data', 'entity', 'plant.json')
|
file_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'resources', 'data', 'entity', 'plant.json')
|
||||||
f = open(file_path)
|
with open(file_path) as f:
|
||||||
data = json.load(f)
|
data = json.load(f)
|
||||||
f.close()
|
|
||||||
return data[c.PLANT_IMAGE_RECT]
|
return data[c.PLANT_IMAGE_RECT]
|
||||||
|
|
||||||
pg.init()
|
pg.init()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user