ui.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723
  1. # THIS IS A SOURCE CODE FILE FROM I'M NOT EVEN HUMAN THE GAME.
  2. # IT COULD BE USED IN A DIFFERENT PIECE OF SOFTWARE ( LIKE A
  3. # DIFFERENT GAME ), BUT IT WAS ORIGINALLY WRITTEN FOR I'M NOT
  4. # EVEN HUMAN THE GAME.
  5. # THE DEVELOPERS OF THE GAME ARE : (C) J.Y.AMIHUD, AYYZEE AND
  6. # OTHER CONTRIBUTORS. THIS AND OTHER FILES IN THIS GAME,
  7. # UNLESS SPECIFICALLY NOTED, COULD BE USED UNDER THE TERMS OF
  8. # GNU GENERAL PUBLIC LICENSE VERSION 3 OR ANY LATER VERSION.
  9. import os
  10. import json
  11. import cairo
  12. import math
  13. def load_palete(game):
  14. """Loads colors from assets/palete.json"""
  15. with open("assets/palete.json") as f:
  16. game.palete = json.load(f)
  17. def distance(list1, list2):
  18. """Calculates distnaces between two vectors"""
  19. d = []
  20. for n, i in enumerate(list1):
  21. try:
  22. d.append(max((i-list2[n]),(list2[n]-i)))
  23. except:
  24. d.append(0)
  25. return sum(d) / len(d)
  26. def color(game, layer, code, alpha=1):
  27. """Sets a cairo brush color from a palete by looking for the
  28. closest match."""
  29. # If it's a code in hex like #FF0000 so we need to convert it
  30. # to numbers first
  31. if type(code) == str and code.startswith("#") and (len(code) -1) %3 == 0:
  32. new_c = []
  33. for c in range(3):
  34. ind = int((len(code)-1)/3)
  35. new_c.append( int(code[ind*c+1:ind*c+ind+1], 16)/255 )
  36. code = new_c
  37. # If it's a code in RGB, we look for the nearest
  38. # color on the palete.
  39. if type(code) == list or type(code) == tuple:
  40. name = "black" # Place holder for a palete entry
  41. dist = 10000000000 # Imaginary large number
  42. # Chekcing for the lowest possible deviation of color
  43. for color in game.palete:
  44. new_dist = distance(game.palete[color], code)
  45. if dist > new_dist:
  46. name = color
  47. dist = new_dist
  48. r, g, b = game.palete[name]
  49. elif type(code) == str:
  50. try:
  51. r, g, b = game.palete[code]
  52. except:
  53. r, g, b = 0, 0, 0
  54. else:
  55. r, g, b = 0, 0, 0
  56. layer.set_source_rgba(r,g,b,alpha)
  57. def cache_sprite_sheet(game, sheet, asset=False):
  58. """This function will cache sprite sheets into game.images"""
  59. if sheet not in game.images:
  60. # Loading the sheet image
  61. grid = cairo.ImageSurface.create_from_png(sheet)
  62. # Loading metadata
  63. try:
  64. with open(sheet.replace(".png", ".json")) as f:
  65. metadata = json.load(f)
  66. except:
  67. metadata = {}
  68. width = 1
  69. height = 1
  70. try:
  71. width = metadata["resolution"][0]
  72. height = metadata["resolution"][1]
  73. except:
  74. pass
  75. # Getting the resolution of the cell
  76. cellx = int(grid.get_width()/width)
  77. celly = int(grid.get_height()/height)
  78. data = {}
  79. if asset:
  80. game.elements[asset[0]][asset[1]] = []
  81. for x in range(width):
  82. for y in range(height):
  83. # Creating individual cells
  84. cell = cairo.ImageSurface(cairo.FORMAT_ARGB32,
  85. cellx,
  86. celly)
  87. drawcell = cairo.Context(cell)
  88. drawcell.set_antialias(cairo.ANTIALIAS_NONE)
  89. drawcell.set_source_surface(grid,
  90. 1-(cellx*x+1),
  91. 1-(celly*y+1))
  92. drawcell.paint()
  93. # Putting the cell into the data as in "0:0" or "43:69".
  94. cellname = str(x)+":"+str(y)
  95. cellnameindata = cellname
  96. celldata = {}
  97. try:
  98. #print("SEE IT PLEASE!", game.elements[asset[0]][asset[1]])
  99. if asset and metadata[cellname] not in game.elements[asset[0]][asset[1]]:
  100. game.elements[asset[0]][asset[1]].append(metadata[cellname].copy())
  101. cellnameindata = metadata[cellname]["title"]
  102. celldata = metadata[cellname]
  103. except:
  104. pass
  105. celldata["binary"] = cell
  106. if cellnameindata not in data:
  107. data[cellnameindata] = [celldata]
  108. else:
  109. data[cellnameindata].append(celldata)
  110. try:
  111. data["title"] = metadata["title"]
  112. except:
  113. data["title"] = sheet
  114. try:
  115. data["menu_color"] = metadata["menu_color"]
  116. except:
  117. data["menu_color"] = "#000000"
  118. try:
  119. data["authors"] = metadata["authors"]
  120. except:
  121. try:
  122. data["authors"] = metadata["autors"]
  123. except:
  124. data["authors"] = []
  125. try:
  126. data["licenses"] = metadata["licenses"]
  127. except:
  128. data["licenses"] = []
  129. #data["current_frame"] = 0 # Don't need it quite yet
  130. game.images[sheet] = data
  131. def image(game, layer, x, y, name, code="0:0", color=False, offset=False, alpha=1, frame=None, dynamic=False):
  132. x = int(x)
  133. y = int(y)
  134. """This function draws an image into a canvas. And load it if
  135. it's not loaded"""
  136. # Load the whole image as a sprite ( if it's not loaded yet )
  137. cache_sprite_sheet(game, name)
  138. # Drawing the image
  139. source = game.images[name].get(code,
  140. list(game.images[name].values())[-5])
  141. # We need to know what frame of an animation to use 'anim_pulse' and
  142. # how much frames ago the pulse was changed. We record the current frame
  143. # into the "anim_last" on every change.
  144. if "anim_pulse" not in game.current:
  145. game.current["anim_pulse"] = 0
  146. if "anim_last" not in game.current:
  147. game.current["anim_last"] = game.current["frame"]
  148. # And so if a specific amount of frames ( 1 second of frames / 10 ) is passed
  149. if ( game.current["frame"] - game.current["anim_last"] ) > int(game.FPS/10):
  150. game.current["anim_pulse"] += 1 # We advance one frame
  151. game.current["anim_last"] = game.current["frame"] # And we record the time of
  152. # this change
  153. # The current frame is the modulo of the 'anim_pulse' by the amount of frames in
  154. # this particular animation.
  155. if frame == None:
  156. cf = game.current["anim_pulse"] % len(source)
  157. else:
  158. cf = frame
  159. if offset:
  160. off = source[cf].get("offset", [0,0])
  161. x += off[0]
  162. y += off[1]
  163. if dynamic:
  164. x -= int(64/2)
  165. y -= int(36/2)
  166. if color: #If we forcing color
  167. layer.mask_surface(source[cf]["binary"],x, y)
  168. layer.set_antialias(cairo.ANTIALIAS_NONE)
  169. p = layer.get_source()
  170. p.set_filter(cairo.FILTER_NEAREST)
  171. layer.fill()
  172. else: #If we just using the image
  173. layer.set_source_surface(source[cf]["binary"], x, y)
  174. layer.set_antialias(cairo.ANTIALIAS_NONE)
  175. p = layer.get_source()
  176. p.set_filter(cairo.FILTER_NEAREST)
  177. if alpha == 1:
  178. layer.paint()
  179. else:
  180. layer.paint_with_alpha(alpha)
  181. def text(game, layer, string, x, y, color=True, align="left"):
  182. """This function will draw text using an assets/font.png file."""
  183. for n, l in enumerate(string):
  184. ax = x+(8*n)
  185. if align == "center":
  186. ax = ax - int(len(string)*4)
  187. elif align == "right":
  188. ax = ax - int(len(string)*8)
  189. image(game, layer, ax, y, "assets/font.png", l, color=color)
  190. def button(game, layer, x, y, w, h,
  191. menu="", icon="", string="", func=False,
  192. scroll="", borders=True, btnTxtColor="#00FF00"):
  193. """Draws a button"""
  194. # Let's add this button into a menu.
  195. menu_selected = False
  196. select_number = 0
  197. if menu:
  198. if menu not in game.menus :
  199. game.menus[menu] = {"selected":0,
  200. "buttons":[]}
  201. bmenudata = [x,y]
  202. if bmenudata not in game.menus[menu]["buttons"]:
  203. game.menus[menu]["buttons"].append(bmenudata)
  204. for n, i in enumerate(game.menus[menu]["buttons"]):
  205. if i == bmenudata:
  206. select_number = n
  207. if n == game.menus[menu]["selected"]:
  208. menu_selected = True
  209. if scroll and scroll in game.scroll:
  210. # Making sure that while I press either UP or DOWN it will
  211. # scroll to the correct spot.
  212. if (65362 in game.current["keys"]\
  213. or 65364 in game.current["keys"])\
  214. and menu_selected:
  215. game.scroll[scroll] = 0 - y + int(game.current["h"] / 2)
  216. y = y + int(game.scroll[scroll])
  217. x = int(round(x))
  218. y = int(round(y))
  219. w = int(round(w))
  220. h = int(round(h))
  221. color(game, layer, btnTxtColor)
  222. layer.set_line_width(1)
  223. if borders:
  224. layer.rectangle(x,y,w,h)
  225. layer.stroke()
  226. mo = False
  227. if int(game.current["mx"]) in range(x, x+w) \
  228. and int(game.current["my"]) in range(y, y+h):
  229. mo = True
  230. if mo or menu_selected:
  231. if menu:
  232. game.menus[menu]["selected"] = select_number
  233. if mo:
  234. layer.rectangle( x+1,y+1, w-3, h-3 )
  235. layer.fill()
  236. else:
  237. #layer.rectangle( x+1,y+1, w-2, h-2 )
  238. #layer.stroke()
  239. #Fancy lines
  240. layer.set_line_width(3)
  241. layer.move_to(x,y+h/3)
  242. layer.line_to(x,y)
  243. layer.line_to(x+w/3, y)
  244. layer.stroke()
  245. layer.move_to(x+w,y+h-h/3)
  246. layer.line_to(x+w,y+h)
  247. layer.line_to(x+w-w/3, y+h)
  248. layer.stroke()
  249. layer.set_line_width(1)
  250. # If you clicked the button
  251. if ( game.previous["LMB"] \
  252. and int(game.previous["LMB"][0]) in range(x, x+w) \
  253. and int(game.previous["LMB"][1]) in range(y, y+h) ) \
  254. or ( menu_selected and 65293 in game.previous["keys"]):
  255. color(game, layer, "yellow")
  256. layer.rectangle( x+1,y+1, w-3, h-3 )
  257. layer.fill()
  258. # If the is doing something
  259. if (( not game.current["LMB"] and game.previous["LMB"] and mo) \
  260. or (65293 not in game.current["keys"] \
  261. and 65293 in game.previous["keys"])) \
  262. and func:
  263. func()
  264. game.previous["LMB"] = False
  265. game.current["LMB"] = False
  266. game.current["keys"] = []
  267. game.previous["keys"] = []
  268. if icon:
  269. if mo:
  270. color(game, layer, "black")
  271. image(game, layer, x+2,y+1,"assets/menu_icons.png",
  272. icon, True)
  273. if string:
  274. if mo:
  275. color(game, layer, "black")
  276. widthleft = w - 4
  277. xd = x
  278. if icon:
  279. widthleft -= 12
  280. xd += 12
  281. sl = int(widthleft / 8)
  282. if len(string)*8 > widthleft:
  283. fp = int(game.current["frame"]/10) % (len(string)+7)
  284. text(game, layer, (string+" "+string)[fp:fp+sl], xd+2, y-int(h/5)+3 )
  285. #elif len(string)*8 > widthleft:
  286. # text(game, layer, string[:sl-3]+"...", xd+2, y-int(h/5)+3 )
  287. else:
  288. text(game, layer, string, x+int(w/2), y-int(h/5)+3, align="center")
  289. def button_navigate(game, menu):
  290. """This function will run in each layer to provide keyboard navigation."""
  291. # We don't want to run it if non of the arrow keys are pressed.
  292. # Thus we check for either of the arrow keys is pressed.
  293. if 65361 in game.current["keys"]\
  294. or 65362 in game.current["keys"]\
  295. or 65363 in game.current["keys"]\
  296. or 65364 in game.current["keys"]:
  297. # We want to know things about the currently selected button
  298. select = game.menus[menu]["selected"] # It's number in the list
  299. cur = game.menus[menu]["buttons"][select] # It's coordinates x, y
  300. prevdistance = 100000000000 # Arbitrary huge number
  301. # For all buttons in the menu list, we are going to see if it's
  302. # in a correct direction from the currently selected button, based on
  303. # the arrow key:
  304. # 65361 - RIGHT
  305. # 65362 - UP
  306. # 65363 - LEFT
  307. # 65364 - DOWN
  308. # But since there could be more then one button towards the correct
  309. # side, we are also checking for the closest one ( by comparing the
  310. # lowest possible distance to the currently selected button ).
  311. for n, i in enumerate( game.menus[menu]["buttons"] ):
  312. curdistance = distance(i, cur)
  313. if ((65361 in game.current["keys"] and i[0] < cur[0] )\
  314. or (65362 in game.current["keys"] and i[1] < cur[1] )\
  315. or (65363 in game.current["keys"] and i[0] > cur[0] )\
  316. or (65364 in game.current["keys"] and i[1] > cur[1] ))\
  317. and (curdistance < prevdistance)\
  318. and i != cur:
  319. select = n
  320. prevdistance = curdistance
  321. # And we restart keys, so it will not do this on each frame
  322. game.current["keys"] = []
  323. # And we write the number of the selected key
  324. game.menus[menu]["selected"] = select
  325. def scroll_area(game, layer, menu, x, y, width, height, max_value,
  326. strenght=6):
  327. """This function makes an invisible area in the UI, where there
  328. could be scroolling."""
  329. if max_value == 0:
  330. max_value = 1
  331. x = int(x)
  332. y = int(y)
  333. width = int(width)
  334. height = int(height)
  335. max_value += 5
  336. amount = 0.0
  337. if int(game.current['mx']) in range(x, x+width) \
  338. and int(game.current['my']) in range(y, y+height):
  339. amount = game.current["scroll"][1] * strenght
  340. game.current["scroll"] = [0, 0]
  341. # Middle mouse drag ( for those who have no working wheel
  342. # like myself, lol )
  343. if game.current["MMB"]\
  344. and int(game.current["mx"]) in range(x, x+width)\
  345. and int(game.current["my"]) in range(y, y+height):
  346. amount = 0- ( game.current["my"] - game.previous["my"] )
  347. def logic():
  348. # Scroll logic
  349. game.scroll[menu] -= amount
  350. # If too low
  351. if game.scroll[menu] < (1-max_value+height):
  352. game.scroll[menu] = (1-max_value+height)
  353. # If too high
  354. if game.scroll[menu] > 0:
  355. game.scroll[menu] = 0
  356. logic()
  357. if game.current["testing"]:
  358. color(game, layer, "red")
  359. layer.rectangle(x,y+1,width-1,height-1)
  360. layer.stroke()
  361. def blur(surface, game, amount):
  362. # This function will blur a given layer by scaling it down and scaling it
  363. # back up. It will be doing it only when a given blur setting it active.
  364. # To avoid all kinds of Zero devision problems. And speed up the draw if
  365. # using animated blur values.
  366. if amount < 2: # When Blue value was less then 3 it felt sharp but not enough
  367. return surface # Which caused sense of uneasiness.
  368. # If to active blur. Will be changed in the graphics settings.
  369. if game.settings.get("Blur", True):
  370. # scaling down
  371. surface1 = cairo.ImageSurface(cairo.FORMAT_ARGB32, game.current['w'],
  372. game.current['h'])
  373. slacedownlayer = cairo.Context(surface1)
  374. slacedownlayer.scale(1/amount,
  375. 1/amount)
  376. slacedownlayer.set_source_surface(surface, 0 , 0)
  377. if game.settings["NN-Downscaler-Blur"]:
  378. p = slacedownlayer.get_source()
  379. p.set_filter(cairo.FILTER_NEAREST)
  380. slacedownlayer.paint()
  381. #scaling back up
  382. surface2 = cairo.ImageSurface(cairo.FORMAT_ARGB32, game.current['w'],
  383. game.current['h'])
  384. slaceuplayer = cairo.Context(surface2)
  385. slaceuplayer.scale(amount,
  386. amount)
  387. slaceuplayer.set_source_surface(surface1, 0 , 0)
  388. if game.settings["NN-Upscaler-Blur"]:
  389. p = slaceuplayer.get_source()
  390. p.set_filter(cairo.FILTER_NEAREST)
  391. slaceuplayer.paint()
  392. return surface2
  393. else:
  394. return surface
  395. def round_rect(layer, x, y, width, height, r):
  396. if width < r*2:
  397. width = r*2
  398. if height < r*2:
  399. height = r*2
  400. layer.move_to(x,y+r)
  401. layer.arc(x+r, y+r, r, math.pi, 3*math.pi/2)
  402. layer.arc(x+width-r, y+r, r, 3*math.pi/2, 0)
  403. layer.arc(x+width-r, y+height-r, r, 0, math.pi/2)
  404. layer.arc(x+r, y+height-r, r, math.pi/2, math.pi)
  405. layer.close_path()
  406. def fancy_health_bar(game, x, y, w, h, oldlayer, value):
  407. # This is going to be a fancy health bar, dah.
  408. # Setting up a cairo layer
  409. surface = cairo.ImageSurface(cairo.FORMAT_ARGB32,
  410. int(w),
  411. int(h))
  412. layer = cairo.Context(surface)
  413. layer.set_antialias(cairo.ANTIALIAS_NONE)
  414. # RED PART
  415. round_rect(layer, 0,0, w,h,h/2)
  416. layer.clip()
  417. color(game, layer, "red")
  418. layer.rectangle(0,0,w,h)
  419. layer.fill()
  420. color(game, layer, "brown")
  421. layer.rectangle(0,0,w,h/3)
  422. layer.fill()
  423. color(game, layer, "dark_red")
  424. layer.rectangle(0,0,w,h/5)
  425. layer.fill()
  426. color(game, layer, "bright_pink")
  427. layer.rectangle(w/2,h-5,w*0.45, 2)
  428. layer.fill()
  429. # shadow
  430. color(game, layer, "dark_red")
  431. round_rect(layer, 0,0, (w*value)+5 ,h ,h/2)
  432. layer.fill()
  433. # GREEN PART
  434. round_rect(layer, 0,0, w*value ,h ,h/2)
  435. layer.clip()
  436. color(game, layer, "dark_green2")
  437. layer.rectangle(0,0,w,h)
  438. layer.fill()
  439. color(game, layer, "dark_green")
  440. round_rect(layer, 0-h/5,0-h/5, (w*value) ,h ,h/2)
  441. layer.fill()
  442. color(game, layer, "green")
  443. round_rect(layer, 0-h/3,0-h/3, (w*value) ,h ,h/2)
  444. layer.fill()
  445. color(game, layer, "bright_green")
  446. layer.rectangle(w*0.05,3,(w*0.45)*value, 2)
  447. layer.fill()
  448. oldlayer.set_source_surface(surface, x, y)
  449. oldlayer.paint()
  450. layer.set_line_width(1)
  451. color(game, oldlayer, "black")
  452. round_rect(oldlayer, x,y, w,h,h/2)
  453. oldlayer.stroke()
  454. def useless_interface_1(game, layer, x,y,w,h):
  455. # This will draw a background pattern
  456. frame = game.current["frame"]
  457. imx = max(w/3*2-100+30, w/3-30)
  458. imy = int(h/2)-75
  459. # 79th photo
  460. if frame > 60:
  461. factor = min(1, max((frame-60)/20, 0))
  462. color(game, layer, "green")
  463. layer.set_line_width(1)
  464. if frame > 80:
  465. layer.set_line_width(2)
  466. layer.move_to(imx-5, imy+5)
  467. layer.line_to(imx, imy-2)
  468. layer.line_to(imx+170*factor+5, imy-2)
  469. layer.line_to(imx+170*factor+5, imy+150*factor-2)
  470. layer.line_to(imx+170*factor, imy+150*factor+3)
  471. layer.line_to(imx-5,imy+150*factor+3)
  472. layer.line_to(imx-5,imy+5)
  473. layer.stroke_preserve()
  474. if frame > 80 and frame not in range(85, 90) and frame not in range(95,100):
  475. color(game, layer, "dark_blue")
  476. layer.fill()
  477. image(game, layer,
  478. imx-30,
  479. imy,
  480. "assets/menu-bg.png")
  481. # Buttons area
  482. if frame > 40:
  483. layer.set_line_width(2)
  484. else:
  485. layer.set_line_width(1)
  486. color(game, layer, "green")
  487. factor = min(1, max(frame/20, 0))
  488. layer.move_to(10,h-55*factor)
  489. layer.line_to(15, h-60*factor)
  490. layer.line_to(w/3+5, h-60*factor)
  491. layer.line_to(w/3+5, h-7)
  492. layer.line_to(w/3, h-3)
  493. layer.line_to(10,h-3)
  494. layer.line_to(10,h-55*factor)
  495. layer.stroke_preserve()
  496. if frame > 40 and frame not in range(45, 50) and frame not in range(55,60):
  497. color(game, layer, "dark_blue")
  498. layer.fill()
  499. factor = min(1, max((frame-40)/5, 0))
  500. color(game, layer, "green")
  501. layer.set_line_width(1)
  502. connectY = min(imy+150, h-70)
  503. layer.move_to(w/4, h-60)
  504. layer.line_to(w/4, (h-60)+((connectY-1)-(h-60))*factor)
  505. layer.stroke()
  506. factor = min(1, max((frame-45)/5, 0))
  507. layer.move_to(w/4, connectY)
  508. layer.line_to((w/4)+((imx-5)-(w/4))*factor, connectY)
  509. layer.stroke()
  510. if h > 200 and w > 400:
  511. if frame < 200:
  512. string = "I'm Not Even Human"
  513. part = max(0, min(int(frame/5)-20, len(string)))
  514. add_ = ""
  515. if part != len(string):
  516. add_ = "_"
  517. color(game, layer, "green")
  518. text(game, layer, string[:part]+add_,
  519. 15,
  520. 15)
  521. else:
  522. image(game, layer, 10, 15, "assets/game-title.png")
  523. else:
  524. text(game, layer, "INEH",
  525. 15,
  526. 15)