forked from bedohswe/p3he
		
	Compare commits
	
		
			No commits in common. "eb6b4b987c65c2d171578650d9fbd5867b4089af" and "dfb6594ec599e212c5d34fcc3cecbebe93a7b8f7" have entirely different histories.
		
	
	
		
			eb6b4b987c
			...
			dfb6594ec5
		
	
		
							
								
								
									
										20
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										20
									
								
								README.md
									
									
									
									
									
								
							| @ -2,29 +2,25 @@ | |||||||
| 
 | 
 | ||||||
| My school project | My school project | ||||||
| 
 | 
 | ||||||
| ## Loading games | ## Load levels | ||||||
| 
 | 
 | ||||||
| You can load a game by passing the name of the game as an argument: | You can load a level by pass the path to the level as an argument, see example: | ||||||
| 
 | 
 | ||||||
| ```sh | ```sh | ||||||
| python3 main.py triangle | python3 main.py maps/triangle.json | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| Game can be loaded only if it is located in the games/ directory. | ## Controls | ||||||
| If no game is specified then "squareroom" will be loaded. |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| ## Controls (might be overriden by games) |  | ||||||
| 
 | 
 | ||||||
| | Descripton                         | Key | | | Descripton                         | Key | | ||||||
| |----------------------------------------|:---:| | |------------------------------------|:---:| | ||||||
| | Move forward                           |  W  | | | Move forwards                      |  W  | | ||||||
| | Move backwards                     |  D  | | | Move backwards                     |  D  | | ||||||
| | Strafe left                        |  Q  | | | Strafe left                        |  Q  | | ||||||
| | Strafe right                       |  E  | | | Strafe right                       |  E  | | ||||||
| | Turn 180 degrees                       |  R  | | | Turn exactly 180 degrees           |  R  | | ||||||
| | Toggle FPS cap                     | F1  | | | Toggle FPS cap                     | F1  | | ||||||
| | Create a wall                      | F2  | | | Create a wall                      | F2  | | ||||||
| | Toggle debug info                  | F3  | | | Toggle debug info                  | F3  | | ||||||
| | Print current position to the terminal | F4  | | | Print to terminal current position | F4  | | ||||||
| | Save level to `./levels` folder    | F5  | | | Save level to `./levels` folder    | F5  | | ||||||
|  | |||||||
| @ -1,57 +0,0 @@ | |||||||
| import pygame |  | ||||||
| from engineevents import EngineEvent |  | ||||||
| from constants import ROT, IROT, OFFSET, I |  | ||||||
| from gyro import GyroVector |  | ||||||
| from levels import open_level, save_level, make_wall |  | ||||||
| from alert import Alert |  | ||||||
| 
 |  | ||||||
| def defaultcontrols(): |  | ||||||
|     aoEngineEvents = list() |  | ||||||
|     for event in pygame.event.get(): |  | ||||||
|         if event.type == pygame.QUIT: |  | ||||||
|             aoEngineEvents.append(EngineEvent("bCont", lambda _: False)) |  | ||||||
|         if event.type == pygame.KEYDOWN: |  | ||||||
|             if event.key == pygame.K_r: |  | ||||||
|                 aoEngineEvents.append(EngineEvent(None, lambda args: args[0].rotate(-1), tsArguments=("gPlayer",))) |  | ||||||
|             if event.key == pygame.K_F3: |  | ||||||
|                 aoEngineEvents.append(EngineEvent("debugInfo", lambda args: not args[0])) |  | ||||||
|             if event.key == pygame.K_F1: |  | ||||||
|                 aoEngineEvents.append(EngineEvent("bCap", lambda args: not args[0])) |  | ||||||
|             if event.key == pygame.K_F2: |  | ||||||
|                 def fX(args): |  | ||||||
|                     #0: wall_buffer |  | ||||||
|                     #1: gPlayer |  | ||||||
|                     #2: level |  | ||||||
|                     if (len(args[0]) == 1): |  | ||||||
|                         args[0].append(args[1].cPos) |  | ||||||
|                         make_wall(args[0], args[2]) |  | ||||||
|                         args[0].clear() |  | ||||||
|                     else: |  | ||||||
|                         args[0].append(args[1].cPos) |  | ||||||
|                 aoEngineEvents.append(EngineEvent(None, fX, tsArguments=("wall_buffer", "gPlayer", "level") )) |  | ||||||
|             if event.key == pygame.K_F5: |  | ||||||
|                 #0: level |  | ||||||
|                 #1: alert_append |  | ||||||
|                 #2: display |  | ||||||
|                 def fX(args): |  | ||||||
|                     filename = save_level(args[0]) |  | ||||||
|                     alert = Alert(f"File saved as {filename}", args[2]) |  | ||||||
| 
 |  | ||||||
