From f18244f8377a437db26210c48340edfdfeb8808b Mon Sep 17 00:00:00 2001 From: moranshanzha <1412472761@qq.com> Date: Sun, 2 Nov 2025 17:36:24 +0800 Subject: [PATCH 1/2] test --- .idea/.gitignore | 3 + .idea/PythonPlantsVsZombies.iml | 8 + .../inspectionProfiles/profiles_settings.xml | 6 + .idea/misc.xml | 4 + .idea/modules.xml | 8 + .idea/vcs.xml | 6 + source/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 148 bytes source/__pycache__/constants.cpython-312.pyc | Bin 0 -> 4857 bytes source/__pycache__/main.cpython-312.pyc | Bin 0 -> 1100 bytes source/__pycache__/tool.cpython-312.pyc | Bin 0 -> 10902 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 158 bytes .../component/__pycache__/map.cpython-312.pyc | Bin 0 -> 3242 bytes .../__pycache__/menubar.cpython-312.pyc | Bin 0 -> 27971 bytes .../__pycache__/plant.cpython-312.pyc | Bin 0 -> 61568 bytes .../__pycache__/zombie.cpython-312.pyc | Bin 0 -> 23153 bytes source/component/plant.py | 59 +++++++ source/component/zombie.py | 146 +++++++++++++++++- source/constants.py | 32 ++++ .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 154 bytes .../state/__pycache__/level.cpython-312.pyc | Bin 0 -> 39109 bytes .../__pycache__/mainmenu.cpython-312.pyc | Bin 0 -> 4438 bytes .../state/__pycache__/screen.cpython-312.pyc | Bin 0 -> 3796 bytes source/state/level.py | 22 +++ 23 files changed, 293 insertions(+), 1 deletion(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/PythonPlantsVsZombies.iml create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 source/__pycache__/__init__.cpython-312.pyc create mode 100644 source/__pycache__/constants.cpython-312.pyc create mode 100644 source/__pycache__/main.cpython-312.pyc create mode 100644 source/__pycache__/tool.cpython-312.pyc create mode 100644 source/component/__pycache__/__init__.cpython-312.pyc create mode 100644 source/component/__pycache__/map.cpython-312.pyc create mode 100644 source/component/__pycache__/menubar.cpython-312.pyc create mode 100644 source/component/__pycache__/plant.cpython-312.pyc create mode 100644 source/component/__pycache__/zombie.cpython-312.pyc create mode 100644 source/state/__pycache__/__init__.cpython-312.pyc create mode 100644 source/state/__pycache__/level.cpython-312.pyc create mode 100644 source/state/__pycache__/mainmenu.cpython-312.pyc create mode 100644 source/state/__pycache__/screen.cpython-312.pyc diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 00000000..26d33521 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/PythonPlantsVsZombies.iml b/.idea/PythonPlantsVsZombies.iml new file mode 100644 index 00000000..d0876a78 --- /dev/null +++ b/.idea/PythonPlantsVsZombies.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 00000000..105ce2da --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 00000000..2a2c1b9b --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 00000000..35c0ec56 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 00000000..35eb1ddf --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/source/__pycache__/__init__.cpython-312.pyc b/source/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..73ec3c5396418342ac03f2f9bbd5faa895cb48c3 GIT binary patch literal 148 zcmX@j%ge<81OoExnIQTxh(HIQS%4zb87dhx8U0o=6fpsLpFwJV8M|1;B$wu;CFZ5a z1XPw}B^G=4F<|$LkeT{^GF7%}*)KNwq6t V1)9MK#Kj=SM`lJw#v*1Q3jo=BBFX>& literal 0 HcmV?d00001 diff --git a/source/__pycache__/constants.cpython-312.pyc b/source/__pycache__/constants.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..929394fe14d6507593451367c86e1965f411d322 GIT binary patch literal 4857 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 literal 0 HcmV?d00001 diff --git a/source/component/__pycache__/map.cpython-312.pyc b/source/component/__pycache__/map.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..92b1ac8fc61151be05de7104ca3abcdfcc7e3bbe GIT binary patch literal 3242 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 literal 0 HcmV?d00001 diff --git a/source/component/__pycache__/zombie.cpython-312.pyc b/source/component/__pycache__/zombie.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7eaed3faa92a867319b02f4a347b324313c4a75e GIT binary patch literal 23153 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

