__init__.py 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980
  1. #!/usr/bin/env python
  2. #! coding: utf-8
  3. #! python3
  4. # Kazeged engine main file
  5. # Dirty as hay, if you learn python with this file, you may learn garbage/bad practices.
  6. # 2022-2024
  7. import sys
  8. import random
  9. import os
  10. from engine.GM import *
  11. enginePath = None
  12. if getattr( sys, 'frozen', False ) :
  13. enginePath = os.path.join( os.path.dirname( sys.executable ), "engine" )
  14. elif __file__:
  15. enginePath = os.path.dirname( __file__ )
  16. engineFont = None
  17. def init() :
  18. global engineFont
  19. global enginePath
  20. game_init()
  21. #enginePath = os.path.dirname( os.path.realpath( __file__ ) )
  22. fontPath = os.path.join( enginePath, "aesymatt.ttf" )
  23. engineFont = font_add( fontPath, 20 )
  24. #==============================================================================
  25. # Engine intro
  26. #==============================================================================
  27. def intro() :
  28. global engineFont
  29. init()
  30. draw_set_color( (255,255,255) )
  31. draw_set_halign( fa_center )
  32. draw_set_valign( fa_middle )
  33. running = True
  34. counter = 0
  35. step = 0
  36. titleFont = font_add( None, 64 )
  37. titleText = "Kazeged"
  38. subText = random.choice( ["Stories to tell", "The mane engine", "Always mare", "Horsing around"] )
  39. W,H = surface_get_target().get_size()
  40. while running:
  41. # Event handling, gets all event from the event queue
  42. for event in pygame.event.get():
  43. if event.type == pygame.QUIT:
  44. running = False
  45. # Step
  46. counter += 1
  47. if step == 0 :
  48. C = 192 * min( 1, (counter/63.0) )
  49. if counter > 63 :
  50. counter = 0
  51. step = 1
  52. elif step == 1:
  53. C = 192
  54. if counter > 63 :
  55. counter = 0
  56. step = 2
  57. elif step == 2:
  58. C = 192 * (1-min( 1, (counter/31.0) ))
  59. if counter > 31 :
  60. running = False
  61. else :
  62. C = 255
  63. # Draw
  64. draw_clear()
  65. r = random.randrange(-2,2)
  66. y = (H/2)-16
  67. draw_set_font( titleFont )
  68. draw_text_color( ((W/2)+2+r, y+2+r), titleText, (C,0,0) )
  69. draw_text_color( ((W/2)-2+r, y+2+r), titleText, (0,C,0) )
  70. draw_text_color( ((W/2)+r, y-2+r), titleText, (0,0,C) )
  71. draw_text_color( (W/2, y), titleText, (0,0,0) )
  72. draw_set_font( engineFont )
  73. draw_text_color( (W/2, y+48), subText, (C,C,C) )
  74. # surface.fill( blendColor, None, 0x3 ) #0x3 = BLEND_RGB_MULT
  75. pygame.display.flip()
  76. GM.game_clock.tick(60)
  77. pygame.event.poll()
  78. # Reset text atributes to default
  79. draw_set_color( c_white )
  80. draw_set_halign( fa_left )
  81. draw_set_valign( fa_top )
  82. #==============================================================================
  83. # Displays a list of game
  84. #
  85. # Returns the game path, None if none choosen
  86. #==============================================================================
  87. def game_selection( gameLibFolder="" ) :
  88. init()
  89. selectedGamePath = None
  90. selectedGameIndex = 0
  91. # Default font
  92. #engineFont = font_add( "engine/aesymatt.ttf", 20 )
  93. # Default icon
  94. defaultIcon = background_add( "__game_selection_defaultIcon__", "engine/icon - small.png" )
  95. # Background music
  96. musicPlaying = False
  97. musicFile = "engine/8bit Bossa - compressed.mp3"
  98. if os.path.exists( musicFile ) :
  99. pygame.mixer.music.load( musicFile )
  100. pygame.mixer.music.play( loops=-1 )
  101. musicPlaying = True
  102. else:
  103. warning( "Background music not found" )
  104. # Configure display
  105. pygame.display.set_caption( "Game selector - Kazeged" )
  106. draw_set_color( (0xff,0x99,0x33) )
  107. draw_set_halign( fa_left )
  108. draw_set_valign( fa_top )
  109. draw_set_font( engineFont )
  110. # Scan games
  111. info( "Loading game list..." )
  112. pygame.display.flip()
  113. gameList = []
  114. # default folder
  115. if os.path.exists( "game" ) :
  116. path = "game"
  117. configFile = os.path.join( path, "read me.txt" )
  118. if os.path.exists( configFile ) :
  119. print( "Default game " )
  120. name = "default game"
  121. icon = background_add( "__game_selection_defaultGameIcon__", os.path.join( path, "icon.png" ) )
  122. if icon == None : icon = defaultIcon
  123. gameList.append( {"name":name, "path":path, "icon":icon} )
  124. # from game lib
  125. if os.path.exists( gameLibFolder ) :
  126. _, directories, files = next( os.walk( gameLibFolder ) )
  127. # Check wich folder is a game
  128. for directory in directories :
  129. configFile = os.path.join( gameLibFolder,directory, "read me.txt" )
  130. if os.path.exists( configFile ) :
  131. print( "Game: "+directory )
  132. path = os.path.join( gameLibFolder,directory )
  133. name = directory
  134. icon = background_add( f"__game_selection_{name}GameIcon__", os.path.join( path, "icon.png" ) )
  135. if icon == None : icon = defaultIcon
  136. gameList.append( {"name":name, "path":path, "icon":icon} )
  137. else :
  138. warning( "game lib folder does not exists." )
  139. if len( gameList ) == 0 :
  140. warning( "No game found." )
  141. displayTop = 40
  142. lineHeight = 40
  143. running = True
  144. while running:
  145. # Event handling, gets all event from the event queue
  146. for event in pygame.event.get():
  147. if event.type == pygame.QUIT:
  148. running = False
  149. if event.type == pygame.MOUSEBUTTONDOWN:
  150. if (selectedGameIndex >= 0) and (selectedGameIndex < len(gameList)) :
  151. selectedGamePath = gameList[ selectedGameIndex ]["path"]
  152. running = False
  153. # Get the highlighted game (if any)
  154. mouse_x, mouse_y = pygame.mouse.get_pos()
  155. if mouse_y > displayTop :
  156. selectedGameIndex = int( (mouse_y-displayTop) / lineHeight)
  157. else:
  158. selectedGameIndex = -1
  159. # Displays the games found
  160. draw_clear( (0x47,0x33,0x1f) )
  161. draw_set_valign( fa_top )
  162. draw_text( (4,4), "Select a game" )
  163. draw_set_valign( fa_middle )
  164. currentIndex = 0
  165. for game in gameList :
  166. Y = displayTop+(lineHeight*currentIndex)
  167. if currentIndex == selectedGameIndex :
  168. draw_set_color( (139,101,29) )
  169. draw_rectangle( (1,Y+1), (638,Y+lineHeight-1), outline=False )
  170. draw_set_color( (0xff,0x99,0x33) )
  171. draw_rectangle( (1,Y+1), (638,Y+lineHeight-1), outline=True )
  172. draw_set_color( (0xff,0x99,0x33) )
  173. draw_text( (40,Y+20), game["name"] )
  174. if game["icon"] != None :
  175. draw_background( game["icon"], 4, Y+4 )
  176. currentIndex += 1
  177. pygame.display.flip()
  178. GM.game_clock.tick(60)
  179. # Stop the music
  180. if musicPlaying :
  181. pygame.mixer.music.stop()
  182. pygame.mixer.music.unload()
  183. # Reset text atributes to default
  184. draw_set_color( c_white )
  185. draw_set_halign( fa_left )
  186. draw_set_valign( fa_top )
  187. return selectedGamePath
  188. #==============================================================================
  189. # Reads a Kazeged file
  190. #==============================================================================
  191. def read_kazeged_file( kazegedFile=None ) :
  192. data = None
  193. if os.path.exists( kazegedFile ):
  194. data = []
  195. # Reads the file line by line
  196. with open( kazegedFile, "r" ) as file:
  197. RAWData = file.readlines()
  198. # Treat and arrange the data in an easier to treat format
  199. currentLine = 1
  200. for line in RAWData:
  201. # Remove end of line and trailing spaces
  202. line = line.strip()
  203. # if the line is not empty, let's treat it
  204. if len(line) >= 1 :
  205. # if the line is not a comment
  206. if line.strip()[0] != "#" :
  207. # Slash the spaces
  208. items = line.split( " " )
  209. # Find if any quote surrounded string have been severed during slashing
  210. #TODO rewrite this whole mess...
  211. stringStarted = False
  212. startIndex = 0
  213. currentIndex = 0
  214. toPop = []
  215. for blob in items:
  216. # If the blob is surrounded by quotes try to fix things
  217. if len( blob ) > 1 :
  218. if (blob[0] == '"') and (blob[-1] == '"') :
  219. if stringStarted :
  220. error( f"line {currentLine} malformed! token {blob} ({line[0:12]}...)" )
  221. break
  222. else:
  223. info( f"line {currentLine}. token {blob} don't need quotes ({line[0:12]}...)" )
  224. items[currentIndex] = items[currentIndex][1:-1]
  225. # Is start of a string reached?
  226. #TODO this mess don't recognize things like " string", "string " or " string " because the quote is conidered a blob
  227. if len( blob ) > 0 :
  228. if (blob[0] == '"') :
  229. stringStarted = True
  230. startIndex = currentIndex
  231. else:
  232. if stringStarted == True :
  233. if blob[-1] == '"':
  234. text = ' '.join( items[startIndex:currentIndex+1] )[1:-1]
  235. items[startIndex] = text
  236. # Add indexes to pop latest first to, well, keep the index meaning something
  237. toPop.insert( 0, [(startIndex+1), (currentIndex-startIndex)] )
  238. stringStarted = False
  239. else:
  240. pass
  241. else:
  242. pass
  243. #Is it a blank/empty item not part of a string?
  244. if not stringStarted :
  245. if blob in ['', ' ', '\t'] :
  246. toPop.insert( 0, [currentIndex,1] )
  247. currentIndex += 1
  248. # Pop the unwanted items
  249. # The list should start by the last idexes, so we can pop them out
  250. for index in toPop:
  251. for i in range(index[1]):
  252. items.pop( index[0] )
  253. # Add our fresh items to the data gathered
  254. data.append( items )
  255. else:
  256. #print( "Comment: "+line[1:] )
  257. pass
  258. currentLine += 1
  259. else:
  260. error( f"can't find file {kazegedFile}" )
  261. return data
  262. class text_engine:
  263. font = None
  264. position = (0,0)
  265. size = (512,64)
  266. lines = (2,32)
  267. color = (0,0,0)
  268. backgroundColor = (255,255,255,128)
  269. scrollSpeed = 1
  270. scrollCpt = -1
  271. def set_font( self, font=None ):
  272. self.font = font
  273. def set_position( self, position=(0,0) ):
  274. self.position = position
  275. def set_size( self, size=(512,64) ):
  276. self.size = size
  277. def set_lines( self, lines=(2,32) ):
  278. self.lines = lines
  279. def set_color( self, color=(0,0,0) ):
  280. self.color = color
  281. def set_background_color( self, backgroundColor=(255,255,192,192) ):
  282. self.backgroundColor = backgroundColor
  283. def set_scroll_speed( self, scrollSpeed=2 ) :
  284. self.scrollSpeed = scrollSpeed
  285. # if -1, scroll from start, else, int represent the number of chars left to draw
  286. # ( so 0 is static full drawn text)
  287. def reset_scrolling( self, forceValue=-1 ) :
  288. self.scrollCpt = forceValue
  289. def draw_text( self, text=["Yay!"], forceStatic=False ) :
  290. if isinstance( text, list ) :
  291. if len( text ) > 0 :
  292. top = self.position
  293. size = self.size
  294. # draw_set_alpha( 179/255 )
  295. draw_set_color( self.backgroundColor )
  296. draw_rectangle( top, (top[0]+size[0],top[1]+size[1]), outline=False )
  297. # draw_set_alpha( 1 )
  298. draw_set_color( self.color )
  299. txty = 12-3 # Pixel perfecet is 12, 3 is because of the font
  300. # Curate/sanitize text
  301. curatedText = []
  302. for line in text :
  303. if isinstance( line, str ) :
  304. curatedText.append( line.strip() )
  305. # Manage scrolling
  306. totalNbChars = 0
  307. for line in curatedText :
  308. totalNbChars += len( line )
  309. if self.scrollCpt == -1 :
  310. self.scrollCpt = totalNbChars
  311. draw_set_font( self.font )
  312. alreadyDrawnChars = 0
  313. for line in curatedText :
  314. if alreadyDrawnChars < (totalNbChars-self.scrollCpt) :
  315. xoffset = 4 + (8*(31-len(line)))
  316. charToDraw = (totalNbChars-self.scrollCpt)-alreadyDrawnChars
  317. draw_text( (top[0]+xoffset,top[1]+txty), line[:charToDraw] )
  318. txty+=self.lines[1]
  319. alreadyDrawnChars += len(line)
  320. # Manage scrolling
  321. if self.scrollCpt > 0 :
  322. self.scrollCpt -= self.scrollSpeed
  323. if self.scrollCpt < 0 :
  324. self.scrollCpt = 0
  325. draw_set_color( c_white )
  326. else :
  327. warning( "draw text called, but no text" )
  328. else:
  329. warning( "draw text called, but argument is not a list" )
  330. return self.scrollCpt
  331. #==============================================================================
  332. # A Kazeged game
  333. #==============================================================================
  334. class obj_game:
  335. """---"""
  336. name = "Unamed game"
  337. resolution = (320,240)
  338. saveEnabled = False
  339. basePath = None
  340. firstScene = None
  341. mainMenu = ["Quit"]
  342. options = None
  343. scene = None
  344. text = None
  345. time = 0
  346. timeSteps = ( "day", "night" )
  347. # Backgrounds
  348. backgrounds = []
  349. #
  350. def __init__( self, basePath=None ):
  351. # init()
  352. self.basePath = basePath
  353. self.text = text_engine()
  354. self.load_configuration()
  355. def load_configuration( self ):
  356. # Load the config file
  357. data = read_kazeged_file( os.path.join( self.basePath, "configuration/game.txt" ) )
  358. if data == None :
  359. error( "No config File!" )
  360. exit( 1 )
  361. # Apply config
  362. for blob in data :
  363. token = blob[0]
  364. nbArgs = len( blob )
  365. # TODO Use a selection if porting to python 3.10
  366. if token == "Name":
  367. if nbArgs > 1 :
  368. self.name = blob[1]
  369. else :
  370. warning( "configuration: 'Name' empty" )
  371. elif token == "Title":
  372. if nbArgs > 1 : file = os.path.join( self.basePath, "configuration", blob[1] )
  373. background_add( "__main_menu_background__", file )
  374. elif token == "Size":
  375. if nbArgs >= 3 :
  376. self.resolution = ( int(blob[1]), int(blob[2]) )
  377. window_set( self.resolution, flags=pygame.RESIZABLE|pygame.SCALED|pygame.SHOWN )
  378. # pygame.display.toggle_fullscreen()
  379. else :
  380. warning( "configuration: 'Size' don't have enough parameters. Skipped." )
  381. elif token == "Save":
  382. if blob[1] == "ON" :
  383. self.saveEnabled = True
  384. else :
  385. self.saveEnabled = False
  386. elif token == "Time":
  387. self.time = 0
  388. self.timeSteps = blob[1:]
  389. elif token == "Font":
  390. self.textFont = font_add( os.path.join( self.basePath, "configuration", blob[1] ), blob[2] )
  391. self.text.set_font( self.textFont )
  392. draw_set_font( self.textFont )
  393. elif token == "Music":
  394. fileA = None
  395. fileB = None
  396. if nbArgs > 1 : fileA = os.path.join( self.basePath, "configuration", blob[1] )
  397. if nbArgs > 2 : fileB = os.path.join( self.basePath, "configuration", blob[2] )
  398. if fileA != None :
  399. # Simple music, no intro part
  400. if fileB == None :
  401. music_add( "__main_menu_music__", fileA )
  402. # Else, music with intro
  403. else :
  404. music_add( "__main_menu_music__", fileB, fileA )
  405. elif token == "Start":
  406. if nbArgs > 1 :
  407. self.firstScene = blob[1]
  408. else :
  409. warning( "configuration: starting scene is empty" )
  410. # Option name [defaultValue [description]]
  411. elif token == "Option":
  412. if self.options == None : self.options = {}
  413. oName = None
  414. oVals = {"value":0, "description":None}
  415. if nbArgs > 1 : oName = blob[1]
  416. if nbArgs > 2 : oVals["value"] = blob[2]
  417. if nbArgs > 3 : oVals["description"] = blob[3]
  418. if oVals["description"] == None : oVals["description"] = oName
  419. if oName != None :
  420. if oName not in self.options :
  421. self.options[oName] = oVals
  422. else :
  423. warning( f"configuration: option '{oName}' already defined. Keeping the first one." )
  424. elif token == "TextPosition" :
  425. if nbArgs >= 3 :
  426. self.text.set_position( (int(blob[1]), int(blob[2])) )
  427. else:
  428. warning( "configuration: 'TextPosition' don't have enough parameters. Skipping." )
  429. elif token == "TextSize" :
  430. if nbArgs >= 3 :
  431. self.text.set_size( (int(blob[1]), int(blob[2])) )
  432. else:
  433. warning( "configuration: 'TextSize' don't have enough parameters. Skipping." )
  434. elif token == "TextLines" :
  435. if nbArgs >= 3 :
  436. self.text.set_lines( (int(blob[1]), int(blob[2])) )
  437. else:
  438. warning( "configuration: 'TextLines' don't have enough parameters. Skipping." )
  439. elif token == "TextColor" :
  440. if nbArgs >= 2 :
  441. self.text.set_color( sanitize_color(blob[1]) )
  442. else:
  443. warning( "configuration: 'TextColor' don't have enough parameters. Skipping." )
  444. elif token == "TextBackgroundColor" :
  445. if nbArgs >= 2 :
  446. self.text.set_background_color( sanitize_color(blob[1]) )
  447. else:
  448. warning( "configuration: 'TextBackgroundColor' don't have enough parameters. Skipping." )
  449. elif token=="TextScrollSpeed" :
  450. if nbArgs >= 2 :
  451. self.text.set_scroll_speed( float(blob[1]) )
  452. else:
  453. warning( "configuration: 'TextScrollSpeed' don't have enough parameters. Skipping." )
  454. else :
  455. warning( f"configuration: token '{token}' unknown." )
  456. if self.name != None :
  457. pygame.display.set_caption( f"{self.name} - Kazeged" )
  458. self.mainMenu = []
  459. if self.firstScene == None :
  460. error( "configuration: No starting scene set!" )
  461. else :
  462. self.mainMenu.append( "Play" )
  463. if self.saveEnabled : self.mainMenu.append( "Load" )
  464. if self.options != None : self.mainMenu.append( "Options" )
  465. self.mainMenu.append( "Quit" )
  466. #----------------------------------------
  467. # Load a scene
  468. def scene_load( self, sceneName=None ) :
  469. # First, cleanup
  470. self.scene_unload()
  471. # Then, try to load the new scene
  472. # It's so clunky and fat and not optimized... sorry...
  473. if sceneName != None :
  474. scenePath = os.path.join( self.basePath, "scenes", sceneName, "descriptor.txt")
  475. if os.path.exists( scenePath ) :
  476. info( f"Loading scene '{sceneName}'..." )
  477. self.scene = {
  478. "name":sceneName,
  479. "ressources":{
  480. "actors":[],
  481. "backgrounds":[],
  482. "musics":[],
  483. "sounds":[],
  484. },
  485. "actions":[]
  486. }
  487. data = read_kazeged_file( scenePath )
  488. # Parse the data
  489. for line in data :
  490. print( line )
  491. # ("Music", "Background", "Character", "Fadein", "Message")
  492. if line[0] == "Kazeged" :
  493. # info( "Cheers m8! ^:)" )
  494. pass
  495. elif line[0] == "Actor" :
  496. file = line[1]
  497. name = ''.join(os.path.splitext(os.path.basename(line[1]))[:-1])
  498. if not background_exists( name ) :
  499. path = self.search_ressource( file, "actors", sceneName )
  500. if path != None :
  501. # Suceeded in loading the resource?
  502. if background_add( name, path ) == name :
  503. self.scene["ressources"]["actors"].append( name )
  504. actor = {"kind":"Actor", "name":name}
  505. if "COORDS" in line :
  506. print( "COORDS!!!1!" )
  507. self.scene["actions"].append( actor )
  508. elif line[0] == "Background" :
  509. file = line[1]
  510. name = ''.join(os.path.splitext(os.path.basename(line[1]))[:-1])
  511. if not background_exists( name ) :
  512. path = self.search_ressource( file, "backgrounds", sceneName )
  513. if path != None :
  514. # Suceeded in loading the resource?
  515. if background_add( name, path ) == name :
  516. self.scene["ressources"]["backgrounds"].append( name )
  517. self.scene["actions"].append( {"kind":"Background", "name":name} )
  518. elif line[0] == "Message" :
  519. if len(line) > 1 :
  520. self.scene["actions"].append( {"kind":"Message", "lines":line[1:]} )
  521. elif line[0] == "Music" :
  522. file = line[1]
  523. name = ''.join(os.path.splitext(os.path.basename(line[1]))[:-1])
  524. if not music_exists( name ) :
  525. path = self.search_ressource( file, "musics", sceneName )
  526. if path != None :
  527. # Suceeded in loading the resource?
  528. if music_add( name, path, intro=None ) == name :
  529. self.scene["ressources"]["musics"].append( name )
  530. self.scene["actions"].append( {"kind":"Music", "name":name} )
  531. else :
  532. error( f"scene loader: can't find scene '{scenePath}'" )
  533. else :
  534. error( "scene loader: scene is 'None'" )
  535. print( self.scene )
  536. #----------------------------------------
  537. # Unload the ressources loaded by the previous scene
  538. def scene_unload( self ) :
  539. print( self.scene )
  540. if self.scene != None :
  541. if "ressources" in self.scene :
  542. deleters = {
  543. "actors":background_delete,
  544. "backgrounds":background_delete,
  545. "musics":music_delete,
  546. "sounds":sound_delete
  547. }
  548. for reskind in self.scene["ressources"] :
  549. for res in self.scene["ressources"][reskind] :
  550. print( f"delete {reskind} {res}" )
  551. deleters[reskind]( res )
  552. self.scene = {}
  553. print( self.scene )
  554. #----------------------------------------
  555. # Look for a ressource in the game folder
  556. def search_ressource( self, name=None, sharedFolder=None, sceneName=None) :
  557. path = None
  558. if name != None :
  559. path = os.path.join( self.basePath, sharedFolder, name )
  560. if not os.path.exists( path ) :
  561. path = os.path.join( self.basePath, "scenes", sceneName, name )
  562. if not os.path.exists( path ) :
  563. path = None
  564. print( f" {path}" )
  565. return path
  566. #----------------------------------------
  567. # Main menu
  568. def main_menu( self ) :
  569. background_color = c_black
  570. draw_set_font( self.textFont )
  571. # Create a surface on screen
  572. screen = surface_get_target()
  573. display = pygame.Surface( self.resolution )
  574. music_play( "__main_menu_music__", loop=True )
  575. # Variable to control the main loop
  576. running = True
  577. click = False
  578. state = "main menu"
  579. displayTop = (self.resolution[1]/2)
  580. lineHeight = 24
  581. # main loop
  582. while running:
  583. # Event handling, gets all event from the event queue
  584. for event in pygame.event.get():
  585. if event.type == pygame.QUIT:
  586. running = False
  587. if event.type == pygame.MOUSEBUTTONDOWN:
  588. click = True
  589. # Draw
  590. draw_clear( background_color )
  591. draw_background( "__main_menu_background__", 0,0 )
  592. # Get the highlighted game (if any)
  593. mouse_x, mouse_y = pygame.mouse.get_pos()
  594. if mouse_y > displayTop :
  595. selectedOption = int( (mouse_y-displayTop) / lineHeight)
  596. else:
  597. selectedOption = -1
  598. # Displays options
  599. if state == "main menu" :
  600. draw_text( (4,4), self.name )
  601. draw_set_halign( fa_center )
  602. draw_set_valign( fa_middle )
  603. selected = True
  604. for index in range( len(self.mainMenu) ) :
  605. x = (self.resolution[0]/2)
  606. y = (self.resolution[1]/2) + (lineHeight*index)
  607. if index == selectedOption :
  608. draw_set_color( (127,101,29) )
  609. draw_rectangle( (0.5*x,y+1), (1.5*x,y+lineHeight-1), outline=False )
  610. draw_set_color( c_white )
  611. draw_text( (x,y+(0.5*lineHeight)), self.mainMenu[index] )
  612. draw_set_halign( fa_left )
  613. draw_set_valign( fa_top )
  614. if click :
  615. if selectedOption in range( len(self.mainMenu) ) :
  616. if self.mainMenu[selectedOption] == "Play" :
  617. # state = "run game"
  618. # Run the game itself (the first scene)
  619. pygame.mixer.music.stop()
  620. self.scene_load( self.firstScene )
  621. self.loop()
  622. music_stop()
  623. state = "main menu"
  624. music_play( "__main_menu_music__", loop=True )
  625. elif self.mainMenu[selectedOption] == "Load" :
  626. state = "save menu"
  627. elif self.mainMenu[selectedOption] == "Options" :
  628. state = "options"
  629. elif self.mainMenu[selectedOption] == "Quit" :
  630. running = False
  631. elif state == "options" :
  632. draw_text( (4,4), "Options" )
  633. if click :
  634. state = "main menu"
  635. elif state == "save menu" :
  636. draw_text( (4,4), "Save menu" )
  637. if click :
  638. state = "main menu"
  639. else :
  640. state == "main menu"
  641. # Update the display
  642. pygame.display.flip()
  643. GM.game_clock.tick(60)
  644. click = False
  645. return None
  646. #----------------------------------------
  647. # Main game loop
  648. def loop( self ) :
  649. background_color = c_black
  650. draw_set_font( self.textFont )
  651. # Create a surface on screen
  652. screen = surface_get_target()
  653. display = pygame.Surface( self.resolution )
  654. state = "game"
  655. # Variable to control the main loop
  656. running = False
  657. if self.scene == None :
  658. error( "No scene loaded. Check that the game is not corrupted." )
  659. state = "error"
  660. else :
  661. running = True
  662. # Init
  663. readNextItem = True
  664. currentItemIndex = 0
  665. current_background = None
  666. current_music = None
  667. current_character = None
  668. current_characterCoords = (0,0)
  669. current_text = None
  670. charLeft = 0
  671. fadeColor = (0,0,0)
  672. fadeTimeLeft = 0
  673. click = False
  674. # main loop
  675. while running:
  676. # Event handling, gets all event from the event queue
  677. click = False
  678. for event in pygame.event.get():
  679. if event.type == pygame.QUIT:
  680. running = False
  681. if event.type == pygame.MOUSEBUTTONDOWN:
  682. click = True
  683. # Scan next item in scene if needed
  684. if readNextItem :
  685. if currentItemIndex >= len(self.scene["actions"]) :
  686. running = False
  687. else :
  688. item = self.scene["actions"][currentItemIndex]
  689. # "kind":"Fadein", "duration":60, "color":(0,0,0)
  690. if item["kind"] == "Fadein" :
  691. draw_text( (4,36), "FadeIn" )
  692. pass
  693. # "kind":"Background", "name":"bck_name"
  694. if item["kind"] == "Background" :
  695. current_background = item["name"]
  696. # "kind":"Music", "name":"msk_name", "LOOPING":True
  697. elif item["kind"] == "Music" :
  698. music_stop()
  699. current_music = item["name"]
  700. looping = False
  701. if "LOOPING" in item :
  702. looping = True
  703. music_play( current_music, looping )
  704. # "kind":"Character", "name":"bck_name", "COORDS":[78,0]
  705. elif item["kind"] == "Actor" :
  706. current_character = item["name"]
  707. if "COORDS" in item :
  708. current_characterCoords = item["COORDS"]
  709. # "kind":"Message", "lines":["text", "text"]
  710. elif item["kind"] == "Message" :
  711. current_text = item["lines"]
  712. self.text.reset_scrolling()
  713. charLeft = 999
  714. readNextItem = False
  715. currentItemIndex += 1
  716. # Pass to the next item if we can
  717. if click and (readNextItem == False) :
  718. if charLeft == 0 :
  719. readNextItem = True
  720. # Draw
  721. if state == "error" :
  722. draw_clear( c_maroon )
  723. draw_text( (4,4), "Error, no scene loaded." )
  724. draw_text( (4,24), "Please check that your game files are not corrupted." )
  725. else :
  726. draw_clear( background_color )
  727. draw_background( current_background, 0,0 )
  728. draw_text( (4,4), self.scene["name"] )
  729. # Background
  730. draw_background( current_character, current_characterCoords[0], current_characterCoords[1] )
  731. # Text
  732. if current_text != None :
  733. if click :
  734. self.text.reset_scrolling( 0 )
  735. charLeft = self.text.draw_text( current_text )
  736. # Update the display
  737. pygame.display.flip()
  738. GM.game_clock.tick(60)
  739. return None
  740. #==============================================================================
  741. #
  742. #==============================================================================
  743. def game_start( gameFolder ) :
  744. info( f"Loading game {gameFolder}" )
  745. # Sanity check
  746. if gameFolder == None :
  747. game_end()
  748. else :
  749. # Initialization
  750. init()
  751. game = obj_game( gameFolder )
  752. # Let's play!
  753. game.main_menu()
  754. pass