|                     args[1](alert, 5) |  | ||||||
|                 aoEngineEvents.append(EngineEvent(None, fX, tsArguments=("level", "alert_append", "display") )) |  | ||||||
|             if event.key == pygame.K_F4: |  | ||||||
|                 aoEngineEvents.append(EngineEvent(None, lambda args: print(args[0].cPos), tsArguments=("gPlayer",)))  |  | ||||||
|     keys = pygame.key.get_pressed() |  | ||||||
|     if keys[pygame.K_d]: |  | ||||||
|         aoEngineEvents.append(EngineEvent(None, lambda args: args[0].rotate( ROT), tsArguments=("gPlayer",))) |  | ||||||
|     if keys[pygame.K_a]: |  | ||||||
|         aoEngineEvents.append(EngineEvent(None, lambda args: args[0].rotate(IROT), tsArguments=("gPlayer",))) |  | ||||||
|     if keys[pygame.K_q]: |  | ||||||
|         aoEngineEvents.append(EngineEvent(None, lambda args: args[0].__iadd__(GyroVector(OFFSET * args[0].cRot*I, 1)), tsArguments=("gPlayer",))) |  | ||||||
|     if keys[pygame.K_e]: |  | ||||||
|         aoEngineEvents.append(EngineEvent(None, lambda args: args[0].__isub__(GyroVector(OFFSET * args[0].cRot*I, 1)), tsArguments=("gPlayer",))) |  | ||||||
|     if keys[pygame.K_w]: |  | ||||||
|         aoEngineEvents.append(EngineEvent(None, lambda args: args[0].__isub__(GyroVector(OFFSET * args[0].cRot  , 1)), tsArguments=("gPlayer",))) |  | ||||||
|     if keys[pygame.K_s]: |  | ||||||
|         aoEngineEvents.append(EngineEvent(None, lambda args: args[0].__iadd__(GyroVector(OFFSET * args[0].cRot  , 1)), tsArguments=("gPlayer",))) |  | ||||||
|     return aoEngineEvents |  | ||||||
							
								
								
									
										4
									
								
								draw.py
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								draw.py
									
									
									
									
									
								
							| @ -22,7 +22,7 @@ class DrawnSegment: | |||||||
|         self.color = color |         self.color = color | ||||||
| 
 | 
 | ||||||
| @jit#(cache=True,parallel=True) | @jit#(cache=True,parallel=True) | ||||||
| def draw(level,gPlayer,fov,res,dscale): | def draw(level,gPlayer,fov,res): | ||||||
|     EMPTYDRAWN = DrawnSegment(0, BLACK) |     EMPTYDRAWN = DrawnSegment(0, BLACK) | ||||||
|     drawn = [EMPTYDRAWN] * res |     drawn = [EMPTYDRAWN] * res | ||||||
|     irot = exp(fov/res*I) |     irot = exp(fov/res*I) | ||||||
| @ -37,7 +37,7 @@ def draw(level,gPlayer,fov,res,dscale): | |||||||
|                 continue |                 continue | ||||||
|             if cDot(rot,MobiusAdd(cInt,-gPlayer.cPos)) > 0: |             if cDot(rot,MobiusAdd(cInt,-gPlayer.cPos)) > 0: | ||||||
|                 continue |                 continue | ||||||
|             rDist = MobiusDist(cInt,gPlayer.cPos) * dscale |             rDist = MobiusDist(cInt,gPlayer.cPos)*1.1 | ||||||
|             if j.isFinite and not cBetween(Poincare2Klein(j.pointA),Poincare2Klein(j.pointB),Poincare2Klein(cInt)): |             if j.isFinite and not cBetween(Poincare2Klein(j.pointA),Poincare2Klein(j.pointB),Poincare2Klein(cInt)): | ||||||
|                 continue |                 continue | ||||||
|             if m is None: |             if m is None: | ||||||
|  | |||||||
| @ -1,11 +0,0 @@ | |||||||
| class EngineEvent: |  | ||||||
|     def __init__(self, sVariableToModify, fLambda, tsArguments=None): |  | ||||||
|         self.sVariableToModify = sVariableToModify |  | ||||||
|         self.fLambda = fLambda |  | ||||||
|         if tsArguments is not None: |  | ||||||
|             self.tsArguments = tsArguments |  | ||||||
|         else: |  | ||||||
|             self.tsArguments = (sVariableToModify,) |  | ||||||
| 
 |  | ||||||