7ww>9kSvTz=sp8{0&+6lv`X9eBJ;byU z$7S|L+K&Yw905*7U^>c%Z|JBb*;_2wxj=@TOQ-9#xR%MKX*~Bbxirm}y-Y4m1LjzA zagAVn2{egkF}%04y^;4D(SWGeM#F>4wYS;g3m&>&YidinFbke9Nve#6L7$Dr^bd)s zk6+9;^1>4I4*HqK6qA((*X~gJp!mcZlS>n(=}pvqD_{OL9`dgcc}$Hc|1M?zLn7ZJ z@*EMG|MK^VkcoXOpeeyT>yD46{^mRCnO6Iliv57d4~cw2WRl1q5h0x?|1ptIiO?4? zWHpo>ds;MJSq4y;I6 zeqqsA+i2Uc_kM8+Etqb4l;Wp{{r9V?CnDpKn*&M}3YFil2~GUs_%Ggt8mei8a=c$q zIJS3m@8k)kU|pti+^7_9yqQdGI;?Cu%#`AjXiIk%)0R+c#cS@a-I>nNn(i!3YkIyG ztw}BA=-MiOMx&LXAOD1SL<(pqXoBgBu{lqbTk}%0}TxR}vTon!1Fyr^qrROz5FgN&lU` zo~6v$C$V0C2EHC!tM#wZ=`xWs)RCrFr`77!q4Y0>(ZSZ(n~A;6oQhb82}jwchznZ7O{I&K#%) zTH0m=TtfkeUCOdcH8&jWQgv`&q-Hm(nYJ73%4V}vtE;9kqiNKx|2?i6t|aEUY9xJg zgIqPD#cgHN7woJ`z9kP=EM~tA1$y%=Im*oxBQ_Mkc-Y*MwYY@n5C99^Aw6Pw=q{lg z<~|C1=|CGp|5qV;Fk?LlDRxUxRd7jru-`Zpw zCFPlzWa`f$LZx2@`R4pa#`cI;!QVaPJ1d*8|FQhe3U1Qu9Nl^SvQkj@Gj<-iy(f$< zmtetnUZ*XdsbJ`SY1u^1c+RN%AGF9d_p%!@cVaVQ`xkzy?~7cvCG!~0r702)B5%=n ziHLCLQmx@!qU3NM9ZiZ}#vjL&aWpBrub=)ZolFH(UHZsP&vyT0()Kb5j<#IsrPz0g z{0R}ZU?a`XemBG0^;6Ft?flcT^4}o~8YLv&0Krz1!;ub@e#`gwLc!nBzp6*Bh|^Ir zeEjO^k<+O_l@h3WgcBf+l1KY;L`U7V;zt4=k18u1Yp-p4B;fIA*GfnIwUdtoJRTi( Y(90Xf3*|xymTL_X&i|W$2V3v_7hR2R)Bpeg literal 0 HcmV?d00001 diff --git a/source/component/plant.py b/source/component/plant.py index 684065b1..bf261e24 100644 --- a/source/component/plant.py +++ b/source/component/plant.py @@ -54,6 +54,7 @@ def __init__(self, x, start_y, dest_y, name, damage, ice): self.ice = ice self.state = c.FLY self.current_time = 0 + self.knockback = 0 # 击退效果,默认为0表示没有击退 def loadFrames(self, frames, name): frame_list = tool.GFX[name] @@ -825,6 +826,64 @@ def loadImages(self, name, scale): self.frames = self.idle_frames +# Chinese Red Theme Plants +class RedPeony(Plant): + """红牡丹 - 中国红主题植物,能够释放花粉攻击多个僵尸""" + def __init__(self, x, y, zombie_groups, map_y): + Plant.__init__(self, x, y, c.RED_PEONY, c.RED_PEONY_HEALTH, None) + self.shoot_timer = 0 + self.map_y = map_y + self.zombie_groups = zombie_groups + self.attack_range = 3 # 攻击范围为3行 + + def attacking(self): + if (self.current_time - self.shoot_timer) > 3000: + # 释放花粉攻击范围内的所有僵尸 + for i in range(-self.attack_range, self.attack_range + 1): + tmp_y = self.map_y + i + if tmp_y < 0 or tmp_y >= c.GRID_Y_LEN: + continue + for zombie in self.zombie_groups[tmp_y]: + if self.canAttack(zombie): + zombie.setDamage(c.RED_PEONY_POLLEN_DAMAGE) + self.shoot_timer = self.current_time + + def canAttack(self, zombie): + if (self.state != c.SLEEP and zombie.state != c.DIE and + self.rect.x <= zombie.rect.right and + self.rect.right + c.GRID_X_SIZE * 2 >= zombie.rect.x): + return True + return False + +class FirecrackerPlant(Plant): + """鞭炮花 - 中国红主题植物,能够发射鞭炮攻击僵尸""" + def __init__(self, x, y, bullet_group): + Plant.__init__(self, x, y, c.FIRECRACKER_PLANT, c.FIRECRACKER_PLANT_HEALTH, bullet_group) + self.shoot_timer = 0 + self.bullet_speed = 6 + + def attacking(self): + if (self.current_time - self.shoot_timer) > 2500: + # 发射鞭炮子弹 + self.bullet_group.add(Bullet(self.rect.right, self.rect.y, self.rect.y, + c.BULLET_PEA, c.FIRECRACKER_DAMAGE, False)) + self.shoot_timer = self.current_time + +class LionDancePea(Plant): + """舞狮豌豆 - 中国红主题植物,能够发射具有击退效果的豌豆""" + def __init__(self, x, y, bullet_group): + Plant.__init__(self, x, y, c.LION_DANCE_PEA, c.LION_DANCE_PEA_HEALTH, bullet_group) + self.shoot_timer = 0 + + def attacking(self): + if (self.current_time - self.shoot_timer) > 3000: + # 发射具有击退效果的豌豆 + bullet = Bullet(self.rect.right, self.rect.y, self.rect.y, + c.BULLET_PEA, c.LION_DANCE_PEA_DAMAGE, False) + bullet.knockback = c.LION_DANCE_PEA_KNOCKBACK + self.bullet_group.add(bullet) + self.shoot_timer = self.current_time + class WallNutBowling(Plant): def __init__(self, x, y, map_y, level): Plant.__init__(self, x, y, c.WALLNUTBOWLING, 1, None) diff --git a/source/component/zombie.py b/source/component/zombie.py index 73780f13..133c004f 100644 --- a/source/component/zombie.py +++ b/source/component/zombie.py @@ -411,4 +411,148 @@ def loadImages(self): color = c.WHITE self.loadFrames(frame_list[i], name, tool.ZOMBIE_RECT[name]['x'], color) - self.frames = self.helmet_walk_frames \ No newline at end of file + self.frames = self.helmet_walk_frames + +# Chinese Red Theme Zombies +class LanternZombie(Zombie): + """灯笼僵尸 - 中国红主题僵尸,手持灯笼照亮周围区域""" + def __init__(self, x, y, head_group): + Zombie.__init__(self, x, y, c.LANTERN_ZOMBIE, c.LANTERN_ZOMBIE_HEALTH, head_group) + self.speed = c.LANTERN_ZOMBIE_SPEED + self.lantern_light_range = c.LANTERN_LIGHT_RANGE + self.lantern_on = True + + def loadImages(self): + self.walk_frames = [] + self.attack_frames = [] + self.losthead_walk_frames = [] + self.losthead_attack_frames = [] + self.die_frames = [] + self.boomdie_frames = [] + + walk_name = self.name + attack_name = self.name + 'Attack' + losthead_walk_name = self.name + 'LostHead' + losthead_attack_name = self.name + 'LostHeadAttack' + die_name = c.NORMAL_ZOMBIE + 'Die' + boomdie_name = c.BOOMDIE + + frame_list = [self.walk_frames, self.attack_frames, self.losthead_walk_frames, + self.losthead_attack_frames, self.die_frames, self.boomdie_frames] + name_list = [walk_name, attack_name, losthead_walk_name, + losthead_attack_name, die_name, boomdie_name] + + for i, name in enumerate(name_list): + self.loadFrames(frame_list[i], name, tool.ZOMBIE_RECT[name]['x']) + + self.frames = self.walk_frames + +class LionDanceZombie(Zombie): + """舞狮僵尸 - 中国红主题僵尸,穿着舞狮服装,具有较高的生命值和攻击力""" + def __init__(self, x, y, head_group): + Zombie.__init__(self, x, y, c.LION_DANCE_ZOMBIE, c.LION_DANCE_ZOMBIE_HEALTH, head_group) + self.speed = c.LION_DANCE_ZOMBIE_SPEED + self.damage = c.LION_DANCE_ATTACK_DAMAGE + self.helmet = True + + def loadImages(self): + self.helmet_walk_frames = [] + self.helmet_attack_frames = [] + self.walk_frames = [] + self.attack_frames = [] + self.losthead_walk_frames = [] + self.losthead_attack_frames = [] + self.die_frames = [] + self.boomdie_frames = [] + + helmet_walk_name = self.name + helmet_attack_name = self.name + 'Attack' + walk_name = self.name + 'NoCostume' + attack_name = self.name + 'NoCostumeAttack' + losthead_walk_name = self.name + 'LostHead' + losthead_attack_name = self.name + 'LostHeadAttack' + die_name = self.name + 'Die' + boomdie_name = c.BOOMDIE + + frame_list = [self.helmet_walk_frames, self.helmet_attack_frames, + self.walk_frames, self.attack_frames, self.losthead_walk_frames, + self.losthead_attack_frames, self.die_frames, self.boomdie_frames] + name_list = [helmet_walk_name, helmet_attack_name, + walk_name, attack_name, losthead_walk_name, + losthead_attack_name, die_name, boomdie_name] + + for i, name in enumerate(name_list): + self.loadFrames(frame_list[i], name, tool.ZOMBIE_RECT[name]['x']) + + self.frames = self.helmet_walk_frames + +class ChineseNewYearZombie(Zombie): + """春节僵尸 - 中国红主题僵尸,穿着春节服装,死亡时掉落金币""" + def __init__(self, x, y, head_group, gold_group): + Zombie.__init__(self, x, y, c.CHINESE_NEW_YEAR_ZOMBIE, c.CHINESE_NEW_YEAR_ZOMBIE_HEALTH, head_group) + self.speed = c.CHINESE_NEW_YEAR_ZOMBIE_SPEED + self.gold_group = gold_group + self.gold_drop = c.CHINESE_NEW_YEAR_GOLD_DROP + + def loadImages(self): + self.walk_frames = [] + self.attack_frames = [] + self.losthead_walk_frames = [] + self.losthead_attack_frames = [] + self.die_frames = [] + self.boomdie_frames = [] + + walk_name = self.name + attack_name = self.name + 'Attack' + losthead_walk_name = self.name + 'LostHead' + losthead_attack_name = self.name + 'LostHeadAttack' + die_name = self.name + 'Die' + boomdie_name = c.BOOMDIE + + frame_list = [self.walk_frames, self.attack_frames, self.losthead_walk_frames, + self.losthead_attack_frames, self.die_frames, self.boomdie_frames] + name_list = [walk_name, attack_name, losthead_walk_name, + losthead_attack_name, die_name, boomdie_name] + + for i, name in enumerate(name_list): + self.loadFrames(frame_list[i], name, tool.ZOMBIE_RECT[name]['x']) + + self.frames = self.walk_frames + + def dying(self): + # 死亡时掉落金币 + if not self.dead: + for i in range(self.gold_drop): + gold = Gold(self.rect.centerx, self.rect.y) + self.gold_group.add(gold) + self.dead = True + super().dying() + +class Gold(pg.sprite.Sprite): + """金币类 - 春节僵尸死亡时掉落""" + def __init__(self, x, y): + pg.sprite.Sprite.__init__(self) + self.image = tool.get_image(tool.GFX['Gold'], 0, 0, 30, 30, c.BLACK, 1) + self.rect = self.image.get_rect() + self.rect.centerx = x + self.rect.y = y + self.y_vel = -2 # 金币向上飘 + self.gravity = 0.1 # 重力效果 + self.live_time = 5000 # 金币存在时间 + self.created_time = pg.time.get_ticks() + + def update(self, game_info): + # 金币向上飘然后下落 + self.y_vel += self.gravity + self.rect.y += self.y_vel + + # 金币存在时间结束后消失 + if pg.time.get_ticks() - self.created_time > self.live_time: + self.kill() + + def checkCollision(self, x, y): + if(x >= self.rect.x and x <= self.rect.right and + y >= self.rect.y and y <= self.rect.bottom): + self.kill() + return True + return False \ No newline at end of file diff --git a/source/constants.py b/source/constants.py index 0c4e2b6e..b4950f25 100644 --- a/source/constants.py +++ b/source/constants.py @@ -93,6 +93,10 @@ HYPNOSHROOM = 'HypnoShroom' WALLNUTBOWLING = 'WallNutBowling' REDWALLNUTBOWLING = 'RedWallNutBowling' +# Chinese Red Theme Plants +RED_PEONY = 'RedPeony' # 红牡丹 +FIRECRACKER_PLANT = 'FirecrackerPlant' # 鞭炮花 +LION_DANCE_PEA = 'LionDancePea' # 舞狮豌豆 PLANT_HEALTH = 5 WALLNUT_HEALTH = 30 @@ -100,6 +104,15 @@ WALLNUT_CRACKED2_HEALTH = 10 WALLNUT_BOWLING_DAMAGE = 10 +# Chinese Red Theme Plant Properties +RED_PEONY_HEALTH = 15 # 红牡丹生命值 +RED_PEONY_POLLEN_DAMAGE = 3 # 红牡丹花粉伤害 +FIRECRACKER_PLANT_HEALTH = 10 # 鞭炮花生命值 +FIRECRACKER_DAMAGE = 2 # 鞭炮伤害 +LION_DANCE_PEA_HEALTH = 5 # 舞狮豌豆生命值 +LION_DANCE_PEA_DAMAGE = 4 # 舞狮豌豆伤害 +LION_DANCE_PEA_KNOCKBACK = 50 # 舞狮豌豆击退效果 + PRODUCE_SUN_INTERVAL = 7000 FLOWER_SUN_INTERVAL = 22000 SUN_LIVE_TIME = 7000 @@ -129,6 +142,10 @@ CARD_ICESHROOM = 'card_iceshroom' CARD_HYPNOSHROOM = 'card_hypnoshroom' CARD_REDWALLNUT = 'card_redwallnut' +# Chinese Red Theme Plant Cards +CARD_RED_PEONY = 'card_redpeony' +CARD_FIRECRACKER_PLANT = 'card_firecrackerplant' +CARD_LION_DANCE_PEA = 'card_liondancepea' #BULLET INFO BULLET_PEA = 'PeaNormal' @@ -145,6 +162,10 @@ FLAG_ZOMBIE = 'FlagZombie' NEWSPAPER_ZOMBIE = 'NewspaperZombie' BOOMDIE = 'BoomDie' +# Chinese Red Theme Zombies +LANTERN_ZOMBIE = 'LanternZombie' # 灯笼僵尸 +LION_DANCE_ZOMBIE = 'LionDanceZombie' # 舞狮僵尸 +CHINESE_NEW_YEAR_ZOMBIE = 'ChineseNewYearZombie' # 春节僵尸 LOSTHEAD_HEALTH = 5 NORMAL_HEALTH = 10 @@ -153,6 +174,17 @@ BUCKETHEAD_HEALTH = 30 NEWSPAPER_HEALTH = 15 +# Chinese Red Theme Zombie Properties +LANTERN_ZOMBIE_HEALTH = 15 # 灯笼僵尸生命值 +LANTERN_ZOMBIE_SPEED = 1.2 # 灯笼僵尸移动速度 +LANTERN_LIGHT_RANGE = 3 # 灯笼照亮范围 +LION_DANCE_ZOMBIE_HEALTH = 30 # 舞狮僵尸生命值 +LION_DANCE_ZOMBIE_SPEED = 0.8 # 舞狮僵尸移动速度 +LION_DANCE_ATTACK_DAMAGE = 2 # 舞狮僵尸攻击伤害 +CHINESE_NEW_YEAR_ZOMBIE_HEALTH = 25 # 春节僵尸生命值 +CHINESE_NEW_YEAR_ZOMBIE_SPEED = 1.0 # 春节僵尸移动速度 +CHINESE_NEW_YEAR_GOLD_DROP = 5 # 春节僵尸掉落金币数量 + ATTACK_INTERVAL = 1000 ZOMBIE_WALK_INTERVAL = 70 diff --git a/source/state/__pycache__/__init__.cpython-312.pyc b/source/state/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..58f7eae18be13b37579c8d43b48e03a71aa216fd GIT binary patch literal 154 zcmX@j%ge<81OoExnIQTxh(HIQS%4zb87dhx8U0o=6fpsLpFwJVS-M!oB$wu;CFZ5a z1XPw}u*uC& bDa}c>D`Ewj#0bR2AjU^#Mn=XWW*`dy<%K0p literal 0 HcmV?d00001 diff --git a/source/state/__pycache__/level.cpython-312.pyc b/source/state/__pycache__/level.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a25d4b94f6b99db34505685d266ecace4396ab5f GIT binary patch literal 39109 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 literal 0 HcmV?d00001 diff --git a/source/state/__pycache__/mainmenu.cpython-312.pyc b/source/state/__pycache__/mainmenu.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a64d3d7a447223a7c237c43e8804763f62d17880 GIT binary patch literal 4438 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 Date: Mon, 3 Nov 2025 19:28:44 +0800 Subject: [PATCH 2/2] test --- backend/__init__.py | 6 + backend/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 344 bytes backend/__pycache__/app.cpython-312.pyc | Bin 0 -> 2363 bytes backend/__pycache__/models.cpython-312.pyc | Bin 0 -> 11177 bytes backend/app.db | Bin 0 -> 65536 bytes backend/app.py | 44 +++ backend/migrations/README | 1 + .../__pycache__/env.cpython-312.pyc | Bin 0 -> 4529 bytes backend/migrations/alembic.ini | 50 +++ backend/migrations/env.py | 113 ++++++ backend/migrations/script.py.mako | 24 ++ .../0016b73ba5fb_initial_migration.py | 102 ++++++ ...b73ba5fb_initial_migration.cpython-312.pyc | Bin 0 -> 7057 bytes backend/models.py | 158 +++++++++ backend/requirements.txt | 9 + backend/routes/__init__.py | 4 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 269 bytes .../routes/__pycache__/auth.cpython-312.pyc | Bin 0 -> 12002 bytes .../routes/__pycache__/logs.cpython-312.pyc | Bin 0 -> 9755 bytes .../__pycache__/resources.cpython-312.pyc | Bin 0 -> 15438 bytes backend/routes/auth.py | 224 ++++++++++++ backend/routes/logs.py | 221 ++++++++++++ backend/routes/resources.py | 330 ++++++++++++++++++ ke | 0 source/__pycache__/constants.cpython-312.pyc | Bin 4857 -> 4857 bytes .../__pycache__/plant.cpython-312.pyc | Bin 61568 -> 61568 bytes .../__pycache__/zombie.cpython-312.pyc | Bin 23153 -> 23153 bytes source/component/plant.py | 59 ---- source/component/zombie.py | 146 +------- source/constants.py | 32 -- .../state/__pycache__/level.cpython-312.pyc | Bin 39109 -> 39109 bytes source/state/level.py | 22 -- 32 files changed, 1287 insertions(+), 258 deletions(-) create mode 100644 backend/__init__.py create mode 100644 backend/__pycache__/__init__.cpython-312.pyc create mode 100644 backend/__pycache__/app.cpython-312.pyc create mode 100644 backend/__pycache__/models.cpython-312.pyc create mode 100644 backend/app.db create mode 100644 backend/app.py create mode 100644 backend/migrations/README create mode 100644 backend/migrations/__pycache__/env.cpython-312.pyc create mode 100644 backend/migrations/alembic.ini create mode 100644 backend/migrations/env.py create mode 100644 backend/migrations/script.py.mako create mode 100644 backend/migrations/versions/0016b73ba5fb_initial_migration.py create mode 100644 backend/migrations/versions/__pycache__/0016b73ba5fb_initial_migration.cpython-312.pyc create mode 100644 backend/models.py create mode 100644 backend/requirements.txt create mode 100644 backend/routes/__init__.py create mode 100644 backend/routes/__pycache__/__init__.cpython-312.pyc create mode 100644 backend/routes/__pycache__/auth.cpython-312.pyc create mode 100644 backend/routes/__pycache__/logs.cpython-312.pyc create mode 100644 backend/routes/__pycache__/resources.cpython-312.pyc create mode 100644 backend/routes/auth.py create mode 100644 backend/routes/logs.py create mode 100644 backend/routes/resources.py create mode 100644 ke diff --git a/backend/__init__.py b/backend/__init__.py new file mode 100644 index 00000000..9ea1dc3a --- /dev/null +++ b/backend/__init__.py @@ -0,0 +1,6 @@ +# 导入Flask应用和扩展 +from .app import app, db +from .models import User, Role, Permission, Log, Resource + +# 导入创建初始数据的函数 +from .models import create_initial_data \ No newline at end of file diff --git a/backend/__pycache__/__init__.cpython-312.pyc b/backend/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4fc768ead33dab724b553b70f8159b03c6b4573e GIT binary patch literal 344 zcmXw!ze)o^5XNV3?_UDJB8`O);5Ak@kzi#h5&~K*2gl}ijf?vy?A{gk7Ir>^@8BC) z+iYWDA?bvGMJjiLPVt-hzJYI;x5;D-p-!Gk@f!MP6#td{4ZC?5o`C^F5)=?nh#(Yo zA`^uPRw+qUMlzL?Tot4^>~PqXq(m^JRklSTqDnlbn~hJ{s>*zN>$vsl(n#*d3vQKg zP8jX+wORXe$(`wK%PZukt>v`ijnJYKR5pxu^rjkF93pKR;B#ddm(FI<$s>x0GGY`F ze|&*mAnebtF6y?|_f)Uz#n3$%y^vIQ?#>aTR)V{FMcYTNS=}5?HqF^)*q8HgqnG^B d9)*zjjhhC1V}#Hb9Dl&<9cDi{pi?^u{x1+KSLXl# literal 0 HcmV?d00001 diff --git a/backend/__pycache__/app.cpython-312.pyc b/backend/__pycache__/app.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..707451d9244bc032a3e3500734da548e19e5c2e8 GIT binary patch literal 2363 zcmb7FO-vg{6rT02*T!HMT*02@FCGNS^G5g5%OI2bBBR952z&f;0ssqq4D z@to??+=AQUoob!t5j+;ptMx*?13(5gJZ8$#1uv03&}+*Kfc2>!yM*eQ>Z|fLC%bH~ z6~?3$xuG}Zx6$e;yDkzzt|O5Ls#=>%9k;paS;O*V)xzd#SO#podcvc2we~4{9y`i- zo*Xr5ueWM%sDXMZ`ae3>Xy>kU?4ufS8?CHOHP9m#+FS!|p(vRuSDmWRMr0m(VViwp zp`B>RyzF{}4!Ko6s&;B!LYLMpbZb3AkJc;nYCge7Cfh2vsePJX@N4}-zcwHYkUjCH z#>?G1NTEM9aZoPva{H4GJ9ACX{zE-G|AXhip`KkIcupNsSGRqCx#t0RTrm(%zy`T@ zX%GO^LT)*8h@bCCpY6$j9G~L@{YIiw~Qp++Uw z6(xzJ45Yx9H{w?$T}nbkBrZG?jZG8lNH`jai&K%C`PQ6~MMEY`Aeb3ig1MnZh+y7h zeyu7QI2;TH^SqQ!pONQrJI!%9JQ2BaQydM&LzhCah!c zw~)M1jOI;&doSV&M77e}Zb=|D^%*{)QC8*1DbcX31b}G6IR}g(^=}Tv} z5K;ht)HknB7W+cQmP_09;UX7Cy+n`QqNquVE{bu|2B!??v&q09b}rE6L-cZ1Ex5^2 zh-71=@&+REn*rEcR%_)|L_F0smvv*%e@fV4m^8mGX;2g~ zOUlPCsNYc#9b*z^FrP5=1tp2Ov_wakG87%VqEabC^BG*a{ocFd=- z(=xYui&>P_5gonNN#rBkd-p|>Fss55bb*L;AQ#rk69B({F)<0fH+n%n1>kU!1(EI(#17`RY_>7*XN+3W| zGTdv>RswD7nT_Rdmmggyg0^44`5nOiyE?bA`t54*>=%}Kh{SO?A9mjBT>JFC@128T pMwxvUIGXk>mY`h2TK*;5xx=|0j^7(!KUNrj#kKFTfa|ix_7{U-9Z)a=Bd{SR z%=j6W5p;q+#D;Z#9rPK23+cn0p9>rO2KsCW8N(*ODa`wMmeDbyZnUbvwDc<&=}1ip zQ|JjMcw<-Lsq{7cOYYVka<^%5f2>z$@!PjtpE{zozQ2eB zTdvQs!}U2|yFQ1Gxx@&rPZ_}teD_uAo?(iba8b%CpsaoarJGXL0Hx;*lvR{+7f|kg z17$U(tOd%tH&E6H^`A0+PhyX6?{i#^k5zaf7!zYsSbUBf2@pDAz^7Ao4T}+xz))a3 zD9iVvLOmqRad2=@l;uDyIwnSxnmm$-LqwEE@>na0hD13K zmqj8gCj1FV!nd^3A_@JAtYRG}QaDHwfiW?mn2;POlC%R9id$tcE|Rb$%ThEVEB4X@ zl9oNf3xnnVICK7C{ioEjMaItxj9&*kNe?C{AkOX?^3)& zr7DMlzPRgHfqP;o?jnB2oQzv=GVhp^smO_Y!WY+O*@_RWSi?)fYRtuVZLfK^zFZ$_ zUvy|$SQ$UCF>O5qcH8{6n8i~3AAo%Vzk{#JCs@U5L0!`!jZr)72}fb=Pn-o$?DwGm z@b41b0Cx-EA^f#MHNZLne1N}R@BrMC*yY=;a5upe`iUKwKfeax;-XG5#v{@vaZ%wT z@lYr@5E7r?$Ke!!MV9QQKZ;2Zhl24?Y*9z{0!HDZk@z@TwZdHo+fjHqJ^)NeLNQRI z0}JCq!DM1lub5?VFd7lS*2n>T)ie+s90PM5%m)>~F$}tU{9A7V0Kt%*U`C-=*&ae@ z{F(j+3!thn9@wqOVoOrVHOfV%s9I${KBa5m*0 z5w!j;V)+7A#(BJ6@?FH*WHDBBmPt32xg3Qmm7M@m%(I3QOP5y~&Q5Y)^Yx?^aGNCN z073r?EzsE{F*91eG8n}M>&RnoFyt5@KzbAWkmCqW0N5m_W3yXNR#QngBb)IhRmabq zO$%+S2ilhpv@hLXadu|;PF3Qf#(=iCZ?-4#lcMHtFpIiAA8Z^+5F;@umH^q&H!C_x zAV*LNx`9*;yM{r-i z8bPJNZj@|ONKEc3O1ueA-zgnkq^TSc2-$3TTL`&l&$S#W~on9sh$F$o=*d;dkBUNO+946(Z`EOTh} zK29|dXK5sWc!k3^6iW#$VMV&2}htbn%|R~k+`+w4*-DO*VNAS&i1}!^k(m5 z_u9Uu)qO3?`&wQyI=gqqvR2(NcW(CFLUg6NUD>n$-wl9$p$F0zJmd0EefIhGqJKfz z0s+Mm2!Pv(L+%k!Y=OWh@nA?DAqJREF`kcx;^By5>WRd}VUf`PwPL&xBT{6T97Ym@ z7!FDyYH*6FD+-GZMii?g2Yw(DX-Ex>6U`Y8qy`=6Ys((o%Zhku)!5^baFT@m$T> z)YY}BJu^*nzFA+o>rwA~?~)-`^G{)3{J%JV<;=M|Q@xra zUmg0l*59<|-2PPW3nOFS3$lDP^-=m_X6#GLnyY?BH)ol(q)%pQa;}yQJ!?Ojy71D- z8hi*oK=2`pupPmB>qdh?-33%LgP@sWE2fNp1Xay3|5sARLY(t&nJhvgq;Z53t)m(T z(I=)tyrIWJ&4hR-{I-~dFD7bmI0Jqfhcdw|x&$?G#pqhm1S=$Jwt}or?8QW_N^n3b zR-JJATm-X;d?tqB@*=^g_x!&AkUa<*04SDgMNdb(i0wpxQ<8%K-aduG5YV{w zJ?!DMid7KhK_ZQ#>mit$Qg=oE27A{L+(6Kez=eQ1r4O-3ozhM0VLqd}A5<>YFgh#w z*8sM3Kh~PbNDsJ?p81}I!H0b-RY%^^ zkyJN0sPOI&pO8%j;Na+#n`mz@o<4{ z{hxJz?I;O;ACsSoM&p5_0tt_0<(BOiTQD`MXhNyjf_z|=*!(*$k(A!uvP44Hq9sGI zOjC^BSZduW+G=P?;LA!XY7FJom6Sv6$SS%<5x6RdRPa0MD31sg#tE)xcf#d!EBfz6hx4w-2Jc)XSXFv1 z08sr8o=!^fpXP~BNQn;1H0VQ&#<=&k4$5!_q)?3IG!GD=r*Qq^{g`5cict*CyC(;xr70<`dt{lnEL>ga z`sLN1UtRL896a-7Jmm+8nH|Dt2DV`=Eiw%n1+Icra<8{KR^w?KVp zyV+hahrjM&1dL3>flw3sU*u+~Jg;??w8ma=*Q$%qaCuwuDc!J@yZxQJ?joNzyY4s3 zQsl8i7rv=N|GJ90hNBm&M$v>TTABN=p>k{J7IxCM`E4;v$^CoiEbmr`0grk2k4aNS z+tU5lQukj+-9LX_l%sJnD83Q{ND%nZbhMO+(lekM1I%!_MPF0{0YQ6x+c01`Q;_D3 zS*p80SquhaAV4t~>`8ay;V6B&Pz=xKsP)?j24n~qUWx);@Ca@{F)k|RAqh%jRI#b%O6k^Y4RvB zpU6}{{D&1!ORDcRhtwj!rbmb84}%1*HsDkiYj#`%(}*f#Uvar3S98`rHLCd3YSXitW zwYDogc+2fc&rNXhM=`v|p-tcfLz(Q{8}x=H{)$%6%LErFlpmQtLjznv_w1judgC@% zZV{m*N?*A1d zOdpnFJ`*J^>4988Dhrh&fU8%`RKK*FMk=fB>x0d zSWuHQ5V2CsOU_ztOkH_V&+PNPWH`fjmif9#ZgT9IXaB0FdD+vv>Sn(TdMb4>S6^-lLq^`$$$w!QPh49qqhOx3Qr z`q}zbck{BlIdd}WZeDSpSaqLTcAr{tpHB7u&gyyQsY~C>wq4G7dL}L3G#*)PJht3; zY_+j{xv_nv@#Lf>$M1W_Tc#~jmTAY7Vn>)^8MVCJ2y>)?v(*sAN~ zvg_muCM&g`zec06tS*Y|(@KHQ>SU@hy6 z{)`nsqwPQ7hRro~{vR*AFfrz;9N)0PGiGaX-b9XX+%Rrl&^@}KldJBt%kHx)?sJ6& zEu4gzvo~(#Jl~(RtnpQ={QhNr{{k%K2z{zu=4;DvuPl!<{-pQEy{nvOne%+j?arsO zyVNUGJ?t2Dv$^kyWGpGhhg;wqmpFm12=z!nn)`h@|# zwpR@O_^D0VeF#m1@hnX5Hx$EF{2oQ!tn@Ym_dYFM&@7eiBXSdQtq8sg;9LHTdYk+K z`5YcGBa-(4e4%Gq_V-M~@0sd9Fg<@@F2hx(6RR5|QzJ7E);N2r>nA-w?wOR}{cE6r!WYRhrgwn4IP&^vW0>kAv>!7JIzvzN0CN4N1_i6jbHZE`-D zISaLlvx{e+?q505lfCtEj=KZAJ-RI8-mo;X9;j)&KlT0w1Ko{_tc`6hAfUUk*Tf!y u>)qPf+6@M}8&wV%Z<;q2DuIMf7PT}--v+EA0POA&hMPWayUBNcRX7^ z>GWEbr*A2XimECf=(?gPDgU|XKZlQ5|6pcV@PDeI<8g;6<(;!{GuhvjY~rbs&18RA z$S(Ypc_Z`l+;{W8&i|P1&i<18X7>BU)8yf}A*e?H0R#~E-wB*QUr1^zE9zz5vuaJd z*J;|W*>~-p8#>JHRE%QP(5uCV`-UEB&~xdO=xjO-y;QCmdqzbsA5``7@&5jro=%Cb z`R!~%E3K%C({9*bxzC&atE*}Cy-rXzL*bc)P+{@n&H1FZzOG){A*!z3 zYdNm#blL;3P(|hjwotRcn*=oK`j-d?L#^r3d9Ogqtzphl%Z;dm@g-DIH$W|B658?l zWYp1baEG*^s)fs$xukaIj{5cISHQO0>G$e(Z}4+o<{Z=t#WC48DKpzv%ho?BR(3ur zR&s@njl7h%-iz+2bvx2{;`mA3wY<~o^EbD*Bj=y?TeY_3G+*r}61!o$^`6u9{H&42 z5N(cYT6NDkv-O7u2m3~`EGp|g+w$y&>A(N`&|iqv(qltZ_q&aos->0n_m{`T+v%is z`?mV>K`;%?&NKgOPQv#q{-L|)cz()^g|Bzop56BJs_~h>i-dL(&wA!wpC-Hmt5&zu z^Q?B=9_~K>J=R;vq_(uAzFZHmM9PWhti*M-_e{)FDIf1gGR|)KsdOEEVKdU)wOseh zPOo8}TJ9(*3!C@lr1UfE%g{ zw?BxE=BD_bzBl`)9Sfpk>ip5Ifd1me^?oe&ql83%wL^`im)N5VebO@h_bYN}H1tVK z3Om?*gIA;E`R;U5TV7T#J`KViZJ?v08Hr$2KVi+jZit)qph(19GYM^Pd0a+}{%W%@ zI=*$DSCiW6s(O(MqPLoMtLD^2VvE<{NO4MXh%^d}!D(i@-@0C2TbuH-**Pd5RVzjR z?TsE8!R&rf*QG#1=#zbXC~4+IcE009IL zKmY**5I_I{1Q3Xcz=Aq;cVnaQZtcCTnzemWOZ#zg{U4LbB@6@*KmY**5I_I{1Q0*~ z0R#pD+3CBf@b$mA|1W*YA1MS7KmY**5I_I{1Q0*~0R#|;oq)Li&-H)orj>vYKmY** z5I_I{1Q0*~0R#|`1i1e%aZo@20R#|0009ILKmY**5I`W70^I+P<+KtM0tg_000Iag zfB*srAbVZV;;d|qFUz&`N@m4ycZuLCy=<~!?f^KCGh?|OA*GAT#lltwuQ zv`LNwZHApg`GHq?{nH3NyU8ChXpVt#^Y(GN;a^mIBF#9_X+_0nb!}S7!6=q35rQ={ zBNdA<3}=>|tp_;n#51AY7E+dwT2w4y#1@WN!Vz0IWeKO2h0{xs)8DMcTkidMDb~M% z0pAm;C6eyclt+p~6g1UGRWBBNnOk#VLd8OE$m z-Q`(ToSiy;G{HV$&f*Mzp8&)dQQOE_Q)BVgvQN=w4XvT)88E?UCH)wpnH&nC+x zTh|f8w|?_1*0zBdD%Nyw?9-EWbFbChyDaqC!jL5l{qFL^zGdOqlgP1k7MLpbJ~E2_ zFgpGQ`)HT}x|J|~q)KXb1{dar>8)s_?r?%2Ot?-k1D*S*rhc;u0W#y8jx^V1^Hg$I zlM#i8%V--{XcvpSPD7I~U{jJMQ!>&)Cp;q=62zRtn-Zall&1()o)hu8cB?y1xvGl? zfOiBQBMqdC*20NT=05w*O1OVb*k=npme8~KT}v2PiVUp9_SmugR&4)LaKCH49onF` zb+z$N=zunWdM!_9cQw#U#aS1SSq+MOoj0CGChrUTz$gd4WFJ&^g#f6@S0U1usDp-BtJLJ?L{rQ%IOcLxxhmpa|Lhii^`_P#RP;%0Fc##iEnpndtQPISM`t-C4 z(0EWR=rSITMnz~>W)wq&+yY6bM6e;6Gnfu(su5=ub4JXU3{#ZE?|tyT%P(oNsOd1H ziRWCP9Rd-(l$)u|h_IuS&ElddO{v(Gn!yB%k|EAwRUH<^GjpPGT`3kJ6x{>8oy07` zk_qVQip*kB*0qDCh;Lv`)G(Ie7Vn-JzjQ$~b#V%|yCEqmJ)!a@XC+mI`|JMohw)9E z1>$${6EA`8qsQ$GPc zsKjcSn@97E%$|f3UX99yzvWcvu2~u;m4!Q{U$Ka ze9ruWfe5b2*Qr%~2{+bjoYb9mHJ|g(}>@U4&Q0fP1l9$e5M9w2~? zEY>8V=n%4A@X^4c@d<(FhrSy%6c8Zy>RH8IqtT~f*Ofi|*aj$-n95T3@EZ+_W_UIq z0;!*{>ZT?}4)}0OPRs{nmb%aaJI~ew?0gljfvM7bb(4-90(tm==|Hu5HeWTB&kq%I z(04ijTlbgdCY-obGW8tR00vBap3pRVt7`(3H3Rh|04e~lK@+wl%Gdo=ibT(1!^rqDnVQJ0vE6{;y2bEGRnJPQaccO#$bJZ? z7#D#+mX0;u8@>O|-FFrz9-do?jjgsESY+=0@b>vtp~V)uEuq^M1}$N5MHpHYd+tPk zb@)!hT5|9G*xlGt=gFtZx7Rt8JOm_u!mGn5l8~c7Cei^XTJtN?O&4grmX5jK0fc^# zIEgB@W}xUW92QjU6@m2IiTb3gHj<*!6n#lQB?p0kM(H^oJbTiyiVRIK#6{Je3T>&Y zGeTkLJ$Q^i0x6@7y&QMyMFKT;+R1(^*}phvB@f%l(^m3y`NC>A`Ll(eEG)IWxg0*S z6h5(fWa2iz)bxfG8F|*!`RP#ky}$8$Y`)XtJD2&cbrj)Ji-RsOw^@<4brj^ItF7H@ zodds~c+SCKJ%ogP8)4|Zh@(V{-Oz6}^e;CI0BZB8wRp1~@3G=NEAc)6bUyV`fc@M} zUnXEkOVrd>ZF0-#gEx58c=XJ1_Hn4?%n$>fDN)gxQjcPmyyVLo|TYURxZ!h!d za%dwIVoq!{am?}ceTWI$Xs?C#F0~%~qH8(*)(RS1jkkK1SbWQhAF|^kR(zz)ZzNf! sYa`yobZ-obOlloUxL`;5n9a6ZZ2Jn^`R8E!(_rV{gKbZP9d3C12d^c~*8l(j literal 0 HcmV?d00001 diff --git a/backend/migrations/alembic.ini b/backend/migrations/alembic.ini new file mode 100644 index 00000000..ec9d45c2 --- /dev/null +++ b/backend/migrations/alembic.ini @@ -0,0 +1,50 @@ +# A generic, single database configuration. + +[alembic] +# template used to generate migration files +# file_template = %%(rev)s_%%(slug)s + +# set to 'true' to run the environment during +# the 'revision' command, regardless of autogenerate +# revision_environment = false + + +# Logging configuration +[loggers] +keys = root,sqlalchemy,alembic,flask_migrate + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARN +handlers = console +qualname = + +[logger_sqlalchemy] +level = WARN +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = +qualname = alembic + +[logger_flask_migrate] +level = INFO +handlers = +qualname = flask_migrate + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S diff --git a/backend/migrations/env.py b/backend/migrations/env.py new file mode 100644 index 00000000..4c970927 --- /dev/null +++ b/backend/migrations/env.py @@ -0,0 +1,113 @@ +import logging +from logging.config import fileConfig + +from flask import current_app + +from alembic import context + +# this is the Alembic Config object, which provides +# access to the values within the .ini file in use. +config = context.config + +# Interpret the config file for Python logging. +# This line sets up loggers basically. +fileConfig(config.config_file_name) +logger = logging.getLogger('alembic.env') + + +def get_engine(): + try: + # this works with Flask-SQLAlchemy<3 and Alchemical + return current_app.extensions['migrate'].db.get_engine() + except (TypeError, AttributeError): + # this works with Flask-SQLAlchemy>=3 + return current_app.extensions['migrate'].db.engine + + +def get_engine_url(): + try: + return get_engine().url.render_as_string(hide_password=False).replace( + '%', '%%') + except AttributeError: + return str(get_engine().url).replace('%', '%%') + + +# add your model's MetaData object here +# for 'autogenerate' support +# from myapp import mymodel +# target_metadata = mymodel.Base.metadata +config.set_main_option('sqlalchemy.url', get_engine_url()) +target_db = current_app.extensions['migrate'].db + +# other values from the config, defined by the needs of env.py, +# can be acquired: +# my_important_option = config.get_main_option("my_important_option") +# ... etc. + + +def get_metadata(): + if hasattr(target_db, 'metadatas'): + return target_db.metadatas[None] + return target_db.metadata + + +def run_migrations_offline(): + """Run migrations in 'offline' mode. + + This configures the context with just a URL + and not an Engine, though an Engine is acceptable + here as well. By skipping the Engine creation + we don't even need a DBAPI to be available. + + Calls to context.execute() here emit the given string to the + script output. + + """ + url = config.get_main_option("sqlalchemy.url") + context.configure( + url=url, target_metadata=get_metadata(), literal_binds=True + ) + + with context.begin_transaction(): + context.run_migrations() + + +def run_migrations_online(): + """Run migrations in 'online' mode. + + In this scenario we need to create an Engine + and associate a connection with the context. + + """ + + # this callback is used to prevent an auto-migration from being generated + # when there are no changes to the schema + # reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html + def process_revision_directives(context, revision, directives): + if getattr(config.cmd_opts, 'autogenerate', False): + script = directives[0] + if script.upgrade_ops.is_empty(): + directives[:] = [] + logger.info('No changes in schema detected.') + + conf_args = current_app.extensions['migrate'].configure_args + if conf_args.get("process_revision_directives") is None: + conf_args["process_revision_directives"] = process_revision_directives + + connectable = get_engine() + + with connectable.connect() as connection: + context.configure( + connection=connection, + target_metadata=get_metadata(), + **conf_args + ) + + with context.begin_transaction(): + context.run_migrations() + + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() diff --git a/backend/migrations/script.py.mako b/backend/migrations/script.py.mako new file mode 100644 index 00000000..2c015630 --- /dev/null +++ b/backend/migrations/script.py.mako @@ -0,0 +1,24 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" +from alembic import op +import sqlalchemy as sa +${imports if imports else ""} + +# revision identifiers, used by Alembic. +revision = ${repr(up_revision)} +down_revision = ${repr(down_revision)} +branch_labels = ${repr(branch_labels)} +depends_on = ${repr(depends_on)} + + +def upgrade(): + ${upgrades if upgrades else "pass"} + + +def downgrade(): + ${downgrades if downgrades else "pass"} diff --git a/backend/migrations/versions/0016b73ba5fb_initial_migration.py b/backend/migrations/versions/0016b73ba5fb_initial_migration.py new file mode 100644 index 00000000..807f0ae6 --- /dev/null +++ b/backend/migrations/versions/0016b73ba5fb_initial_migration.py @@ -0,0 +1,102 @@ +"""Initial migration + +Revision ID: 0016b73ba5fb +Revises: +Create Date: 2025-11-03 19:21:31.818139 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '0016b73ba5fb' +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('permission', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(length=50), nullable=False), + sa.Column('description', sa.String(length=255), nullable=True), + sa.Column('created_at', sa.DateTime(), nullable=True), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('name') + ) + op.create_table('role', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(length=50), nullable=False), + sa.Column('description', sa.String(length=255), nullable=True), + sa.Column('created_at', sa.DateTime(), nullable=True), + sa.Column('updated_at', sa.DateTime(), nullable=True), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('name') + ) + op.create_table('user', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('username', sa.String(length=80), nullable=False), + sa.Column('email', sa.String(length=120), nullable=False), + sa.Column('password_hash', sa.String(length=128), nullable=False), + sa.Column('is_verified', sa.Boolean(), nullable=True), + sa.Column('created_at', sa.DateTime(), nullable=True), + sa.Column('updated_at', sa.DateTime(), nullable=True), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('email'), + sa.UniqueConstraint('username') + ) + op.create_table('log', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('user_id', sa.Integer(), nullable=False), + sa.Column('title', sa.String(length=100), nullable=False), + sa.Column('content', sa.Text(), nullable=False), + sa.Column('tags', sa.String(length=255), nullable=True), + sa.Column('importance', sa.Integer(), nullable=True), + sa.Column('created_at', sa.DateTime(), nullable=True), + sa.Column('updated_at', sa.DateTime(), nullable=True), + sa.ForeignKeyConstraint(['user_id'], ['user.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('resource', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('user_id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(length=100), nullable=False), + sa.Column('type', sa.String(length=50), nullable=False), + sa.Column('file_path', sa.String(length=255), nullable=False), + sa.Column('thumbnail_path', sa.String(length=255), nullable=True), + sa.Column('description', sa.String(length=255), nullable=True), + sa.Column('is_active', sa.Boolean(), nullable=True), + sa.Column('created_at', sa.DateTime(), nullable=True), + sa.Column('updated_at', sa.DateTime(), nullable=True), + sa.ForeignKeyConstraint(['user_id'], ['user.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('roles_permissions', + sa.Column('role_id', sa.Integer(), nullable=False), + sa.Column('permission_id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['permission_id'], ['permission.id'], ), + sa.ForeignKeyConstraint(['role_id'], ['role.id'], ), + sa.PrimaryKeyConstraint('role_id', 'permission_id') + ) + op.create_table('roles_users', + sa.Column('user_id', sa.Integer(), nullable=False), + sa.Column('role_id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['role_id'], ['role.id'], ), + sa.ForeignKeyConstraint(['user_id'], ['user.id'], ), + sa.PrimaryKeyConstraint('user_id', 'role_id') + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('roles_users') + op.drop_table('roles_permissions') + op.drop_table('resource') + op.drop_table('log') + op.drop_table('user') + op.drop_table('role') + op.drop_table('permission') + # ### end Alembic commands ### diff --git a/backend/migrations/versions/__pycache__/0016b73ba5fb_initial_migration.cpython-312.pyc b/backend/migrations/versions/__pycache__/0016b73ba5fb_initial_migration.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..20d2e5af13e642cd3cf29a851c5b7c0ca53d21f3 GIT binary patch literal 7057 zcmeHMOKcm*8Qvv_4^g6CmQ72tVOOCXbCL$#( z=mT9~XJ_aC_nZGYGx+zjXfz_g_41vt@=2Q@d`(~2<*RizEyf5t& zY2UB<(+Hz<0Q=KHxI=J;hHNRi7Kyw;ZYd`8 z;uGVk_`pE&T;}{xMjoEYR4K$v#Uo<|ku4G*hb9#t92guvolKq{7>XxHQiI9VQ1Z-$ zC zvCl&fZ;i9wO09$aAEOmID_ov0c!&WH-StZKE7s!R$Dx&)%zHo$*FkOaphg;&wb_H( z!X(^!Zfeg*Y~A8yJ?vw;d62&B!N)XsgO9;$=dwJ+4e9@Nh5`7H=_ z)()PRo^CbbA^!VfxPz^49xL(I!8XRX(%j%Wc*KLq)A`_IJ?5G5VD`wP9zs} zc^qwUe|z3zRbC$VdFZ>Xo{h&`|5G9IY}5~H-AV3R$K&n;?iaY+yp^#J_p>#0rDmVH z=i?VW8uDy<@H_m|d1-G};&Jb7w~67toi;5AiNrN0QXocNF=>^<@hSKc6ez-4QB~!P zN*tugd9pqTkqyH^m1sF@)@jDX%o<98mPpqB3A8CEl68w9SduLV89IC!DHbqPM5-f2 zlNe5zwl3av_{Is4ysW6}@6&#?Ae-iUx`Cxx*_>UUr(?~EDcvH5GNTae_*FgUgeZWd zU?*THmP*z!Wea6>%_161Ysop&i75GkZdkIGC37zS8Q>2a#MFyM7WA=71>!W#C@PT( zGU(E3%@*?+4J4_)H^HoO)>3W}r-SNYN?YbJoe&+O5=6H~;dRdQ3iHjmY;@x0Dg>3= zOtcuZzT&i0O(j{bfqkaz1jlr>nAe=p1Za{2O9kJw3`NU1VOmp~Qu4&MIEkjm7<@UEU<=rCjjD$vYkWkhfG)3OGoUTi{L6n^K#AkmNHb4&H`?tqa)7hdn zBWtm_Se*RHFwW|_Y<(|SIm5OX>sXWtHb~mTr`9ENAWGgO8?cG|C9-;^@-l^5uoAm2n%5!04B# z_jA%PT4N3jRNHFV-}G!zIvZWjU}SCUH%;YX9i)pHN>wS_^x&IaYXx%k{t zqMTSBw0q7xI&7m?m^!(8#{JUW(z4%-OgkLZMwQahb zUYKSk!QQ|?(fRtebX9Fy}0 zHKXjYa%`b{8A4`=v9nk*?IS;_U{5SjmrRx?7p_#!+UNpf-{GiT^}X@n@^WIicMY9) z_1p}(ZF){J^u6f$gy6(R7a0g6?8uIH76+F`$|K8TcJIKWVH>?x`(m~AnLqme)bvqP z<&Yg8{uFI@w-Z!{3369ybSJ+Ex~PS9hOvhb+6`^Aw$USXx8L z@93PyFsy)2)cNUdK<|xHP%W=t=Vx`4x@V!y3Uyshf5VulVb0CEciq{84P$QpPY?w- zvxKi1FM`>Oe%i!o(@h)t+r)r>LxMpMfC*o9w~b&!FI4wh<18goHyG!jahkCHo>twV zQtJJCrSmCwq7#^9B*bNIH3_0 zPCS?tV3Rf_U2}I@gDSI4*YCp@^Exi7vChUjRYmI6E literal 0 HcmV?d00001 diff --git a/backend/models.py b/backend/models.py new file mode 100644 index 00000000..d886e52a --- /dev/null +++ b/backend/models.py @@ -0,0 +1,158 @@ +from datetime import datetime +from .app import db +from werkzeug.security import generate_password_hash, check_password_hash +from flask_jwt_extended import create_access_token, create_refresh_token + +# 用户角色关联表 +roles_users = db.Table('roles_users', + db.Column('user_id', db.Integer, db.ForeignKey('user.id'), primary_key=True), + db.Column('role_id', db.Integer, db.ForeignKey('role.id'), primary_key=True) +) + +# 角色权限关联表 +roles_permissions = db.Table('roles_permissions', + db.Column('role_id', db.Integer, db.ForeignKey('role.id'), primary_key=True), + db.Column('permission_id', db.Integer, db.ForeignKey('permission.id'), primary_key=True) +) + +class User(db.Model): + id = db.Column(db.Integer, primary_key=True) + username = db.Column(db.String(80), unique=True, nullable=False) + email = db.Column(db.String(120), unique=True, nullable=False) + password_hash = db.Column(db.String(128), nullable=False) + is_verified = db.Column(db.Boolean, default=False) + created_at = db.Column(db.DateTime, default=datetime.utcnow) + updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) + + # 关联角色 + roles = db.relationship('Role', secondary=roles_users, lazy='subquery', + backref=db.backref('users', lazy=True)) + + def __repr__(self): + return f'' + + def set_password(self, password): + self.password_hash = generate_password_hash(password) + + def check_password(self, password): + return check_password_hash(self.password_hash, password) + + def get_access_token(self): + return create_access_token(identity=self.id) + + def get_refresh_token(self): + return create_refresh_token(identity=self.id) + + def has_permission(self, permission_name): + for role in self.roles: + for permission in role.permissions: + if permission.name == permission_name: + return True + return False + +class Role(db.Model): + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(50), unique=True, nullable=False) + description = db.Column(db.String(255)) + created_at = db.Column(db.DateTime, default=datetime.utcnow) + updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) + + # 关联权限 + permissions = db.relationship('Permission', secondary=roles_permissions, lazy='subquery', + backref=db.backref('roles', lazy=True)) + + def __repr__(self): + return f'' + +class Permission(db.Model): + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(50), unique=True, nullable=False) + description = db.Column(db.String(255)) + created_at = db.Column(db.DateTime, default=datetime.utcnow) + + def __repr__(self): + return f'' + +class Log(db.Model): + id = db.Column(db.Integer, primary_key=True) + user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) + title = db.Column(db.String(100), nullable=False) + content = db.Column(db.Text, nullable=False) + tags = db.Column(db.String(255)) + importance = db.Column(db.Integer, default=1) # 1-5 + created_at = db.Column(db.DateTime, default=datetime.utcnow) + updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) + + # 关联用户 + user = db.relationship('User', backref=db.backref('logs', lazy=True)) + + def __repr__(self): + return f'' + +class Resource(db.Model): + id = db.Column(db.Integer, primary_key=True) + user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) + name = db.Column(db.String(100), nullable=False) + type = db.Column(db.String(50), nullable=False) # skin, model, etc. + file_path = db.Column(db.String(255), nullable=False) + thumbnail_path = db.Column(db.String(255)) + description = db.Column(db.String(255)) + is_active = db.Column(db.Boolean, default=False) + created_at = db.Column(db.DateTime, default=datetime.utcnow) + updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) + + # 关联用户 + user = db.relationship('User', backref=db.backref('resources', lazy=True)) + + def __repr__(self): + return f'' + +# 创建初始数据 +def create_initial_data(): + # 创建权限 + permissions = [ + ('log_create', 'Create log'), + ('log_read', 'Read log'), + ('log_update', 'Update log'), + ('log_delete', 'Delete log'), + ('resource_upload', 'Upload resource'), + ('resource_read', 'Read resource'), + ('resource_update', 'Update resource'), + ('resource_delete', 'Delete resource'), + ('user_manage', 'Manage user'), + ('role_manage', 'Manage role'), + ] + + for name, description in permissions: + permission = Permission.query.filter_by(name=name).first() + if not permission: + permission = Permission(name=name, description=description) + db.session.add(permission) + + # 创建角色 + admin_role = Role.query.filter_by(name='admin').first() + if not admin_role: + admin_role = Role(name='admin', description='Administrator role') + # 赋予所有权限 + admin_role.permissions = Permission.query.all() + db.session.add(admin_role) + + user_role = Role.query.filter_by(name='user').first() + if not user_role: + user_role = Role(name='user', description='Regular user role') + # 赋予基本权限 + user_role.permissions = Permission.query.filter_by(name.in_([ + 'log_create', 'log_read', 'log_update', 'log_delete', + 'resource_upload', 'resource_read', 'resource_update', 'resource_delete' + ])).all() + db.session.add(user_role) + + # 创建管理员用户 + admin_user = User.query.filter_by(username='admin').first() + if not admin_user: + admin_user = User(username='admin', email='admin@example.com', is_verified=True) + admin_user.set_password('admin123') + admin_user.roles.append(admin_role) + db.session.add(admin_user) + + db.session.commit() \ No newline at end of file diff --git a/backend/requirements.txt b/backend/requirements.txt new file mode 100644 index 00000000..3a7bc3bd --- /dev/null +++ b/backend/requirements.txt @@ -0,0 +1,9 @@ +Flask==2.3.3 +Flask-SQLAlchemy==3.0.5 +Flask-Migrate==4.0.5 +Flask-JWT-Extended==4.5.3 +Flask-CORS==4.0.0 +Werkzeug==2.3.7 +itsdangerous==2.1.2 +Pillow==10.0.0 +python-dotenv==1.0.0 \ No newline at end of file diff --git a/backend/routes/__init__.py b/backend/routes/__init__.py new file mode 100644 index 00000000..ece10e7e --- /dev/null +++ b/backend/routes/__init__.py @@ -0,0 +1,4 @@ +# 导入路由蓝图 +from .auth import bp as auth_bp +from .logs import bp as logs_bp +from .resources import bp as resources_bp \ No newline at end of file diff --git a/backend/routes/__pycache__/__init__.cpython-312.pyc b/backend/routes/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1f3c90b9824eb95255062485c55b65eb08b6f698 GIT binary patch literal 269 zcmX@j%ge<81iyPZGV6f!V-N=hn4pZ$8bHQ$h7^Vr#vF!R#waF62%8zmW`VL<(iDQn34+oG}&*lB$k$B6fpzEZn1-y@ks@@SaR~yi*K=mND!B^D784h zv?w{X_!bYG36xmL@EPQUU)C;GG0CNQX^DC1F#(k&8TokuIf;2C#bL!!`MF7%sl_o# ziOJcic_}eP`K2X5ZhU-ZUS>&ryk0@&FAkgB{FKt1RJ$TxpjjZd74rd!56p~=j87Tl P?=r~WW6&((1_}WHW~@tx literal 0 HcmV?d00001 diff --git a/backend/routes/__pycache__/auth.cpython-312.pyc b/backend/routes/__pycache__/auth.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2041980e5dcc7bbcecfdbff443b9625389dd14e4 GIT binary patch literal 12002 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 literal 0 HcmV?d00001 diff --git a/backend/routes/__pycache__/resources.cpython-312.pyc b/backend/routes/__pycache__/resources.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..83fbc294782d0d48c92c65b78c057737b17fc932 GIT binary patch literal 15438 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 literal 0 HcmV?d00001 diff --git a/backend/routes/auth.py b/backend/routes/auth.py new file mode 100644 index 00000000..855637d3 --- /dev/null +++ b/backend/routes/auth.py @@ -0,0 +1,224 @@ +from flask import Blueprint, request, jsonify +from backend.app import db +from backend.models import User, Role +from flask_jwt_extended import jwt_required, get_jwt_identity +from werkzeug.security import generate_password_hash, check_password_hash +from itsdangerous import URLSafeTimedSerializer +import smtplib +from email.mime.text import MIMEText +from email.mime.multipart import MIMEMultipart +import os + +bp = Blueprint('auth', __name__) + +# 配置邮箱 +MAIL_SERVER = os.environ.get('MAIL_SERVER') or 'smtp.gmail.com' +MAIL_PORT = int(os.environ.get('MAIL_PORT') or 587) +MAIL_USE_TLS = os.environ.get('MAIL_USE_TLS') or True +MAIL_USERNAME = os.environ.get('MAIL_USERNAME') or 'your-email@gmail.com' +MAIL_PASSWORD = os.environ.get('MAIL_PASSWORD') or 'your-email-password' +MAIL_DEFAULT_SENDER = os.environ.get('MAIL_DEFAULT_SENDER') or 'your-email@gmail.com' + +# 创建序列化器 +serializer = URLSafeTimedSerializer(os.environ.get('SECRET_KEY') or 'your-secret-key-here') + +@bp.route('/register', methods=['POST']) +def register(): + data = request.get_json() + + if not data or not data.get('username') or not data.get('email') or not data.get('password'): + return jsonify({'message': 'Missing required fields'}), 400 + + # 检查用户名是否已存在 + if User.query.filter_by(username=data.get('username')).first(): + return jsonify({'message': 'Username already exists'}), 409 + + # 检查邮箱是否已存在 + if User.query.filter_by(email=data.get('email')).first(): + return jsonify({'message': 'Email already exists'}), 409 + + # 创建新用户 + user = User( + username=data.get('username'), + email=data.get('email') + ) + user.set_password(data.get('password')) + + # 赋予默认角色 + default_role = Role.query.filter_by(name='user').first() + if default_role: + user.roles.append(default_role) + + db.session.add(user) + db.session.commit() + + # 发送验证邮件 + send_verification_email(user) + + return jsonify({'message': 'User created successfully. Please check your email to verify your account.'}), 201 + +@bp.route('/verify/', methods=['GET']) +def verify_email(token): + try: + email = serializer.loads(token, salt='email-verification-salt', max_age=3600) # 1小时有效期 + except: + return jsonify({'message': 'Invalid or expired token'}), 400 + + user = User.query.filter_by(email=email).first() + if not user: + return jsonify({'message': 'User not found'}), 404 + + if user.is_verified: + return jsonify({'message': 'Email already verified'}), 400 + + user.is_verified = True + db.session.commit() + + return jsonify({'message': 'Email verified successfully'}), 200 + +@bp.route('/login', methods=['POST']) +def login(): + data = request.get_json() + + if not data or not data.get('email') or not data.get('password'): + return jsonify({'message': 'Missing required fields'}), 400 + + user = User.query.filter_by(email=data.get('email')).first() + if not user or not user.check_password(data.get('password')): + return jsonify({'message': 'Invalid email or password'}), 401 + + if not user.is_verified: + return jsonify({'message': 'Please verify your email first'}), 403 + + # 创建访问令牌和刷新令牌 + access_token = user.get_access_token() + refresh_token = user.get_refresh_token() + + return jsonify({ + 'access_token': access_token, + 'refresh_token': refresh_token, + 'user': { + 'id': user.id, + 'username': user.username, + 'email': user.email, + 'roles': [role.name for role in user.roles] + } + }), 200 + +@bp.route('/refresh', methods=['POST']) +@jwt_required(refresh=True) +def refresh(): + current_user_id = get_jwt_identity() + user = User.query.get(current_user_id) + + if not user: + return jsonify({'message': 'User not found'}), 404 + + access_token = user.get_access_token() + + return jsonify({'access_token': access_token}), 200 + +@bp.route('/forgot-password', methods=['POST']) +def forgot_password(): + data = request.get_json() + + if not data or not data.get('email'): + return jsonify({'message': 'Missing required fields'}), 400 + + user = User.query.filter_by(email=data.get('email')).first() + if not user: + return jsonify({'message': 'User not found'}), 404 + + # 发送重置密码邮件 + send_password_reset_email(user) + + return jsonify({'message': 'Password reset email sent. Please check your email.'}), 200 + +@bp.route('/reset-password/', methods=['POST']) +def reset_password(token): + try: + email = serializer.loads(token, salt='password-reset-salt', max_age=3600) # 1小时有效期 + except: + return jsonify({'message': 'Invalid or expired token'}), 400 + + user = User.query.filter_by(email=email).first() + if not user: + return jsonify({'message': 'User not found'}), 404 + + data = request.get_json() + if not data or not data.get('password') or not data.get('confirm_password'): + return jsonify({'message': 'Missing required fields'}), 400 + + if data.get('password') != data.get('confirm_password'): + return jsonify({'message': 'Passwords do not match'}), 400 + + user.set_password(data.get('password')) + db.session.commit() + + return jsonify({'message': 'Password reset successfully'}), 200 + +@bp.route('/me', methods=['GET']) +@jwt_required() +def get_current_user(): + current_user_id = get_jwt_identity() + user = User.query.get(current_user_id) + + if not user: + return jsonify({'message': 'User not found'}), 404 + + return jsonify({ + 'id': user.id, + 'username': user.username, + 'email': user.email, + 'is_verified': user.is_verified, + 'roles': [role.name for role in user.roles], + 'created_at': user.created_at, + 'updated_at': user.updated_at + }), 200 + +# 发送验证邮件 +def send_verification_email(user): + token = serializer.dumps(user.email, salt='email-verification-salt') + verification_url = f'http://localhost:5000/api/auth/verify/{token}' + + subject = 'Verify your email for Plants vs Zombies' + body = f'Hi {user.username},\n\nPlease click the link below to verify your email:\n{verification_url}\n\nThis link will expire in 1 hour.\n\nThanks,\nThe Plants vs Zombies Team' + + send_email(user.email, subject, body) + +# 发送重置密码邮件 +def send_password_reset_email(user): + token = serializer.dumps(user.email, salt='password-reset-salt') + reset_url = f'http://localhost:5000/api/auth/reset-password/{token}' + + subject = 'Reset your password for Plants vs Zombies' + body = f"Hi {user.username},\n\nYou requested a password reset. Please click the link below to reset your password:\n{reset_url}\n\nThis link will expire in 1 hour.\n\nIf you didn't request this, you can safely ignore this email.\n\nThanks,\nThe Plants vs Zombies Team" + + send_email(user.email, subject, body) + +# 发送邮件 +def send_email(to, subject, body): + try: + # 创建邮件消息 + msg = MIMEMultipart() + msg['From'] = MAIL_DEFAULT_SENDER + msg['To'] = to + msg['Subject'] = subject + + # 添加邮件正文 + msg.attach(MIMEText(body, 'plain')) + + # 连接到SMTP服务器 + server = smtplib.SMTP(MAIL_SERVER, MAIL_PORT) + server.starttls() + server.login(MAIL_USERNAME, MAIL_PASSWORD) + + # 发送邮件 + server.send_message(msg) + + # 关闭连接 + server.quit() + except Exception as e: + print(f'Error sending email: {str(e)}') + # 在这里可以添加日志记录 + pass \ No newline at end of file diff --git a/backend/routes/logs.py b/backend/routes/logs.py new file mode 100644 index 00000000..11ff54ab --- /dev/null +++ b/backend/routes/logs.py @@ -0,0 +1,221 @@ +from flask import Blueprint, request, jsonify +from backend.app import db +from backend.models import Log, User +from flask_jwt_extended import jwt_required, get_jwt_identity +from datetime import datetime + +bp = Blueprint('logs', __name__) + +@bp.route('/logs', methods=['POST']) +@jwt_required() +def create_log(): + current_user_id = get_jwt_identity() + user = User.query.get(current_user_id) + + if not user: + return jsonify({'message': 'User not found'}), 404 + + if not user.has_permission('log_create'): + return jsonify({'message': 'Permission denied'}), 403 + + data = request.get_json() + + if not data or not data.get('title') or not data.get('content'): + return jsonify({'message': 'Missing required fields'}), 400 + + # 创建新日志 + log = Log( + user_id=current_user_id, + title=data.get('title'), + content=data.get('content'), + tags=data.get('tags'), + importance=data.get('importance', 1) + ) + + db.session.add(log) + db.session.commit() + + return jsonify({ + 'message': 'Log created successfully', + 'log': { + 'id': log.id, + 'title': log.title, + 'content': log.content, + 'tags': log.tags, + 'importance': log.importance, + 'created_at': log.created_at, + 'updated_at': log.updated_at + } + }), 201 + +@bp.route('/logs', methods=['GET']) +@jwt_required() +def get_logs(): + current_user_id = get_jwt_identity() + user = User.query.get(current_user_id) + + if not user: + return jsonify({'message': 'User not found'}), 404 + + if not user.has_permission('log_read'): + return jsonify({'message': 'Permission denied'}), 403 + + # 获取查询参数 + page = request.args.get('page', 1, type=int) + per_page = request.args.get('per_page', 10, type=int) + sort_by = request.args.get('sort_by', 'created_at') + sort_order = request.args.get('sort_order', 'desc') + tags = request.args.get('tags') + importance = request.args.get('importance', type=int) + start_date = request.args.get('start_date') + end_date = request.args.get('end_date') + + # 构建查询 + query = Log.query.filter_by(user_id=current_user_id) + + # 过滤标签 + if tags: + tag_list = tags.split(',') + for tag in tag_list: + query = query.filter(Log.tags.like(f'%{tag}%')) + + # 过滤重要程度 + if importance: + query = query.filter_by(importance=importance) + + # 过滤日期范围 + if start_date: + try: + start = datetime.strptime(start_date, '%Y-%m-%d') + query = query.filter(Log.created_at >= start) + except ValueError: + return jsonify({'message': 'Invalid start date format. Use YYYY-MM-DD'}), 400 + + if end_date: + try: + end = datetime.strptime(end_date, '%Y-%m-%d') + query = query.filter(Log.created_at <= end) + except ValueError: + return jsonify({'message': 'Invalid end date format. Use YYYY-MM-DD'}), 400 + + # 排序 + if sort_order == 'desc': + query = query.order_by(getattr(Log, sort_by).desc()) + else: + query = query.order_by(getattr(Log, sort_by).asc()) + + # 分页 + paginated_logs = query.paginate(page=page, per_page=per_page, error_out=False) + + # 格式化结果 + logs = [] + for log in paginated_logs.items: + logs.append({ + 'id': log.id, + 'title': log.title, + 'content': log.content, + 'tags': log.tags, + 'importance': log.importance, + 'created_at': log.created_at, + 'updated_at': log.updated_at + }) + + return jsonify({ + 'logs': logs, + 'total': paginated_logs.total, + 'pages': paginated_logs.pages, + 'current_page': paginated_logs.page + }), 200 + +@bp.route('/logs/', methods=['GET']) +@jwt_required() +def get_log(log_id): + current_user_id = get_jwt_identity() + user = User.query.get(current_user_id) + + if not user: + return jsonify({'message': 'User not found'}), 404 + + if not user.has_permission('log_read'): + return jsonify({'message': 'Permission denied'}), 403 + + log = Log.query.filter_by(id=log_id, user_id=current_user_id).first() + + if not log: + return jsonify({'message': 'Log not found'}), 404 + + return jsonify({ + 'id': log.id, + 'title': log.title, + 'content': log.content, + 'tags': log.tags, + 'importance': log.importance, + 'created_at': log.created_at, + 'updated_at': log.updated_at + }), 200 + +@bp.route('/logs/', methods=['PUT']) +@jwt_required() +def update_log(log_id): + current_user_id = get_jwt_identity() + user = User.query.get(current_user_id) + + if not user: + return jsonify({'message': 'User not found'}), 404 + + if not user.has_permission('log_update'): + return jsonify({'message': 'Permission denied'}), 403 + + log = Log.query.filter_by(id=log_id, user_id=current_user_id).first() + + if not log: + return jsonify({'message': 'Log not found'}), 404 + + data = request.get_json() + + # 更新日志信息 + if data.get('title'): + log.title = data.get('title') + if data.get('content'): + log.content = data.get('content') + if data.get('tags') is not None: + log.tags = data.get('tags') + if data.get('importance') is not None: + log.importance = data.get('importance') + + db.session.commit() + + return jsonify({ + 'message': 'Log updated successfully', + 'log': { + 'id': log.id, + 'title': log.title, + 'content': log.content, + 'tags': log.tags, + 'importance': log.importance, + 'created_at': log.created_at, + 'updated_at': log.updated_at + } + }), 200 + +@bp.route('/logs/', methods=['DELETE']) +@jwt_required() +def delete_log(log_id): + current_user_id = get_jwt_identity() + user = User.query.get(current_user_id) + + if not user: + return jsonify({'message': 'User not found'}), 404 + + if not user.has_permission('log_delete'): + return jsonify({'message': 'Permission denied'}), 403 + + log = Log.query.filter_by(id=log_id, user_id=current_user_id).first() + + if not log: + return jsonify({'message': 'Log not found'}), 404 + + db.session.delete(log) + db.session.commit() + + return jsonify({'message': 'Log deleted successfully'}), 200 \ No newline at end of file diff --git a/backend/routes/resources.py b/backend/routes/resources.py new file mode 100644 index 00000000..ac0e0013 --- /dev/null +++ b/backend/routes/resources.py @@ -0,0 +1,330 @@ +from flask import Blueprint, request, jsonify, send_from_directory +from backend.app import db +from backend.models import Resource, User +from flask_jwt_extended import jwt_required, get_jwt_identity +import os +from werkzeug.utils import secure_filename +from PIL import Image + +bp = Blueprint('resources', __name__) + +# 配置上传设置 +UPLOAD_FOLDER = os.path.join(os.path.abspath(os.path.dirname(__file__)), '..', 'uploads') +THUMBNAIL_FOLDER = os.path.join(UPLOAD_FOLDER, 'thumbnails') +ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif', 'bmp', 'obj', 'fbx', 'glb', 'gltf'} +MAX_CONTENT_LENGTH = 100 * 1024 * 1024 # 100MB + +# 创建上传目录 +if not os.path.exists(UPLOAD_FOLDER): + os.makedirs(UPLOAD_FOLDER) +if not os.path.exists(THUMBNAIL_FOLDER): + os.makedirs(THUMBNAIL_FOLDER) + +# 检查文件扩展名是否允许 +def allowed_file(filename): + return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS + +# 生成缩略图 +def generate_thumbnail(image_path, thumbnail_path, size=(150, 150)): + try: + with Image.open(image_path) as img: + img.thumbnail(size) + img.save(thumbnail_path) + return True + except Exception as e: + print(f'Error generating thumbnail: {str(e)}') + return False + +@bp.route('/resources', methods=['POST']) +@jwt_required() +def upload_resource(): + current_user_id = get_jwt_identity() + user = User.query.get(current_user_id) + + if not user: + return jsonify({'message': 'User not found'}), 404 + + if not user.has_permission('resource_upload'): + return jsonify({'message': 'Permission denied'}), 403 + + # 检查请求中是否包含文件部分 + if 'file' not in request.files: + return jsonify({'message': 'No file part'}), 400 + + file = request.files['file'] + + # 如果用户没有选择文件,浏览器会提交一个空文件 + if file.filename == '': + return jsonify({'message': 'No selected file'}), 400 + + # 检查文件类型 + if not allowed_file(file.filename): + return jsonify({'message': 'File type not allowed'}), 400 + + # 获取其他表单数据 + name = request.form.get('name', file.filename.rsplit('.', 1)[0]) + type = request.form.get('type', 'other') + description = request.form.get('description', '') + + # 安全地保存文件名 + filename = secure_filename(file.filename) + + # 为每个用户创建单独的目录 + user_upload_folder = os.path.join(UPLOAD_FOLDER, str(current_user_id)) + if not os.path.exists(user_upload_folder): + os.makedirs(user_upload_folder) + + # 保存文件 + file_path = os.path.join(user_upload_folder, filename) + file.save(file_path) + + # 生成缩略图(仅对图片文件) + thumbnail_path = None + if filename.rsplit('.', 1)[1].lower() in {'png', 'jpg', 'jpeg', 'gif', 'bmp'}: + thumbnail_filename = f'thumbnail_{filename}' + thumbnail_path = os.path.join(THUMBNAIL_FOLDER, str(current_user_id), thumbnail_filename) + + # 为每个用户创建单独的缩略图目录 + user_thumbnail_folder = os.path.join(THUMBNAIL_FOLDER, str(current_user_id)) + if not os.path.exists(user_thumbnail_folder): + os.makedirs(user_thumbnail_folder) + + # 生成缩略图 + generate_thumbnail(file_path, thumbnail_path) + + # 创建资源记录 + resource = Resource( + user_id=current_user_id, + name=name, + type=type, + file_path=file_path, + thumbnail_path=thumbnail_path, + description=description + ) + + db.session.add(resource) + db.session.commit() + + return jsonify({ + 'message': 'Resource uploaded successfully', + 'resource': { + 'id': resource.id, + 'name': resource.name, + 'type': resource.type, + 'description': resource.description, + 'is_active': resource.is_active, + 'created_at': resource.created_at, + 'updated_at': resource.updated_at + } + }), 201 + +@bp.route('/resources', methods=['GET']) +@jwt_required() +def get_resources(): + current_user_id = get_jwt_identity() + user = User.query.get(current_user_id) + + if not user: + return jsonify({'message': 'User not found'}), 404 + + if not user.has_permission('resource_read'): + return jsonify({'message': 'Permission denied'}), 403 + + # 获取查询参数 + page = request.args.get('page', 1, type=int) + per_page = request.args.get('per_page', 10, type=int) + sort_by = request.args.get('sort_by', 'created_at') + sort_order = request.args.get('sort_order', 'desc') + type = request.args.get('type') + is_active = request.args.get('is_active', type=bool) + + # 构建查询 + query = Resource.query.filter_by(user_id=current_user_id) + + # 过滤类型 + if type: + query = query.filter_by(type=type) + + # 过滤激活状态 + if is_active is not None: + query = query.filter_by(is_active=is_active) + + # 排序 + if sort_order == 'desc': + query = query.order_by(getattr(Resource, sort_by).desc()) + else: + query = query.order_by(getattr(Resource, sort_by).asc()) + + # 分页 + paginated_resources = query.paginate(page=page, per_page=per_page, error_out=False) + + # 格式化结果 + resources = [] + for resource in paginated_resources.items: + resources.append({ + 'id': resource.id, + 'name': resource.name, + 'type': resource.type, + 'description': resource.description, + 'is_active': resource.is_active, + 'created_at': resource.created_at, + 'updated_at': resource.updated_at + }) + + return jsonify({ + 'resources': resources, + 'total': paginated_resources.total, + 'pages': paginated_resources.pages, + 'current_page': paginated_resources.page + }), 200 + +@bp.route('/resources/', methods=['GET']) +@jwt_required() +def get_resource(resource_id): + current_user_id = get_jwt_identity() + user = User.query.get(current_user_id) + + if not user: + return jsonify({'message': 'User not found'}), 404 + + if not user.has_permission('resource_read'): + return jsonify({'message': 'Permission denied'}), 403 + + resource = Resource.query.filter_by(id=resource_id, user_id=current_user_id).first() + + if not resource: + return jsonify({'message': 'Resource not found'}), 404 + + return jsonify({ + 'id': resource.id, + 'name': resource.name, + 'type': resource.type, + 'description': resource.description, + 'is_active': resource.is_active, + 'created_at': resource.created_at, + 'updated_at': resource.updated_at + }), 200 + +@bp.route('/resources/', methods=['PUT']) +@jwt_required() +def update_resource(resource_id): + current_user_id = get_jwt_identity() + user = User.query.get(current_user_id) + + if not user: + return jsonify({'message': 'User not found'}), 404 + + if not user.has_permission('resource_update'): + return jsonify({'message': 'Permission denied'}), 403 + + resource = Resource.query.filter_by(id=resource_id, user_id=current_user_id).first() + + if not resource: + return jsonify({'message': 'Resource not found'}), 404 + + data = request.form + + # 更新资源信息 + if data.get('name'): + resource.name = data.get('name') + if data.get('type'): + resource.type = data.get('type') + if data.get('description') is not None: + resource.description = data.get('description') + if data.get('is_active') is not None: + resource.is_active = data.get('is_active') == 'true' or data.get('is_active') == True + + # 检查是否有新文件上传 + if 'file' in request.files: + file = request.files['file'] + if file.filename != '' and allowed_file(file.filename): + # 删除旧文件 + if os.path.exists(resource.file_path): + os.remove(resource.file_path) + + # 删除旧缩略图 + if resource.thumbnail_path and os.path.exists(resource.thumbnail_path): + os.remove(resource.thumbnail_path) + + # 保存新文件 + filename = secure_filename(file.filename) + user_upload_folder = os.path.join(UPLOAD_FOLDER, str(current_user_id)) + file_path = os.path.join(user_upload_folder, filename) + file.save(file_path) + resource.file_path = file_path + + # 生成新的缩略图(仅对图片文件) + if filename.rsplit('.', 1)[1].lower() in {'png', 'jpg', 'jpeg', 'gif', 'bmp'}: + thumbnail_filename = f'thumbnail_{filename}' + thumbnail_path = os.path.join(THUMBNAIL_FOLDER, str(current_user_id), thumbnail_filename) + generate_thumbnail(file_path, thumbnail_path) + resource.thumbnail_path = thumbnail_path + else: + resource.thumbnail_path = None + + db.session.commit() + + return jsonify({ + 'message': 'Resource updated successfully', + 'resource': { + 'id': resource.id, + 'name': resource.name, + 'type': resource.type, + 'description': resource.description, + 'is_active': resource.is_active, + 'created_at': resource.created_at, + 'updated_at': resource.updated_at + } + }), 200 + +@bp.route('/resources/', methods=['DELETE']) +@jwt_required() +def delete_resource(resource_id): + current_user_id = get_jwt_identity() + user = User.query.get(current_user_id) + + if not user: + return jsonify({'message': 'User not found'}), 404 + + if not user.has_permission('resource_delete'): + return jsonify({'message': 'Permission denied'}), 403 + + resource = Resource.query.filter_by(id=resource_id, user_id=current_user_id).first() + + if not resource: + return jsonify({'message': 'Resource not found'}), 404 + + # 删除文件 + if os.path.exists(resource.file_path): + os.remove(resource.file_path) + + # 删除缩略图 + if resource.thumbnail_path and os.path.exists(resource.thumbnail_path): + os.remove(resource.thumbnail_path) + + db.session.delete(resource) + db.session.commit() + + return jsonify({'message': 'Resource deleted successfully'}), 200 + +@bp.route('/resources//download', methods=['GET']) +@jwt_required() +def download_resource(resource_id): + current_user_id = get_jwt_identity() + user = User.query.get(current_user_id) + + if not user: + return jsonify({'message': 'User not found'}), 404 + + if not user.has_permission('resource_read'): + return jsonify({'message': 'Permission denied'}), 403 + + resource = Resource.query.filter_by(id=resource_id, user_id=current_user_id).first() + + if not resource: + return jsonify({'message': 'Resource not found'}), 404 + + if not os.path.exists(resource.file_path): + return jsonify({'message': 'File not found'}), 404 + + return send_from_directory(os.path.dirname(resource.file_path), os.path.basename(resource.file_path), as_attachment=True) \ No newline at end of file diff --git a/ke b/ke new file mode 100644 index 00000000..e69de29b diff --git a/source/__pycache__/constants.cpython-312.pyc b/source/__pycache__/constants.cpython-312.pyc index 929394fe14d6507593451367c86e1965f411d322..6fcfa450a2d5af97140a102e3bb5c5551e2cbcd3 100644 GIT binary patch delta 22 ccmeyV`cswnG%qg~0}wFkv1e*- 3000: - # 释放花粉攻击范围内的所有僵尸 - for i in range(-self.attack_range, self.attack_range + 1): - tmp_y = self.map_y + i - if tmp_y < 0 or tmp_y >= c.GRID_Y_LEN: - continue - for zombie in self.zombie_groups[tmp_y]: - if self.canAttack(zombie): - zombie.setDamage(c.RED_PEONY_POLLEN_DAMAGE) - self.shoot_timer = self.current_time - - def canAttack(self, zombie): - if (self.state != c.SLEEP and zombie.state != c.DIE and - self.rect.x <= zombie.rect.right and - self.rect.right + c.GRID_X_SIZE * 2 >= zombie.rect.x): - return True - return False - -class FirecrackerPlant(Plant): - """鞭炮花 - 中国红主题植物,能够发射鞭炮攻击僵尸""" - def __init__(self, x, y, bullet_group): - Plant.__init__(self, x, y, c.FIRECRACKER_PLANT, c.FIRECRACKER_PLANT_HEALTH, bullet_group) - self.shoot_timer = 0 - self.bullet_speed = 6 - - def attacking(self): - if (self.current_time - self.shoot_timer) > 2500: - # 发射鞭炮子弹 - self.bullet_group.add(Bullet(self.rect.right, self.rect.y, self.rect.y, - c.BULLET_PEA, c.FIRECRACKER_DAMAGE, False)) - self.shoot_timer = self.current_time - -class LionDancePea(Plant): - """舞狮豌豆 - 中国红主题植物,能够发射具有击退效果的豌豆""" - def __init__(self, x, y, bullet_group): - Plant.__init__(self, x, y, c.LION_DANCE_PEA, c.LION_DANCE_PEA_HEALTH, bullet_group) - self.shoot_timer = 0 - - def attacking(self): - if (self.current_time - self.shoot_timer) > 3000: - # 发射具有击退效果的豌豆 - bullet = Bullet(self.rect.right, self.rect.y, self.rect.y, - c.BULLET_PEA, c.LION_DANCE_PEA_DAMAGE, False) - bullet.knockback = c.LION_DANCE_PEA_KNOCKBACK - self.bullet_group.add(bullet) - self.shoot_timer = self.current_time - class WallNutBowling(Plant): def __init__(self, x, y, map_y, level): Plant.__init__(self, x, y, c.WALLNUTBOWLING, 1, None) diff --git a/source/component/zombie.py b/source/component/zombie.py index 133c004f..73780f13 100644 --- a/source/component/zombie.py +++ b/source/component/zombie.py @@ -411,148 +411,4 @@ def loadImages(self): color = c.WHITE self.loadFrames(frame_list[i], name, tool.ZOMBIE_RECT[name]['x'], color) - self.frames = self.helmet_walk_frames - -# Chinese Red Theme Zombies -class LanternZombie(Zombie): - """灯笼僵尸 - 中国红主题僵尸,手持灯笼照亮周围区域""" - def __init__(self, x, y, head_group): - Zombie.__init__(self, x, y, c.LANTERN_ZOMBIE, c.LANTERN_ZOMBIE_HEALTH, head_group) - self.speed = c.LANTERN_ZOMBIE_SPEED - self.lantern_light_range = c.LANTERN_LIGHT_RANGE - self.lantern_on = True - - def loadImages(self): - self.walk_frames = [] - self.attack_frames = [] - self.losthead_walk_frames = [] - self.losthead_attack_frames = [] - self.die_frames = [] - self.boomdie_frames = [] - - walk_name = self.name - attack_name = self.name + 'Attack' - losthead_walk_name = self.name + 'LostHead' - losthead_attack_name = self.name + 'LostHeadAttack' - die_name = c.NORMAL_ZOMBIE + 'Die' - boomdie_name = c.BOOMDIE - - frame_list = [self.walk_frames, self.attack_frames, self.losthead_walk_frames, - self.losthead_attack_frames, self.die_frames, self.boomdie_frames] - name_list = [walk_name, attack_name, losthead_walk_name, - losthead_attack_name, die_name, boomdie_name] - - for i, name in enumerate(name_list): - self.loadFrames(frame_list[i], name, tool.ZOMBIE_RECT[name]['x']) - - self.frames = self.walk_frames - -class LionDanceZombie(Zombie): - """舞狮僵尸 - 中国红主题僵尸,穿着舞狮服装,具有较高的生命值和攻击力""" - def __init__(self, x, y, head_group): - Zombie.__init__(self, x, y, c.LION_DANCE_ZOMBIE, c.LION_DANCE_ZOMBIE_HEALTH, head_group) - self.speed = c.LION_DANCE_ZOMBIE_SPEED - self.damage = c.LION_DANCE_ATTACK_DAMAGE - self.helmet = True - - def loadImages(self): - self.helmet_walk_frames = [] - self.helmet_attack_frames = [] - self.walk_frames = [] - self.attack_frames = [] - self.losthead_walk_frames = [] - self.losthead_attack_frames = [] - self.die_frames = [] - self.boomdie_frames = [] - - helmet_walk_name = self.name - helmet_attack_name = self.name + 'Attack' - walk_name = self.name + 'NoCostume' - attack_name = self.name + 'NoCostumeAttack' - losthead_walk_name = self.name + 'LostHead' - losthead_attack_name = self.name + 'LostHeadAttack' - die_name = self.name + 'Die' - boomdie_name = c.BOOMDIE - - frame_list = [self.helmet_walk_frames, self.helmet_attack_frames, - self.walk_frames, self.attack_frames, self.losthead_walk_frames, - self.losthead_attack_frames, self.die_frames, self.boomdie_frames] - name_list = [helmet_walk_name, helmet_attack_name, - walk_name, attack_name, losthead_walk_name, - losthead_attack_name, die_name, boomdie_name] - - for i, name in enumerate(name_list): - self.loadFrames(frame_list[i], name, tool.ZOMBIE_RECT[name]['x']) - - self.frames = self.helmet_walk_frames - -class ChineseNewYearZombie(Zombie): - """春节僵尸 - 中国红主题僵尸,穿着春节服装,死亡时掉落金币""" - def __init__(self, x, y, head_group, gold_group): - Zombie.__init__(self, x, y, c.CHINESE_NEW_YEAR_ZOMBIE, c.CHINESE_NEW_YEAR_ZOMBIE_HEALTH, head_group) - self.speed = c.CHINESE_NEW_YEAR_ZOMBIE_SPEED - self.gold_group = gold_group - self.gold_drop = c.CHINESE_NEW_YEAR_GOLD_DROP - - def loadImages(self): - self.walk_frames = [] - self.attack_frames = [] - self.losthead_walk_frames = [] - self.losthead_attack_frames = [] - self.die_frames = [] - self.boomdie_frames = [] - - walk_name = self.name - attack_name = self.name + 'Attack' - losthead_walk_name = self.name + 'LostHead' - losthead_attack_name = self.name + 'LostHeadAttack' - die_name = self.name + 'Die' - boomdie_name = c.BOOMDIE - - frame_list = [self.walk_frames, self.attack_frames, self.losthead_walk_frames, - self.losthead_attack_frames, self.die_frames, self.boomdie_frames] - name_list = [walk_name, attack_name, losthead_walk_name, - losthead_attack_name, die_name, boomdie_name] - - for i, name in enumerate(name_list): - self.loadFrames(frame_list[i], name, tool.ZOMBIE_RECT[name]['x']) - - self.frames = self.walk_frames - - def dying(self): - # 死亡时掉落金币 - if not self.dead: - for i in range(self.gold_drop): - gold = Gold(self.rect.centerx, self.rect.y) - self.gold_group.add(gold) - self.dead = True - super().dying() - -class Gold(pg.sprite.Sprite): - """金币类 - 春节僵尸死亡时掉落""" - def __init__(self, x, y): - pg.sprite.Sprite.__init__(self) - self.image = tool.get_image(tool.GFX['Gold'], 0, 0, 30, 30, c.BLACK, 1) - self.rect = self.image.get_rect() - self.rect.centerx = x - self.rect.y = y - self.y_vel = -2 # 金币向上飘 - self.gravity = 0.1 # 重力效果 - self.live_time = 5000 # 金币存在时间 - self.created_time = pg.time.get_ticks() - - def update(self, game_info): - # 金币向上飘然后下落 - self.y_vel += self.gravity - self.rect.y += self.y_vel - - # 金币存在时间结束后消失 - if pg.time.get_ticks() - self.created_time > self.live_time: - self.kill() - - def checkCollision(self, x, y): - if(x >= self.rect.x and x <= self.rect.right and - y >= self.rect.y and y <= self.rect.bottom): - self.kill() - return True - return False \ No newline at end of file + self.frames = self.helmet_walk_frames \ No newline at end of file diff --git a/source/constants.py b/source/constants.py index b4950f25..0c4e2b6e 100644 --- a/source/constants.py +++ b/source/constants.py @@ -93,10 +93,6 @@ HYPNOSHROOM = 'HypnoShroom' WALLNUTBOWLING = 'WallNutBowling' REDWALLNUTBOWLING = 'RedWallNutBowling' -# Chinese Red Theme Plants -RED_PEONY = 'RedPeony' # 红牡丹 -FIRECRACKER_PLANT = 'FirecrackerPlant' # 鞭炮花 -LION_DANCE_PEA = 'LionDancePea' # 舞狮豌豆 PLANT_HEALTH = 5 WALLNUT_HEALTH = 30 @@ -104,15 +100,6 @@ WALLNUT_CRACKED2_HEALTH = 10 WALLNUT_BOWLING_DAMAGE = 10 -# Chinese Red Theme Plant Properties -RED_PEONY_HEALTH = 15 # 红牡丹生命值 -RED_PEONY_POLLEN_DAMAGE = 3 # 红牡丹花粉伤害 -FIRECRACKER_PLANT_HEALTH = 10 # 鞭炮花生命值 -FIRECRACKER_DAMAGE = 2 # 鞭炮伤害 -LION_DANCE_PEA_HEALTH = 5 # 舞狮豌豆生命值 -LION_DANCE_PEA_DAMAGE = 4 # 舞狮豌豆伤害 -LION_DANCE_PEA_KNOCKBACK = 50 # 舞狮豌豆击退效果 - PRODUCE_SUN_INTERVAL = 7000 FLOWER_SUN_INTERVAL = 22000 SUN_LIVE_TIME = 7000 @@ -142,10 +129,6 @@ CARD_ICESHROOM = 'card_iceshroom' CARD_HYPNOSHROOM = 'card_hypnoshroom' CARD_REDWALLNUT = 'card_redwallnut' -# Chinese Red Theme Plant Cards -CARD_RED_PEONY = 'card_redpeony' -CARD_FIRECRACKER_PLANT = 'card_firecrackerplant' -CARD_LION_DANCE_PEA = 'card_liondancepea' #BULLET INFO BULLET_PEA = 'PeaNormal' @@ -162,10 +145,6 @@ FLAG_ZOMBIE = 'FlagZombie' NEWSPAPER_ZOMBIE = 'NewspaperZombie' BOOMDIE = 'BoomDie' -# Chinese Red Theme Zombies -LANTERN_ZOMBIE = 'LanternZombie' # 灯笼僵尸 -LION_DANCE_ZOMBIE = 'LionDanceZombie' # 舞狮僵尸 -CHINESE_NEW_YEAR_ZOMBIE = 'ChineseNewYearZombie' # 春节僵尸 LOSTHEAD_HEALTH = 5 NORMAL_HEALTH = 10 @@ -174,17 +153,6 @@ BUCKETHEAD_HEALTH = 30 NEWSPAPER_HEALTH = 15 -# Chinese Red Theme Zombie Properties -LANTERN_ZOMBIE_HEALTH = 15 # 灯笼僵尸生命值 -LANTERN_ZOMBIE_SPEED = 1.2 # 灯笼僵尸移动速度 -LANTERN_LIGHT_RANGE = 3 # 灯笼照亮范围 -LION_DANCE_ZOMBIE_HEALTH = 30 # 舞狮僵尸生命值 -LION_DANCE_ZOMBIE_SPEED = 0.8 # 舞狮僵尸移动速度 -LION_DANCE_ATTACK_DAMAGE = 2 # 舞狮僵尸攻击伤害 -CHINESE_NEW_YEAR_ZOMBIE_HEALTH = 25 # 春节僵尸生命值 -CHINESE_NEW_YEAR_ZOMBIE_SPEED = 1.0 # 春节僵尸移动速度 -CHINESE_NEW_YEAR_GOLD_DROP = 5 # 春节僵尸掉落金币数量 - ATTACK_INTERVAL = 1000 ZOMBIE_WALK_INTERVAL = 70 diff --git a/source/state/__pycache__/level.cpython-312.pyc b/source/state/__pycache__/level.cpython-312.pyc index a25d4b94f6b99db34505685d266ecace4396ab5f..2e9dd927a1331b5096b3f73239783573cde22c7b 100644 GIT binary patch delta 25 fcmX@Qk?H70Cce|Wyj%=Gz^KQbDH*