import bge # Blender Game Engine (UPBGE) import aud # Sounds import threading # Multithreading import trace import sys import time import math import mathutils import random ############################################################################### # ct_lib.py # @title: User library # @project: CodeTower # @lang: fr,en # @authors: Philippe Roy # @copyright: Copyright (C) 2022 Philippe Roy # @license: GNU GPL # # This game is a tower defense coding game. The towers are driven by Python code. # Ce simulateur est un jeu du type tower defense où les tours sont à piloter par la programmation Python. # # Commands trigged by button : cmd_* # Commands trigged by 3D scene objects : scn_* # Commands trigged by user (student or map designer) : ct_* # 3D scene manipulation : manip_* # ############################################################################### scene = bge.logic.getCurrentScene() # Colors tower_purple = [0.202, 0.114, 0.521,1] tower_turquoise = [0.051, 0.270, 0.279,1] tower_magenta = [0.799, 0.005, 0.314,1] tower_orange = [0.799, 0.130, 0.063,1] tower_yellow = [0.799, 0.617, 0.021, 1] tower_green = [0.246, 0.687, 0.078, 1] tower_red = [0.799, 0.031, 0.038, 1] tower_blue = [0.127, 0.456, 1.000, 1] tower_black = [0, 0, 0, 1] ray_yellow = [0.799, 0.617, 0.021, 1] # [0.8, 0.619, 0.021]) ray_blue = [0.127, 0.456, 1.000, 1] ray_black = [0, 0, 0, 1] color_kaykit_black = [0.019, 0.032, 0.037, 1] # Sounds audiodev = aud.Device() snd_build = aud.Sound('asset/sounds/build.wav') sndbuff_build = aud.Sound.cache(snd_build) snd_archer = aud.Sound('asset/sounds/archer.wav') sndbuff_archer = aud.Sound.cache(snd_archer) snd_mage = aud.Sound('asset/sounds/mage.wav') sndbuff_mage = aud.Sound.cache(snd_mage) snd_life = aud.Sound('asset/sounds/life.ogg') sndbuff_life = aud.Sound.cache(snd_life) ############################################################################### # Données générales ############################################################################### def ct_level_current(): return scene.objects['Points']['level'] def ct_level(): return scene.objects['Points']['level_max'] ############################################################################### # Méthode kill pour les tâches (threads) ############################################################################### class thread_with_trace(threading.Thread): def __init__(self, *args, **keywords): threading.Thread.__init__(self, *args, **keywords) self.killed = False def start(self): self.__run_backup = self.run self.run = self.__run threading.Thread.start(self) def __run(self): sys.settrace(self.globaltrace) self.__run_backup() self.run = self.__run_backup def globaltrace(self, frame, event, arg): if event == 'call': return self.localtrace else: return None def localtrace(self, frame, event, arg): if self.killed: if event == 'line': raise SystemExit() return self.localtrace def kill(self): self.killed = True ############################################################################### # Start et stop des tâches (threads) ############################################################################### def thread_start(threads, type_txt, fct): threads.append(thread_with_trace(target = fct)) threads[len(threads)-1].start() print ("Thread",type_txt, "#", len(threads)-1, "open.") def thread_stop(threads, type_txt): i=0 zombie_flag=False for t in threads: if not t.is_alive(): print ("Thread",type_txt, "#",i,"closed.") else: print ("Thread",type_txt, "#",i,"still open ...") t.kill() t.join() if not t.is_alive(): print ("Thread",type_txt, "#",i,"killed.") else: print ("Thread",type_txt, "#",i,"zombie...") zombie_flag=True i +=1 if zombie_flag==False: print ("All threads",type_txt, "are closed.") return True else: print ("There are zombies threads",type_txt, ".") return False ############################################################################### # Vagues (minions) ############################################################################### # Minion caracteristics : category (class), level, hp, speed, armor, bounty, lifes_damage # Minion 3d object : body (male,female,old, ...), variante (A,B,C,D, ...), level # Création d'un minion def ct_minion(x,y,cat,level): category=cat+"-lv"+str(level) minion_3d= scene.objects['Terrain']['minion_3d'] body = random.choice(minion_3d[category][0])+"_"+random.choice(minion_3d[category][1])+"_"+random.choice(minion_3d[category][2]) ct_minion_details(x,y,cat,level,body) # Création d'un minion détaillée def ct_minion_details(x,y,cat,level,body="Knight_m_A_common"): category=cat+"-lv"+str(level) # Pause while scene.objects['Terrain']['run'] == False: time.sleep(0.01) # Object 3D minion= scene.addObject(body, scene.objects['Terrain']) minion.worldScale=[0.25,0.25,0.25] minion.worldPosition=[x,y,0.1] scene.objects['Terrain']['idm']=scene.objects['Terrain']['idm']+1 minion.name="wm("+str(scene.objects['Terrain']['idm'])+")" # Wave minion (wm), identifier minion (idm) scene.objects['Points']['minions'] +=1 scene.objects['Points']['minions_run'] +=1 # Caracteristics minion_carac= scene.objects['Terrain']['minion_carac'] minion['cat']=minion_carac[category][0] minion['level']=minion_carac[category][1] minion['hp']=minion_carac[category][2] minion['speed']=minion_carac[category][3] minion['speed_base']=minion_carac[category][3] minion['armor']=minion_carac[category][4] minion['bounty']=minion_carac[category][5] minion['lifes_damage']=minion_carac[category][6] minion['buff']=[] minion['resist']=[] # Actuator Steering minion.actuators['Steering'].navmesh=scene.objects[scene.objects['Terrain']['navmesh']] minion.actuators['Steering'].target=scene.objects[scene.objects['Terrain']['endtile']] minion.actuators['Steering'].distance=0.5 minion.actuators['Steering'].velocity=minion['speed_base']*scene.objects['Terrain']['speed'] # Destruction d'un minion def scn_minion_dead(cont): obj = cont.owner scene.objects['Points']['minions'] -=1 scene.objects['Points']['minions_run'] -=1 scene.objects['Points']['kills'] +=1 scene.objects['Points']['coins']= scene.objects['Points']['coins']+obj['bounty'] obj.endObject() ############################################################################### # Spells ############################################################################### # Buff/debuff Minion def scn_minion_affect(cont): obj = cont.owner # print (obj.name, obj['buff']) slow_state=False # Etats actif for debuff_i in obj['buff']: if debuff_i[1] <= 0: obj['buff'].remove(debuff_i) continue if debuff_i[0] == "slow": slow_state=True debuff_i[1] -= scene.objects['Terrain']['speed'] # Effets if slow_state: obj.actuators['Steering'].velocity =(obj['speed_base']*scene.objects['Terrain']['speed'])/2 else: obj.actuators['Steering'].velocity = obj['speed_base']*scene.objects['Terrain']['speed'] ############################################################################### # Tours ############################################################################### # Tower caracteristics : category (class), damage, speed, range # Création d'une tour def ct_build(x,y, cat='Archer tower', tower_name="Tower", color=tower_purple, tower_3d="square-A"): tower_minion_3d= scene.objects['Terrain']['tower_minion_3d'] if cat=='Archer tower': # Archer category="Archer-lv1" if cat=='Mage tower': # Mage category="Mage-lv1" body = random.choice(tower_minion_3d[category][0])+"_"+random.choice(tower_minion_3d[category][1])+"_"+random.choice(tower_minion_3d[category][2]) ct_build_details(x,y, cat, tower_name, color, tower_3d, body) # Création d'une tour détaillée def ct_build_details(x,y, cat='Archer tower', tower_name="Tower", color=tower_purple, tower_3d="square-A", body="Archer_m_A_common"): # Vérification de la place if [x,y] in scene.objects['Terrain']['scene_tile_noncontruct'] or [x,y] in scene.objects['Terrain']['scene_tile_tower']: return False # Vérification du niveau scene.objects['Points']['level']= scene.objects['Points']['level'] + 1 if scene.objects['Points']['level'] > scene.objects['Points']['level_max'] : tour= scene.addObject("Tower_error", scene.objects['Terrain']) tour.worldPosition=[x,y,0.2] tour.worldScale=[1,1,1] scene.objects['Terrain']['scene_tile_tower'].append([x,y]) return False # Objets 3D tour= scene.addObject('Tower-'+tower_3d, scene.objects['Terrain']) tour.color = color tour.worldPosition=[x,y,0.2] tour.worldScale=[1,1,1] scene.objects['Terrain']['scene_tile_tower'].append([x,y]) tower_minion= scene.addObject(body, scene.objects['Terrain']) tower_minion["type_towerminion"]=False tower_minion.name="tm("+str(x)+','+str(y)+")" # Tower minion (tm) tower_minion.worldPosition=[x,y,1] tower_minion.worldScale=[0.25,0.25,0.25] # Sounds audiodev.play(sndbuff_build) # Caracteristics tower_carac= scene.objects['Terrain']['tower_carac'] tour['cat']=tower_carac[cat][0] tour['tower_name']=tower_name tour['xp']=0 tour['lvl_current']=1 tour['lvl']=1 tour['damage']=tower_carac[cat][1] tour['speed']=tower_carac[cat][2] tour['range']=tower_carac[cat][3] tour['techno']=[] tour['cast']="slow" tour['cast_duration']=2 # Capteur Near tour.sensors['Near'].distance=tour['range'] tour.sensors['Near'].skippedTicks =round(1/(tour['speed']*scene.objects['Terrain']['speed'])) return True # Suppression d'une tour def ct_remove(x,y): for obj_i in scene.objects: if "type_tower" in obj_i.getPropertyNames(): if x == obj_i.worldPosition.x and y == obj_i.worldPosition.y: scene.objects["tm("+str(round(obj_i.worldPosition.x))+','+str(round(obj_i.worldPosition.y))+")"].endObject() obj_i.endObject() scene.objects['Points']['level']= scene.objects['Points']['level'] - 1 # Réaction d'une tour def scn_tower_near(cont): obj = cont.owner sensor = obj.sensors['Near'] # Tir # FIXME: tir sur le plus avancé if sensor.positive and len(sensor.hitObjectList)>0 and scene.objects['Terrain']['run']==True : target=sensor.hitObjectList[0] # Orientation du tower minion towerminion="tm("+str(round(obj.worldPosition.x))+','+str(round(obj.worldPosition.y))+")" angle =math.atan((target.worldPosition.y-obj.worldPosition.y)/(target.worldPosition.x-obj.worldPosition.x)) if target.worldPosition.x>obj.worldPosition.x: angle2=math.pi/2+angle-scene.objects[towerminion].worldOrientation.to_euler().z angle3=angle else: angle2=math.pi+math.pi/2+angle-scene.objects[towerminion].worldOrientation.to_euler().z angle3=math.pi+angle scene.objects[towerminion].applyRotation((0, 0, angle2), False) # Bullet (3d object) (vitesse lente) # -> tendance au plantage # if scene.objects['Terrain']['speed']<1: # bullet= scene.addObject("Bullet", scene.objects['Terrain']) # bullet.mass=0.001 # bullet.applyForce=((0,0,9.81),True) # bullet.worldPosition=[obj.worldPosition.x,obj.worldPosition.y,1.5] # bullet.worldScale=[0.75,0.75,0.75] # # bullet.worldScale=[0.5,0.5,0.5] # bullet.worldLinearVelocity.x = (target.worldPosition.x-bullet.worldPosition.x)*bullet['velocity'] # bullet.worldLinearVelocity.y= (target.worldPosition.y-bullet.worldPosition.y)*bullet['velocity'] # bullet.worldLinearVelocity.z = (target.worldPosition.z+0.1-bullet.worldPosition.z)*bullet['velocity'] # Sounds if obj['cat']=="Archer tower": audiodev.play(sndbuff_archer) if obj['cat']=="Mage tower": audiodev.play(sndbuff_mage) # Ligne (drawLine) (vitesse rapide) # if scene.objects['Terrain']['speed']>=1: if scene.objects['Terrain']['speed']<10: # Pas d'animation à 10 -> plantage # Archer (tir de flêche) if obj['cat']=="Archer tower": if target.name in scene.objects: scene.objects['Terrain']['draw_process']=True scene.objects['Terrain']['draw_list'].append([5, "arrow", [obj.worldPosition.x, obj.worldPosition.y, obj.worldPosition.z+0.8],target.name, angle3, ray_black, 5]) # Cast zone if obj['cat']=="Mage tower": # Mage (cast) scene.objects['Terrain']['draw_process']=True scene.objects['Terrain']['draw_list'].append([30, "cast", [obj.worldPosition.x, obj.worldPosition.y, obj.worldPosition.z+0.8],ray_blue,30]) # Rayon if obj['cat']=="Test": if target.name in scene.objects: scene.objects['Terrain']['draw_process']=True scene.objects['Terrain']['draw_list'].append([5, "ray", [obj.worldPosition.x, obj.worldPosition.y, obj.worldPosition.z+0.8], target.name, angle3, ray_yellow,5]) # Suivi du minion # Dégats target['hp'] = target['hp'] - obj['damage'] if target['hp']<=0: target['dead']=True # Cast (buff and debuff) if obj['cat']=="Mage tower": for target_i in sensor.hitObjectList: target_i['buff'].append([obj['cast'], obj['cast_duration']]) ############################################################################### # Carte ############################################################################### # Fin def ct_map_end(x,y): mapend= scene.addObject("Map_end", scene.objects['Terrain']) mapend.worldPosition=[x,y,0.2] mapend.worldScale=[0.25,0.25,0.25] # Minion arrivé à la fin def scn_map_end_near(cont): obj = cont.owner sensor = obj.sensors['Near'] for i in range (len(sensor.hitObjectList)) : sensor.hitObjectList[i].endObject() audiodev.play(sndbuff_life) if scene.objects['Points']['lifes']>0: scene.objects['Points']['lifes']= scene.objects['Points']['lifes']-sensor.hitObjectList[0]['lifes_damage'] scene.objects['Points']['minions_run'] -=1 # Drapeau de fin def ct_map_endflag(x,y): endflag= scene.addObject("Map_endflag", scene.objects['Terrain']) endflag.worldPosition=[x,y,0.3] endflag.worldScale=[0.3,0.3,0.3] if round(x) == x : if round(y) == y : scene.objects['Terrain']['scene_tile_noncontruct'].append([x,y]) else: scene.objects['Terrain']['scene_tile_noncontruct'].append([x,math.floor(y)]) scene.objects['Terrain']['scene_tile_noncontruct'].append([x,math.ceil(y)]) else: if round(y) == y : scene.objects['Terrain']['scene_tile_noncontruct'].append([math.floor(x),y]) scene.objects['Terrain']['scene_tile_noncontruct'].append([math.ceil(x),y]) else: scene.objects['Terrain']['scene_tile_noncontruct'].append([math.floor(x),math.floor(y)]) scene.objects['Terrain']['scene_tile_noncontruct'].append([math.floor(x),math.ceil(y)]) scene.objects['Terrain']['scene_tile_noncontruct'].append([math.ceil(x),math.floor(y)]) scene.objects['Terrain']['scene_tile_noncontruct'].append([math.ceil(x),math.ceil(y)]) ############################################################################### # Temporisation ############################################################################### def ct_sleep (duration): time.sleep(duration*(1/scene.objects['Terrain']['speed'])) # def ct_tempo (duration): # scene.objects['Terrain']['delay_cmd']=0 # while scene.objects['Terrain']['delay_cmd']x0: angle2=angle_xy else: angle2=math.pi+angle_xy x2=x0+(((6-draw_cmd[0])*step)*(math.cos(angle2))) y2=y0+(((6-draw_cmd[0])*step)*(math.sin(angle2))) z2=z0-(((6-draw_cmd[0])*step_z)*(math.sin(angle_z))) x3=x0+(((6-draw_cmd[0])*step+step)*(math.cos(angle2))) y3=y0+(((6-draw_cmd[0])*step+step)*(math.sin(angle2))) z3=z0-(((6-draw_cmd[0])*step_z+step_z)*(math.sin(angle_z))) bge.render.drawLine([x2,y2, z2], [x3,y3,z3], draw_cmd[5]) draw_cmd[0] = draw_cmd[0]-scene.objects['Terrain']['speed'] # if scene.objects['Terrain']['speed']<1: # draw_cmd[0] = draw_cmd[0]-scene.objects['Terrain']['speed'] # else: # draw_cmd[0] = draw_cmd[0]-1 # bge.render.drawLine([draw_cmd[2][0]+((6-draw_cmd[0])*0.25)*(math.cos(draw_cmd[4])), draw_cmd[2][1]+((6-draw_cmd[0])*0.25)*(math.sin(draw_cmd[4])),draw_cmd[2][2]], # [draw_cmd[2][0]+((6-draw_cmd[0])*0.25+0.25)*(math.cos(draw_cmd[4])), draw_cmd[2][1]+((6-draw_cmd[0])*0.25+0.25)*(math.sin(draw_cmd[4])),draw_cmd[2][2]], # draw_cmd[5]) # Mage (cast) # FIXME : Problème if draw_cmd[1]=="cast": # Mage (cast) circle(draw_cmd[2], 3.1-draw_cmd[0]*0.1, draw_cmd[3]) circle(draw_cmd[2], 3-draw_cmd[0]*0.1, draw_cmd[3]) circle(draw_cmd[2], 2.9-draw_cmd[0]*0.1, draw_cmd[3]) draw_cmd[0] = draw_cmd[0]-scene.objects['Terrain']['speed'] # if scene.objects['Terrain']['speed']<=2: # draw_cmd[0] = draw_cmd[0]-scene.objects['Terrain']['speed'] # if scene.objects['Terrain']['speed']==4: # draw_cmd[0] = draw_cmd[0]-draw_cmd[4]/2 # circle(draw_cmd[2], 3, draw_cmd[3]) # simple # radius=[3,3,2.5,2.5,2,2,1.5,1.5,1,1,1] # basé sur un tableau # circle(draw_cmd[2], radius[draw_cmd[0]], draw_cmd[3]) # Rayon if draw_cmd[1]=="ray": if draw_cmd[3] in scene.objects: x0 = draw_cmd[2][0]+0.25*(math.cos(draw_cmd[4])) y0 = draw_cmd[2][1]+0.25*(math.sin(draw_cmd[4])) x1 = scene.objects[draw_cmd[3]].worldPosition.x y1 = scene.objects[draw_cmd[3]].worldPosition.y z1 = scene.objects[draw_cmd[3]].worldPosition.z bge.render.drawLine([x0,y0, draw_cmd[2][2]], [x1,y1,z1], draw_cmd[5]) # suivi minion # bge.render.drawLine([draw_cmd[2][0]+0.25*(math.cos(draw_cmd[4])), draw_cmd[2][1]+0.25*(math.sin(draw_cmd[4])), draw_cmd[2][2]], draw_cmd[3], draw_cmd[5]) # décalage minion # bge.render.drawLine(draw_cmd[2], draw_cmd[3], draw_cmd[5]) # simple draw_cmd[0] = draw_cmd[0]-scene.objects['Terrain']['speed'] # Suppression des draws finis i=0 for draw_cmd in scene.objects['Terrain']['draw_list']: if draw_cmd[0]<=0: scene.objects['Terrain']['draw_list'].pop(i) else: i=i+1 if len(scene.objects['Terrain']['draw_list'])==0: scene.objects['Terrain']['draw_process']=False