| class EngineEventProcessingError(Exception): |  | ||||||
|     pass |  | ||||||
| @ -1,4 +0,0 @@ | |||||||
| from engineevents import EngineEvent |  | ||||||
| 
 |  | ||||||
| def init(aoEngineEvents): |  | ||||||
|     aoEngineEvents.append(EngineEvent('level', lambda _: [])) |  | ||||||
| @ -1,17 +0,0 @@ | |||||||
| import importlib.resources as impr |  | ||||||
| import sys |  | ||||||
| import json |  | ||||||
| import serialize |  | ||||||
| from engineevents import EngineEvent |  | ||||||
| 
 |  | ||||||
| def init(aoEngineEvents): |  | ||||||
|     curm = sys.modules[__name__] |  | ||||||
|     files = impr.files(curm) |  | ||||||
|     level = None |  | ||||||
|     with files.joinpath("squareroom.json").open('r') as leveldir: |  | ||||||
|         level = json.loads(leveldir.read(), object_hook=serialize.object_hook) |  | ||||||
|     aoEngineEvents.append(EngineEvent( |  | ||||||
|             'level', |  | ||||||
|             lambda _: level, |  | ||||||
|             tsArguments=() |  | ||||||
|             )) |  | ||||||
| @ -1 +0,0 @@ | |||||||
| [{"__type__": "objSegment", "isFinite": true, "pointA": {"__type__": "complex", "real": 0.6, "imag": 0.0}, "pointB": {"__type__": "complex", "real": 0.0, "imag": 0.6}, "color": [255, 0, 0]}, {"__type__": "objSegment", "isFinite": true, "pointA": {"__type__": "complex", "real": 0.6, "imag": 0.0}, "pointB": {"__type__": "complex", "real": 0.0, "imag": -0.6}, "color": [0, 255, 0]}, {"__type__": "objSegment", "isFinite": true, "pointA": {"__type__": "complex", "real": -0.6, "imag": 0.0}, "pointB": {"__type__": "complex", "real": 0.0, "imag": -0.6}, "color": [0, 0, 255]}, {"__type__": "objSegment", "isFinite": true, "pointA": {"__type__": "complex", "real": -0.6, "imag": 0.0}, "pointB": {"__type__": "complex", "real": 0.0, "imag": 0.6}, "color": [255, 255, 0]}] |  | ||||||
| @ -1,17 +0,0 @@ | |||||||
| import importlib.resources as impr |  | ||||||
| import sys |  | ||||||
| import json |  | ||||||
| import serialize |  | ||||||
| from engineevents import EngineEvent |  | ||||||
| 
 |  | ||||||
| def init(aoEngineEvents): |  | ||||||
|     curm = sys.modules[__name__] |  | ||||||
|     files = impr.files(curm) |  | ||||||
|     level = None |  | ||||||
|     with files.joinpath("triangle.json").open('r') as leveldir: |  | ||||||
|         level = json.loads(leveldir.read(), object_hook=serialize.object_hook) |  | ||||||
|     aoEngineEvents.append(EngineEvent( |  | ||||||
|             'level', |  | ||||||
|             lambda _: level, |  | ||||||
|             tsArguments=() |  | ||||||
|             )) |  | ||||||
| @ -1 +0,0 @@ | |||||||
| [{"__type__": "objSegment", "isFinite": true, "pointA": {"__type__": "complex", "real": 0.38529100476842437, "imag": 0.0}, "pointB": {"__type__": "complex", "real": -0.2372040619364459, "imag": 0.36629343026935063}, "color": [255, 0, 0]}, {"__type__": "objSegment", "isFinite": true, "pointA": {"__type__": "complex", "real": -0.2372040619364459, "imag": 0.36629343026935063}, "pointB": {"__type__": "complex", "real": -0.22354939429564802, "imag": -0.26820653903460523}, "color": [0, 255, 0]}, {"__type__": "objSegment", "isFinite": true, "pointA": {"__type__": "complex", "real": -0.22354939429564802, "imag": -0.26820653903460523}, "pointB": {"__type__": "complex", "real": 0.39868528559672944, "imag": 0.017873216691274733}, "color": [0, 0, 255]}] |  | ||||||
							
								
								
									
										137
									
								
								main.py
									
									
									
									
									
								
							
							
						
						
									
										137
									
								
								main.py
									
									
									
									
									
								
							| @ -2,21 +2,20 @@ | |||||||
| 
 | 
 | ||||||
| from math import copysign, pi, acos | from math import copysign, pi, acos | ||||||
| import sys | import sys | ||||||
| import os |  | ||||||
| from importlib import import_module |  | ||||||
| 
 | 
 | ||||||
| import pygame | import pygame | ||||||
| import pygame.freetype | import pygame.freetype | ||||||
| 
 | 
 | ||||||
| from gyro import GyroVector, Poincare2Klein | from gyro import GyroVector, Poincare2Klein | ||||||
|  | from levels import open_level, save_level, make_wall | ||||||
| from constants import I, WHITE, BLACK, IROT, ROT, OFFSET | from constants import I, WHITE, BLACK, IROT, ROT, OFFSET | ||||||
| from draw import draw, DrawnSegment | from draw import draw | ||||||
| from alert import Alert | from alert import Alert | ||||||
| from engineevents import EngineEvent, EngineEventProcessingError |  | ||||||
| from defaultcontrols import defaultcontrols |  | ||||||
| 
 | 
 | ||||||
| gOrigin = GyroVector(0,1) | gOrigin = GyroVector(0,1) | ||||||
| 
 | 
 | ||||||
|  | SKY = (127,127,255) | ||||||
|  | GROUND = (102, 51, 0) | ||||||
| 
 | 
 | ||||||
| def renderDebugInfo(gPlayer, clock, fontSize = 18): | def renderDebugInfo(gPlayer, clock, fontSize = 18): | ||||||
|     font = pygame.freetype.Font(None, fontSize) |     font = pygame.freetype.Font(None, fontSize) | ||||||
| @ -33,11 +32,7 @@ def nonzerocap(n): | |||||||
|     if n <= 0: return 1 |     if n <= 0: return 1 | ||||||
|     return n |     return n | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| def mainLoop(): | def mainLoop(): | ||||||
|     sky = (127,127,255) |  | ||||||
|     ground = (102, 51, 0) |  | ||||||
|     bCap = True |     bCap = True | ||||||
|     bCont = True |     bCont = True | ||||||
|     gPlayer = GyroVector(0,1) |     gPlayer = GyroVector(0,1) | ||||||
| @ -47,10 +42,6 @@ def mainLoop(): | |||||||
|     debugInfo = True |     debugInfo = True | ||||||
|     wall_buffer = [] |     wall_buffer = [] | ||||||
|     alerts = [] |     alerts = [] | ||||||
|     aoEngineEvents = [] |  | ||||||
|     fvControl_ao = defaultcontrols |  | ||||||
|     level = [] |  | ||||||
|     iDistScale = 1.1 |  | ||||||
| 
 | 
 | ||||||
|     def alert_append(alert, delay): |     def alert_append(alert, delay): | ||||||
|         if len(alerts) >= 1: |         if len(alerts) >= 1: | ||||||
| @ -59,87 +50,58 @@ def mainLoop(): | |||||||
|         alerts.append(alert) |         alerts.append(alert) | ||||||
|         alert.start_hide_thread(alerts, delay) |         alert.start_hide_thread(alerts, delay) | ||||||
| 
 | 
 | ||||||
|     def processevents(): |  | ||||||
|         nonlocal bCap |  | ||||||
|         nonlocal bCont |  | ||||||
|         nonlocal gPlayer |  | ||||||
|         nonlocal display |  | ||||||
|         nonlocal fontSize |  | ||||||
|         nonlocal debugInfo |  | ||||||
|         nonlocal wall_buffer |  | ||||||
|         nonlocal alerts |  | ||||||
|         nonlocal alert_append |  | ||||||
|         nonlocal aoEngineEvents |  | ||||||
|         nonlocal level |  | ||||||
|         nonlocal sky |  | ||||||
|         nonlocal ground |  | ||||||
|         nonlocal iDistScale |  | ||||||
|         state = { |  | ||||||
|             'bCap': bCap, |  | ||||||
|             'bCont': bCont, |  | ||||||
|             'gPlayer': gPlayer, |  | ||||||
|             'display': display, |  | ||||||
|             'fontSize': fontSize, |  | ||||||
|             'debugInfo': debugInfo, |  | ||||||
|             'wall_buffer': wall_buffer, |  | ||||||
|             'alerts': alerts, |  | ||||||
|             'alert_append': alert_append, |  | ||||||
|             'aoEngineEvents': aoEngineEvents, |  | ||||||
|             'level': level, |  | ||||||
|             'sky': sky, |  | ||||||
|             'ground': ground, |  | ||||||
|             'iDistScale': iDistScale |  | ||||||
|         } |  | ||||||
|         for i in aoEngineEvents: |  | ||||||
|             if i.sVariableToModify == "bCap": |  | ||||||
|                 bCap        = i.fLambda(list(map(lambda oX: state[oX], i.tsArguments))) |  | ||||||
|             elif i.sVariableToModify == "bCont": |  | ||||||
|                 bCont       = i.fLambda(list(map(lambda oX: state[oX], i.tsArguments))) |  | ||||||
|             elif i.sVariableToModify == "gPlayer": |  | ||||||
|                 gPlayer     = i.fLambda(list(map(lambda oX: state[oX], i.tsArguments))) |  | ||||||
|             elif i.sVariableToModify == "fontSize": |  | ||||||
|                 fontSize    = i.fLambda(list(map(lambda oX: state[oX], i.tsArguments))) |  | ||||||
|             elif i.sVariableToModify == "wall_buffer": |  | ||||||
|                 wall_buffer = i.fLambda(list(map(lambda oX: state[oX], i.tsArguments))) |  | ||||||
|             elif i.sVariableToModify == "alerts": |  | ||||||
|                 alerts      = i.fLambda(list(map(lambda oX: state[oX], i.tsArguments))) |  | ||||||
|             elif i.sVariableToModify == "sky": |  | ||||||
|                 sky         = i.fLambda(list(map(lambda oX: state[oX], i.tsArguments))) |  | ||||||
|             elif i.sVariableToModify == "ground": |  | ||||||
|                 ground      = i.fLambda(list(map(lambda oX: state[oX], i.tsArguments))) |  | ||||||
|             elif i.sVariableToModify == "debugInfo": |  | ||||||
|                 debugInfo   = i.fLambda(list(map(lambda oX: state[oX], i.tsArguments))) |  | ||||||
|             elif i.sVariableToModify == "level": |  | ||||||
|                 level       = i.fLambda(list(map(lambda oX: state[oX], i.tsArguments))) |  | ||||||
|             elif i.sVariableToModify == "iDistScale": |  | ||||||
|                 iDistScale  = i.fLambda(list(map(lambda oX: state[oX], i.tsArguments))) |  | ||||||
|             elif i.sVariableToModify is None: |  | ||||||
|                               i.fLambda(list(map(lambda oX: state[oX], i.tsArguments))) |  | ||||||
|             else: |  | ||||||
|                 raise EngineEventProcessingError('Unknown variable: ' + i.sVariableToModify) |  | ||||||
| 
 |  | ||||||
|         aoEngineEvents.clear() |  | ||||||
|     try: |     try: | ||||||
|         game = import_module('games.' +  sys.argv[1]) |         level = open_level(sys.argv[1]) | ||||||
|     except IndexError: |     except IndexError: | ||||||
|         game = import_module("games.squareroom") |         level = open_level("maps/squareroom.json") | ||||||
| 
 |  | ||||||
|     game.init(aoEngineEvents) |  | ||||||
|     processevents() |  | ||||||
| 
 | 
 | ||||||
|     while bCont: |     while bCont: | ||||||
|         aoEngineEvents = fvControl_ao() |         for event in pygame.event.get(): | ||||||
|         processevents() |             if event.type == pygame.QUIT: | ||||||
|         display.fill(WHITE) |                 bCont = False | ||||||
|         if len(level) != 0: |             if event.type == pygame.KEYDOWN: | ||||||
|             drawn = draw(level,gPlayer,pi/4,320,iDistScale) |                 if event.key == pygame.K_r: | ||||||
|  |                     gPlayer.cRot *= -1 | ||||||
|  |                 if event.key == pygame.K_F3: | ||||||
|  |                     debugInfo = not debugInfo | ||||||
|  |                 if event.key == pygame.K_F1: | ||||||
|  |                     bCap = not bCap | ||||||
|  |                 if event.key == pygame.K_F2: | ||||||
|  |                     if (len(wall_buffer) == 1): | ||||||
|  |                         wall_buffer.append(gPlayer.cPos) | ||||||
|  |                         make_wall(wall_buffer, level) | ||||||
|  |                         wall_buffer.clear() | ||||||
|                     else: |                     else: | ||||||
|             drawn = [DrawnSegment(0, BLACK)] * 320 |                         wall_buffer.append(gPlayer.cPos) | ||||||
|         pygame.draw.rect(display,sky,    (0,0,1280,360)) |                 if event.key == pygame.K_F5: | ||||||
|         pygame.draw.rect(display,ground, (0,360,1280,360)) |                     filename = save_level(level) | ||||||
|  |                     alert = Alert(f"File saved as {filename}", display) | ||||||
|  | 
 | ||||||
|  |                     alert_append(alert, 5) | ||||||
|  |                 if event.key == pygame.K_F4: | ||||||
|  |                     print(gPlayer.cPos) | ||||||
|  | 
 | ||||||
|  |         keys = pygame.key.get_pressed() | ||||||
|  |         if keys[pygame.K_d]: | ||||||
|  |             gPlayer.rotate(ROT) | ||||||
|  |         if keys[pygame.K_a]: | ||||||
|  |             gPlayer.rotate(IROT) | ||||||
|  |         if keys[pygame.K_q]: | ||||||
|  |             gPlayer += GyroVector(OFFSET * gPlayer.cRot*I, 1) | ||||||
|  |         if keys[pygame.K_e]: | ||||||
|  |             gPlayer -= GyroVector(OFFSET * gPlayer.cRot*I, 1) | ||||||
|  |         if keys[pygame.K_w]: | ||||||
|  |             gPlayer -= GyroVector(OFFSET * gPlayer.cRot, 1) | ||||||
|  |         if keys[pygame.K_s]: | ||||||
|  |             gPlayer += GyroVector(OFFSET * gPlayer.cRot, 1) | ||||||
|  | 
 | ||||||
|  |         display.fill(WHITE) | ||||||
|  |         drawn = draw(level,gPlayer,pi/4,320) | ||||||
|  |         pygame.draw.rect(display,SKY,    (0,0,1280,360)) | ||||||
|  |         pygame.draw.rect(display,GROUND, (0,360,1280,360)) | ||||||
|         n = 0 |         n = 0 | ||||||
|         for i in drawn: |         for i in drawn: | ||||||
|             pygame.draw.rect(display,i.color, (n,int((nonzerocap(i.dist)) * 360),4,int((1 -nonzerocap(i.dist)) * 1280))) |             pygame.draw.rect(display,i.color, (n,int((nonzerocap(i.dist)) * 360),8,int((1 -nonzerocap(i.dist)) * 1280))) | ||||||
|             n += 4 |             n += 4 | ||||||
|         gPlayer.normalize() |         gPlayer.normalize() | ||||||
|         if debugInfo: |         if debugInfo: | ||||||
| @ -175,6 +137,5 @@ def main(): | |||||||
|     return retstatus |     return retstatus | ||||||
| 
 | 
 | ||||||
| if __name__ == "__main__": | if __name__ == "__main__": | ||||||
|     sys.path.append(os.path.realpath(__file__)) |  | ||||||
|     if not main(): |     if not main(): | ||||||
|         print("An error occured") |         print("An error occured") | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user